diff --git a/code/app_screen.odin b/code/app_screen.odin index e7a8809..6590b44 100644 --- a/code/app_screen.odin +++ b/code/app_screen.odin @@ -47,10 +47,62 @@ ui_screen_menu_bar :: proc( captures : rawptr = nil ) -> (should_raise : b32 = f profile("App Menu Bar") fmt :: str_fmt_alloc + @(deferred_none = ui_theme_pop) + ui_theme_app_menu_bar_default :: proc() + { + @static theme : UI_Theme + @static loaded : b32 = false + if true && ! loaded + { + layout := UI_Layout { + flags = {}, + anchor = range2({},{}), + alignment = {0.5, 0.5}, + text_alignment = {0.0, 1.5}, + font_size = 12, + margins = {0, 0, 0, 0}, + padding = {0, 0, 0, 0}, + border_width = 0.6, + pos = {0, 0}, + size = range2({},{}) + } + style := UI_Style { + bg_color = Color_ThmDark_BG, + border_color = Color_ThmDark_Border_Default, + corner_radii = {}, + blur_size = 0, + font = get_state().default_font, + text_color = Color_ThmDark_Text_Default, + cursor = {}, + } + + // loaded = true + layout_combo := to_ui_layout_combo(layout) + style_combo := to_ui_style_combo(style) + { + using layout_combo.hot + using style_combo.hot + bg_color = Color_ThmDark_Btn_BG_Hot + text_color = Color_ThmDark_Text_Hot + } + { + using layout_combo.active + using style_combo.active + bg_color = Color_ThmDark_Btn_BG_Active + text_color = Color_ThmDark_Text_Active + } + + theme = UI_Theme { + layout_combo, style_combo + } + } + ui_layout_push(theme.layout) + ui_style_push(theme.style) + } + using state := get_state() using screen_ui { - using state := get_state(); using screen_ui.menu_bar ui_theme_app_menu_bar_default() container = ui_hbox( .Left_To_Right, "Menu Bar" ) @@ -78,7 +130,7 @@ ui_screen_menu_bar :: proc( captures : rawptr = nil ) -> (should_raise : b32 = f spacer.layout.size.min.x = 30 // TODO(Ed): Implement an external composition for theme interpolation using the settings btn - settings_btn.widget = ui_button("Settings Btn") + settings_btn.widget = ui_button("Menu Bar: Settings Btn") { using settings_btn text = str_intern("Settings") @@ -109,13 +161,14 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b if size.x < min_size.x do size.x = min_size.x if size.y < min_size.y do size.y = min_size.y + ui_theme_transparent() container = ui_widget("Settings Menu", {}) { using container - layout.flags = { .Fixed_Width, .Fixed_Height, .Origin_At_Anchor_Center, .Fixed_Position_X, .Fixed_Position_Y } - layout.alignment = { 0.5, 0.5 } - // style.bg_color = Color_3D_BG + layout.flags = { .Fixed_Width, .Fixed_Height, .Fixed_Position_X, .Fixed_Position_Y, .Origin_At_Anchor_Center } + style.bg_color = Color_ThmDark_Translucent_Panel style.border_color = { 0, 0, 0, 200 } + layout.alignment = {0.0, 0.0} layout.border_width = 1.0 layout.pos = pos layout.size = range2( size, {}) @@ -130,7 +183,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b vbox := ui_vbox_begin( .Top_To_Bottom, "Settings Menu: VBox", {.Mouse_Clickable}, compute_layout = true) { - vbox.style.bg_color = Color_BG_Panel_Translucent + should_raise |= b32(vbox.active) ui_parent(vbox) ui_layout( UI_Layout { @@ -138,14 +191,14 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b // alignment = {0, 1}, }) ui_style( UI_Style { - bg_color = Color_Transparent, + // bg_color = Color_Transparent, font = default_font, text_color = Color_White, }) ui_style_ref().hot.bg_color = Color_Blue frame_bar := ui_hbox_begin(.Left_To_Right, "Settings Menu: Frame Bar", { .Mouse_Clickable, .Focusable, .Click_To_Focus }) { - frame_bar.style.bg_color = Color_BG_Panel + // frame_bar.style.bg_color = Color_BG_Panel frame_bar.layout.flags = {.Fixed_Height} frame_bar.layout.size.min.y = 50 // frame_bar.layout.anchor.ratio.y = 0.8 @@ -168,8 +221,8 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b ui_style(ui_style_peek()) style := ui_style_ref() - style.default.bg_color = Color_Black - style.hot.bg_color = Color_Frame_Hover + // style.default.bg_color = Color_Black + // style.hot.bg_color = Color_Frame_Hover maximize_btn := ui_button("Settings Menu: Maximize Btn") { using maximize_btn @@ -185,8 +238,8 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b else do text = str_intern("max") } - style.default.bg_color = Color_GreyRed - style.hot. bg_color = Color_Red + // style.default.bg_color = Color_GreyRed + // style.hot. bg_color = Color_Red close_btn := ui_button("Settings Menu: Close Btn") { using close_btn @@ -226,7 +279,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b { using drop_down_bar text = str_intern("drop_down_bar") - style.bg_color = { 55, 55, 55, 100 } + // style.bg_color = { 55, 55, 55, 100 } style.font = default_font style.text_color = Color_White layout.flags = {.Fixed_Height} @@ -243,7 +296,7 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b style.font = default_font style.text_color = Color_White layout.flags = {.Origin_At_Anchor_Center} - layout.alignment = {0.5, 0.25} // ??? (Wtf is this alignment) + layout.alignment = {0.0, 0.0} // ??? (Wtf is this alignment) layout.anchor.ratio.x = 1.0 layout.font_size = 12 layout.margins = {0,0, 15, 0} diff --git a/code/colors.odin b/code/colors.odin index 1e0b8cc..3c02445 100644 --- a/code/colors.odin +++ b/code/colors.odin @@ -41,6 +41,12 @@ Color_ThmDark_DarkLimit :: Color {10, 10, 10, 255} Color_ThmDark_BG :: Color {33, 33, 33, 255} +Color_ThmDark_Translucent_Panel :: Color { 0, 0, 0, 60} + +Color_ThmDark_ResizeHandle_Default :: Color_Transparent +Color_ThmDark_ResizeHandle_Hot :: Color { 72, 72, 72, 90} +Color_ThmDark_ResizeHandle_Active :: Color { 88, 88, 88, 90} + Color_ThmDark_Border_Default :: Color { 64, 64, 64, 255} Color_ThmDark_Btn_BG_Default :: Color { 40, 40, 40, 255} @@ -53,4 +59,4 @@ Color_ThmDark_Text_Active :: Color {240, 240, 240, 255} // Light Theme -// LightTheme_BG :: Color { 120, 120, 120, 255 } \ No newline at end of file +// LightTheme_BG :: Color { 120, 120, 120, 255 } diff --git a/code/engine_api.odin b/code/engine_api.odin index 3d45657..8d25ae0 100644 --- a/code/engine_api.odin +++ b/code/engine_api.odin @@ -318,9 +318,9 @@ tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32 rl.PollInputEvents() - debug.draw_ui_box_bounds_points = false + debug.draw_ui_box_bounds_points = true debug.draw_UI_padding_bounds = false - debug.draw_ui_content_bounds = false + debug.draw_ui_content_bounds = true should_close = update( host_delta_time ) render() @@ -398,4 +398,3 @@ clean_frame :: proc() verify( alloc_error == .None, "Failed to allocate transient slab" ) } } - diff --git a/code/grime_grime.odin b/code/grime_grime.odin index 90034b6..860a582 100644 --- a/code/grime_grime.odin +++ b/code/grime_grime.odin @@ -274,6 +274,11 @@ to_writer :: proc { str_builder_to_writer, } +to_ui_layout_side :: proc { + to_ui_layout_side_f32, + to_ui_layout_side_vec2, +} + ui_floating :: proc { ui_floating_just_builder, ui_floating_with_capture, diff --git a/code/grime_stack.odin b/code/grime_stack.odin index 1088941..504e294 100644 --- a/code/grime_stack.odin +++ b/code/grime_stack.odin @@ -10,6 +10,10 @@ StackFixed :: struct ( $ Type : typeid, $ Size : u32 ) { items : [ Size ] Type, } +stack_clear :: #force_inline proc ( using stack : ^StackFixed( $Type, $Size)) { + idx = 0 +} + stack_push :: #force_inline proc( using stack : ^ StackFixed( $ Type, $ Size ), value : Type ) { verify( idx < len( items ), "Attempted to push on a full stack" ) diff --git a/code/tick_update.odin b/code/tick_update.odin index cc02763..7196558 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -210,7 +210,7 @@ update :: proc( delta_time : f64 ) -> b32 frame_style_flags : UI_LayoutFlags = { .Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, - // .Origin_At_Anchor_Center, + .Origin_At_Anchor_Center, } default_layout := UI_Layout { flags = frame_style_flags, @@ -239,7 +239,7 @@ update :: proc( delta_time : f64 ) -> b32 // test_hover_n_click() // test_draggable() // test_text_box() - // test_parenting( & default_layout, & frame_style_default ) + test_parenting( & default_layout, & frame_style_default ) // test_whitespace_ast( & default_layout, & frame_style_default ) } //endregion Workspace Imgui Tick diff --git a/code/ui_box.odin b/code/ui_box.odin index 13288ac..76ed0ad 100644 --- a/code/ui_box.odin +++ b/code/ui_box.odin @@ -124,7 +124,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) parent := stack_peek( & parent_stack ) if parent != nil { - when false { + when true { dll_full_push_back( parent, curr_box, nil ) } else diff --git a/code/ui_floating.odin b/code/ui_floating.odin index 84483d6..17ac2e7 100644 --- a/code/ui_floating.odin +++ b/code/ui_floating.odin @@ -15,7 +15,6 @@ UI_FloatingManager :: struct { using links : DLL_NodeFL(UI_Floating), build_queue : Array(UI_Floating), tracked : HMapChainedPtr(UI_Floating), - // tracked : HMapZPL(UI_Floating), } ui_floating_startup :: proc( self : ^UI_FloatingManager, allocator : Allocator, build_queue_cap, tracked_cap : u64, dbg_name : string = "" ) -> AllocatorError @@ -31,7 +30,6 @@ ui_floating_startup :: proc( self : ^UI_FloatingManager, allocator : Allocator, } tracked_dbg_name := str_intern(str_fmt_tmp("%s: tracked", dbg_name)) - // self.tracked, error = zpl_hmap_init_reserve( UI_Floating, allocator, tracked_cap, dbg_name = tracked_dbg_name.str ) self.tracked, error = hmap_chained_init(UI_Floating, uint(tracked_cap), allocator, dbg_name = tracked_dbg_name.str ) if error != AllocatorError.None { @@ -89,14 +87,12 @@ ui_floating_manager_end :: proc() ui_floating_build :: proc() { - ui := ui_context() - screen_ui := cast(^UI_ScreenState) ui + ui := ui_context() using floating := get_state().ui_floating_context for to_enqueue in array_to_slice( build_queue) { key := ui_key_from_string(to_enqueue.label) - // lookup := zpl_hmap_get( & tracked, transmute(u64) key ) lookup := hmap_chained_get( tracked, transmute(u64) key ) // Check if entry is already present @@ -109,7 +105,6 @@ ui_floating_build :: proc() if lookup == nil { error : AllocatorError - // lookup, error = zpl_hmap_set( & tracked, transmute(u64) key, to_enqueue ) lookup, error = hmap_chained_set( tracked, transmute(u64) key, to_enqueue ) if error != AllocatorError.None { ensure(false, "Failed to allocate entry to hashtable") @@ -178,35 +173,5 @@ ui_floating_build :: proc() { dll_full_pop( to_raise, floating ) dll_full_push_back( floating, to_raise, nil ) - // PopEntry: - // { - // if first == nil { - // first = to_raise - // last = to_raise - // break PopEntry - // } - // if to_raise == first - // { - // first = to_raise.next - // to_raise.next.prev = nil - // break PopEntry - // } - // if to_raise == last - // { - // // Do nothing no need to modify order - // return - // } - - // left := to_raise.prev - // right := to_raise.next - - // left.next = right - // right.prev = left - // } - - // last.next = to_raise - // to_raise.prev = last - // last.next = nil - // last = to_raise } } diff --git a/code/ui_layout.odin b/code/ui_layout.odin index 79d50a0..dbcbd8c 100644 --- a/code/ui_layout.odin +++ b/code/ui_layout.odin @@ -8,10 +8,15 @@ import "core:math/linalg" // Alignment presets -LayoutAlign_OriginTL_TopLeft :: Vec2{ 0, 1} -LayoutAlign_OriginTL_TopRight :: Vec2{-1, 1} -LayoutAlign_OriginTL_Centered :: Vec2{ 0, 1} -LayoutAlign_OriginTL_BottomLeft :: Vec2{ 0, 1} +LayoutAlign_OriginTL_Top :: Vec2{0.5, 0} +LayoutAlign_OriginTL_TopLeft :: Vec2{ 0, 0} +LayoutAlign_OriginTL_TopRight :: Vec2{ 1, 0} +LayoutAlign_OriginTL_Centered :: Vec2{0.5, 0.5} +LayoutAlign_OriginTL_Bottom :: Vec2{0.5, 1} +LayoutAlign_OriginTL_BottomLeft :: Vec2{ 0, 1} +LayoutAlign_OriginTL_BottomRight :: Vec2{ 1, 1} + +// LayoutAlign_OriginTL_ Layout_OriginCenter_Centered :: Vec2{0.5, 0.5} @@ -130,8 +135,9 @@ UI_LayoutCombo :: struct #raw_union { } } -to_ui_layout_side :: #force_inline proc( pixels : f32 ) -> UI_LayoutSide { return { pixels, pixels, pixels, pixels } } -to_ui_layout_combo :: #force_inline proc( layout : UI_Layout ) -> UI_LayoutCombo { return { layouts = {layout, layout, layout, layout} } } +to_ui_layout_side_f32 :: #force_inline proc( pixels : f32 ) -> UI_LayoutSide { return { pixels, pixels, pixels, pixels } } +to_ui_layout_side_vec2 :: #force_inline proc( v : Vec2) -> UI_LayoutSide { return { v.x, v.x, v.y, v.y} } +to_ui_layout_combo :: #force_inline proc( layout : UI_Layout ) -> UI_LayoutCombo { return { layouts = {layout, layout, layout, layout} } } /* Layout Interface diff --git a/code/ui_layout_compute.odin b/code/ui_layout_compute.odin index d68a1cb..ae9a746 100644 --- a/code/ui_layout_compute.odin +++ b/code/ui_layout_compute.odin @@ -125,13 +125,22 @@ ui_box_compute_layout :: proc( box : ^UI_Box, // 6. Determine the box bounds // Adjust Alignment of pivot position alignment := layout.alignment - bounds := range2( - rel_pos - adjusted_size * alignment, - rel_pos + adjusted_size * (vec2_one - alignment), - ) + bounds : Range2 + if ! (.Origin_At_Anchor_Center in layout.flags) { - bounds.min -= { 0, adjusted_size.y } - bounds.max -= { 0, adjusted_size.y } + // The convention offset adjust the box so that the top-left point is at the top left of the anchor's bounds + tl_convention_offset := adjusted_size * {0, -1} + bounds = range2( + rel_pos - adjusted_size * alignment + tl_convention_offset, + rel_pos + adjusted_size * (vec2_one - alignment) + tl_convention_offset, + ) + } + else { + centered_convention_offset := adjusted_size * -0.5 + bounds = range2( + (rel_pos + centered_convention_offset) - adjusted_size * -alignment , + (rel_pos + centered_convention_offset) + adjusted_size * (alignment + vec2_one), + ) } // Determine Padding's outer bounds diff --git a/code/ui_signal.odin b/code/ui_signal.odin index 19726b7..bae9469 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -11,6 +11,8 @@ UI_Signal :: struct { right_clicked : b8, double_clicked : b8, keyboard_clicked : b8, + left_shift : b8, + left_ctrl : b8, active : b8, hot : b8, @@ -64,6 +66,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas left_pressed := pressed( input.mouse.left ) left_released := released( input.mouse.left ) + left_shift := pressed(input.keyboard.left_shift) + mouse_clickable := UI_BoxFlag.Mouse_Clickable in box.flags keyboard_clickable := UI_BoxFlag.Keyboard_Clickable in box.flags @@ -84,7 +88,9 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas ui.last_pressed_key = box.key ui.active_start_style = box.style - signal.pressed = true + signal.pressed = true + signal.left_clicked = b8(left_pressed) + signal.left_shift = b8(left_shift) // TODO(Ed) : Support double-click detection } @@ -129,7 +135,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas hot_vacant := ui.hot == UI_Key(0) active_vacant := ui.active == UI_Key(0) // (active_vacant is_active) - if signal.cursor_over + if signal.cursor_over && active_vacant { if ! hot_vacant { prev := ui_box_from_key( ui.curr_cache, ui.hot ) diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 56a2fb5..38a6640 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -73,7 +73,7 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S // ui_parent(frame) parent_layout := default_layout ^ parent_layout.size = range2( { 300, 300 }, {} ) - parent_layout.alignment = { 0.5, 0.5 } + parent_layout.alignment = { 0.0, 0.0 } // parent_layout.margins = { 100, 100, 100, 100 } parent_layout.padding = { 5, 10, 5, 5 } parent_layout.pos = { 0, 0 } @@ -103,6 +103,7 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S parent.layout.pos = debug.draggable_box_pos parent.layout.size.min = debug.draggable_box_size } + ui_resizable_handles( & parent, & debug.draggable_box_pos, & debug.draggable_box_size) child_layout := default_layout ^ child_layout.size = range2({ 100, 100 }, { 0, 0 }) @@ -208,11 +209,11 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : line_hbox.text = StrRunesPair {} ui_parent(line_hbox) - chunk_layout := text_layout + chunk_layout := text_layout chunk_layout.alignment = { 0.0, 1.0 } - chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 }) - chunk_layout.pos = {} - chunk_layout.flags = { .Fixed_Position_X, .Size_To_Text } + chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 }) + chunk_layout.pos = {} + chunk_layout.flags = { .Fixed_Position_X, .Size_To_Text } chunk_style := text_style ui_theme( to_ui_layout_combo(chunk_layout), to_ui_style_combo(chunk_style) ) diff --git a/code/ui_theme.odin b/code/ui_theme.odin index beb7c0c..74de9d6 100644 --- a/code/ui_theme.odin +++ b/code/ui_theme.odin @@ -36,7 +36,6 @@ ui_theme_via_theme :: #force_inline proc( theme : UI_Theme ) { ui_style_push( theme.style ) } - /* UI Themes: Comprise of UI_Box's layout & style @@ -44,93 +43,13 @@ Provides presets for themes and their interface for manipulating the combo stack */ // TODO(Ed): Eventually this will have a configuration wizard, and we'll save the presets -/* -UI_Theme_Template :: UI_Theme { - UI_Layout { - flags = {}, - anchor = Range2{{},{}}, - alignment = {}, - text_alignment = {}, - font_size = {}, - margins = {}, - padding = {}, - border_width = {}, - pos = {}, - size = Range2{{},{}} - }, - UI_Style { - bg_color = {}, - corner_radii = {}, - blur_size = 0, - font = {}, - text_color = {}, - cursor = 0, - } -} -*/ - -@(deferred_none = ui_theme_pop) -ui_theme_app_menu_bar_default :: proc() -{ - @static theme : UI_Theme - // @static loaded : b32 = false - // if true && ! loaded - // { - layout := UI_Layout { - flags = {}, - anchor = range2({},{}), - alignment = {0.5, 0.5}, - text_alignment = {0.0, 1.5}, - font_size = 12, - margins = {0, 0, 0, 0}, - padding = {0, 0, 0, 0}, - border_width = 0.6, - pos = {0, 0}, - size = range2({},{}) - } - style := UI_Style { - bg_color = Color_ThmDark_BG, - border_color = Color_ThmDark_Border_Default, - corner_radii = {}, - blur_size = 0, - font = get_state().default_font, - text_color = Color_ThmDark_Text_Default, - cursor = {}, - } - - // loaded = true - layout_combo := to_ui_layout_combo(layout) - style_combo := to_ui_style_combo(style) - { - using layout_combo.hot - using style_combo.hot - bg_color = Color_ThmDark_Btn_BG_Hot - text_color = Color_ThmDark_Text_Hot - } - { - using layout_combo.active - using style_combo.active - bg_color = Color_ThmDark_Btn_BG_Active - text_color = Color_ThmDark_Text_Active - } - - theme = UI_Theme { - layout_combo, style_combo - } - // } - - - ui_layout_push(theme.layout) - ui_style_push(theme.style) -} - @(deferred_none = ui_theme_pop) ui_theme_btn_default :: proc() { @static theme : UI_Theme - // @static loaded : b32 = false - // if true && ! loaded - // { + @static loaded : b32 = false + if ! loaded + { layout := UI_Layout { flags = {}, anchor = range2({},{}), @@ -152,8 +71,6 @@ ui_theme_btn_default :: proc() text_color = Color_ThmDark_Text_Default, cursor = {}, } - - // loaded = true layout_combo := to_ui_layout_combo(layout) style_combo := to_ui_style_combo(style) { @@ -170,13 +87,64 @@ ui_theme_btn_default :: proc() text_color = Color_ThmDark_Text_Active margins = {2, 2, 2, 2} } + theme = UI_Theme { + layout_combo, style_combo + } + loaded = true + } + ui_layout_push(theme.layout) + ui_style_push(theme.style) +} + +@(deferred_none = ui_theme_pop) +ui_theme_transparent :: proc() +{ + @static theme : UI_Theme + @static loaded : b32 = false + if ! loaded || true + { + layout := UI_Layout { + flags = {}, + anchor = range2({},{}), + alignment = {0, 0}, + text_alignment = {0.0, 0.0}, + font_size = 16, + margins = {0, 0, 0, 0}, + padding = {0, 0, 0, 0}, + border_width = 0, + pos = {0, 0}, + size = range2({},{}) + } + style := UI_Style { + bg_color = Color_Transparent, + border_color = Color_Transparent, + corner_radii = {}, + blur_size = 0, + font = get_state().default_font, + text_color = Color_ThmDark_Text_Default, + cursor = {}, + } + + layout_combo := to_ui_layout_combo(layout) + style_combo := to_ui_style_combo(style) + { + using layout_combo.disabled + using style_combo.disabled + } + { + using layout_combo.hot + using style_combo.hot + } + { + using layout_combo.active + using style_combo.active + } theme = UI_Theme { layout_combo, style_combo } - // } - - + loaded = true + } ui_layout_push(theme.layout) ui_style_push(theme.style) } diff --git a/code/ui_ui.odin b/code/ui_ui.odin index c0efd53..f2b07b0 100644 --- a/code/ui_ui.odin +++ b/code/ui_ui.odin @@ -70,8 +70,6 @@ UI_InteractState :: struct { UI_Key :: distinct u64 - - UI_Scalar :: f32 UI_ScalarConstraint :: struct { @@ -177,6 +175,11 @@ 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.active_start_signal.cursor_pos +} + ui_ws_drag_delta :: #force_inline proc "contextless" () -> Vec2 { using state := get_state() return screen_to_ws_view_pos(input.mouse.pos) - state.ui_context.active_start_signal.cursor_pos @@ -190,6 +193,8 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) get_state().ui_context = ui using get_state().ui_context + stack_clear( & layout_combo_stack ) + stack_clear( & style_combo_stack ) array_clear( render_queue ) temp := prev_cache diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index 8331afe..a7cb86c 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -81,8 +81,7 @@ ui_hbox_end_auto :: proc( hbox : UI_HBox ) { UI_Resizable :: struct { using widget : UI_Widget, handle_width : f32, - color_non_default : Color, - color_default : Color, + theme : ^UI_Theme, left : bool, right : bool, top : bool, @@ -95,9 +94,8 @@ UI_Resizable :: struct { } ui_resizable_begin :: proc( label : string, flags : UI_BoxFlags = {}, - handle_width : f32 = 15, - handle_color_non_default : Color = Color_ResizeHandle, - handle_color_default : Color = Color_Transparent, + handle_width : f32 = 15, + theme : ^UI_Theme, left := true, right := true, top := true, @@ -111,18 +109,17 @@ ui_resizable_begin :: proc( label : string, flags : UI_BoxFlags = {}, resizable.box = ui_box_make(flags, label) resizable.signal = ui_signal_from_box(resizable.box) - resizable.handle_width = handle_width - resizable.color_non_default = handle_color_non_default - resizable.color_default = handle_color_default - resizable.left = left - resizable.right = right - resizable.top = top - resizable.bottom = bottom - resizable.corner_tr = corner_tr - resizable.corner_tl = corner_tl - resizable.corner_br = corner_br - resizable.corner_bl = corner_bl - resizable.compute_layout = compute_layout + resizable.handle_width = handle_width + resizable.theme = theme + resizable.left = left + resizable.right = right + resizable.top = top + resizable.bottom = bottom + resizable.corner_tr = corner_tr + resizable.corner_tl = corner_tl + resizable.corner_br = corner_br + resizable.corner_bl = corner_bl + resizable.compute_layout = compute_layout return } @@ -130,8 +127,7 @@ ui_resizable_end :: proc( resizable : ^UI_Resizable, pos, size : ^Vec2 ) { using resizable ui_resizable_handles( & widget, pos, size, handle_width, - color_non_default, - color_default, + theme, left, right, top, @@ -154,9 +150,8 @@ ui_resizable_end_auto :: proc() { // Adds resizable handles to a widget // TODO(Ed): Add centered resize support (use center alignment on shift-click) ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2, - handle_width : f32 = 15, - handle_color_non_default : Color = Color_ResizeHandle, - handle_color_default : Color = Color_Transparent, + handle_width : f32 = 15, + theme : ^UI_Theme = nil, left := true, right := true, top := true, @@ -176,72 +171,114 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2, handle_corner_tl : UI_Widget handle_corner_br : UI_Widget handle_corner_bl : UI_Widget - ui_parent(parent) - flags := UI_BoxFlags { .Mouse_Clickable, .Focusable } - layout_bar_width := UI_Layout { - flags = { .Fixed_Width }, - alignment = {1, 0}, - margins = { handle_width, handle_width, 0, 0 }, - size = range2({handle_width, 0}, {}), + @(deferred_none = ui_theme_pop) + theme_handle :: proc( base : ^UI_Theme, margins, size : Vec2, flags : UI_LayoutFlags = {}) + { + layout_combo : UI_LayoutCombo + style_combo : UI_StyleCombo + if base != nil + { + layout_combo = base.layout + style_combo = base.style + { + layout_combo.default.margins = {margins.x, margins.x, margins.y, margins.y} + layout_combo.default.size.min = size + } + { + layout_combo.hot.margins = {margins.x, margins.x, margins.y, margins.y} + layout_combo.hot.size.min = size + } + { + layout_combo.active.margins = {margins.x, margins.x, margins.y, margins.y} + layout_combo.active.size.min = size + } + } + else + { + layout := UI_Layout { + flags = flags, + anchor = range2({},{}), + alignment = {0, 0}, + text_alignment = {0.0, 0.0}, + font_size = 16, + margins = to_ui_layout_side(margins), + padding = {0, 0, 0, 0}, + border_width = 0, + pos = {0, 0}, + size = range2(size,{}) + } + style := UI_Style { + bg_color = Color_Transparent, + border_color = Color_Transparent, + corner_radii = {5, 0, 0, 0}, + blur_size = 0, + font = get_state().default_font, + text_color = Color_ThmDark_Text_Default, + cursor = {}, + } + layout_combo = to_ui_layout_combo(layout) + style_combo = to_ui_style_combo(style) + { + using layout_combo.hot + using style_combo.hot + bg_color = Color_ThmDark_ResizeHandle_Hot + } + { + using layout_combo.active + using style_combo.active + bg_color = Color_ThmDark_ResizeHandle_Active + } + } + theme := UI_Theme { + layout_combo, style_combo + } + ui_layout_push(theme.layout) + ui_style_push(theme.style) } - style_bar := UI_Style { - bg_color = Color_ResizeHandle, - corner_radii = { 5, 0, 0, 0 } - } - theme_bar := to_ui_style_combo(style_bar) - theme_bar.default.bg_color = handle_color_default - theme_bar.default.corner_radii[0] = 0 - ui_layout(layout_bar_width) - ui_style(theme_bar) - layout_bar_height := layout_bar_width - layout_bar_height.flags = {.Fixed_Height} - layout_bar_height.size.min = {0, handle_width} - layout_bar_height.margins = { 0, 0, handle_width, handle_width } - layout_bar_height.alignment = {0, -1} - context.user_ptr = & parent.label - name :: proc( ) -> StrRunesPair { + flags := UI_BoxFlags { .Mouse_Clickable } + + name :: proc( label : string ) -> string { parent_label := (transmute(^string) context.user_ptr) ^ - return str_intern(str_fmt_tmp("%v: %v", )) + return str_intern(str_fmt_alloc("%v: %v", parent_label, label )).str } + context.user_ptr = & parent.label - if left do handle_left = ui_widget("Settings Menu: Resize Left Handle", flags ) + #region("Handle & Corner construction") + theme_handle( theme, {handle_width, 0}, {handle_width,0}) + if left { + handle_left = ui_widget(name("resize_handle_left"), flags ) + handle_left.layout.anchor.left = 0 + handle_left.layout.anchor.right = 1 + handle_left.layout.alignment = {1, 0} + } if right { - handle_right = ui_widget("Settings Menu: Resize Right Handle", flags) + handle_right = ui_widget(name("resize_handle_right"), flags ) handle_right.layout.anchor.left = 1 - handle_right.layout.alignment = { 0, 0 } } - - ui_layout(layout_bar_height) - ui_style_ref().default.bg_color = handle_color_default - if top do handle_top = ui_widget("Settings Menu: Resize Top Border", flags ) + theme_handle( theme, {0, handle_width}, {0, handle_width}) + if top { + handle_top = ui_widget(name("resize_handle_top"), flags ) + handle_top.layout.anchor.bottom = 1 + handle_top.layout.alignment = {0, -1} + } if bottom { - handle_bottom = ui_widget("Settings Menu: Resize Bottom Border", flags) - using handle_bottom.layout - anchor.top = 1 - alignment = { 0, 0 } + handle_bottom = ui_widget("resize_handle_bottom", flags) + handle_bottom.layout.anchor.top = 1 + handle_bottom.layout.alignment = { 0, 0 } } - - layout_corner := UI_Layout { - flags = { .Fixed_Width, .Fixed_Height }, - alignment = {1, -1}, - size = range2({handle_width, handle_width}, {}), + theme_handle( theme, {0,0}, {handle_width, handle_width}, {.Fixed_Width, .Fixed_Height} ) + if corner_tl { + handle_corner_tl = ui_widget(name("corner_top_left"), flags) + handle_corner_tl.layout.alignment = {1, -1} } - style_corner := UI_Style { - bg_color = Color_ResizeHandle, - corner_radii = { 5, 0, 0, 0 }, - } - ui_theme(layout_corner, style_corner) - ui_style_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 = ui_widget(name("corner_top_right"), flags) handle_corner_tr.layout.anchor = range2({1, 0}, {}) handle_corner_tr.layout.alignment = {0, -1} } - if corner_bl { handle_corner_bl = ui_widget("Settings Menu: Corner BL", flags) handle_corner_bl.layout.anchor = range2({}, {0, 1}) @@ -252,27 +289,42 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2, handle_corner_br.layout.anchor = range2({1, 0}, {0, 1}) handle_corner_br.layout.alignment = {0, 0} } + #endregion("Handle & Corner construction") process_handle_drag :: #force_inline proc ( handle : ^UI_Widget, direction : Vec2, size_delta : Vec2, target_alignment : Vec2, + target_center_aligned : Vec2, pos : ^Vec2, size : ^Vec2, alignment : ^Vec2, ) -> b32 { - ui := get_state().ui_context - if ui.last_pressed_key != handle.key { return false } + @static active_context : ^UI_State + @static was_dragging : b32 = false + @static start_size : Vec2 - size_delta := size_delta + ui := get_state().ui_context + if ui.last_pressed_key != handle.key do return false + using handle + + direction := direction + target_alignment := target_alignment + + size_delta := ui_drag_delta() pos_adjust := size^ * (alignment^ - target_alignment) - @static was_dragging : b32 = false - - using handle if active { - size^ += size_delta * direction + if pressed + { + active_context = ui + start_size = size^ + if .Origin_At_Anchor_Center in parent.layout.flags { + pos_adjust = size^ * 0.5 * direction + } + } + size^ = start_size + size_delta * direction if pressed { pos^ -= pos_adjust } @@ -283,26 +335,49 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2, } else if released && was_dragging { - pos^ += pos_adjust - alignment^ = target_alignment - was_dragging = false + // This needed to be added as for some reason, this was getting called in screen_ui even when we were resizing with a handle in a worksapce + if active_context != ui do return false + + if .Origin_At_Anchor_Center in parent.layout.flags { + pos_adjust = size^ * 0.5 * direction + } + pos^ += pos_adjust + alignment^ = target_alignment + was_dragging = false + start_size = 0 + active_context = ui } return was_dragging } - delta := get_state().input.mouse.delta + state := get_state() + delta := state.input.mouse.delta //state.ui_context == & state.screen_ui ? state.input.mouse.delta : ui_ws_drag_delta() alignment := & parent.layout.alignment - if right do drag_signal |= process_handle_drag( & handle_right, { 1, 0 }, delta, {0, 0}, pos, size, alignment ) - if left do drag_signal |= process_handle_drag( & handle_left, { -1, 0 }, delta, {1, 0}, pos, size, alignment ) - if top do drag_signal |= process_handle_drag( & handle_top, { 0, 1 }, delta, {0, 0}, pos, size, alignment ) - if bottom do drag_signal |= process_handle_drag( & handle_bottom, { 0, -1 }, delta, {0, 1}, pos, size, alignment ) - if corner_tr do drag_signal |= process_handle_drag( & handle_corner_tr, { 1, 1 }, delta, {0, 0}, pos, size, alignment ) - if corner_tl do drag_signal |= process_handle_drag( & handle_corner_tl, { -1, 1 }, delta, {1, 0}, pos, size, alignment ) - if corner_br do drag_signal |= process_handle_drag( & handle_corner_br, { 1, -1 }, delta, {0, 1}, pos, size, alignment ) - if corner_bl do drag_signal |= process_handle_drag( & handle_corner_bl, { -1, -1 }, delta, {1, 1}, pos, size, alignment ) + if .Origin_At_Anchor_Center in parent.layout.flags + { + if right do drag_signal |= process_handle_drag( & handle_right, { 1, 0}, delta, { 0.5, 0}, {}, pos, size, alignment ) + if left do drag_signal |= process_handle_drag( & handle_left, {-1, 0}, delta, {-0.5, 0}, {}, pos, size, alignment ) + if top do drag_signal |= process_handle_drag( & handle_top, { 0, 1}, delta, { 0, 0.5}, {}, pos, size, alignment ) + if bottom do drag_signal |= process_handle_drag( & handle_bottom, { 0, -1}, delta, { 0, -0.5}, {}, pos, size, alignment ) + if corner_tr do drag_signal |= process_handle_drag( & handle_corner_tr, { 1, 1}, delta, { 0.5, 0.5}, {}, pos, size, alignment ) + if corner_tl do drag_signal |= process_handle_drag( & handle_corner_tl, {-1, 1}, delta, {-0.5, 0.5}, {}, pos, size, alignment ) + if corner_br do drag_signal |= process_handle_drag( & handle_corner_br, { 1, -1}, delta, { 0.5, -0.5}, {}, pos, size, alignment ) + if corner_bl do drag_signal |= process_handle_drag( & handle_corner_bl, {-1, -1}, delta, {-0.5, -0.5}, {}, pos, size, alignment ) + } + else + { + if right do drag_signal |= process_handle_drag( & handle_right, { 1, 0 }, delta, {0, 0}, {}, pos, size, alignment ) + if left do drag_signal |= process_handle_drag( & handle_left, { -1, 0 }, delta, {1, 0}, {}, pos, size, alignment ) + if top do drag_signal |= process_handle_drag( & handle_top, { 0, 1 }, delta, {0, -1}, {}, pos, size, alignment ) + if bottom do drag_signal |= process_handle_drag( & handle_bottom, { 0, -1 }, delta, {0, 0}, {}, pos, size, alignment ) + if corner_tr do drag_signal |= process_handle_drag( & handle_corner_tr, { 1, 1 }, delta, {0, -1}, {}, pos, size, alignment ) + if corner_tl do drag_signal |= process_handle_drag( & handle_corner_tl, { -1, 1 }, delta, {1, -1}, {}, pos, size, alignment ) + if corner_br do drag_signal |= process_handle_drag( & handle_corner_br, { 1, -1 }, delta, {0, 0}, {}, pos, size, alignment ) + if corner_bl do drag_signal |= process_handle_drag( & handle_corner_bl, { -1, -1 }, delta, {1, 0}, {}, pos, size, alignment ) + } - ui_box_compute_layout(parent) + if drag_signal && compute_layout do ui_box_compute_layout(parent) return } #endregion("Resizable")