verify: implement P_Array_Append_One
[ajla.git] / stdlib / ui / widget / input.ajla
blobf7e5e3fe951695e4199fd8d4ded68f8e55feb6be
1 {*
2  * Copyright (C) 2024 Mikulas Patocka
3  *
4  * This file is part of Ajla.
5  *
6  * Ajla is free software: you can redistribute it and/or modify it under the
7  * terms of the GNU General Public License as published by the Free Software
8  * Foundation, either version 3 of the License, or (at your option) any later
9  * version.
10  *
11  * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13  * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * Ajla. If not, see <https://www.gnu.org/licenses/>.
17  *}
19 private unit ui.widget.input;
21 uses ui.widget.common;
23 type input_state;
25 fn input_init(color_scheme : bytes, prop : bytes, delete_on_type : bool, w : world, app : appstate, id : wid) : (world, appstate, input_state);
26 fn input_get_width(app : appstate, com : widget_common, st : input_state, x : int) : int;
27 fn input_get_height(app : appstate, com : widget_common, st : input_state, x : int) : int;
28 fn input_reflow(app : appstate, com : widget_common, st : input_state) : (appstate, widget_common, input_state);
29 fn input_redraw(app : appstate, curs : curses, com : widget_common, st : input_state) : curses;
30 fn input_get_cursor(app : appstate, com : widget_common, st : input_state) : (int, int);
31 fn input_accepts_key(app : appstate, com : widget_common, st : input_state, k : event_keyboard) : bool;
32 fn input_process_event(w : world, app : appstate, com : widget_common, st : input_state, wev : wevent) : (world, appstate, widget_common, input_state);
34 const input_class ~flat := widget_class.[
35         t : input_state,
36         name : "input",
37         is_selectable : true,
38         get_width : input_get_width,
39         get_height : input_get_height,
40         reflow : input_reflow,
41         redraw : input_redraw,
42         get_cursor : input_get_cursor,
43         accepts_key : input_accepts_key,
44         process_event : input_process_event,
47 const input_not_selectable_class ~flat := widget_class.[
48         t : input_state,
49         name : "input",
50         is_selectable : false,
51         get_width : input_get_width,
52         get_height : input_get_height,
53         reflow : input_reflow,
54         redraw : input_redraw,
55         get_cursor : input_get_cursor,
56         accepts_key : input_accepts_key,
57         process_event : input_process_event,
60 implementation
62 record input_state [
63         color_scheme : bytes;
64         prop : bytes;
65         text : string;
66         offset : int;
67         cursor : int;
68         delete_on_type : bool;
71 fn input_init(color_scheme : bytes, prop : bytes, delete_on_type : bool, implicit w : world, implicit app : appstate, id : wid) : (world, appstate, input_state)
73         property_observe(id, prop);
74         property_observe(id, prop + "-cpos");
75         var text := property_get(prop).s;
76         property_set(prop + "-cpos", property.i.(len(text)));
77         return input_state.[
78                 color_scheme : color_scheme,
79                 prop : prop,
80                 text : text,
81                 offset : 0,
82                 cursor : len(text),
83                 delete_on_type : delete_on_type,
84         ];
87 fn input_get_width(app : appstate, com : widget_common, st : input_state, x : int) : int
89         return x;
92 fn input_get_height(app : appstate, com : widget_common, st : input_state, x : int) : int
94         return 1;
97 fn input_reflow(implicit app : appstate, implicit com : widget_common, implicit st : input_state) : (appstate, widget_common, input_state)
99         var x := com.size_x;
100         if x > 0 then [
101                 var sl := string_length(st.text[ .. st.cursor]);
102 test_again:
103                 var cpos := -st.offset + sl;
104                 var clen := select(x >= 2 and st.cursor <> len(st.text), 1, char_length(st.text[st.cursor]));
105                 if cpos + clen > x then [
106                         st.offset += 1;
107                         goto test_again;
108                 ]
109                 if cpos < 0 then [
110                         st.offset -= 1;
111                         goto test_again;
112                 ]
113         ]
116 fn input_redraw(implicit app : appstate, implicit curs : curses, com : widget_common, st : input_state) : curses
118         property_set_attrib(property_get_attrib(st.color_scheme + "input", #aaaa, #aaaa, #aaaa, #0000, #0000, #0000, 0, 0));
119         curses_set_pos(-st.offset, 0);
120         curses_print(st.text);
121         curses_print(fill(char, ' ', com.size_x));
124 fn input_get_cursor(app : appstate, com : widget_common, st : input_state) : (int, int)
126         return -st.offset + string_length(st.text[ .. st.cursor]), 0;
129 fn input_accepts_key(app : appstate, com : widget_common, st : input_state, k : event_keyboard) : bool
131         return  k.key = key_left or
132                 k.key = key_right or
133                 k.key = key_delete or
134                 k.key = key_backspace or
135                 k.key = key_home or
136                 k.key = key_end or
137                 k.key = 'D' and (k.flags and key_flag_ctrl) <> 0 or
138                 k.key = 'H' and (k.flags and key_flag_ctrl) <> 0 or
139                 k.key = 'A' and (k.flags and key_flag_ctrl) <> 0 or
140                 k.key = 'E' and (k.flags and key_flag_ctrl) <> 0 or
141                 k.key >= 0 and k.flags = 0;
144 fn input_process_event(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : input_state, wev : wevent) : (world, appstate, widget_common, input_state)
146         if wev is keyboard then [
147                 var k := wev.keyboard;
148                 if k.key = key_left then [
149                         st.delete_on_type := false;
150                         if st.cursor > 0 then [
151                                 st.cursor -= 1;
152                                 input_reflow();
153                                 goto setprop_redraw;
154                         ]
155                 ]
156                 if k.key = key_right then [
157                         st.delete_on_type := false;
158                         if st.cursor < len(st.text) then [
159                                 st.cursor += 1;
160                                 input_reflow();
161                                 goto setprop_redraw;
162                         ]
163                 ]
164                 if k.key = key_delete or k.key = 'D' and (k.flags and key_flag_ctrl) <> 0 then [
165                         st.delete_on_type := false;
166                         if st.cursor < len(st.text) then [
167                                 st.text := st.text[ .. st.cursor] + st.text[st.cursor + 1 .. ];
168                                 input_reflow();
169                                 goto setprop_redraw;
170                         ]
171                 ]
172                 if k.key = key_backspace or k.key = 'H' and (k.flags and key_flag_ctrl) <> 0 then [
173                         st.delete_on_type := false;
174                         if st.cursor > 0 then [
175                                 st.text := st.text[ .. st.cursor - 1] + st.text[st.cursor .. ];
176                                 st.cursor -= 1;
177                                 input_reflow();
178                                 goto setprop_redraw;
179                         ]
180                 ]
181                 if k.key = key_home or k.key = 'A' and (k.flags and key_flag_ctrl) <> 0 then [
182                         st.delete_on_type := false;
183                         st.cursor := 0;
184                         input_reflow();
185                         goto setprop_redraw;
186                 ]
187                 if k.key = key_end or k.key = 'E' and (k.flags and key_flag_ctrl) <> 0 then [
188                         st.delete_on_type := false;
189                         st.cursor := len(st.text);
190                         input_reflow();
191                         goto setprop_redraw;
192                 ]
193                 if k.key >= 0 and k.flags = 0 then [
194                         if st.delete_on_type then [
195                                 st.text := [ k.key ];
196                                 st.cursor := 1;
197                                 st.delete_on_type := false;
198                         ] else [
199                                 st.text := st.text[ .. st.cursor] + [ k.key ] + st.text[st.cursor .. ];
200                                 st.cursor += 1;
201                         ]
202                         input_reflow();
203                         goto setprop_redraw;
204                 ]
205         ]
206         if wev is mouse then [
207                 var mx, my := widget_relative_mouse_coords(com.self, wev.mouse);
208                 var m := wev.mouse;
209                 if m.buttons bt 0 then [
210                         st.delete_on_type := false;
211                         //eval debug("mouse: " + ntos(m.x) + ", " + ntos(m.y));
212                         var offs := -st.offset;
213                         for i := 0 to len(st.text) do [
214                                 offs += char_length(st.text[i]);
215                                 if offs > mx then [
216                                         st.cursor := i;
217                                         input_reflow();
218                                         goto setprop_redraw;
219                                 ]
220                         ]
221                         st.cursor := len(st.text);
222                         input_reflow();
223                         goto setprop_redraw;
224                 ]
225         ]
226         if wev is property_changed then [
227                 var new_text := property_get(st.prop).s;
228                 if st.text <> new_text then [
229                         st.text := new_text;
230                         st.cursor := len(st.text);
231                         input_reflow();
232                 ]
233                 var cursor := property_get(st.prop + "-cpos").i;
234                 if cursor <> st.cursor then [
235                         cursor := max(0, cursor);
236                         cursor := min(len(st.text), cursor);
237                         st.cursor := cursor;
238                         input_reflow();
239                 ]
240                 goto setprop_redraw;
241         ]
242         return;
243 setprop_redraw:
244         property_set(st.prop, property.s.(st.text));
245         property_set(st.prop + "-cpos", property.i.(st.cursor));
246         widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[
247                 x1 : 0,
248                 x2 : com.size_x,
249                 y1 : 0,
250                 y2 : com.size_y,
251         ]));