From 4a53a158e0b2499440383069112b81c72e326663 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 9 Mar 2024 13:55:47 -0500 Subject: [PATCH] Lefted text box test to its own widget proc, fixed overlapping widget interaction! --- code/logger.odin | 2 +- code/text.odin | 4 +- code/tick_render.odin | 48 ++++++++++++++++++++--- code/tick_update.odin | 88 ++++++++++++++----------------------------- code/ui.odin | 28 +++++++++----- code/ui_signal.odin | 51 ++++++++++++++++--------- code/ui_tests.odin | 6 +-- code/ui_widgets.odin | 17 +++++++++ 8 files changed, 146 insertions(+), 98 deletions(-) diff --git a/code/logger.odin b/code/logger.odin index 7ec663a..e7c249c 100644 --- a/code/logger.odin +++ b/code/logger.odin @@ -8,7 +8,7 @@ import str "core:strings" import "core:time" import core_log "core:log" -Max_Logger_Message_Width :: 80 +Max_Logger_Message_Width :: 300 LogLevel :: core_log.Level diff --git a/code/text.odin b/code/text.odin index 40534c7..4d5df93 100644 --- a/code/text.odin +++ b/code/text.odin @@ -89,7 +89,7 @@ draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, // Raylib's equivalent doesn't take a length for the string (making it a pain in the ass) // So this is a 1:1 copy except it takes Odin strings -measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> AreaSize +measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2 { px_size := math.round( points_to_pixels( font_size ) ) rl_font := to_rl_Font( font, font_size ) @@ -98,7 +98,7 @@ measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_D // Note(Ed) : raylib font size is in pixels so this is also. @static text_line_spacing : f32 = 15 - text_size : AreaSize + text_size : Vec2 if rl_font.texture.id == 0 || len(text) == 0 { return text_size diff --git a/code/tick_render.odin b/code/tick_render.odin index a04d6de..0e7e156 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -73,6 +73,18 @@ render :: proc() rl.DrawCircleV( cursor_pos, 10, Color_White_A125 ) } + ui := project.workspace.ui + + hot_box := zpl_hmap_get( ui.curr_cache, u64(ui.hot) ) + active_box := zpl_hmap_get( ui.curr_cache, u64(ui.active) ) + if hot_box != nil { + debug_text("Hot Box: %v", hot_box.label.str ) + } + if active_box != nil{ + debug_text("Active Box: %v", active_box.label.str ) + } + debug_text("Active Resizing: %v", ui.active_start_signal.resizing) + debug.draw_debug_text_y = 50 } //endregion Render Screenspace @@ -143,10 +155,36 @@ render_mode_2d :: proc() } rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color ) - rl.DrawRectangleRoundedLines( rect_padding, style.layout.corner_radii[0], 9, 2, Color_Debug_UI_Padding_Bounds ) - rl.DrawRectangleRoundedLines( rect_content, style.layout.corner_radii[0], 9, 2, Color_Debug_UI_Content_Bounds ) - rl.DrawCircleV( render_bounds.p0, 5, Color_Red ) - rl.DrawCircleV( render_bounds.p1, 5, Color_Blue ) + + line_thickness := 1 * (1 / cam.zoom) + + rl.DrawRectangleRoundedLines( rect_padding, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Padding_Bounds ) + rl.DrawRectangleRoundedLines( rect_content, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Content_Bounds ) + if .Mouse_Resizable in current.flags { + resize_border_width := cast(f32) get_state().config.ui_resize_border_width + + resize_percent_width := style.size * (1.0 / resize_border_width) + resize_border_non_range := add(current.computed.bounds, range2( + { resize_percent_width.x, -resize_percent_width.x }, + { -resize_percent_width.x, resize_percent_width.x })) + + render_resize := range2( + world_to_screen_pos(resize_border_non_range.min), + world_to_screen_pos(resize_border_non_range.max), + ) + rect_resize := rl.Rectangle { + render_resize.min.x, + render_resize.min.y, + render_resize.max.x - render_resize.min.x, + render_resize.max.y - render_resize.min.y, + } + rl.DrawRectangleRoundedLines( rect_resize, style.layout.corner_radii[0], 9, line_thickness, Color_Red ) + } + + // if current + + // rl.DrawCircleV( render_bounds.p0, 5, Color_Red ) + // rl.DrawCircleV( render_bounds.p1, 5, Color_Blue ) if len(current.text.str) > 0 { draw_text_string_cached( current.text, world_to_screen_pos(computed.text_pos), style.font_size, style.text_color ) @@ -163,7 +201,7 @@ render_mode_2d :: proc() rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed ) } - rl.DrawCircleV( { 0, 0 }, 1, Color_White ) + rl.DrawCircleV( { 0, 0 }, 1 * (1 / cam.zoom), Color_White ) rl.EndMode2D() } diff --git a/code/tick_update.odin b/code/tick_update.odin index 24017a8..e7ce935 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -197,87 +197,55 @@ update :: proc( delta_time : f64 ) -> b32 .Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, } + default_layout := UI_Layout { + anchor = {}, + // alignment = { 0.0, 0.5 }, + alignment = { 0.5, 0.5 }, + text_alignment = { 0.5, 0.5 }, + // alignment = { 1.0, 1.0 }, + // corner_radii = { 0.3, 0.3, 0.3, 0.3 }, + pos = { 0, 0 }, + size = { 200, 200 }, + } + frame_style_default := UI_Style { flags = frame_style_flags, bg_color = Color_BG_TextBox, font = default_font, font_size = 30, text_color = Color_White, + layout = default_layout, } - frame_style_disabled := UI_Style { - flags = frame_style_flags, - bg_color = Color_Frame_Disabled, - font = default_font, - font_size = 30, - text_color = Color_White, - } - frame_style_hovered := UI_Style { - flags = frame_style_flags, - bg_color = Color_Frame_Hover, - font = default_font, - font_size = 30, - text_color = Color_White, - } - frame_style_select := UI_Style { - flags = frame_style_flags, - bg_color = Color_Frame_Select, - font = default_font, - font_size = 30, - text_color = Color_White, - } + frame_theme := UI_StyleTheme { styles = { frame_style_default, - frame_style_disabled, - frame_style_hovered, - frame_style_select, + frame_style_default, + frame_style_default, + frame_style_default, }} + frame_theme.disabled.bg_color = Color_Frame_Disabled + frame_theme.hovered.bg_color = Color_Frame_Hover + frame_theme.focused.bg_color = Color_Frame_Select ui_style_theme( frame_theme ) - default_layout := UI_Layout { - anchor = {}, - // alignment = { 0.0, 0.5 }, - alignment = { 0.5, 0.5 }, - text_alignment = { 1.0, 1.0 }, - // alignment = { 1.0, 1.0 }, - corner_radii = { 0.3, 0.3, 0.3, 0.3 }, - pos = { 0, 0 }, - size = { 200, 200 }, - } - ui_set_layout( default_layout ) - // test_hover_n_click() - // test_draggable() - config.ui_resize_border_width = 2 + test_draggable() + + config.ui_resize_border_width = 20 // First box with text!!!! + when true { @static pos : Vec2 + style := ui_style_peek( .Default ) + ui_style_theme( { styles = { style, style, style, style, }} ) text := str_intern( "Lorem ipsum dolor sit amet") + font_size := 30 - text_size := measure_text_size( text.str, default_font, 30, 0 ) - - ui_style_theme( { styles = { - frame_style_default, - frame_style_default, - frame_style_default, - frame_style_default, - }}) - - layout := default_layout - layout.size = cast(Vec2) text_size - layout.size.y *= 4 - layout.size.x *= 1.5 - // layout.size.y *= 3.248 - // layout.size.x *= 1.348 - // layout.size.x *= 1.348 - layout.padding = ui_layout_padding( 30 ) - ui_set_layout( layout ) - - text_box := ui_widget( "TEXT BOX!", UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus } ) + text_box := ui_text("TEXT BOX!", text, 30, flags = { .Mouse_Clickable }) if text_box.first_frame { pos = text_box.style.layout.pos - text_box.text = text } if text_box.dragging { @@ -286,6 +254,8 @@ update :: proc( delta_time : f64 ) -> b32 text_box.style.layout.pos = pos } + + // test_draggable() } //endregion Imgui Tick diff --git a/code/ui.odin b/code/ui.odin index b1f048e..4cd1132 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -55,10 +55,12 @@ UI_AnchorPresets :: enum u32 { UI_BoxFlag :: enum u64 { Disabled, Focusable, + Click_To_Focus, Mouse_Clickable, + Mouse_Resizable, + Keyboard_Clickable, - Click_To_Focus, Scroll_X, Scroll_Y, @@ -164,7 +166,6 @@ UI_Signal :: struct { released : b8, dragging : b8, resizing : b8, - hovering : b8, cursor_over : b8, commit : b8, } @@ -281,17 +282,16 @@ UI_State :: struct { // flag_stack : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ), hot : UI_Key, - active_mouse : [MouseBtn.count] UI_Key, - active : UI_Key, hot_resizable : b32, - active_resizing : b32, // Locks the user into a resizing state for the active box until they release the active key + hot_start_style : UI_Style, + + active_mouse : [MouseBtn.count] UI_Key, + active : UI_Key, + active_start_signal : UI_Signal, clipboard_copy : UI_Key, last_clicked : UI_Key, - cursor_active_start : Vec2, - - hot_start_style : UI_Style, active_start_style : UI_Style, last_pressed_key : [MouseBtn.count] UI_Key, @@ -413,7 +413,7 @@ ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 { ui_drag_delta :: #force_inline proc "contextless" () -> Vec2 { using state := get_state() - return ui_cursor_pos() - state.ui_context.cursor_active_start + return ui_cursor_pos() - state.ui_context.active_start_signal.cursor_pos } ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) @@ -424,7 +424,11 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) curr_cache, prev_cache = swap( curr_cache, prev_cache ) if ui.active == UI_Key(0) { - ui.hot = UI_Key(0) + //ui.hot = UI_Key(0) + ui.active_start_signal = {} + } + if ui.hot == UI_Key(0) { + ui.hot_resizable = false } root = ui_box_make( {}, "root#001" ) @@ -475,6 +479,10 @@ ui_parent :: proc( ui : ^ UI_Box) { ui_parent_push( ui ) } +ui_style_peek :: proc( box_state : UI_StylePreset ) -> UI_Style { + return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] +} + ui_style_ref :: proc( box_state : UI_StylePreset ) -> (^ UI_Style) { return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] } diff --git a/code/ui_signal.odin b/code/ui_signal.odin index aab1479..3af368a 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -13,13 +13,15 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal signal.cursor_pos = ui_cursor_pos() signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds ) - resize_border_width := cast(f32) get_state().config.ui_resize_border_width + resize_border_width := cast(f32) get_state().config.ui_resize_border_width + resize_percent_width := box.style.size * (1.0 / resize_border_width) resize_border_non_range := add(box.computed.bounds, range2( - { resize_border_width, -resize_border_width }, - { -resize_border_width, resize_border_width })) + { resize_percent_width.x, -resize_percent_width.x }, + { -resize_percent_width.x, resize_percent_width.x })) within_resize_range := cast(b8) ! pos_within_range2( signal.cursor_pos, resize_border_non_range ) within_resize_range &= signal.cursor_over + within_resize_range &= .Mouse_Resizable in box.flags left_pressed := pressed( input.mouse.left ) left_released := released( input.mouse.left ) @@ -38,8 +40,6 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal ui.active_mouse[MouseBtn.Left] = box.key ui.last_pressed_key = box.key - - ui.cursor_active_start = signal.cursor_pos ui.active_start_style = box.style signal.pressed = true @@ -48,7 +48,6 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal if mouse_clickable && signal.cursor_over && left_released { - box.active_delta = 0 ui.active = UI_Key(0) ui.active_mouse[MouseBtn.Left] = UI_Key(0) @@ -95,20 +94,33 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal is_hot := ui.hot == box.key is_active := ui.active == box.key - if signal.cursor_over && - ui.hot == UI_Key(0) || is_hot && - ui.active == UI_Key(0) || is_active + if signal.cursor_over { - ui.hot = box.key - is_hot = true + hot_vacant := ui.hot == UI_Key(0) + active_vacant := ui.active == UI_Key(0) - ui.hot_start_style = box.style + if (hot_vacant || is_hot) && + (active_vacant || is_active) + { + // prev_hot := zpl_hmap_get( ui.prev_cache, u64(ui.hot) ) + // prev_hot_label := prev_hot != nil ? prev_hot.label.str : "" + // log( str_fmt_tmp("Detected HOT via CURSOR OVER: %v is_hot: %v is_active: %v prev_hot: %v", box.label.str, is_hot, is_active, prev_hot_label )) + ui.hot = box.key + is_hot = true + + ui.hot_start_style = box.style + } + } + else + { + is_hot = false + if ui.hot == box.key { + ui.hot = UI_Key(0) + } } - if ! is_active { - ui.hot_resizable = cast(b32) within_resize_range - } - signal.resizing = cast(b8) is_active && (within_resize_range || ui.active_resizing) + signal.resizing = cast(b8) is_active && (within_resize_range || ui.active_start_signal.resizing) + ui.hot_resizable = cast(b32) (is_hot && within_resize_range) || signal.resizing // State Deltas update if is_hot @@ -142,8 +154,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal box.disabled_delta = 0 } - ui.active_resizing = cast(b32) is_active && signal.resizing - signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_resizing) + signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_start_signal.resizing) // Update style if not in default state { @@ -160,6 +171,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal if ! was_active { box.prev_style = box.style box.style_delta = 0 + log( str_fmt_tmp("NEW ACTIVE: %v", box.label.str)) } box.style = stack_peek( & ui.theme_stack ).focused } @@ -184,5 +196,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal } } + if is_active && ! was_active { + ui.active_start_signal = signal + } return signal } diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 39235cf..6e3c968 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -23,7 +23,7 @@ test_draggable :: proc() state := get_state(); using state ui := ui_context - draggable := ui_widget( "Draggable Box!", UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus } ) + draggable := ui_widget( "Draggable Box!", UI_BoxFlags { .Mouse_Clickable, .Mouse_Resizable } ) if draggable.first_frame { debug.draggable_box_pos = draggable.style.layout.pos debug.draggable_box_size = draggable.style.layout.size @@ -40,14 +40,14 @@ test_draggable :: proc() og_layout := ui_context.active_start_style.layout center := debug.draggable_box_pos - original_distance := linalg.distance(ui.cursor_active_start, center) + original_distance := linalg.distance(ui.active_start_signal.cursor_pos, center) cursor_distance := linalg.distance(draggable.cursor_pos, center) scale_factor := cursor_distance * (1 / original_distance) debug.draggable_box_size = og_layout.size * scale_factor } - if ui.hot_resizable || ui.active_resizing { + if (ui.hot == draggable.key) && (ui.hot_resizable || ui.active_start_signal.resizing) { draggable.style.bg_color = Color_Blue } diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index 87d664b..a6c979a 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -20,3 +20,20 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge return } +ui_text :: proc( label : string, content : StringCached, font_size : f32 = 24, font := Font_Default, flags : UI_BoxFlags ) -> UI_Widget +{ + state := get_state(); using state + + font := font + if font == Font_Default { + font = default_font + } + text_size := measure_text_size( content.str, font, font_size, 0 ) + + box := ui_box_make( flags, "TEXT BOX!" ) + signal := ui_signal_from_box( box ) + + box.text = content + box.style.layout.size = text_size + return { box, signal } +}