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.common;
25 fn wid_is_valid~inline(id : wid) : bool;
26 implicit fn instance_eq_wid : class_eq(wid);
31 record event_set_property [
37 keyboard : event_keyboard;
39 resize : event_resize;
40 redraw : event_redraw;
41 set_property : event_set_property;
43 property_changed : bytes;
54 record widget_common [
61 sub_widgets : list(wid);
64 record property_attrib [
65 fg_r fg_g fg_b bg_r bg_g bg_b : uint16;
85 get_width : fn(appstate, widget_common, t, int) : int;
86 get_height : fn(appstate, widget_common, t, int) : int;
87 get_property : fn(appstate, widget_common, t, bytes) : property;
88 reflow : fn(appstate, widget_common, t) : (appstate, widget_common, t);
89 redraw : fn(appstate, curses, widget_common, t) : curses;
90 get_cursor : fn(appstate, widget_common, t) : (int, int);
91 get_pivot : fn(appstate, widget_common, t) : (int, int);
92 accepts_key : fn(appstate, widget_common, t, event_keyboard) : bool;
93 process_event : fn(world, appstate, widget_common, t, wevent) : (world, appstate, widget_common, t);
102 implicit fn instance_eq_property : class_eq(property);
104 fn widget_get_app(app : appstate) : wid;
105 fn widget_get_common(app : appstate, id : wid) : widget_common;
106 fn widget_get_class(app : appstate, id : wid) : bytes;
107 fn widget_get_underlying(app : appstate, id : wid) : wid;
108 fn widget_get_pivot(app : appstate, id : wid) : (int, int);
109 fn widget_get_width(app : appstate, id : wid, pref_x : int) : int;
110 fn widget_get_height(app : appstate, id : wid, x : int) : int;
111 fn widget_get_property(app : appstate, id : wid, prop : bytes) : property;
112 fn widget_send_async_event(w : world, app : appstate, id : wid, wev : wevent) : world;
113 fn widget_get_async_event_function(w : world, app : appstate, id : wid) : fn(ww : world, wev : wevent) : world;
114 fn widget_get_async_event_exchange_function(w : world, app : appstate, id : wid) : fn(ww : world, wev : wevent) : world;
115 fn widget_relative_mouse_coords(app : appstate, id : wid, m : event_mouse) : (int, int);
116 fn widget_enqueue_events(app : appstate, wev : list(wid_wevent)) : appstate;
117 fn widget_enqueue_event(app : appstate, id : wid, wev : wevent) : appstate;
118 fn widget_enqueue_event_to_underlying(app : appstate, id : wid, wev : wevent) : appstate;
119 fn widget_move(app : appstate, id : wid, x y size_x size_y off_x off_y : int) : appstate;
120 fn widget_place(app : appstate, id : wid, offs_x : int, size_x : int, yp : int) : (appstate, int);
121 fn widgets_get_width(app : appstate, ids : list(wid), x_space : int, x : int) : int;
122 fn widgets_place(app : appstate, ids : list(wid), align : widget_align, x_space y_space : int, offs_x : int, size_x : int, yp : int) : (appstate, int);
123 fn widget_is_top(app : appstate, id : wid) : bool;
124 fn widget_redraw_subwidgets(app : appstate, curs : curses, com : widget_common) : curses;
125 fn widget_get_cursor(implicit app : appstate, id : wid) : (int, int);
126 fn widget_accepts_key(app : appstate, id : wid, wev : wevent) : bool;
127 fn widget_should_forward(app : appstate, com : widget_common, wev : wevent) : bool;
128 fn widget_activate(w : world, app : appstate, com : widget_common, id : wid) : (world, appstate, widget_common);
129 fn widget_forward(w : world, app : appstate, com : widget_common, wev : wevent) : (world, appstate, widget_common);
130 fn widget_new(w : world, app : appstate, parent : wid, const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t), flt : bool) : (world, appstate, wid);
131 fn widget_new_window(w : world, app : appstate, const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t), flt : bool) : (world, appstate, wid);
132 fn widget_destroy_onclick(app : appstate, id : wid) : appstate;
134 fn property_get(app : appstate, name : bytes) : property;
135 fn property_set(app : appstate, name : bytes, value : property) : appstate;
136 fn property_observe(app : appstate, id : wid, name : bytes) : appstate;
137 fn property_serialize(p : property) : bytes;
138 fn property_deserialize(b : bytes) : property;
139 fn property_save(app : appstate, names : list(bytes)) : bytes;
140 fn property_load(implicit app : appstate, names : list(bytes), str : bytes) : appstate;
142 fn properties_backup(app : appstate, names : list(bytes)) : appstate;
143 fn properties_revert(app : appstate, names : list(bytes)) : appstate;
145 fn new_property_attrib(fg_r fg_g fg_b bg_r bg_g bg_b : uint16, attr mono : uint8) : property_attrib;
146 fn property_get_attrib(app : appstate, name : bytes, fg_r fg_g fg_b bg_r bg_g bg_b : uint16, mono attr : uint8) : property_attrib;
147 fn property_set_attrib(a : property_attrib, curs : curses) : curses;
149 fn app_suspend(w : world, app : appstate) : (world, appstate);
150 fn app_resume(w : world, app : appstate) : (world, appstate);
151 fn app_run(w : world, d : dhandle, h : list(handle), env : treemap(bytes, bytes), const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t)) : world;
158 const wid_none : wid := -1;
159 fn wid_is_valid~inline(id : wid) : bool := id <> wid_none;
161 implicit fn instance_eq_wid : class_eq(wid) :=
163 equal : lambda(a b : wid) [ return a = b; ],
167 common : widget_common;
168 const cls : widget_class;
172 record widget_property [
174 observers : list(wid);
180 env : treemap(bytes, bytes);
181 widgets : list(widget);
185 events : list(wid_wevent);
186 properties : list(widget_property);
187 async_mq : msgqueue(wevent);
190 fn widget_get_app(app : appstate) : wid
195 fn widget_get_common(app : appstate, id : wid) : widget_common
197 return app.widgets[id].common;
200 fn widget_get_class(app : appstate, id : wid) : bytes
202 return app.widgets[id].cls.name;
205 fn widget_get_underlying(app : appstate, id : wid) : wid
207 const wi := app.widgets[id];
208 const pa := app.widgets[wi.common.parent];
210 for i := 0 to len(pa.common.sub_widgets) do [
211 if pa.common.sub_widgets[i] = id then
213 u := pa.common.sub_widgets[i];
215 abort internal("widget_get_underlying: widget not found in the parent list");
218 fn widget_get_pivot(app : appstate, id : wid) : (int, int)
220 const wi := app.widgets[id];
221 const pa := app.widgets[wi.common.parent];
222 var pivot_x, pivot_y := 0, 0;
223 for i := 0 to len(pa.common.sub_widgets) do [
224 if pa.common.sub_widgets[i] = id then [
225 return pivot_x, pivot_y;
227 const un := app.widgets[pa.common.sub_widgets[i]];
228 if not is_uninitialized_record(un.cls.get_pivot) then [
229 pivot_x, pivot_y := un.cls.get_pivot(app, un.common, un.state);
230 pivot_x += un.common.x;
231 pivot_y += un.common.y;
234 abort internal("widget_get_pivot: widget not found in the parent list");
237 fn widget_get_width(app : appstate, id : wid, pref_x : int) : int
239 const wi := app.widgets[id];
240 if is_uninitialized_record(wi.cls.get_width) then
242 return wi.cls.get_width(app, wi.common, wi.state, pref_x);
245 fn widget_get_height(app : appstate, id : wid, x : int) : int
247 const wi := app.widgets[id];
248 if is_uninitialized_record(wi.cls.get_height) then
250 return wi.cls.get_height(app, wi.common, wi.state, x);
253 fn widget_send_async_event(implicit w : world, app : appstate, id : wid, wev : wevent) : world
255 msgqueue_send(app.async_mq, id, wev);
258 fn widget_get_async_event_function(w : world, app : appstate, id : wid) : fn(ww : world, wev : wevent) : world
260 return lambda(ww : world, wev : wevent) : world [
261 return msgqueue_send(ww, wevent, app.async_mq, id, wev);
265 fn widget_get_async_event_exchange_function(w : world, app : appstate, id : wid) : fn(ww : world, wev : wevent) : world
267 return lambda(ww : world, wev : wevent) : world [
268 var nw, xtag, xev := msgqueue_receive_tag_nonblock(ww, wevent, app.async_mq, id);
270 return msgqueue_send(ww, wevent, app.async_mq, id, wev);
274 fn widget_get_property(app : appstate, id : wid, prop : bytes) : property
276 const wi := app.widgets[id];
277 if is_uninitialized_record(wi.cls.get_property) then
279 return wi.cls.get_property(app, wi.common, wi.state, prop);
282 fn widget_relative_mouse_coords(app : appstate, id : wid, m : event_mouse) : (int, int)
286 while id <> wid_none do [
287 var com := app.widgets[id].common;
297 fn widget_enqueue_events(implicit app : appstate, wev : list(wid_wevent)) : appstate
299 app.events := wev + app.events;
302 fn widget_enqueue_event(implicit app : appstate, id : wid, wev : wevent) : appstate
304 return widget_enqueue_events([ wid_wevent.[
310 fn widget_enqueue_event_to_underlying(implicit app : appstate, id : wid, wev : wevent) : appstate
312 var u := widget_get_underlying(id);
313 if wid_is_valid(u) then
314 widget_enqueue_event(u, wev);
317 fn widget_move(implicit app : appstate, id : wid, x y size_x size_y off_x off_y : int) : appstate
319 app.widgets[id].common.x := x;
320 app.widgets[id].common.y := y;
321 app.widgets[id].common.size_x := size_x;
322 app.widgets[id].common.size_y := size_y;
323 app.widgets[id].common.off_x := off_x;
324 app.widgets[id].common.off_y := off_y;
325 if not is_uninitialized_record(app.widgets[id].cls.reflow) then [
326 mutable wi := app.widgets[id];
327 wi.common, wi.state := wi.cls.reflow(wi.common, wi.state);
328 app.widgets[id] := wi;
332 fn widget_place(implicit app : appstate, id : wid, offs_x : int, size_x : int, yp : int) : (appstate, int)
334 var ys := widget_get_height(id, size_x);
335 widget_move(id, offs_x, yp, size_x, ys, 0, 0);
339 record widget_flow_line [
346 fn widgets_flow(implicit app : appstate, ids : list(wid), x_space : int, pref_x : int) : list(widget_flow_line)
348 var flow := empty(widget_flow_line);
349 var flow_line := widget_flow_line.[ ids : empty(wid), x : empty(int), width : empty(int), x_size : 0 ];
351 for i := 0 to len(ids) do [
352 var l := widget_get_width(ids[i], pref_x);
353 if x > 0, x + x_space + l > pref_x then [
355 flow_line := widget_flow_line.[ ids : empty(wid), x : empty(int), width : empty(int), x_size : 0 ];
358 x += select(x > 0, 0, x_space);
359 flow_line.ids +<= ids[i];
361 flow_line.width +<= l;
363 flow_line.x_size := x;
370 fn widgets_get_width(implicit app : appstate, ids : list(wid), x_space : int, pref_x : int) : int
372 var flow := widgets_flow(ids, x_space, pref_x);
375 for i := 0 to len(flow) do [
376 max_x := max(max_x, flow[i].x_size);
382 fn widgets_place(implicit app : appstate, ids : list(wid), align : widget_align, x_space y_space : int, offs_x : int, size_x : int, yp : int) : (appstate, int)
384 var flow := widgets_flow(ids, x_space, size_x);
385 for i := 0 to len(flow) do [
388 var flow_line := flow[i];
389 var x_offset := offs_x;
390 if align is center then
391 x_offset += size_x - flow_line.x_size shr 1;
392 if align is right then
393 x_offset += size_x - flow_line.x_size;
395 for j := 0 to len(flow_line.ids) do [
396 var yh := widget_get_height(flow_line.ids[j], flow_line.width[j]);
397 widget_move(flow_line.ids[j], x_offset + flow_line.x[j], yp, flow_line.width[j], yh, 0, 0);
398 y_size := max(y_size, yh);
405 fn widget_get_top(implicit app : appstate, id : wid) : wid
407 if not wid_is_valid(id) then
409 const wi := app.widgets[id];
410 var idx := len(wi.common.sub_widgets) - 1;
411 while idx >= 0, app.widgets[wi.common.sub_widgets[idx]].common.flt do
414 return wi.common.sub_widgets[idx];
418 fn widget_is_top(implicit app : appstate, id : wid) : bool
420 var top := widget_get_top(app.widgets[id].common.parent);
424 fn wi_to_rect(com : widget_common) : rect
426 return rect.[ x1 : com.x, x2 : com.x + com.size_x, y1 : com.y, y2 : com.y + com.size_y ];
429 fn widget_redraw_subwidgets(implicit app : appstate, implicit curs : curses, com : widget_common) : curses
431 for i := 0 to len(com.sub_widgets) do [
432 var id := com.sub_widgets[i];
433 const wi := app.widgets[id];
434 var sc := curses_get_scissors();
435 sc := rect_intersection(sc, wi_to_rect(wi.common));
437 for j := i + 1 to len(com.sub_widgets) do [
438 var id2 := com.sub_widgets[j];
439 const wi2 := app.widgets[id2];
440 var r := wi_to_rect(wi2.common);
441 rs := rect_set_exclude(rs, r);
443 var r := rect_set_to_rect(rs);
444 curses_restrict_viewport(r.x1, r.x2, r.y1, r.y2, wi.common.x, wi.common.y);
445 if curses_valid_viewport() then
446 wi.cls.redraw(wi.common, wi.state);
447 curses_revert_viewport();
451 fn widget_get_cursor(implicit app : appstate, id : wid) : (int, int)
453 var cx, cy := -1, -1;
456 const wi := app.widgets[id];
457 xo += wi.common.x + wi.common.off_x;
458 yo += wi.common.y + wi.common.off_y;
459 if not is_uninitialized_record(wi.cls.get_cursor) then [
460 var x, y := wi.cls.get_cursor(wi.common, wi.state);
461 if x >= 0, y >= 0 then
462 cx, cy := x + xo, y + yo;
465 id := widget_get_top(id);
466 if wid_is_valid(id) then
471 fn set_cursor(implicit app : appstate, implicit curs : curses) : curses
473 curses_set_cursor(widget_get_cursor(app.wm_wid));
476 fn widget_accepts_key(implicit app : appstate, id : wid, wev : wevent) : bool
478 if wev is keyboard then [
479 const wi := app.widgets[id];
480 if is_uninitialized_record(wi.cls.accepts_key) then
482 return wi.cls.accepts_key(wi.common, wi.state, wev.keyboard);
487 fn widget_should_forward(implicit app : appstate, implicit com : widget_common, wev : wevent) : bool
489 var l := len(com.sub_widgets);
492 return widget_accepts_key(com.sub_widgets[l - 1], wev);
495 fn widget_select(implicit w : world, implicit app : appstate, implicit com : widget_common, wev : wevent) : (world, appstate, widget_common)
497 var l := len(com.sub_widgets);
498 var old_top := com.sub_widgets[l - 1];
500 if wev is keyboard then [
501 if wev.keyboard.key = key_right or wev.keyboard.key = key_down or (wev.keyboard.key = key_tab and wev.keyboard.flags = 0) then [
505 if wev.keyboard.key = key_left or wev.keyboard.key = key_up or (wev.keyboard.key = key_tab and wev.keyboard.flags <> 0) then [
510 if wev is mouse, wev.mouse.buttons <> 0 or wev.mouse.prev_buttons <> 0 or wev.mouse.wx <> 0 or wev.mouse.wy <> 0 then [
512 var mx, my := widget_relative_mouse_coords(com.self, wev.mouse);
514 sw := com.sub_widgets[i];
516 x1 : app.widgets[sw].common.x,
517 x2 : app.widgets[sw].common.x + app.widgets[sw].common.size_x,
518 y1 : app.widgets[sw].common.y,
519 y2 : app.widgets[sw].common.y + app.widgets[sw].common.size_y,
521 if app.widgets[sw].cls.is_selectable, rect_test_point(r, mx, my) then
526 while com.sub_widgets[l - 1] <> sw do [
527 com.sub_widgets := [ com.sub_widgets[l - 1] ] + com.sub_widgets[ .. l - 1];
529 widget_enqueue_event(sw, wev);
536 com.sub_widgets := [ com.sub_widgets[l - 1] ] + com.sub_widgets[ .. l - 1];
538 com.sub_widgets := com.sub_widgets[1 .. ] + [ com.sub_widgets[0] ];
540 if not app.widgets[com.sub_widgets[l - 1]].cls.is_selectable then
544 var new_top := com.sub_widgets[l - 1];
545 if old_top = new_top then
548 widget_enqueue_event(new_top, wevent.change_focus);
549 widget_enqueue_event(old_top, wevent.change_focus);
552 fn widget_activate(implicit w : world, implicit app : appstate, implicit com : widget_common, id : wid) : (world, appstate, widget_common)
554 var l := len(com.sub_widgets);
555 var old_top := com.sub_widgets[l - 1];
557 while com.sub_widgets[l - 1] <> id do [
558 com.sub_widgets := [ com.sub_widgets[l - 1] ] + com.sub_widgets[ .. l - 1];
561 var new_top := com.sub_widgets[l - 1];
562 if old_top = new_top then
565 widget_enqueue_event(new_top, wevent.change_focus);
566 widget_enqueue_event(old_top, wevent.change_focus);
569 fn widget_forward(implicit w : world, implicit app : appstate, implicit com : widget_common, wev : wevent) : (world, appstate, widget_common)
571 if widget_should_forward(wev) then [
572 widget_enqueue_event(com.sub_widgets[len(com.sub_widgets) - 1], wev);
575 if wev is keyboard or wev is mouse then [
580 fn widget_new(implicit w : world, implicit app : appstate, parent : wid, const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t), flt : bool) : (world, appstate, wid)
582 var id : wid := len(app.widgets);
583 app.widgets +<= widget.[
584 common : widget_common.[
594 sub_widgets : empty(wid),
600 mutable wi := widget.[
601 common : app.widgets[id].common,
605 app.widgets[id] := wi;
606 if wid_is_valid(parent) then [
607 var pos := len(app.widgets[parent].common.sub_widgets);
608 while pos > 0, app.widgets[app.widgets[parent].common.sub_widgets[pos - 1]].common.flt do
610 app.widgets[parent].common.sub_widgets := app.widgets[parent].common.sub_widgets[ .. pos] + [ id ] + app.widgets[parent].common.sub_widgets[pos .. ];
615 fn widget_new_window(implicit w : world, implicit app : appstate, const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t), flt : bool) : (world, appstate, wid)
617 var old_top := widget_get_top(app.wm_wid);
618 var id := widget_new(app.wm_wid, cls, init, flt);
619 widget_enqueue_event(id, wevent.resize.(event_resize.[
620 x : app.widgets[app.wm_wid].common.size_x,
621 y : app.widgets[app.wm_wid].common.size_y,
623 var new_top := widget_get_top(app.wm_wid);
624 if old_top <> new_top then [
625 if wid_is_valid(old_top) then
626 widget_enqueue_event(old_top, wevent.change_focus);
627 if wid_is_valid(new_top) then
628 widget_enqueue_event(new_top, wevent.change_focus);
633 fn widget_destroy_onclick(implicit app : appstate, id : wid) : appstate
635 if app.widgets[app.widgets[id].common.parent].cls.name = "dialog" then [
636 widget_enqueue_event(app.widgets[id].common.parent, wevent.close);
638 var events := empty(wid_wevent);
639 while app.widgets[id].cls.name = "menu" or app.widgets[id].cls.name = "mainmenu" do [
640 events +<= wid_wevent.[
644 id := widget_get_underlying(id);
646 widget_enqueue_events(events);
653 fn wm_init(implicit w : world, implicit app : appstate, id : wid) : (world, appstate, wm_state)
659 fn wm_redraw(implicit app : appstate, implicit curs : curses, com : widget_common, st : wm_state) : curses
661 widget_redraw_subwidgets(com);
664 fn wm_process_event(implicit w : world, implicit app : appstate, implicit com : widget_common, implicit st : wm_state, wev : wevent) : (world, appstate, widget_common, wm_state)
666 //eval debug("wm_process_event: received event");
667 if wev is resize then [
668 com.size_x := wev.resize.x;
669 com.size_y := wev.resize.y;
670 var wwl := empty(wid_wevent);
671 for i := 0 to len(com.sub_widgets) do [
673 id : com.sub_widgets[i],
679 wev : wevent.redraw.(event_redraw.[
686 widget_enqueue_events(wwl);
689 if len(com.sub_widgets) = 0 then
691 const sub := app.widgets[com.sub_widgets[len(com.sub_widgets) - 1]];
692 widget_enqueue_event(sub.common.self, wev);
695 const wm_class~flat := widget_class.[
698 is_selectable : true,
700 process_event : wm_process_event,
703 fn get_redraw_rect(implicit app : appstate, id : wid, r : rect) : rect
705 while id <> wid_none do [
706 var com := app.widgets[id].common;
712 if id <> wid_none then [
713 var com := app.widgets[id].common;
723 fn do_redraw(implicit app : appstate, r : rect) : appstate
725 const wm := app.widgets[app.wm_wid];
726 implicit curs := app.curs;
727 curses_restrict_viewport(r.x1, r.x2, r.y1, r.y2, 0, 0);
728 wm.cls.redraw(wm.common, wm.state);
729 curses_revert_viewport();
733 fn delete_widget(implicit app : appstate, id : wid) : appstate
735 const wi := app.widgets[id];
736 for i := 0 to len(wi.common.sub_widgets) do [
737 delete_widget(wi.common.sub_widgets[i]);
739 var parent := wi.common.parent;
740 var old_top := widget_get_top(parent);
741 const pa := app.widgets[parent];
742 for i := 0 to len(pa.common.sub_widgets) do [
743 if pa.common.sub_widgets[i] = id then [
744 app.widgets[parent].common.sub_widgets := app.widgets[parent].common.sub_widgets[ .. i] + app.widgets[parent].common.sub_widgets[i + 1 .. ];
745 app.widgets[id] := uninitialized(widget);
746 var new_top := widget_get_top(parent);
747 if old_top <> new_top then [
748 if wid_is_valid(old_top) then
749 widget_enqueue_event(old_top, wevent.change_focus);
750 if wid_is_valid(new_top) then
751 widget_enqueue_event(new_top, wevent.change_focus);
756 abort internal("delete_widget: widget not found in the parent list");
759 fn property_equal(a b : property) : bool
761 if ord a <> ord b then
778 return a.a.fg_r = b.a.fg_r and
779 a.a.fg_g = b.a.fg_g and
780 a.a.fg_b = b.a.fg_b and
781 a.a.bg_r = b.a.bg_r and
782 a.a.bg_g = b.a.bg_g and
783 a.a.bg_b = b.a.bg_b and
784 a.a.attr = b.a.attr and
788 abort internal("property_equal: impossible option type " + ntos(ord a));
791 implicit fn instance_eq_property : class_eq(property) :=
793 equal : property_equal,
796 fn property_to_id(name : bytes) : int
799 for i := 0 to len(name) do [
801 result := (result shl 8) + c;
806 fn property_get(app : appstate, name : bytes) : property
808 var n := property_to_id(name);
809 var p := app.properties[n].val;
810 if is_uninitialized(p) then
813 var s := property_serialize(p);
814 var p2 := property_deserialize(s);
816 abort internal("error deserializing " + ntos(ord p) + " -> " + ntos(ord p2));
821 fn property_set(implicit app : appstate, name : bytes, value : property) : appstate
823 var n := property_to_id(name);
824 if is_uninitialized(app.properties[n]) then [
825 app.properties[n] := widget_property.[
827 observers : empty(wid),
831 if app.properties[n].val <> value then [
832 app.properties[n].val := value;
835 while i < len(app.properties[n].observers) do [
836 var id := app.properties[n].observers[i];
837 if is_uninitialized(app.widgets[id]) then [
838 app.properties[n].observers := app.properties[n].observers[ .. i] + app.properties[n].observers[i + 1 .. ];
841 widget_enqueue_event(id, wevent.property_changed.(name));
847 fn property_observe(app : appstate, id : wid, name : bytes) : appstate
849 var n := property_to_id(name);
850 if is_uninitialized(app.properties[n]) then [
851 app.properties[n] := widget_property.[
853 observers : empty(wid),
856 app.properties[n].observers +<= id;
860 fn property_serialize(p : property) : bytes
865 return "o:" + select(p.o, "0", "1");
867 return "i:" + ntos(p.i);
869 return "r:" + ntos(p.r.num) + "." + ntos(p.r.den);
885 r += "(" + property_serialize(x) + ")";
889 return "a:" + ntos(p.a.fg_r) + "." + ntos(p.a.fg_g) + "." + ntos(p.a.fg_b) + "." +
890 ntos(p.a.bg_r) + "." + ntos(p.a.bg_g) + "." + ntos(p.a.bg_b) + "." +
891 ntos(p.a.attr) + "." + ntos(p.a.mono);
894 return "w:" + ntos(p.w);
895 abort internal("property_serialize: impossible option type " + ntos(ord p));
898 fn property_deserialize(b : bytes) : property
903 return property.o.(false);
905 return property.o.(true);
906 if b[0] = 'i', b[1] = ':' then
907 return property.i.(ston(b[2 .. ]));
908 if b[0] = 'r', b[1] = ':' then [
910 var idx := list_search(b, '.');
911 return property.r.(rational.[ num : ston(b[ .. idx]), den : ston(b[idx + 1 .. ])]);
913 if b[0] = 'b', b[1] = ':' then [
914 var l := list_break(b[2 .. ], '.');
915 var res := empty(byte);
918 return property.b.(res);
920 if b[0] = 's', b[1] = ':' then [
921 var l := list_break(b[2 .. ], '.');
922 var res := empty(char);
925 return property.s.(res);
927 if b[0] = 'l', b[1] = ':' then [
928 var res := empty(property);
930 while len_greater_than(b, 0) do [
933 for i := 0 to len(b) do [
946 res +<= property_deserialize(b[1 .. idx]);
949 return property.l.(res);
951 if b[0] = 'a', b[1] = ':' then [
952 var l := list_break(b[2 .. ], '.');
953 var n := map(l, ston);
954 return property.a.(property_attrib.[
965 if b[0] = 'w', b[1] = ':' then [
966 return property.w.(ston(b[2 .. ]));
971 fn property_save(implicit app : appstate, names : list(bytes)) : bytes
975 var p := property_get(n);
976 var s := property_serialize(p);
977 saved += n + ":" + s + nl;
982 fn property_load(implicit app : appstate, names : list(bytes), str : bytes) : appstate
984 var ts := treeset_from_list(names);
985 var lines := list_break_to_lines(str);
986 for line in lines do [
987 var c := list_search(line, ':');
990 var name := line[ .. c];
991 if not treeset_test(ts, name) then
993 var prop := property_deserialize(line[c + 1 .. ]);
994 if not is_exception prop then
995 property_set(name, prop);
999 fn properties_backup(implicit app : appstate, names : list(bytes)) : appstate
1001 for i := 0 to len(names) do [
1002 property_set("backup-" + names[i], property_get(names[i]));
1006 fn properties_revert(implicit app : appstate, names : list(bytes)) : appstate
1008 for i := 0 to len(names) do [
1009 property_set(names[i], property_get("backup-" + names[i]));
1013 fn new_property_attrib(fg_r fg_g fg_b bg_r bg_g bg_b : uint16, attr mono : uint8) : property_attrib
1015 return property_attrib.[
1027 fn property_get_attrib(implicit app : appstate, name : bytes, fg_r fg_g fg_b bg_r bg_g bg_b : uint16, attr mono : uint8) : property_attrib
1029 var p := property_get(name);
1033 return new_property_attrib(fg_r, fg_g, fg_b, bg_r, bg_g, bg_b, attr, mono);
1036 fn property_set_attrib(a : property_attrib, implicit curs : curses) : curses
1038 if curses_n_colors() >= 16 then [
1039 curses_set_fgcolor(a.fg_r, a.fg_g, a.fg_b);
1040 curses_set_bgcolor(a.bg_r, a.bg_g, a.bg_b);
1041 curses_set_attributes(a.attr);
1043 curses_set_fgcolor(#aaaa, #aaaa, #aaaa);
1044 curses_set_bgcolor(#0000, #0000, #0000);
1045 curses_set_attributes(a.mono);
1049 fn app_suspend(implicit w : world, implicit app : appstate) : (world, appstate)
1051 curses_done(app.curs);
1052 app.curs := exception_make(curses, ec_sync, error_record_field_not_initialized, 0, false);
1055 fn app_resume(implicit w : world, implicit app : appstate) : (world, appstate)
1057 app.curs := curses_init(app.d, app.h, app.env);
1060 fn app_run(implicit w : world, d : dhandle, h : list(handle), env : treemap(bytes, bytes), const cls : widget_class, init : fn(world, appstate, wid) : (world, appstate, cls.t)) : world
1062 var async_mq := msgqueue_new(wevent);
1063 implicit app := appstate.[
1067 widgets : empty(widget),
1069 events : empty(wid_wevent),
1070 properties : infinite_uninitialized(widget_property),
1071 async_mq : async_mq,
1076 app.wm_wid := widget_new(wid_none, wm_class, wm_init, false);
1078 app.app_wid := widget_new(app.wm_wid, cls, init, false);
1080 //property_set("test", property.b.("hello"));
1081 //var val := property_get("test");
1082 //eval debug(val.b);
1083 //sleep(w, 1000000);
1087 while len_greater_than(app.events, 0) do [
1088 var ww := app.events[0];
1089 app.events := app.events[1 .. ];
1090 if ww.id >= 0, is_uninitialized(app.widgets[ww.id]) then
1092 if ww.wev is quit then [
1096 if ww.wev is redraw then [
1098 x1 : ww.wev.redraw.x1,
1099 x2 : ww.wev.redraw.x2,
1100 y1 : ww.wev.redraw.y1,
1101 y2 : ww.wev.redraw.y2,
1103 r := get_redraw_rect(ww.id, r);
1107 if ww.wev is close then [
1110 x2 : app.widgets[ww.id].common.size_x,
1112 y2 : app.widgets[ww.id].common.size_y,
1114 r := get_redraw_rect(ww.id, r);
1115 delete_widget(ww.id);
1119 if not is_uninitialized_record(app.widgets[ww.id].cls.process_event) then [
1120 mutable wi := app.widgets[ww.id];
1121 wi.common, wi.state := wi.cls.process_event(wi.common, wi.state, ww.wev);
1122 app.widgets[ww.id] := wi;
1127 app.curs := set_cursor(app.curs);
1129 app.curs, ev := curses_get_event(app.curs, wevent, async_mq);
1130 var wev := wevent.noop;
1131 if ev is altmq then [
1132 var id, wev := msgqueue_receive(async_mq);
1133 app.events := [ wid_wevent.[ id : id, wev : wev ] ];
1135 ] if ev is keyboard then [
1136 wev.keyboard := ev.keyboard;
1137 ] else if ev is mouse then [
1138 wev.mouse := ev.mouse;
1139 wev.mouse.x := max(min(wev.mouse.x, app.widgets[app.wm_wid].common.size_x - 1), 0);
1140 wev.mouse.y := max(min(wev.mouse.y, app.widgets[app.wm_wid].common.size_y - 1), 0);
1141 ] else if ev is redraw then [
1142 wev.redraw := ev.redraw;
1143 ] else if ev is resize then [
1144 wev.resize := ev.resize;
1145 ] else if ev is suspend then [
1146 var ctrl_z, ctrl_z_token := signal_handle("SIGTSTP");
1150 var b := tty_foreground();
1156 signal_unhandle(ctrl_z);
1160 app.events := [ wid_wevent.[ id : app.wm_wid, wev : wev ] ];
1162 app.curs := curses_update(app.curs);