From 1afe74b4b59d16a73429b338c867484d7d95b885 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 May 2024 02:08:36 -0400 Subject: [PATCH] Some cleanup, resize handles lifted to ui_resizeable_handles Fixed some bugs with the handles as well. Old cruft for resizing was removed. --- code/api.odin | 1 + code/colors.odin | 4 +- code/env.odin | 1 - code/grime_stack.odin | 8 +- code/grime_string_interning.odin | 15 +- code/logger.odin | 1 + code/tick_render.odin | 50 +++--- code/tick_update.odin | 266 +++++++++---------------------- code/ui.odin | 28 +--- code/ui_layout.odin | 6 +- code/ui_signal.odin | 75 +++++---- code/ui_style.odin | 65 ++++---- code/ui_style_themes.odin | 3 + code/ui_tests.odin | 36 +---- code/ui_widgets.odin | 149 ++++++++++++++++- 15 files changed, 347 insertions(+), 361 deletions(-) create mode 100644 code/ui_style_themes.odin diff --git a/code/api.odin b/code/api.odin index ee363e1..527ad1f 100644 --- a/code/api.odin +++ b/code/api.odin @@ -340,6 +340,7 @@ tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32 { // profile("Client tick timing processing") config.engine_refresh_hz = uint(monitor_refresh_hz) + // config.engine_refresh_hz = 10 frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS diff --git a/code/colors.odin b/code/colors.odin index a3f311b..18a7597 100644 --- a/code/colors.odin +++ b/code/colors.odin @@ -9,7 +9,7 @@ Color_Red :: rl.RED Color_White :: rl.WHITE Color_Transparent :: Color { 0, 0, 0, 0 } -Color_BG :: Color { 61, 61, 64, 255 } +Color_BG :: Color { 55, 55, 60, 255 } Color_BG_TextBox :: Color { 32, 32, 32, 180 } Color_BG_Panel :: Color { 32, 32, 32, 255 } Color_BG_Panel_Translucent :: Color { 32, 32, 32, 220 } @@ -21,7 +21,7 @@ Color_GreyRed :: Color { 220, 100, 100, 50 } Color_White_A125 :: Color { 255, 255, 255, 165 } Color_Black :: Color { 0, 0, 0, 255 } Color_Green :: Color { 0, 180, 0, 255 } -Color_ResizeHandle :: Color { 90, 90, 100, 255 } +Color_ResizeHandle :: Color { 20, 40, 50, 80 } Color_3D_BG :: Color { 188, 182 , 170, 255 } diff --git a/code/env.odin b/code/env.odin index c13f8ee..2f23161 100644 --- a/code/env.odin +++ b/code/env.odin @@ -10,7 +10,6 @@ import rl "vendor:raylib" Str_App_State := "App State" - Memory_App : Memory Memory_Base_Address_Persistent :: Terabyte * 1 diff --git a/code/grime_stack.odin b/code/grime_stack.odin index f6ea08f..9cdb0ed 100644 --- a/code/grime_stack.odin +++ b/code/grime_stack.odin @@ -10,14 +10,14 @@ StackFixed :: struct ( $ Type : typeid, $ Size : u32 ) { items : [ Size ] Type, } -stack_push :: proc( using stack : ^ StackFixed( $ Type, $ Size ), value : Type ) { +stack_push :: #force_inline proc( using stack : ^ StackFixed( $ Type, $ Size ), value : Type ) { verify( idx < len( items ), "Attempted to push on a full stack" ) items[ idx ] = value idx += 1 } -stack_pop :: proc( using stack : ^StackFixed( $ Type, $ Size ) ) { +stack_pop :: #force_inline proc( using stack : ^StackFixed( $ Type, $ Size ) ) { verify( idx > 0, "Attempted to pop an empty stack" ) idx -= 1 @@ -26,13 +26,13 @@ stack_pop :: proc( using stack : ^StackFixed( $ Type, $ Size ) ) { } } -stack_peek_ref :: proc( using stack : ^StackFixed( $ Type, $ Size ) ) -> ( ^Type) { +stack_peek_ref :: #force_inline proc "contextless" ( using stack : ^StackFixed( $ Type, $ Size ) ) -> ( ^Type) { last_idx := max( 0, idx - 1 ) if idx > 0 else 0 last := & items[last_idx] return last } -stack_peek :: proc ( using stack : ^StackFixed( $ Type, $ Size ) ) -> Type { +stack_peek :: #force_inline proc "contextless" ( using stack : ^StackFixed( $ Type, $ Size ) ) -> Type { last := max( 0, idx - 1 ) if idx > 0 else 0 return items[last] } diff --git a/code/grime_string_interning.odin b/code/grime_string_interning.odin index d91335b..66cbcb9 100644 --- a/code/grime_string_interning.odin +++ b/code/grime_string_interning.odin @@ -19,11 +19,13 @@ import "core:strings" StringKey :: distinct u64 RunesCached :: []rune -// TODO(Ed): Should this just track the key instead? (by default) StrRunesPair :: struct { str : string, runes : []rune, } +to_str_runes_pair :: proc ( content : string ) -> StrRunesPair { + return { content, to_runes(content) } +} StringCache :: struct { slab : Slab, @@ -65,8 +67,9 @@ str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) { return } -// str_cache_intern_string :: proc( - // cache : ^StringCache, +str_intern_key :: #force_inline proc( content : string ) -> StringKey { return cast(StringKey) crc32( transmute([]byte) content ) } +str_intern_lookup :: #force_inline proc( key : StringKey ) -> (^StrRunesPair) { return zpl_hmap_get( & get_state().string_cache.table, transmute(u64) key ) } + str_intern :: proc( content : string ) -> StrRunesPair @@ -74,8 +77,8 @@ str_intern :: proc( // profile(#procedure) cache := & get_state().string_cache - key := u64( crc32( transmute([]byte) content )) - result := zpl_hmap_get( & cache.table, key ) + key := str_intern_key(content) + result := zpl_hmap_get( & cache.table, transmute(u64) key ) if result != nil { return (result ^) } @@ -98,7 +101,7 @@ str_intern :: proc( slab_validate_pools( get_state().persistent_slab ) // result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) byte_slice(str_mem, length), runes } ) - result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) str_mem, runes } ) + result, alloc_error = zpl_hmap_set( & cache.table, transmute(u64) key, StrRunesPair { transmute(string) str_mem, runes } ) verify( alloc_error == .None, "String cache had a backing allocator error" ) slab_validate_pools( get_state().persistent_slab ) diff --git a/code/logger.odin b/code/logger.odin index 061889b..5605fbf 100644 --- a/code/logger.odin +++ b/code/logger.odin @@ -122,5 +122,6 @@ log :: proc( msg : string, level := LogLevel.Info, loc := #caller_location ) { } logf :: proc( fmt : string, args : ..any, level := LogLevel.Info, loc := #caller_location ) { + // context.allocator = transient_allocator() core_log.logf( level, fmt, args, location = loc ) } diff --git a/code/tick_render.odin b/code/tick_render.odin index a7a19da..79e31df 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -180,28 +180,6 @@ render_mode_2d_workspace :: proc() } // profile_end() - if .Mouse_Resizable in current.flags - { - // profile("Resize Bounds") - resize_border_width := cast(f32) get_state().config.ui_resize_border_width - resize_percent_width := computed_size * (resize_border_width * 1.0/ 200.0) - 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( - ws_view_to_render_pos(resize_border_non_range.min), - ws_view_to_render_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, - } - draw_rectangle_lines( rect_padding, style, Color_Red, line_thickness ) - } - point_radius := 3 * cam_zoom_ratio // profile_begin("circles") @@ -216,7 +194,7 @@ render_mode_2d_workspace :: proc() // profile_end() if len(current.text.str) > 0 { - ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), style.font_size, style.text_color ) + ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), style.layout.font_size, style.text_color ) } } } @@ -290,6 +268,7 @@ render_mode_screenspace :: proc () if debug.mouse_vis { debug_text("Mouse Vertical Wheel: %v", input.mouse.vertical_wheel ) + debug_text("Mouse Delta : %v", input.mouse.delta ) debug_text("Mouse Position (Render) : %v", input.mouse.raw_pos ) debug_text("Mouse Position (Surface) : %v", input.mouse.pos ) debug_text("Mouse Position (Workspace View): %v", surface_to_ws_view_pos(input.mouse.pos) ) @@ -299,18 +278,31 @@ render_mode_screenspace :: proc () ui := & project.workspace.ui - // debug_text("Box Count: %v", ui.built_box_count ) + debug_text("Box Count: %v", ui.built_box_count ) hot_box := ui_box_from_key( ui.curr_cache, ui.hot ) active_box := ui_box_from_key( ui.curr_cache, ui.active ) if hot_box != nil { - debug_text("Hot Box : %v", hot_box.label.str ) - // debug_text("Hot Range2: %v", hot_box.computed.bounds.pts) + debug_text("Worksapce Hot Box : %v", hot_box.label.str ) + debug_text("Workspace Hot Range2: %v", hot_box.computed.bounds.pts) } if active_box != nil{ - // debug_text("Active Box: %v", active_box.label.str ) + debug_text("Workspace Active Box: %v", active_box.label.str ) + } + + ui = & app_ui + + debug_text("Box Count: %v", ui.built_box_count ) + + hot_box = ui_box_from_key( ui.curr_cache, ui.hot ) + active_box = ui_box_from_key( ui.curr_cache, ui.active ) + if hot_box != nil { + debug_text("Hot Box : %v", hot_box.label.str ) + debug_text("Hot Range2: %v", hot_box.computed.bounds.pts) + } + if active_box != nil{ + debug_text("Active Box: %v", active_box.label.str ) } - // debug_text("Active Resizing: %v", ui.active_start_signal.resizing) view := view_get_bounds() // debug_text("View Bounds (World): %v", view.pts ) @@ -432,7 +424,7 @@ render_app_ui :: proc() // profile_end() if len(current.text.str) > 0 { - draw_text_screenspace( current.text, surface_to_render_pos(computed.text_pos), style.font_size, style.text_color ) + draw_text_screenspace( current.text, surface_to_render_pos(computed.text_pos), style.layout.font_size, style.text_color ) } } } diff --git a/code/tick_update.odin b/code/tick_update.odin index e95b5af..29d8529 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -64,7 +64,6 @@ frametime_delta32 :: #force_inline proc "contextless" () -> f32 { return cast(f32) get_state().frametime_delta_seconds } - update :: proc( delta_time : f64 ) -> b32 { profile(#procedure) @@ -210,7 +209,7 @@ update :: proc( delta_time : f64 ) -> b32 { fmt :: str_fmt_alloc - @static bar_pos := Vec2{} + @static bar_pos := Vec2{0, 100} bar_size := vec2( 400, 40 ) menu_bar : UI_Widget @@ -218,28 +217,28 @@ update :: proc( delta_time : f64 ) -> b32 theme := UI_Style { flags = { }, - bg_color = { 0, 0, 0, 30 }, + bg_color = { 0, 0, 0, 30 }, border_color = { 0, 0, 0, 200 }, font = default_font, - font_size = 12, text_color = Color_White, layout = UI_Layout { anchor = {}, border_width = 1.0, + font_size = 12, pos = bar_pos, size = range2( bar_size, {}), // padding = { 10, 10, 10, 10 }, }, } + ui_theme_via_style(theme) - menu_bar = ui_widget("App Menu Bar", UI_BoxFlags {} ) - menu_bar.text = { fmt("%v", bar_pos), {} } - menu_bar.text.runes = to_runes(menu_bar.text.str) + menu_bar = ui_widget("App Menu Bar", {} ) + menu_bar.text = to_str_runes_pair( fmt("%v", bar_pos)) if (menu_bar.first_frame) { - bar_pos = screen_get_corners().top_right - vec2(0, app_window.extent.y * 0.5) + // bar_pos = screen_get_corners().top_right - vec2(0, app_window.extent.y * 0.5) } } // Setup Children @@ -255,49 +254,43 @@ update :: proc( delta_time : f64 ) -> b32 bg_color = Color_Frame_Disabled, font = default_font, - font_size = 18, text_color = Color_White, layout = UI_Layout { anchor = range2( {0, 0}, {0.0, 0} ), alignment = { 0.0, 1.0 }, + font_size = 18, text_alignment = { 0.5, 0.5 }, pos = { 0, 0 }, size = range2( {25, bar_size.y}, {0, 0}) } } - theme := UI_StyleTheme { styles = { - style, - style, - style, - style, - }} + theme := to_ui_styletheme(style) theme.disabled.bg_color = Color_Frame_Disabled - theme.hot.bg_color = Color_White + theme.hot.bg_color = Color_Red theme.active.bg_color = Color_Frame_Select ui_style_theme(theme) move_box : UI_Widget { move_box = ui_button("Move Box") - if move_box.dragging { + if move_box.active { bar_pos += input.mouse.delta } } - // bar_pos = {0, 400} move_settings_spacer := ui_widget("Move-Settings Spacer", {}) - move_settings_spacer.text = str_intern("spacer") - move_settings_spacer.style.font_size = 10 - move_settings_spacer.style.bg_color = Color_Transparent - - // settings_btn : UI_Widget { - settings_btn = ui_button("Settings Btn") - settings_btn.text = str_intern("Settings") - settings_btn.style.flags = { - .Scale_Width_By_Height_Ratio, - } + using move_settings_spacer + text = str_intern("spacer") + style.bg_color = Color_Transparent + style.layout.font_size = 10 + } + + settings_btn = ui_button("Settings Btn") + settings_btn.text = str_intern("Settings") + settings_btn.style.flags = { + .Scale_Width_By_Height_Ratio, } // HBox layout calculation? @@ -319,175 +312,80 @@ update :: proc( delta_time : f64 ) -> b32 } } - @static settings_open := true - if settings_btn.left_clicked || settings_open + if settings_btn.pressed || settings_open { settings_open = true - @static pos := Vec2 {0, 0} - @static size := Vec2 { 600, 800 } - resize_border_width : f32 = 10 - - // Prototype for a resize box - // Will surround one box with a resize borders - // All sides can have their borders toggled - resize_box := ui_widget("Settings Menu: Resize Box", {}) - { - using resize_box - style.pos = pos - style.alignment = { 0.5, 0.5 } - style.bg_color = {} - style.size = range2( size, {}) + resize_border_width : f32 = 20 + @static pos := Vec2 {0, 0} + @static size := Vec2 { 200, 200 } + if size.x < 200 { + size.x = 200 } - ui_parent(resize_box) - - // Resize handles and corners - { - flags := UI_BoxFlags { .Mouse_Clickable, .Focusable } - - style_resize_width := UI_Style { - flags = { .Fixed_Width }, - size = range2({resize_border_width, 0}, {}), - bg_color = Color_ResizeHandle, - alignment = {0, 1}, - margins = { resize_border_width, resize_border_width, 0, 0 }, - } - style_resize_height := style_resize_width - style_resize_height.flags = {.Fixed_Height} - style_resize_height.size.min = {0, resize_border_width} - style_resize_height.margins = { 0, 0, resize_border_width, resize_border_width } - - ui_theme_via_style(style_resize_width) - left := ui_widget("Settings Menu: Resize Left Border", flags ) - right := ui_widget("Settings Menu: Resize Right Border", flags) - right.style.anchor.left = 1 - right.style.alignment = {1, 1} - - ui_theme_via_style(style_resize_height) - top := ui_widget("Settings Menu: Resize Top Border", flags ) - bottom := ui_widget("Settings Menu: Resize Bottom Border", flags) - bottom.style.anchor.top = 1 - bottom.style.alignment = {0, 0} - - style_corner := UI_Style { - flags = { .Fixed_Width, .Fixed_Height }, - size = range2({resize_border_width, resize_border_width}, {}), - bg_color = Color_Blue, - alignment = {0, 1} - } - ui_theme_via_style(style_corner) - corner_tl := ui_widget("Settings Menu: Corner TL", flags) - corner_tr := ui_widget("Settings Menu: Corner TR", flags) - corner_tr.style.anchor = range2({1, 0}, {}) - corner_tr.style.alignment = {1, 1} - - corner_bl := ui_widget("Settings Menu: Corner BL", flags) - corner_bl.style.anchor = range2({}, {0, 1}) - corner_bl.style.alignment = {} - corner_br := ui_widget("Settings Menu: Corner BR", flags) - corner_br.style.anchor = range2({1, 0}, {0, 1}) - corner_br.style.alignment = {1, 0} - - process_handle_drag :: #force_inline proc ( handle : ^UI_Widget, - side_scalar : f32, - size_axis : ^f32, - size_delta_axis : f32, - pos_axis : ^f32, - alignment_axis : ^f32, - alignment_while_dragging : f32 ) - { - if get_state().ui_context.last_pressed_key != handle.key { return } - - @static was_dragging := false - @static within_drag := false - - using handle.signal - if dragging - { - if ! was_dragging { - was_dragging = true - (pos_axis ^) += size_axis^ * 0.5 * side_scalar - } - (size_axis ^) += size_delta_axis * -side_scalar - (alignment_axis ^) = alignment_while_dragging - } - else if was_dragging && released - { - (pos_axis ^) += size_axis^ * 0.5 * -side_scalar - was_dragging = false - } - } - - process_handle_drag( & right , -1.0, & size.x, input.mouse.delta.x, & pos.x, & resize_box.style.alignment.x, 0) - process_handle_drag( & left, 1.0, & size.x, input.mouse.delta.x, & pos.x, & resize_box.style.alignment.x, 1) - process_handle_drag( & top, -1.0, & size.y, input.mouse.delta.y, & pos.y, & resize_box.style.alignment.y, 0) - process_handle_drag( & bottom, 1.0, & size.y, input.mouse.delta.y, & pos.y, & resize_box.style.alignment.y, 1) - - // process_corner_drag :: #force_inline proc() - { - - } + if size.y < 200 { + size.y = 200 } - - settings_menu := ui_widget("Settings Menu", {}) + settings_menu := ui_widget("Settings Menu", {.Mouse_Clickable}) { using settings_menu - style.alignment = { 0.0, 1.0 } - style.bg_color = Color_BG_Panel_Translucent - // style.border_width = 1.0 - // style.border_color = Color_Blue - style.margins = { - resize_border_width, - resize_border_width, - resize_border_width, - resize_border_width, } + style.flags = { + // .Origin_At_Anchor_Center + } + style.pos = pos + style.alignment = { 1.0, 0.5 } + style.bg_color = Color_BG_Panel_Translucent + style.size = range2( size, {}) } - ui_parent(settings_menu) + ui_parent(settings_menu) ui_theme_via_style({ - bg_color = Color_Transparent, - font = default_font, - font_size = 16, + bg_color = Color_Transparent, + font = default_font, text_color = Color_White, - size = range2({0, 40}, {0, 40}) // TODO(Ed): Implment ratio scaling for height + size = range2({0, 40}, {0, 40}), // TODO(Ed): Implment ratio scaling for height + layout = { font_size = 16 }, }) + ui_style_theme_ref().hot.bg_color = Color_Blue frame_bar := ui_widget("Settings Menu: Frame Bar", { .Mouse_Clickable, .Focusable, .Click_To_Focus }) { using frame_bar - style.bg_color = Color_BG_Panel - style.flags = {} + style.bg_color = Color_BG_Panel + style.flags = {} style.alignment = { 0, 1 } - // style.size = {} - style.anchor = range2( {0, 0.95}, {0, 0} ) - ui_parent(frame_bar) + } + ui_parent(frame_bar) - if dragging { - pos += input.mouse.delta - } + if frame_bar.active { + pos += input.mouse.delta + } - title := ui_text("Settings Menu: Title", str_intern("Settings Menu")) - { - using title - style.alignment = {0, 1} - style.margins = { 0, 0, 15, 0} - style.text_alignment = {0.0 , 0.5} - } + title := ui_text("Settings Menu: Title", str_intern("Settings Menu"), {.Disabled}) + { + using title + style.alignment = {0, 1} + style.margins = { 0, 0, 15, 0} + style.text_alignment = {0 , 0.5} + } - close_btn := ui_button("Settings Menu: Close Btn") - { - using close_btn - text = str_intern("close") - style.bg_color = Color_GreyRed - style.size.min = {50, 0} - style.anchor = range2( {1.0, 0}, {}) - style.alignment = {1, 1} - style.text_alignment = {0.5, 0.5} - if close_btn.pressed { - settings_open = false - } + ui_style_theme(ui_style_theme_peek()) + ui_style_theme_ref().default.bg_color = Color_GreyRed + ui_style_theme_ref().hot. bg_color = Color_Red + close_btn := ui_button("Settings Menu: Close Btn") + { + using close_btn + text = str_intern("close") + // style.bg_color = Color_GreyRed + style.size.min = {50, 0} + style.anchor = range2( {1.0, 0}, {}) + style.alignment = {1, 1} + style.text_alignment = {0.5, 0.5} + if close_btn.pressed { + settings_open = false } } + + ui_resizable_handles( & settings_menu, & pos, & size ) } } } @@ -508,6 +406,7 @@ update :: proc( delta_time : f64 ) -> b32 default_layout := UI_Layout { anchor = {}, alignment = { 0., 0.0 }, + font_size = 30, text_alignment = { 0.0, 0.0 }, // corner_radii = { 0.2, 0.2, 0.2, 0.2 }, pos = { 0, 0 }, @@ -516,25 +415,18 @@ update :: proc( delta_time : f64 ) -> b32 } frame_style_default := UI_Style { - flags = frame_style_flags, - bg_color = Color_BG_TextBox, - + flags = frame_style_flags, + bg_color = Color_BG_TextBox, font = default_font, - font_size = 30, text_color = Color_White, layout = default_layout, } - frame_theme := UI_StyleTheme { styles = { - frame_style_default, - frame_style_default, - frame_style_default, - frame_style_default, - }} + frame_theme := to_ui_styletheme(frame_style_default) frame_theme.disabled.bg_color = Color_Frame_Disabled - frame_theme.hot.bg_color = Color_Frame_Hover - frame_theme.active.bg_color = Color_Frame_Select + frame_theme.hot. bg_color = Color_Frame_Hover + frame_theme.active. bg_color = Color_Frame_Select ui_style_theme( frame_theme ) config.ui_resize_border_width = 2.5 diff --git a/code/ui.odin b/code/ui.odin index 767f416..52ebab4 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -114,24 +114,6 @@ UI_ScalarConstraint :: struct { UI_Scalar2 :: [Axis2.Count]UI_Scalar -UI_Signal :: struct { - cursor_pos : Vec2, - drag_delta : Vec2, - scroll : Vec2, - - left_clicked : b8, - right_clicked : b8, - double_clicked : b8, - keyboard_clicked : b8, - - pressed : b8, - released : b8, - dragging : b8, - resizing : b8, - cursor_over : b8, - commit : b8, -} - UI_Box :: struct { // Cache ID key : UI_Key, @@ -192,7 +174,6 @@ UI_State :: struct { // flag_stack : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ), hot : UI_Key, - hot_resizable : b32, hot_start_style : UI_Style, active_mouse : [MouseBtn.count] UI_Key, @@ -330,7 +311,7 @@ ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 { return surface_to_ws_view_pos( input.mouse.pos ) } else { - return input.mouse.pos + return input.mouse.pos } } @@ -355,9 +336,6 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) //ui.hot = UI_Key(0) ui.active_start_signal = {} } - if ui.hot == UI_Key(0) { - ui.hot_resizable = false - } ui.built_box_count = 0 root = ui_box_make( {}, "root#001" ) @@ -419,6 +397,4 @@ ui_parent_pop :: proc() { } @(deferred_none = ui_parent_pop) -ui_parent :: proc( ui : ^UI_Box) { - ui_parent_push( ui ) -} +ui_parent :: #force_inline proc( ui : ^UI_Box) { ui_parent_push( ui ) } diff --git a/code/ui_layout.odin b/code/ui_layout.odin index fecc5cd..73a6dec 100644 --- a/code/ui_layout.odin +++ b/code/ui_layout.odin @@ -106,11 +106,11 @@ ui_compute_layout :: proc( ui : ^UI_State ) } text_size : Vec2 - if style.font_size == computed.text_size.y { + if style.layout.font_size == computed.text_size.y { text_size = computed.text_size } else { - text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 ) + text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.layout.font_size, 0 ) } if size_to_text { @@ -154,7 +154,7 @@ ui_compute_layout :: proc( ui : ^UI_State ) // Determine Content Bounds content_bounds := range2( bounds.min + { layout.padding.left, layout.padding.bottom } + border_offset, - bounds.max - { layout.padding.right, layout.padding.top } - border_offset, + bounds.max - { layout.padding.right, layout.padding.top } - border_offset, ) computed.anchors = anchored_bounds diff --git a/code/ui_signal.odin b/code/ui_signal.odin index 37e29aa..862095a 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -1,5 +1,23 @@ package sectr +UI_Signal :: struct { + cursor_pos : Vec2, + drag_delta : Vec2, + scroll : Vec2, + + left_clicked : b8, + right_clicked : b8, + double_clicked : b8, + keyboard_clicked : b8, + + active : b8, + was_active : b8, + + pressed : b8, + released : b8, + cursor_over : b8, + commit : b8, +} ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal { @@ -15,18 +33,6 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal // profile_begin( "Cursor collision") signal.cursor_pos = ui_cursor_pos() signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds ) - - computed_size := box.computed.bounds.p1 - box.computed.bounds.p0 - - resize_border_width := cast(f32) get_state().config.ui_resize_border_width - resize_percent_width := computed_size * (resize_border_width * 1.0/ 200.0) - resize_border_non_range := add(box.computed.bounds, range2( - { 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 // profile_end() // profile_begin("misc") @@ -46,29 +52,17 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal ui.active = box.key ui.active_mouse[MouseBtn.Left] = box.key - ui.last_pressed_key = box.key - ui.active_start_style = box.style + ui.last_pressed_key = box.key + ui.active_start_style = box.style signal.pressed = true // TODO(Ed) : Support double-click detection } - if mouse_clickable && signal.cursor_over && left_released - { - ui.active = UI_Key(0) - ui.active_mouse[MouseBtn.Left] = UI_Key(0) - - signal.released = true - signal.left_clicked = true - - ui.last_clicked = box.key - } - if mouse_clickable && ! signal.cursor_over && left_released { box.hot_delta = 0 - ui.hot = UI_Key(0) ui.active = UI_Key(0) ui.active_mouse[MouseBtn.Left] = UI_Key(0) @@ -100,14 +94,14 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal is_disabled := UI_BoxFlag.Disabled in box.flags is_hot := ui.hot == box.key is_active := ui.active == box.key + is_rooted := ui.root == box.parent - if signal.cursor_over + if signal.cursor_over && ! is_disabled { hot_vacant := ui.hot == UI_Key(0) active_vacant := ui.active == UI_Key(0) - - if (hot_vacant || is_hot) && - (active_vacant || is_active) + // (active_vacant is_active) + if (hot_vacant && signal.cursor_over || is_hot) { // prev_hot := zpl_hmap_get( ui.prev_cache, u64(ui.hot) ) // prev_hot_label := prev_hot != nil ? prev_hot.label.str : "" @@ -125,10 +119,20 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal ui.hot = UI_Key(0) } } - // profile_end() - 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 + if mouse_clickable && signal.cursor_over && left_released + { + ui.active = UI_Key(0) + ui.active_mouse[MouseBtn.Left] = UI_Key(0) + + signal.released = true + + if was_active { + signal.left_clicked = true + ui.last_clicked = box.key + } + } + // profile_end() // State Deltas update // profile_begin( "state deltas upate") @@ -164,7 +168,9 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal } // profile_end() - signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_start_signal.resizing) + signal.active = cast(b8) is_active + signal.was_active = cast(b8) was_active + // logf("was_active: %v", was_active) // Update style if not in default state { @@ -210,5 +216,6 @@ 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_style.odin b/code/ui_style.odin index 3d5a915..509f8b8 100644 --- a/code/ui_style.odin +++ b/code/ui_style.odin @@ -13,18 +13,18 @@ UI_Layout :: struct { alignment : Vec2, text_alignment : Vec2, - border_width : UI_Scalar, + font_size : f32, margins : UI_LayoutSide, padding : UI_LayoutSide, // TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend) corner_radii : [Corner.Count]f32, + border_width : UI_Scalar, // Position in relative coordinate space. // If the box's flags has Fixed_Position, then this will be its aboslute position in the relative coordinate space - pos : Vec2, - + pos : Vec2, size : Range2, // TODO(Ed) : Should thsi just always be WS_Pos for workspace UI? @@ -33,6 +33,7 @@ UI_Layout :: struct { // tile_pos : WS_Pos, } +// TODO(Ed): Change this to layout flags? Everything so far is just related to auto-layout UI_StyleFlag :: enum u32 { // Will perform scissor pass on children to their parent's bounds @@ -65,8 +66,14 @@ UI_StyleFlag :: enum u32 { // By Default, the origin is at the top left of the anchor's bounds Origin_At_Anchor_Center, - // Will size the box to its text. (Padding & Margins will thicken ) + // TODO(Ed): Implement this! + // For this to work, the children must have a minimum size set & their size overall must be greater than the parent's minimum size + Size_To_Content, + + // Will size the box to its text. Size_To_Text, + + // TODO(Ed): Implement this! Text_Wrap, Count, @@ -92,11 +99,14 @@ UI_Style :: struct { font : FontID, // TODO(Ed): Should this get moved to the layout struct? Techncially font-size is mainly - font_size : f32, text_color : Color, cursor : UI_Cursor, + // TODO(Ed): Should layout be separate from style? + // It technically entirely makes up style flags + // and the only thing shared I guess is the font determining the measured text size + transition time + // Technically we can provide a separate transition time option just for layout... using layout : UI_Layout, // Used with style, prev_style, and style_delta to produce a simple interpolated animation @@ -118,43 +128,28 @@ UI_TextAlign :: enum u32 { Count } -ui_layout_padding :: proc( pixels : f32 ) -> UI_LayoutSide { - return { pixels, pixels, pixels, pixels } -} +to_ui_layoutside :: #force_inline proc( pixels : f32 ) -> UI_LayoutSide { return { pixels, pixels, pixels, pixels } } -ui_style_peek :: proc( box_state : UI_StylePreset ) -> UI_Style { - return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] -} +to_ui_styletheme :: #force_inline proc( style : UI_Style ) -> UI_StyleTheme { return { styles = {style, style, style, style} } } -ui_style_ref :: proc( box_state : UI_StylePreset ) -> (^ UI_Style) { - return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] -} +ui_style_peek :: #force_inline proc( box_state : UI_StylePreset ) -> UI_Style { return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] } +ui_style_ref :: #force_inline proc( box_state : UI_StylePreset ) -> (^ UI_Style) { return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] } -ui_style_set :: proc ( style : UI_Style, box_state : UI_StylePreset ) { - stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] = style -} +ui_style_set :: #force_inline proc ( style : UI_Style, box_state : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] = style } -ui_style_set_layout :: proc ( layout : UI_Layout, preset : UI_StylePreset ) { - stack_peek_ref( & get_state().ui_context.theme_stack ).array[preset].layout = layout -} +ui_style_set_layout :: #force_inline proc ( layout : UI_Layout, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.theme_stack ).array[preset].layout = layout } -ui_style_theme_push :: proc( preset : UI_StyleTheme ) { - push( & get_state().ui_context.theme_stack, preset ) -} - -ui_style_theme_pop :: proc() { - pop( & get_state().ui_context.theme_stack ) -} +ui_style_theme_push :: #force_inline proc( preset : UI_StyleTheme ) { push( & get_state().ui_context.theme_stack, preset ) } +ui_style_theme_pop :: #force_inline proc() { pop( & get_state().ui_context.theme_stack ) } @(deferred_none = ui_style_theme_pop) -ui_style_theme :: proc( preset : UI_StyleTheme ) { - ui_style_theme_push( preset ) -} +ui_style_theme :: #force_inline proc( preset : UI_StyleTheme ) { ui_style_theme_push( preset ) } + +ui_style_theme_peek :: #force_inline proc() -> UI_StyleTheme { return stack_peek( & get_state().ui_context.theme_stack ) } +ui_style_theme_ref :: #force_inline proc() -> (^ UI_StyleTheme) { return stack_peek_ref( & get_state().ui_context.theme_stack ) } @(deferred_none = ui_style_theme_pop) -ui_theme_via_style :: proc ( style : UI_Style ) { - ui_style_theme_push( UI_StyleTheme { styles = { style, style, style, style } }) -} +ui_theme_via_style :: #force_inline proc ( style : UI_Style ) { ui_style_theme_push( UI_StyleTheme { styles = { style, style, style, style } }) } ui_style_theme_set_layout :: proc ( layout : UI_Layout ) { for & preset in stack_peek_ref( & get_state().ui_context.theme_stack ).array { @@ -169,6 +164,4 @@ ui_style_theme_layout_push :: proc ( layout : UI_Layout ) { } @(deferred_none = ui_style_theme_pop) -ui_style_theme_layout :: proc( layout : UI_Layout ) { - ui_style_theme_layout_push(layout) -} +ui_style_theme_layout :: #force_inline proc( layout : UI_Layout ) { ui_style_theme_layout_push(layout) } diff --git a/code/ui_style_themes.odin b/code/ui_style_themes.odin new file mode 100644 index 0000000..1bee476 --- /dev/null +++ b/code/ui_style_themes.odin @@ -0,0 +1,3 @@ +package sectr + +ui_theme_btn_default : proc( ) -> UI_StyleTheme \ No newline at end of file diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 33a2b3f..fd9ae6e 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -43,24 +43,11 @@ test_draggable :: proc() } // Dragging - if draggable.dragging { + if draggable.active { debug.draggable_box_pos += mouse_world_delta() } - // Resize - if draggable.resizing - { - og_layout := ui_context.active_start_style.layout - - center := debug.draggable_box_pos - 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.min * scale_factor - } - - if (ui.hot == draggable.key) && (ui.hot_resizable || ui.active_start_signal.resizing) { + if (ui.hot == draggable.key) { draggable.style.bg_color = Color_Blue } @@ -100,21 +87,10 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S debug.draggable_box_pos = parent.style.layout.pos debug.draggable_box_size = parent.style.layout.size.min } - if parent.dragging { + if parent.active { debug.draggable_box_pos += mouse_world_delta() } - if parent.resizing - { - og_layout := ui_context.active_start_style.layout - - center := debug.draggable_box_pos - original_distance := linalg.distance(ui.active_start_signal.cursor_pos, center) - cursor_distance := linalg.distance(parent.cursor_pos, center) - scale_factor := cursor_distance * (1 / original_distance) - - debug.draggable_box_size = og_layout.size.min * scale_factor - } - if (ui.hot == parent.key) && (ui.hot_resizable || ui.active_start_signal.resizing) { + if (ui.hot == parent.key) { parent.style.bg_color = Color_Blue } parent.style.layout.pos = debug.draggable_box_pos @@ -150,7 +126,7 @@ test_text_box :: proc() style.text_alignment = { 1.0, 1.0 } // style.flags = { .Size_To_Text } style.padding = { 10, 10, 10, 10 } - style.font_size = 32 + style.layout.font_size = 32 ui_style_theme( { styles = { style, style, style, style, }} ) text := str_intern( "Lorem ipsum dolor sit amet") @@ -160,7 +136,7 @@ test_text_box :: proc() pos = text_box.style.layout.pos } - if text_box.dragging { + if text_box.active { pos += mouse_world_delta() } diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index c54aa5c..4d8cd70 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -49,6 +49,7 @@ ui_hbox_begin :: proc( label : string, flags : UI_BoxFlags = {} widget.signal = ui_signal_from_box( widget.box ) return } + ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget { hbox_width := hbox.computed.content.max.y - hbox.computed.content.min.y @@ -73,12 +74,11 @@ ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget { size_req_children += size.min.x continue } - - } availble_flexible_space := hbox_width - size_req_children return hbox } + ui_hbox_auto_end :: proc( vbox : UI_Widget ) { ui_hbox_end(vbox) ui_parent_pop() @@ -90,9 +90,150 @@ ui_hbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (wi ui_parent(widget) return } - //endregion Horizontal Box +// Adds resizable handles to a widget +ui_resizable_handles :: proc( parent : ^UI_Widget, + pos, size : ^Vec2, + handle_width : f32 = 15, + handle_color_non_default : Color = Color_ResizeHandle, + handle_color_default : Color = Color_Transparent, + left := true, + right := true, + top := true, + bottom := true, + corner_tr := true, + corner_tl := true, + corner_br := true, + corner_bl := true, ) +{ + handle_left : UI_Widget + handle_right : UI_Widget + handle_top : UI_Widget + handle_bottom : UI_Widget + handle_corner_tr : UI_Widget + handle_corner_tl : UI_Widget + handle_corner_br : UI_Widget + handle_corner_bl : UI_Widget + + ui_parent(parent) + flags := UI_BoxFlags { .Mouse_Clickable, .Focusable } + + style_bar := UI_Style { + flags = { .Fixed_Width }, + size = range2({handle_width, 0}, {}), + bg_color = Color_ResizeHandle, + alignment = {1, 1}, + margins = { handle_width, handle_width, 0, 0 }, + corner_radii = { 5, 0, 0, 0 } + } + theme_bar := to_ui_styletheme(style_bar) + theme_bar.default.bg_color = handle_color_default + theme_bar.default.corner_radii[0] = 0 + ui_style_theme(theme_bar) + style_resize_height := style_bar + style_resize_height.flags = {.Fixed_Height} + style_resize_height.size.min = {0, handle_width} + style_resize_height.margins = { 0, 0, handle_width, handle_width } + style_resize_height.alignment = {0, 0} + + context.user_ptr = & parent.label + name :: proc( ) -> StrRunesPair { + parent_label := (transmute(^string) context.user_ptr) ^ + return str_intern(str_fmt_tmp("%v: %v", )) + } + + if left do handle_left = ui_widget("Settings Menu: Resize Left Border", flags ) + if right { + handle_right = ui_widget("Settings Menu: Resize Right Border", flags) + handle_right.style.anchor.left = 1 + handle_right.style.alignment = { 0, 1 } + } + + ui_theme_via_style(style_resize_height) + ui_style_theme_ref().default.bg_color = handle_color_default + if top do handle_top = ui_widget("Settings Menu: Resize Top Border", flags ) + if bottom { + handle_bottom = ui_widget("Settings Menu: Resize Bottom Border", flags) + handle_bottom.style.anchor.top = 1 + handle_bottom.style.alignment = { 0, 1 } + } + + style_corner := UI_Style { + flags = { .Fixed_Width, .Fixed_Height }, + size = range2({handle_width, handle_width}, {}), + bg_color = Color_ResizeHandle, + alignment = {1, 0}, + corner_radii = { 5, 0, 0, 0 }, + } + ui_theme_via_style(style_corner) + ui_style_theme_ref().default.bg_color = handle_color_default + if corner_tl do handle_corner_tl = ui_widget("Settings Menu: Corner TL", flags) + if corner_tr { + handle_corner_tr = ui_widget("Settings Menu: Corner TR", flags) + handle_corner_tr.style.anchor = range2({1, 0}, {}) + handle_corner_tr.style.alignment = {0, 0} + } + + if corner_bl { + handle_corner_bl = ui_widget("Settings Menu: Corner BL", flags) + handle_corner_bl.style.anchor = range2({}, {0, 1}) + handle_corner_bl.style.alignment = { 1, 1 } + } + if corner_br { + handle_corner_br = ui_widget("Settings Menu: Corner BR", flags) + handle_corner_br.style.anchor = range2({1, 0}, {0, 1}) + handle_corner_br.style.alignment = {0, 1} + } + + process_handle_drag :: #force_inline proc ( handle : ^UI_Widget, + direction : Vec2, + size_delta : Vec2, + target_alignment : Vec2, + pos : ^Vec2, + size : ^Vec2, + alignment : ^Vec2, ) + { + ui := get_state().ui_context + if ui.last_pressed_key != handle.key { return } + + size_delta := size_delta + pos_adjust := size^ * (alignment^ - target_alignment) + + @static was_dragging := false + + using handle + if active + { + size^ += size_delta * direction + if pressed { + pos^ -= pos_adjust + } + else { + alignment^ = target_alignment + } + was_dragging = true + } + else if released && was_dragging + { + pos^ += pos_adjust + alignment^ = target_alignment + was_dragging = false + } + } + + delta := get_state().input.mouse.delta + alignment := & parent.style.alignment + if right do process_handle_drag( & handle_right, { 1, 0 }, delta, {0, 0}, pos, size, alignment ) + if left do process_handle_drag( & handle_left, { -1, 0 }, delta, {1, 0}, pos, size, alignment ) + if top do process_handle_drag( & handle_top, { 0, 1 }, delta, {0, 0}, pos, size, alignment ) + if bottom do process_handle_drag( & handle_bottom, { 0, -1 }, delta, {0, 1}, pos, size, alignment ) + if corner_tr do process_handle_drag( & handle_corner_tr, { 1, 1 }, delta, {0, 0}, pos, size, alignment ) + if corner_tl do process_handle_drag( & handle_corner_tl, { -1, 1 }, delta, {1, 0}, pos, size, alignment ) + if corner_br do process_handle_drag( & handle_corner_br, { 1, -1 }, delta, {0, 1}, pos, size, alignment ) + if corner_bl do process_handle_drag( & handle_corner_bl, { -1, -1 }, delta, {1, 1}, pos, size, alignment ) +} + ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget { // profile(#procedure) @@ -135,6 +276,7 @@ ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget return { box, signal } } +//region Vertical Box ui_vbox_begin :: proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) { // profile(#procedure) @@ -159,3 +301,4 @@ ui_vbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (wi ui_parent_push(widget) return } +//endregion Vertical Box