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