2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
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
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.
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/>.
19 private unit ui.widget.dialog;
21 uses ui.widget.common;
26 const cls : widget_class;
27 init : fn(world, appstate, wid) : (world, appstate, cls.t);
28 hotkeys : treeset(char);
31 fn dialog_no_event(w : world, app : appstate, com : widget_common, wev : wevent, ids : list(wid)) : (world, appstate, widget_common, bool);
32 fn dialog_init(label : string, entries : list(dialog_entry), selected : int, process_event : fn(world, appstate, widget_common, wevent, list(wid)) : (world, appstate, widget_common, bool), layout : fn(appstate, list(wid), int, int, int, int, int) : (appstate, int, int), color_scheme : bytes, w : world, app : appstate, id : wid) : (world, appstate, dialog_state);
33 fn dialog_redraw(app : appstate, curs : curses, com : widget_common, st : dialog_state) : curses;
34 fn dialog_get_cursor(implicit app : appstate, implicit com : widget_common, implicit st : dialog_state) : (int, int);
35 fn dialog_process_event(w : world, app : appstate, com : widget_common, st : dialog_state, wev : wevent) : (world, appstate, widget_common, dialog_state);
37 const dialog_class~flat := widget_class.[
41 redraw : dialog_redraw,
42 get_cursor : dialog_get_cursor,
43 process_event : dialog_process_event,
48 const horizontal_outer_border := 3;
49 const horizontal_inner_border := 2;
50 const vertical_outer_border := 1;
51 const border_width := 2;
58 entries : list(dialog_entry);
60 process_event : fn(world, appstate, widget_common, wevent, list(wid)) : (world, appstate, widget_common, bool);
61 layout : fn(appstate, list(wid), int, int, int, int, int) : (appstate, int, int);
64 fn dialog_no_event(implicit w : world, implicit app : appstate, implicit com : widget_common, wev : wevent, ids : list(wid)) : (world, appstate, widget_common, bool)
69 fn dialog_init(label : string, entries : list(dialog_entry), selected : int, process_event : fn(world, appstate, widget_common, wevent, list(wid)) : (world, appstate, widget_common, bool), layout : fn(appstate, list(wid), int, int, int, int, int) : (appstate, int, int), color_scheme : bytes, implicit w : world, implicit app : appstate, id : wid) : (world, appstate, dialog_state)
71 var no_controls := false;
72 if selected = -1 then [
76 var ids := fill(wid, wid_none, len(entries));
77 var i := selected - 1;
79 const entry := entries[i];
80 var sid := widget_new(id, entry.cls, entry.init, false);
84 i := len(entries) - 1;
85 while i >= selected do [
86 const entry := entries[i];
87 var sid := widget_new(id, entry.cls, entry.init, false);
91 label := ` ` + label + ` `;
92 var st := dialog_state.[
94 label_len : string_length(label),
95 color_scheme : color_scheme,
96 no_controls : no_controls,
99 process_event : process_event,
105 fn dialog_redraw(implicit app : appstate, implicit curs : curses, com : widget_common, st : dialog_state) : curses
107 property_set_attrib(property_get_attrib(st.color_scheme + "dialog", #0000, #0000, #0000, #aaaa, #aaaa, #aaaa, 0, curses_invert));
108 curses_fill_rect(0, com.size_x, 0, com.size_y, ' ');
109 curses_box(horizontal_outer_border, com.size_x - horizontal_outer_border - 1, vertical_outer_border, com.size_y - vertical_outer_border - 1, border_width);
110 curses_set_pos((com.size_x - st.label_len) shr 1, vertical_outer_border);
111 property_set_attrib(property_get_attrib(st.color_scheme + "dialog-title", #aaaa, #aaaa, #aaaa, #0000, #0000, #0000, 0, 0));
112 curses_print(st.label);
113 curses_restrict_viewport(horizontal_outer_border + 1, com.size_x - horizontal_outer_border - 1, vertical_outer_border + 1, com.size_y - vertical_outer_border - 1, com.off_x, com.off_y);
114 widget_redraw_subwidgets(com);
115 curses_revert_viewport();
118 fn dialog_get_cursor(implicit app : appstate, implicit com : widget_common, implicit st : dialog_state) : (int, int)
120 if len(com.sub_widgets) > 0 then [
121 var top := com.sub_widgets[len(com.sub_widgets) - 1];
122 var x, y := widget_get_cursor(app, top);
123 if x >= horizontal_outer_border + 1 - com.off_x, x < com.size_x - horizontal_outer_border - 1 - com.off_x, y >= vertical_outer_border + 1 - com.off_y, y < com.size_y - vertical_outer_border - 1 - com.off_y then
129 fn dialog_scroll(implicit app : appstate, implicit com : widget_common, implicit st : dialog_state) : (widget_common, dialog_state, bool)
132 if not st.no_controls, len(com.sub_widgets) > 0 then [
133 var top := widget_get_common(com.sub_widgets[len(com.sub_widgets) - 1]);
134 if com.off_y + top.y + top.size_y > com.size_y - vertical_outer_border - 1 then [
135 com.off_y := -(top.y + top.size_y - (com.size_y - vertical_outer_border - 1));
138 if com.off_y + top.y < vertical_outer_border + 1 then [
139 com.off_y := -(top.y - vertical_outer_border - 1);
146 fn dialog_process_event(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : dialog_state, wev : wevent) : (world, appstate, widget_common, dialog_state)
148 if wev is resize then [
149 var x_border := (horizontal_outer_border + 1 + horizontal_inner_border) * 2;
150 var pref_x := (wev.resize.x - x_border) * 9 div 10;
151 var xs, ys := st.layout(st.ids, x_border shr 1, vertical_outer_border + 1, string_length(st.label) - 2, pref_x, wev.resize.x - x_border);
152 com.size_x := xs + x_border;
153 com.size_y := ys + vertical_outer_border + 1;
154 com.size_y := min(com.size_y, wev.resize.y - 1);
155 com.x := (wev.resize.x - com.size_x) shr 1;
156 com.y := (wev.resize.y - com.size_y) shr 1;
158 var sc := dialog_scroll();
161 if wev is change_focus then [
162 if widget_is_top(com.self) then [
163 property_set("fkeys", property.l.([
173 property.s.(`Cancel`),
178 var processed := st.process_event(wev, st.ids);
181 if st.no_controls then [
182 if wev is keyboard, wev.keyboard.key = key_esc or wev.keyboard.key = key_enter or wev.keyboard.key = key_f10 or wev.keyboard.key = ' ' then [
183 widget_enqueue_event(com.self, wevent.close);
186 if wev is mouse, (wev.mouse.buttons and not wev.mouse.prev_buttons) <> 0 then [
187 widget_enqueue_event(com.self, wevent.close);
191 var forwarding := widget_should_forward(wev);
193 if not forwarding, wev is keyboard then [
194 for i in list_iterator_reverse(st.entries) do [
195 if not is_uninitialized_record(st.entries[i].hotkeys), treeset_test(st.entries[i].hotkeys, char_upcase(wev.keyboard.key)) then [
197 widget_enqueue_event(id, wevent.keyboard.(event_keyboard.[
208 var sc := dialog_scroll();
214 widget_enqueue_event(com.self, wevent.redraw.(event_redraw.[