1 // Written in the D programming language. 2 /++ 3 + Authors: KanzakiKino 4 + Copyright: KanzakiKino 2018 5 + License: LGPL-3.0 6 ++/ 7 module w4d.util.textline; 8 import w4d.event; 9 import std.algorithm, 10 std.conv; 11 12 /// A handler of chainging text. 13 alias TextChangeHandler = EventHandler!( void, dstring ); 14 /// A handler of cursor moving. 15 alias CursorMoveHandler = EventHandler!( void, long ); 16 17 /// An object of editable text. 18 class TextLine 19 { 20 /// 21 TextChangeHandler onTextChange; 22 /// 23 CursorMoveHandler onCursorMove; 24 25 protected dstring _text; 26 /// Current text. 27 const @property text () { return _text; } 28 29 protected bool _locked; 30 /// Checks if editing is locked. 31 const @property isLocked () { return _locked; } 32 33 protected long _cursorIndex; 34 /// Index of cursor. 35 const @property cursorIndex () { return _cursorIndex; } 36 37 protected long _selectionIndex; 38 /// Index that selection is beginning. 39 const @property selectionIndex () { return _selectionIndex; } 40 41 /// Checks if text is selected. 42 const @property isSelecting () 43 { 44 return _selectionIndex >= 0 && _selectionIndex != _cursorIndex; 45 } 46 47 /// 48 this () 49 { 50 _text = ""d; 51 _locked = false; 52 _cursorIndex = 0; 53 _selectionIndex = 0; 54 } 55 56 /// Moves cursor to absolute index. 57 void moveCursorTo ( long i, bool selecting = false ) 58 { 59 if ( selecting && !isSelecting ) { 60 _selectionIndex = _cursorIndex; 61 } 62 const temp = _cursorIndex; 63 _cursorIndex = i.clamp( 0, _text.length ); 64 65 if ( !selecting ) { 66 _selectionIndex = -1; 67 } 68 if ( temp != _cursorIndex ) { 69 onCursorMove.call( _cursorIndex ); 70 } 71 } 72 /// Moves cursor to relative index. 73 void moveCursor ( long i, bool selecting = false ) 74 { 75 moveCursorTo( _cursorIndex+i, selecting ); 76 } 77 /// Moves cursor to left. 78 void left ( bool selecting ) 79 { 80 moveCursor( -1, selecting ); 81 } 82 /// Moves cursor to right. 83 void right ( bool selecting ) 84 { 85 moveCursor( 1, selecting ); 86 } 87 /// Moves cursor to home. 88 void home ( bool selecting ) 89 { 90 moveCursorTo( 0, selecting ); 91 } 92 /// Moves cursor to end. 93 void end ( bool selecting ) 94 { 95 moveCursorTo( _text.length, selecting ); 96 } 97 98 const @property leftText () 99 { 100 long index = _cursorIndex; 101 if ( isSelecting ) { 102 index = min( _cursorIndex, _selectionIndex ); 103 } 104 return _text[0 .. index.to!size_t]; 105 } 106 const @property rightText () 107 { 108 long index = _cursorIndex; 109 if ( isSelecting ) { 110 index = max( _cursorIndex, _selectionIndex ); 111 } 112 return _text[index.to!size_t .. $]; 113 } 114 const @property dstring selectedText () 115 { 116 if ( !isSelecting ) return ""d; 117 118 long left = _cursorIndex; 119 long right = _selectionIndex; 120 if ( left > right ) { 121 swap( left, right ); 122 } 123 return _text[left .. right]; 124 } 125 126 /// Inserts text at cursor. 127 void insert ( dstring v ) 128 { 129 if ( isLocked ) return; 130 131 setText( leftText ~v~ rightText ); 132 moveCursor( v.length ); 133 } 134 /// Removes a left char of cursor. 135 void backspace () 136 { 137 if ( isLocked ) return; 138 139 if ( isSelecting ) { 140 removeSelecting(); 141 return; 142 } 143 auto left = leftText; 144 auto right = rightText; 145 146 if ( left.length ) { 147 left = left[0..$-1]; 148 moveCursor( -1 ); 149 setText( left~right ); 150 } 151 } 152 /// Removes a right char of cursor. 153 void del () 154 { 155 if ( isLocked ) return; 156 157 if ( isSelecting ) { 158 removeSelecting(); 159 return; 160 } 161 auto left = leftText; 162 auto right = rightText; 163 164 if ( right.length ) { 165 right = right[1..$]; 166 setText( left~right ); 167 } 168 } 169 170 /// Removes the selected text. 171 void removeSelecting () 172 { 173 if ( !isSelecting || isLocked ) return; 174 175 long cursorMove = 0; 176 if ( _cursorIndex > _selectionIndex ) { 177 cursorMove = -(_cursorIndex-_selectionIndex); 178 } 179 setText( leftText ~ rightText ); 180 181 moveCursor( cursorMove ); 182 } 183 /// Sets all text selected. 184 void selectAll () 185 { 186 moveCursorTo( 0 ); 187 moveCursorTo( _text.length, true ); 188 } 189 /// 190 void deselect () 191 { 192 moveCursor( 0 ); 193 // This method doesn't call onCursorMove. 194 // So requstRedraw won't be called. 195 } 196 197 /// Changes text. 198 void setText ( dstring v ) 199 { 200 if ( _text != v ) { 201 deselect(); 202 _text = v; 203 moveCursor(0); 204 onTextChange.call( v ); 205 } 206 } 207 208 /// Locks editing. 209 void lock () 210 { 211 _locked = true; 212 } 213 /// Unlocks editing. 214 void unlock () 215 { 216 _locked = false; 217 } 218 }