1 module evael.graphics.gui.controls.TextArea; 2 3 import std.string; 4 import std.conv; 5 import std.typecons; 6 7 import evael.graphics.gui.controls.Container; 8 import evael.graphics.gui.controls.TextBlock; 9 import evael.graphics.gui.controls.ScrollBar; 10 11 import evael.utils.Math; 12 13 import evael.utils.Size; 14 import evael.utils.Color; 15 16 class TextArea : Container, IScrollable 17 { 18 alias Line = Tuple!(wstring, "text", Color, "color"); 19 20 private Line[] m_lines; 21 22 /// Position of the text in the textarea 23 private vec2 m_globalTextPosition; 24 25 /// Max lines that can be displayed 26 private int m_maxLines; 27 28 public this(in float x, in float y, in int width, in int height) 29 { 30 this(vec2(x, y,), Size!int(width, height)); 31 } 32 33 public this()(in auto ref vec2 position, in auto ref Size!int size) 34 { 35 super(position, size); 36 37 this.m_name = "textArea"; 38 39 this.m_globalTextPosition = vec2(0.0f); 40 this.m_maxLines = 255; 41 42 // this.addChild(new ScrollBar(0, 0, 20, size.height)); 43 } 44 45 46 public void onScroll(ScrollBar.ScrollDirection direction, in float scrollBarPosition) 47 { 48 49 } 50 51 public override void draw(in float deltaTime) 52 { 53 if(!this.m_isVisible) 54 { 55 return; 56 } 57 58 super.draw(deltaTime); 59 60 auto vg = this.m_nvg; 61 62 nvgSave(vg); 63 64 nvgFontSize(vg, this.m_theme.fontSize); 65 nvgFontFaceId(vg, this.m_theme.font.id); 66 67 float lineh; 68 nvgTextMetrics(vg, null, null, &lineh); 69 70 auto x = this.m_realPosition.x; 71 auto y = this.m_realPosition.y + this.m_globalTextPosition.y; 72 73 NVGtextRow[3] rows; 74 75 foreach(ref line; this.m_lines) 76 { 77 const char* text = line.text.to!string.toStringz(); 78 const char* start = text; 79 const char* end = text + line.text.length; 80 81 int nrows = nvgTextBreakLines(vg, start, end, this.m_size.width, rows.ptr, 3); 82 83 while(nrows) 84 { 85 for(int i = 0; i < nrows; i++) 86 { 87 NVGtextRow* row = &rows[i]; 88 89 // We check if we really need to draw this line 90 if(y >= this.m_realPosition.y) 91 { 92 // TODO : use font class maybe ? 93 if(this.m_theme.drawTextShadow) 94 { 95 nvgFontBlur(vg, 2); 96 nvgFillColor(vg, Color.Black.asNvg); 97 nvgTextAlign(vg, NVGalign.NVG_ALIGN_LEFT | NVGalign.NVG_ALIGN_TOP); 98 nvgText(vg, x + 1.6f, y + 1.6f, row.start, row.end); 99 nvgFontBlur(vg, 0.8); 100 } 101 102 nvgFillColor(vg, line.color.asNvg); 103 nvgTextAlign(vg, NVGalign.NVG_ALIGN_LEFT | NVGalign.NVG_ALIGN_TOP); 104 nvgText(vg, x, y, row.start, row.end); 105 } 106 107 auto nextLineY = y + lineh; 108 109 // We check if next line is gonna be displayed outside of the textarea 110 if(nextLineY <= this.m_realPosition.y + this.m_size.height) 111 { 112 // No 113 y = nextLineY; 114 } 115 else 116 { 117 // Yes, we need to update global text position instead of next line position 118 this.m_globalTextPosition.y = this.m_globalTextPosition.y - lineh; 119 } 120 } 121 122 nrows = nvgTextBreakLines(vg, rows[nrows - 1].next, end, this.m_size.width, rows.ptr, 3); 123 } 124 } 125 126 nvgRestore(vg); 127 } 128 129 public void appendLine(in wstring text, in ref Color color = Color.Black) 130 { 131 // We check if we reached lines limit 132 if(this.m_lines.length == this.m_maxLines) 133 { 134 // We need to remove the first line 135 this.m_lines = this.m_lines[1..$]; 136 137 this.m_globalTextPosition.y = 0.0f; 138 } 139 140 this.m_lines ~= Line(text, color); 141 } 142 143 public void appendLine(in string text, in ref Color color = Color.Black) 144 { 145 this.appendLine(to!wstring(text), color); 146 } 147 148 /** 149 * Clear all lines 150 */ 151 public void clear() nothrow @nogc 152 { 153 this.m_controls = []; 154 this.m_globalTextPosition = vec2(0.0f); 155 } 156 157 @property 158 { 159 public void text(in wstring value) 160 { 161 this.clear(); 162 163 this.appendLine(value); 164 } 165 166 public void maxLines(in int value) nothrow @nogc 167 { 168 this.m_maxLines = value; 169 } 170 } 171 }