From 93ad8a5cdeb8421bcf02195bc470257935062138 Mon Sep 17 00:00:00 2001 From: Alec Goncharow Date: Wed, 27 May 2026 14:34:32 -0400 Subject: 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. --- src/ui_panel.jai | 252 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 src/ui_panel.jai (limited to 'src/ui_panel.jai') 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(); +} -- cgit v1.2.3-70-g09d2