1 module unde.guitk.textarea; 2 3 import derelict.sdl2.sdl; 4 import derelict.sdl2.ttf; 5 import derelict.sdl2.image; 6 7 import std.stdio; 8 import std.math; 9 import std..string; 10 import std.utf; 11 12 import unde.global_state; 13 import unde.guitk.lib; 14 import unde.keybar.lib; 15 import unde.lib; 16 import unde.command_line.lib; 17 import unde.tick; 18 19 version(Windows) 20 { 21 import berkeleydb.all: ssize_t; 22 } 23 24 class TextArea:UIEntry 25 { 26 private SDL_Rect _rect; 27 private UIPage _page; 28 private bool _focus; 29 private string _text; 30 private int fontsize; 31 private int line_height; 32 private ssize_t pos; 33 private SDL_Color color; 34 private ssize_t mouse_pos; 35 private ssize_t first_click; 36 private ssize_t start_selection = -1; 37 private ssize_t end_selection = -1; 38 39 @property SDL_Rect rect() {return _rect;} 40 @property ref string text() {return _text;} 41 42 this(UIPage page, SDL_Rect rect, 43 SDL_Color color = SDL_Color(0xFF, 0xFF, 0xFF, 0xFF)) 44 { 45 _page = page; 46 _rect = rect; 47 fontsize = 9; 48 line_height = cast(int)(round(SQRT2^^fontsize)*1.2); 49 this.color = color; 50 } 51 52 void on_draw(GlobalState gs) 53 { 54 /* Background */ 55 auto r = SDL_RenderCopy(gs.renderer, gs.texture_gray, null, &_rect); 56 if (r < 0) 57 { 58 writefln( "List.on_draw(), 1: Error while render copy: %s", 59 SDL_GetError().fromStringz() ); 60 } 61 62 auto tt = gs.text_viewer.font.get_line_from_cache(_text, 63 fontsize, _rect.w, line_height, color, null, 64 start_selection, end_selection); 65 if (!tt && !tt.texture) 66 { 67 throw new Exception("Can't create text_surface: "~ 68 TTF_GetError().fromStringz().idup()); 69 } 70 71 int x_limit = _rect.x+_rect.w; 72 int y_limit = _rect.y+_rect.h; 73 74 SDL_Rect rect; 75 rect.x = _rect.x; 76 rect.y = _rect.y; 77 rect.w = (rect.x+tt.w < x_limit) ? tt.w : x_limit - rect.x; 78 rect.h = (rect.y+tt.h < y_limit) ? tt.h : y_limit - rect.y; 79 80 SDL_Rect src; 81 src.x = 0; 82 src.y = 0; 83 src.w = (rect.x+tt.w < x_limit) ? tt.w : x_limit - rect.x; 84 src.h = (rect.y+tt.h < y_limit) ? tt.h : y_limit - rect.y; 85 86 mouse_pos = get_position_by_chars( 87 gs.mouse_screen_x - rect.x, 88 gs.mouse_screen_y - rect.y, tt.chars); 89 90 r = SDL_RenderCopy(gs.renderer, tt.texture, &src, &rect); 91 if (r < 0) 92 { 93 writefln( 94 "List.on_draw(), 2: Error while render copy: %s", 95 SDL_GetError().fromStringz() ); 96 } 97 98 /* Render cursor */ 99 if (_focus && pos < tt.chars.length) 100 { 101 rect = tt.chars[pos]; 102 rect.x += _rect.x; 103 rect.y += _rect.y; 104 if (rect.x < x_limit && rect.y < y_limit) 105 { 106 string chr = " "; 107 if (pos < _text.length) 108 chr = _text[pos..pos+_text.stride(pos)]; 109 if (chr == "\n") chr = " "; 110 111 r = SDL_RenderCopy(gs.renderer, gs.texture_cursor, null, &rect); 112 if (r < 0) 113 { 114 writefln( "draw_command_line(), 11: Error while render copy: %s", 115 SDL_GetError().fromStringz() ); 116 } 117 118 auto st = gs.text_viewer.font.get_char_from_cache(chr, fontsize, SDL_Color(0x00, 0x00, 0x20, 0xFF)); 119 if (!st) return; 120 121 r = SDL_RenderCopy(gs.renderer, st.texture, null, &rect); 122 if (r < 0) 123 { 124 writefln( 125 "draw_command_line(), 12: Error while render copy: %s", 126 SDL_GetError().fromStringz() ); 127 } 128 } 129 } 130 } 131 132 void selection_to_buffer(GlobalState gs) 133 { 134 string selection = _text[start_selection..end_selection + _text.mystride(end_selection)]; 135 SDL_SetClipboardText(selection.toStringz()); 136 } 137 138 void shift_selected(GlobalState gs) 139 { 140 if (start_selection < 0 || end_selection < 0) return; 141 string converted; 142 for (ssize_t i=start_selection; i < end_selection + _text.mystride(end_selection); i+=_text.stride(i)) 143 { 144 string chr = _text[i..i+_text.stride(i)]; 145 if (chr.toLower() == chr) 146 { 147 chr = chr.toUpper(); 148 } 149 else 150 chr = chr.toLower(); 151 152 converted ~= chr; 153 } 154 155 _text = _text[0..start_selection] ~ converted ~ _text[end_selection + _text.mystride(end_selection)..$]; 156 } 157 158 bool find_chr(string chr, string[][3] *letters, ref ButtonPos buttonpos) 159 { 160 for (buttonpos.i = 0; buttonpos.i < 3; buttonpos.i++) 161 { 162 for (buttonpos.pos = 0; buttonpos.pos < 163 (*letters)[buttonpos.i].length; buttonpos.pos++) 164 { 165 if ((*letters)[buttonpos.i][buttonpos.pos] == chr) 166 return true; 167 } 168 } 169 return false; 170 } 171 172 void change_layout_selected(GlobalState gs) 173 { 174 if (start_selection < 0 || end_selection < 0) return; 175 string converted; 176 for (ssize_t i=start_selection; i < end_selection + _text.mystride(end_selection); i+=_text.stride(i)) 177 { 178 string chr = _text[i..i+_text.stride(i)]; 179 180 ssize_t prev_mode = gs.keybar.mode-1; 181 if (prev_mode < 0) 182 prev_mode = gs.keybar.layout_modes.length - 1; 183 184 ButtonPos buttonpos; 185 auto letters = &gs.keybar.layout_modes[prev_mode].letters; 186 if (find_chr(chr, letters, buttonpos)) 187 { 188 letters = &gs.keybar.layout_modes[gs.keybar.mode].letters; 189 chr = (*letters)[buttonpos.i][buttonpos.pos]; 190 } 191 letters = &gs.keybar.layout_modes[prev_mode].letters_shift; 192 if (find_chr(chr, letters, buttonpos)) 193 { 194 letters = &gs.keybar.layout_modes[gs.keybar.mode].letters_shift; 195 chr = (*letters)[buttonpos.i][buttonpos.pos]; 196 } 197 letters = &gs.keybar.layout_modes[prev_mode].letters_altgr; 198 if (find_chr(chr, letters, buttonpos)) 199 { 200 letters = &gs.keybar.layout_modes[gs.keybar.mode].letters_altgr; 201 chr = (*letters)[buttonpos.i][buttonpos.pos]; 202 } 203 letters = &gs.keybar.layout_modes[prev_mode].letters_shift_altgr; 204 if (find_chr(chr, letters, buttonpos)) 205 { 206 letters = &gs.keybar.layout_modes[gs.keybar.mode].letters_shift_altgr; 207 chr = (*letters)[buttonpos.i][buttonpos.pos]; 208 } 209 210 converted ~= chr; 211 } 212 213 _text = _text[0..start_selection] ~ converted ~ _text[end_selection + _text.mystride(end_selection)..$]; 214 end_selection = start_selection + converted.length; 215 end_selection -= _text.mystrideBack(end_selection); 216 if (pos > start_selection) pos = _text.length; 217 } 218 219 void delegate(GlobalState gs) on_change; 220 221 void process_event(GlobalState gs, SDL_Event event) 222 { 223 switch( event.type ) 224 { 225 case SDL_TEXTINPUT: 226 char[] input = fromStringz(cast(char*)event.text.text); 227 if (input[0] == char.init) 228 { 229 if (input[1] == '1') 230 { 231 shift_selected(gs); 232 } 233 else 234 { 235 change_layout_selected(gs); 236 } 237 } 238 else 239 { 240 _text = 241 (_text[0..pos] ~ 242 input ~ 243 _text[pos..$]).idup(); 244 pos += input.length; 245 } 246 if (on_change) 247 on_change(gs); 248 break; 249 250 case SDL_MOUSEMOTION: 251 if (gs.mouse_buttons & unDE_MouseButtons.Left) 252 { 253 if (mouse_pos > first_click) 254 { 255 start_selection = first_click; 256 end_selection = mouse_pos; 257 } 258 else 259 { 260 start_selection = mouse_pos; 261 end_selection = first_click; 262 } 263 } 264 break; 265 266 case SDL_MOUSEBUTTONDOWN: 267 switch (event.button.button) 268 { 269 case SDL_BUTTON_LEFT: 270 first_click = mouse_pos; 271 break; 272 case SDL_BUTTON_MIDDLE: 273 break; 274 case SDL_BUTTON_RIGHT: 275 break; 276 default: 277 break; 278 } 279 break; 280 281 case SDL_MOUSEBUTTONUP: 282 switch (event.button.button) 283 { 284 case SDL_BUTTON_LEFT: 285 if (!gs.moved_while_click) 286 { 287 if (SDL_GetTicks() - gs.last_left_click < DOUBLE_DELAY) 288 { 289 } 290 else 291 { 292 set_focus(gs); 293 start_selection = -1; 294 end_selection = -1; 295 } 296 } 297 else 298 { 299 if (_text == "") 300 { 301 start_selection = -1; 302 end_selection = -1; 303 } 304 else 305 { 306 if (mouse_pos > first_click) 307 { 308 start_selection = first_click; 309 end_selection = mouse_pos; 310 } 311 else 312 { 313 start_selection = mouse_pos; 314 end_selection = first_click; 315 } 316 if (end_selection + _text.mystride(end_selection) > _text.length) 317 end_selection = _text.length - 1 - _text.mystrideBack(_text.length-1); 318 selection_to_buffer(gs); 319 } 320 } 321 break; 322 case SDL_BUTTON_MIDDLE: 323 char* clipboard = SDL_GetClipboardText(); 324 if (clipboard) 325 { 326 string buffer = clipboard.fromStringz().idup(); 327 328 _text = 329 (_text[0..pos] ~ 330 buffer ~ 331 _text[pos..$]).idup(); 332 pos += buffer.length; 333 if (on_change) 334 on_change(gs); 335 } 336 break; 337 default: 338 break; 339 } 340 break; 341 342 default: 343 break; 344 } 345 } 346 347 @property UIPage page() {return _page;} 348 349 @property ref bool focus() {return _focus;} 350 351 void on_set_focus(GlobalState gs) 352 { 353 gs.keybar.input_mode = true; 354 } 355 356 void on_unset_focus(GlobalState gs) 357 { 358 gs.keybar.input_mode = false; 359 } 360 361 private void 362 close_page(GlobalState gs) 363 { 364 _page.show = false; 365 } 366 367 private void 368 left(GlobalState gs) 369 { 370 if (pos > 0) 371 pos -= _text.strideBack(pos); 372 } 373 374 private void 375 right(GlobalState gs) 376 { 377 if (pos < _text.length) 378 pos += _text.stride(pos); 379 } 380 381 private void 382 backscape(GlobalState gs) 383 { 384 if (_text > "" && pos > 0 ) 385 { 386 int sb = _text.strideBack(pos); 387 _text = (_text[0..pos-sb] ~ _text[pos..$]).idup(); 388 pos -= sb; 389 if (on_change) 390 on_change(gs); 391 } 392 } 393 394 void set_keybar(GlobalState gs) 395 { 396 gs.keybar.handlers.clear(); 397 gs.keybar.handlers_down.clear(); 398 gs.keybar.handlers_double.clear(); 399 400 gs.keybar.handlers[SDL_SCANCODE_ESCAPE] = KeyHandler(&close_page, "Close layouts settings", "Esc"); 401 gs.keybar.handlers_down[SDL_SCANCODE_LEFT] = KeyHandler(&left, "Left", "←"); 402 gs.keybar.handlers_down[SDL_SCANCODE_RIGHT] = KeyHandler(&right, "Right", "→"); 403 gs.keybar.handlers_down[SDL_SCANCODE_BACKSPACE] = KeyHandler(&backscape, "Backspace", "<--"); 404 } 405 } 406