// 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; text_filter: (char: u32) -> bool = null; 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; text_filter: (char: u32) -> bool; #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_filter_for_nums :: (value: u32) -> bool { return value >= #char "0" && value <= #char "9"; } ui_clear_focus :: () { text_edit = null; text_filter = null; } 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; if text_filter { if !text_filter(value) { continue; } } 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; text_filter = elem.text_filter; } else if elem.type == .BUTTON && mouse_released { if elem.action elem.action(panel.userdata); } hit_element = true; break; } } if !hit_element { panel.focus = 0; ui_clear_focus(); } } return true; } else { if mouse_btn { panel.focus = 0; ui_clear_focus(); } } } 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(); }