aboutsummaryrefslogtreecommitdiff
path: root/src/ui_panel.jai
diff options
context:
space:
mode:
authorAlec Goncharow <alec@goncharow.dev>2026-05-27 14:34:32 -0400
committerAlec Goncharow <alec@goncharow.dev>2026-05-27 14:34:32 -0400
commit93ad8a5cdeb8421bcf02195bc470257935062138 (patch)
treebd76666718c27f3c79e015f6c9f7404a38663207 /src/ui_panel.jai
parent13335d5ee6778ad65d49a774274bae84a64a7edf (diff)
Working widget idea, makings of a maze app
Forgot to commit as I went over the weekend, which is fine, not a ton of interesting stuff just yet.
Diffstat (limited to 'src/ui_panel.jai')
-rw-r--r--src/ui_panel.jai252
1 files changed, 252 insertions, 0 deletions
diff --git a/src/ui_panel.jai b/src/ui_panel.jai
new file mode 100644
index 0000000..cc0a2e2
--- /dev/null
+++ b/src/ui_panel.jai
@@ -0,0 +1,252 @@
+// UI_Panel and UI_Element are loaded into main.jai so they inherit the global imports.
+
+UI_Element_Type :: enum { HEADER; BUTTON; TEXT_INPUT; DYNAMIC_TEXT; }
+
+UI_Element :: struct {
+ type: UI_Element_Type;
+ id: int;
+
+ x0, y0, x1, y1: float;
+ label: string;
+ input_buf: *[..] u8;
+
+ action: (userdata: *void) = null;
+}
+
+UI_Panel :: struct {
+ x, y, w, h: float;
+
+ is_dragging: bool;
+ drag_offset_x: float;
+ drag_offset_y: float;
+
+ is_collapsed: bool;
+ focus: int;
+ elements: [..] UI_Element;
+
+ app_owner: *void; // If non-null, panel is only visible when this app is active
+ userdata: *void;
+ font: *Simp.Dynamic_Font;
+}
+
+#scope_file
+active_panels: [..] *UI_Panel;
+text_edit: *[..] u8;
+
+#scope_export
+ui_register_panel :: (panel: *UI_Panel) {
+ array_add(*active_panels, panel);
+}
+
+ui_unregister_panel :: (panel: *UI_Panel) {
+ array_ordered_remove_by_value(*active_panels, panel);
+}
+
+ui_bring_to_front :: (panel: *UI_Panel) {
+ array_ordered_remove_by_value(*active_panels, panel);
+ array_add(*active_panels, panel);
+}
+
+ui_handle_input :: (active_app: *void) -> (occludes: bool) {
+ occludes := false;
+ mouse_x, mouse_y, success := get_mouse_pointer_position(window, true);
+ if !success return false;
+ mouse_x_f := cast(float) mouse_x;
+ mouse_y_f := cast(float) mouse_y;
+
+ mouse_down := (Input.input_button_states[Input.Key_Code.MOUSE_BUTTON_LEFT] & Input.Key_Current_State.DOWN) != 0;
+
+ mouse_btn := false;
+ mouse_released := false;
+ dirty_text := false;
+ for Input.events_this_frame {
+
+ if it.type == {
+ case .KEYBOARD;
+
+ if it.key_pressed && it.key_code == .BACKSPACE {
+
+ if text_edit {
+ dirty_text = true;
+ // @FixMe this works fine for English characters since most of that character set
+ // is one-byte in UTF8, but will not work correctly for many other language characters.
+ if text_edit.count then pop(text_edit);
+ }
+ }
+
+ if it.key_code == .MOUSE_BUTTON_LEFT {
+ mouse_btn = true;
+ mouse_released = it.key_pressed == 0;
+ }
+
+ case .TEXT_INPUT;
+ if text_edit {
+ dirty_text = true;
+ value := it.utf32;
+
+ str := Unicode.character_utf32_to_utf8(value);
+ for 0..str.count-1 {
+ array_add(text_edit, str[it]);
+ }
+ free(str);
+ }
+ }
+ }
+
+ // Process dragging
+ for panel: active_panels {
+ if panel.app_owner != null && panel.app_owner != active_app continue;
+
+ if panel.is_dragging {
+ if mouse_down {
+ panel.x = mouse_x_f - panel.drag_offset_x;
+ panel.y = mouse_y_f - panel.drag_offset_y;
+ return true;
+ } else {
+ panel.is_dragging = false;
+ }
+ }
+ }
+
+ // Hit test from top to bottom (end of array to start)
+ for < panel: active_panels {
+ if panel.app_owner != null && panel.app_owner != active_app continue;
+
+ // if text input append to builder and rebuild string
+
+ hit_panel := false;
+ if panel.is_collapsed {
+ for elem: panel.elements {
+ if elem.type == .HEADER {
+ abs_x0 := panel.x + elem.x0;
+ abs_y0 := panel.y + elem.y0;
+ abs_x1 := panel.x + elem.x1;
+ abs_y1 := panel.y + elem.y1;
+ if mouse_x_f >= abs_x0 && mouse_x_f <= abs_x1 && mouse_y_f >= abs_y0 && mouse_y_f <= abs_y1 {
+ hit_panel = true;
+ }
+ break;
+ }
+ }
+ } else {
+ if mouse_x_f >= panel.x && mouse_x_f <= panel.x + panel.w &&
+ mouse_y_f >= panel.y && mouse_y_f <= panel.y + panel.h {
+ hit_panel = true;
+ }
+ }
+
+ if hit_panel {
+ if mouse_btn {
+ ui_bring_to_front(panel);
+
+ hit_element := false;
+
+ for * elem: panel.elements {
+ if panel.is_collapsed && elem.type != .HEADER continue;
+
+ abs_x0 := panel.x + elem.x0;
+ abs_y0 := panel.y + elem.y0;
+ abs_x1 := panel.x + elem.x1;
+ abs_y1 := panel.y + elem.y1;
+
+ if mouse_x_f >= abs_x0 && mouse_x_f <= abs_x1 && mouse_y_f >= abs_y0 && mouse_y_f <= abs_y1 {
+ if elem.type == .HEADER {
+ if mouse_x_f >= abs_x1 - 30.0 && mouse_released {
+ panel.is_collapsed = !panel.is_collapsed;
+ } else if !mouse_released {
+ panel.is_dragging = true;
+ panel.drag_offset_x = mouse_x_f - panel.x;
+ panel.drag_offset_y = mouse_y_f - panel.y;
+ }
+ } else if elem.type == .TEXT_INPUT {
+ panel.focus = elem.id;
+ text_edit = elem.input_buf;
+ } else if elem.type == .BUTTON && mouse_released {
+ if elem.action elem.action(panel.userdata);
+ }
+ hit_element = true;
+
+ break;
+ }
+ }
+
+
+ if !hit_element {
+ panel.focus = 0;
+ text_edit = null;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+ui_render :: (active_app: *void) {
+ mouse_x, mouse_y, _ := get_mouse_pointer_position(window, true);
+ mouse_x_f := cast(float) mouse_x;
+ mouse_y_f := cast(float) mouse_y;
+
+ Simp.set_shader_for_color(true);
+ Simp.immediate_begin();
+ Simp.immediate_set_2d_projection(window_width, window_height);
+
+ // Draw from bottom to top
+ for panel: active_panels {
+ if panel.app_owner != null && panel.app_owner != active_app continue;
+
+ if !panel.is_collapsed {
+ // Draw Panel Body Background
+ Simp.immediate_quad(panel.x, panel.y, panel.x + panel.w, panel.y + panel.h, .{0.15, 0.15, 0.15, 0.95});
+ }
+
+
+ for elem: panel.elements {
+ if panel.is_collapsed && elem.type != .HEADER continue;
+
+ abs_x0 := panel.x + elem.x0;
+ abs_y0 := panel.y + elem.y0;
+ abs_x1 := panel.x + elem.x1;
+ abs_y1 := panel.y + elem.y1;
+
+ is_hovered := mouse_x_f >= abs_x0 && mouse_x_f <= abs_x1 && mouse_y_f >= abs_y0 && mouse_y_f <= abs_y1;
+
+ if elem.type == .HEADER {
+ color := ifx is_hovered then Vector4.{0.3, 0.3, 0.3, 1.0} else Vector4.{0.2, 0.2, 0.2, 1.0};
+ Simp.immediate_quad(abs_x0, abs_y0, abs_x1, abs_y1, color);
+
+ // Collapse button
+ btn_color := ifx (is_hovered && mouse_x_f >= abs_x1 - 30.0) then Vector4.{0.8, 0.3, 0.3, 1.0} else Vector4.{0.4, 0.4, 0.4, 1.0};
+ Simp.immediate_quad(abs_x1 - 30.0, abs_y0 + 5.0, abs_x1 - 5.0, abs_y1 - 5.0, btn_color);
+
+ if panel.font {
+ Simp.draw_text(panel.font, cast(int)abs_x0 + 10, cast(int)abs_y0 + 6, elem.label, .{1,1,1,1});
+ Simp.draw_text(panel.font, cast(int)abs_x1 - 25, cast(int)abs_y0 + 6, "-", .{1,1,1,1});
+ }
+ } else if elem.type == .BUTTON {
+ color := ifx is_hovered then Vector4.{0.4, 0.4, 0.4, 1.0} else Vector4.{0.3, 0.3, 0.3, 1.0};
+ Simp.immediate_quad(abs_x0, abs_y0, abs_x1, abs_y1, color);
+ if panel.font Simp.draw_text(panel.font, cast(int)abs_x0 + 60, cast(int)abs_y0 + 6, elem.label, .{1,1,1,1});
+ } else if elem.type == .TEXT_INPUT {
+ color := ifx panel.focus == elem.id then Vector4.{0.4, 0.4, 0.8, 1.0}
+ else ifx is_hovered then Vector4.{0.3, 0.3, 0.3, 1.0} else Vector4.{0.2, 0.2, 0.2, 1.0};
+ Simp.immediate_quad(abs_x0, abs_y0, abs_x1, abs_y1, color);
+ if panel.font {
+ Simp.draw_text(panel.font, cast(int)abs_x0 + 10, cast(int)abs_y0 + 6, elem.label, .{1,1,1,1});
+
+ val: string;
+ val.data = elem.input_buf.data;
+ val.count = elem.input_buf.count;
+
+ // FIXME lol
+ offset_x := elem.label.count * 20;
+
+ Simp.draw_text(panel.font, cast(int)abs_x0 + offset_x, cast(int)abs_y0 + 6, val, .{1,1,1,1});
+ }
+ } else if elem.type == .DYNAMIC_TEXT {
+ if panel.font Simp.draw_text(panel.font, cast(int)abs_x0, cast(int)abs_y0, elem.label, .{0, 1, 0, 1});
+ }
+ }
+ }
+ Simp.immediate_flush();
+}