From 8f19c837628f6da84bba1f05e03e9ff106a82263 Mon Sep 17 00:00:00 2001 From: Synthasmagoria Date: Thu, 30 Jul 2026 21:40:36 +0200 Subject: [PATCH] Element focus --- example/main.odin | 42 ++++++++++++++++----- example/raylib/raygui.odin | 1 + example/raylib/raygui/raygui.h | 12 +++--- ui.odin | 67 ++++++++++++++++++++++++++++------ 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/example/main.odin b/example/main.odin index 039823b..e1068af 100644 --- a/example/main.odin +++ b/example/main.odin @@ -13,7 +13,7 @@ v2 :: [2]f32 V2_ONE :: v2{1.0, 1.0} main :: proc() { - // rl.SetTraceLogLevel(.WARNING) + rl.SetTraceLogLevel(.WARNING) rl.InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION) defer rl.CloseWindow() rl.SetTargetFPS(WINDOW_FRAMERATE) @@ -22,11 +22,16 @@ main :: proc() { ui.init(&ctx, 4000) outer_container_config := ui.ElementConfiguration {margin = {4.0, 8.0}, padding = {12.0, 24.0}, color = ui.col(rl.RED), label = ""} + outer_container_margin_value_string_x := cstring(make([^]u8, 16)) + outer_container_margin_value_string_y := cstring(make([^]u8, 16)) inner_container_config := ui.ElementConfiguration {color = cast(ui.col)rl.GREEN, label = "", margin = {4.0, 4.0}} + inner_container_margin_value_string_x := cstring(make([^]u8, 16)) + inner_container_margin_value_string_y := cstring(make([^]u8, 16)) button_config := ui.ElementConfiguration {margin = {4.0, 4.0}} + button_margin_value_string_x := cstring(make([^]u8, 16)) + button_margin_value_string_y := cstring(make([^]u8, 16)) for !rl.WindowShouldClose() { - ui.start(&ctx, origin = {0, 0}, size = {WINDOW_WIDTH, WINDOW_HEIGHT}) for i in 0..<7 { ui.container_begin(dimensions = {ui.SIZE_AUTO, ui.Size_Percentage(0.1)}, config = inner_container_config) @@ -69,22 +74,27 @@ main :: proc() { defer rl.EndDrawing() rl.ClearBackground(rl.BLACK) - render_ui_elements(elements) + render_ui_elements(&ctx, elements) ui.start(&ctx, {0, 0}, {WINDOW_WIDTH, WINDOW_HEIGHT}) config := ui.ElementConfiguration{margin = V2_ONE * 4, padding = V2_ONE * 4, color = ui.col(rl.MAGENTA)} ui.container_begin({ui.Size_Percentage(0.3), ui.Size_Percentage(0.3)}, config) slider_config := config slider_config.label = "Outer container margin" - ui_slider_2d(1.0, 0.2, slider_config, &outer_container_config.margin, 0.0, 32.0) + ui_input_float_2d(1.0, 0.2, slider_config, &outer_container_config.margin, outer_container_margin_value_string_x, outer_container_margin_value_string_y) slider_config.label = "Inner container margin" - ui_slider_2d(1.0, 0.2, slider_config, &inner_container_config.margin, 0.0, 32.0) + ui_input_float_2d(1.0, 0.2, slider_config, &inner_container_config.margin, inner_container_margin_value_string_x, inner_container_margin_value_string_y) slider_config.label = "Button margin" - ui_slider_2d(1.0, 0.2, slider_config, &button_config.margin, 0.0, 32.0) + ui_input_float_2d(1.0, 0.2, slider_config, &button_config.margin, button_margin_value_string_x, button_margin_value_string_y) ui.container_end() debug_elements := ui.end() - render_ui_elements(debug_elements) + if rl.IsKeyPressed(.TAB) { + ui.focus_next_element(&ctx, {.InputFloat}) + fmt.println(ctx.element_focus_id) + } + + render_ui_elements(&ctx, debug_elements) } } @@ -98,7 +108,17 @@ ui_slider_2d :: proc(width, height: ui.Size_Percentage, config: ui.ElementConfig return container } -render_ui_elements :: proc(elements: []ui.Element) { +ui_input_float_2d :: proc(width, height: ui.Size_Percentage, config: ui.ElementConfiguration, value: ^v2, value_string_x, value_string_y: cstring) -> ^ui.Element { + container := ui.container_begin(dimensions = {width, height}, config = config) + dimensions := ui.Dimensions {ui.Size_Percentage(0.5), ui.Size_Percentage(1.0)} + ui.input_float(dimensions, {margin = config.margin}, {value_string = value_string_x, value = &value.x}) + ui.same_line() + ui.input_float(dimensions, {margin = config.margin}, {value_string = value_string_y, value = &value.y}) + ui.container_end() + return container +} + +render_ui_elements :: proc(ctx: ^ui.Context, elements: []ui.Element) { for element in elements { area := rl.Rectangle(element.area) switch element.type { @@ -108,9 +128,13 @@ render_ui_elements :: proc(elements: []ui.Element) { case .Button: rl.GuiButton(area, element.label) case .Slider: - data := element.data.float_input_data + data := element.data rl.GuiSlider(area, "", "", data.value, data.min, data.max) case .InputFloat: + data := element.data + if rl.GuiValueBoxFloat(area, element.label, data.value_string, data.value, ui.is_element_focused(ctx, element)) == 1 { + ui.focus_element(ctx, element) + } } } } diff --git a/example/raylib/raygui.odin b/example/raylib/raygui.odin index b45e5ec..fba81e1 100644 --- a/example/raylib/raygui.odin +++ b/example/raylib/raygui.odin @@ -280,6 +280,7 @@ foreign lib { GuiDropdownBox :: proc(bounds: Rectangle, text: cstring, active: ^c.int, editMode: bool) -> bool --- // Dropdown Box control, returns selected item GuiSpinner :: proc(bounds: Rectangle, text: cstring, value: ^c.int, minValue, maxValue: c.int, editMode: bool) -> c.int --- // Spinner control, returns selected value GuiValueBox :: proc(bounds: Rectangle, text: cstring, value: ^c.int, minValue, maxValue: c.int, editMode: bool) -> c.int --- // Value Box control, updates input text with numbers + GuiValueBoxFloat :: proc(bounds: Rectangle, text: cstring, text_value: cstring, value: ^c.float, editMode: bool) -> c.int --- // Value box control for float values GuiTextBox :: proc(bounds: Rectangle, text: cstring, textSize: c.int, editMode: bool) -> bool --- // Text Box control, updates input text GuiSlider :: proc(bounds: Rectangle, textLeft: cstring, textRight: cstring, value: ^f32, minValue: f32, maxValue: f32) -> c.int --- // Slider control, returns selected value diff --git a/example/raylib/raygui/raygui.h b/example/raylib/raygui/raygui.h index f7a1161..6e16ec2 100644 --- a/example/raylib/raygui/raygui.h +++ b/example/raylib/raygui/raygui.h @@ -4157,22 +4157,22 @@ int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) { // Draw hue bar:color bars // NOTE: Using DrawRectangleGradientEx(bounds, color1, color2, color2, color1); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 2*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 2*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 3*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 3*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 4*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 4*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); - DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 5*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, + DrawRectangleGradientEx(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 5*(bounds.height/6.0f), bounds.width, bounds.height/6.0f }, Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); } diff --git a/ui.odin b/ui.odin index 8866792..01af6c7 100644 --- a/ui.odin +++ b/ui.odin @@ -11,14 +11,13 @@ rect_get_tr :: #force_inline proc(rect: rect2) -> v2 {return {rect.x + rect.widt rect_get_br :: #force_inline proc(rect: rect2) -> v2 {return {rect.x + rect.width, rect.y + rect.height}} rect_get_bl :: #force_inline proc(rect: rect2) -> v2 {return {rect.x, rect.y + rect.height}} rect_get_size :: #force_inline proc(rect: rect2) -> v2 {return {rect.width, rect.height}} -rect_grow :: proc(rect: rect2, amount: v2) -> rect2 { - return {rect.x - amount.x / 2.0, rect.y - amount.y / 2.0, rect.width + amount.x, rect.height + amount.y} -} +rect_grow :: proc(rect: rect2, amount: v2) -> rect2 {return {rect.x - amount.x / 2.0, rect.y - amount.y / 2.0, rect.width + amount.x, rect.height + amount.y}} ctx: ^Context Context :: struct { origin: v2, + element_focus_id: int, containers: [dynamic]int, container_last_element: [dynamic]int, elements: [dynamic]Element, @@ -29,6 +28,7 @@ CONTAINER_STACK_SIZE :: 16 Element :: struct { area: rect2, + id: int, using config: ElementConfiguration, data: ElementData, type: ElementType, @@ -42,12 +42,19 @@ ElementConfiguration :: struct { } ElementData :: struct { - using float_input_data: ElementData_FloatInput, + min, max: f32, + value: ^f32, + value_string: cstring, +} + +ElementData_Slider :: struct { + min, max: f32, + value: ^f32, } ElementData_FloatInput :: struct { - min, max: f32, - value: ^f32, + value: ^f32, + value_string: cstring, } ElementType :: enum { @@ -57,6 +64,8 @@ ElementType :: enum { InputFloat, } +@rodata element_type_input := bit_set[ElementType] {.InputFloat} + Size_Pixels :: distinct f32 Size_Percentage :: distinct f32 Size_PercentageRemainder :: distinct f32 @@ -104,6 +113,37 @@ same_line :: proc() { ctx.same_line = true } +focus_element :: proc(ctx: ^Context, element: Element) { + ctx.element_focus_id = element.id +} + +unfocus_element :: proc(ctx: ^Context) { + ctx.element_focus_id = -1 +} + +is_element_focused :: proc(ctx: ^Context, element: Element) -> bool { + return ctx.element_focus_id == element.id +} + +focus_next_element :: proc(ctx: ^Context, allowed_focus := element_type_input) { + if ctx.element_focus_id == -1 { + return + } + if ctx.element_focus_id + 1 >= len(ctx.elements) { + ctx.element_focus_id = -1 + return + } + ctx.element_focus_id += 1 + for ctx.element_focus_id < len(ctx.elements) { + fmt.println(ctx.elements[ctx.element_focus_id].type) + if ctx.elements[ctx.element_focus_id].type in allowed_focus { + return + } + ctx.element_focus_id += 1 + } + ctx.element_focus_id = -1 +} + container_begin :: proc(dimensions: Dimensions, config: ElementConfiguration) -> ^Element { area := _resolve_element_area(dimensions, config.margin) element := _append_element({area = area, config = config, type = .Container, dimensions = dimensions}) @@ -158,14 +198,14 @@ button :: proc(dimensions: Dimensions, config: ElementConfiguration) -> ^Element dimensions = dimensions}) } -slider :: proc(dimensions: Dimensions, config: ElementConfiguration, data: ElementData_FloatInput) -> ^Element { +slider :: proc(dimensions: Dimensions, config: ElementConfiguration, data: ElementData_Slider) -> ^Element { assert_no_dimensions_percentage_in_auto_container(dimensions) area := _resolve_element_area(dimensions, config.margin) return _append_element({ area = area, config = config, type = .Slider, - data = {float_input_data = data}, + data = {max = data.max, min = data.min, value = data.value}, dimensions = dimensions}) } @@ -176,7 +216,7 @@ input_float :: proc(dimensions: Dimensions, config: ElementConfiguration, data: area = area, config = config, type = .InputFloat, - data = {float_input_data = data}, + data = {value = data.value, value_string = data.value_string}, dimensions = dimensions}) } @@ -190,8 +230,11 @@ get_element_inner_area :: proc(element: Element) -> rect2 { _append_element :: proc(element: Element) -> ^Element { append(&ctx.elements, element) - ctx.container_last_element[len(ctx.containers) - 1] = len(ctx.elements) - 1 - return &ctx.elements[len(ctx.elements) - 1] + last_element_index := len(ctx.elements) - 1 + last_element := &ctx.elements[last_element_index] + last_element.id = last_element_index + ctx.container_last_element[len(ctx.containers) - 1] = last_element_index + return last_element } _resolve_element_area :: proc(dimensions: Dimensions, margin: v2) -> rect2 { @@ -222,7 +265,7 @@ _resolve_element_outer_size :: proc(dimensions: Dimensions) -> v2 { _resolve_elemment_outer_size_axis :: proc(size: Size, axis: Axis) -> f32 { switch value in size { case Size_Pixels: - return cast(f32)value + return f32(value) case Size_Percentage: inner := get_element_inner_area(_container_current()^) container_size := inner.width if axis == .X else inner.height