From 2b1565e35b0039785ba6416f2389281b0417460e Mon Sep 17 00:00:00 2001 From: Ed_ Date: Fri, 10 May 2024 19:20:50 -0400 Subject: [PATCH] AppUI lifted to its own file, Got horizontal and vertical boxes working --- code/api.odin | 12 ++- code/app_ui.odin | 185 +++++++++++++++++++++++++++++++++++++ code/colors.odin | 2 +- code/env.odin | 2 +- code/text.odin | 12 ++- code/tick_render.odin | 10 +- code/tick_update.odin | 204 +---------------------------------------- code/ui.odin | 9 +- code/ui_signal.odin | 3 +- code/ui_style.odin | 10 ++ code/ui_tests.odin | 2 +- code/ui_widgets.odin | 207 +++++++++++++++++++++++++++++++++++------- docs/Persistent | 0 scripts/build.ps1 | 8 +- toolchain/Odin | 2 +- 15 files changed, 408 insertions(+), 260 deletions(-) create mode 100644 code/app_ui.odin create mode 100644 docs/Persistent diff --git a/code/api.odin b/code/api.odin index 527ad1f..0bad98c 100644 --- a/code/api.odin +++ b/code/api.odin @@ -191,7 +191,15 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem } // Setup the app ui state - ui_startup( & app_ui, cache_allocator = persistent_slab_allocator() ) + { + ui_startup( & app_ui.base, cache_allocator = persistent_slab_allocator() ) + + using app_ui + menu_bar.pos = Vec2(app_window.extent) * { -1, 1 } + menu_bar.size = {200, 40} + + settings_menu.min_size = {200, 200} + } // Demo project setup { @@ -340,7 +348,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 + // config.engine_refresh_hz = 30 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/app_ui.odin b/code/app_ui.odin new file mode 100644 index 0000000..95771a6 --- /dev/null +++ b/code/app_ui.odin @@ -0,0 +1,185 @@ +package sectr + +App_UI_State :: struct +{ + using base : UI_State, + menu_bar : struct + { + pos, size : Vec2, + container : UI_HBox, + settings_btn : struct + { + using widget : UI_Widget, + is_open : b32, + } + }, + settings_menu : struct + { + pos, size, min_size : Vec2, + container : UI_VBox, + }, +} + +ui_app_menu_bar :: proc() +{ + profile("App Menu Bar") + fmt :: str_fmt_alloc + + using state := get_state() + using app_ui + { + using menu_bar + ui_theme_via_style({ + flags = {}, + bg_color = { 0, 0, 0, 30 }, + border_color = { 0, 0, 0, 200 }, + font = default_font, + text_color = Color_White, + layout = { + anchor = {}, + alignment = { 0, 1 }, + border_width = 1.0, + font_size = 12, + pos = menu_bar.pos, + size = range2( menu_bar.size, {}), + }, + }) + container = ui_hbox( .Left_To_Right, "App Menu Bar", { .Mouse_Clickable} ) + + theme := to_ui_styletheme({ + bg_color = Color_Frame_Disabled, + font = default_font, + text_color = Color_White, + layout = { + anchor = range2( {0, 0}, {0, 0} ), + alignment = { 0.0, 0.0 }, + font_size = 18, + text_alignment = { 0.5, 0.5 }, + size = range2({25, 0}, {0, 0}) + } + }) + theme.hot.bg_color = Color_Blue + theme.active.bg_color = Color_Frame_Select + ui_style_theme(theme) + + move_box := ui_button("Move Box"); + { + using move_box + if active { + menu_bar.pos += input.mouse.delta + } + style.anchor.ratio.x = 0.2 + } + + spacer := ui_spacer("Menu Bar: Move Spacer") + spacer.style.flags |= {.Fixed_Width} + spacer.style.size.min.x = 50 + // spacer.style.bg_color = Color_Red + + settings_btn.widget = ui_button("Settings Btn") + settings_btn.text = str_intern("Settings") + settings_btn.style.flags = { + // .Scale_Width_By_Height_Ratio, + .Fixed_Width + } + settings_btn.style.size.min.x = 100 + if settings_btn.pressed { + settings_btn.is_open = true + } + + spacer = ui_spacer("Menu Bar: End Spacer") + spacer.style.anchor.ratio.x = 1.0 + // spacer.style.bg_color = Color_Red + } + + ui_app_settings_menu() +} + +ui_app_settings_menu :: proc() +{ + profile("Settings Menu") + using state := get_state() + using state.app_ui + if menu_bar.settings_btn.pressed || ! menu_bar.settings_btn.is_open { + return + } + + using settings_menu + if size.x < min_size.x { + size.x = min_size.x + } + if size.y < min_size.y { + size.y = min_size.y + } + container = ui_vbox_begin( .Top_To_Bottom, "Settings Menu", {.Mouse_Clickable}) + { + using container + style.flags = { + // .Origin_At_Anchor_Center + } + style.pos = pos + style.alignment = { 0.5, 0.5 } + style.bg_color = Color_BG_Panel_Translucent + style.size = range2( size, {}) + } + ui_parent(container) + { + ui_theme_via_style({ + bg_color = Color_Transparent, + font = default_font, + text_color = Color_White, + layout = { font_size = 16 }, + }) + ui_style_theme_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.flags = {.Fixed_Height} + frame_bar.style.alignment = { 0, 0 } + // frame_bar.style.anchor.ratio.y = 0.25 + frame_bar.style.size.min.y = 50 + if frame_bar.active { + pos += input.mouse.delta + } + ui_parent(frame_bar) + + 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} + style.anchor.ratio.x = 1.0 + } + + ui_style_theme(ui_style_theme_peek()) + theme := ui_style_theme_ref() + theme.default.bg_color = Color_GreyRed + theme.hot. bg_color = Color_Red + close_btn := ui_button("Settings Menu: Close Btn") + { + using close_btn + text = str_intern("close") + style.flags = {.Fixed_Width} + // style.bg_color = Color_GreyRed + style.size.min = {50, 0} + // style.anchor = range2( {1.0, 0}, {}) + // style.alignment = {0, 0} + style.text_alignment = {0.5, 0.5} + style.anchor.ratio.x = 1.0 + if close_btn.pressed { + menu_bar.settings_btn.is_open = false + } + } + + ui_hbox_end(frame_bar, & size.x) + } + + spacer := ui_spacer("Settings Menu: Spacer") + spacer.style.anchor.ratio.y = 1.0 + + ui_vbox_end(container, & size.y) + } + + ui_resizable_handles( & container, & pos, & size ) +} diff --git a/code/colors.odin b/code/colors.odin index 18a7597..f1d1ba4 100644 --- a/code/colors.odin +++ b/code/colors.odin @@ -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 { 20, 40, 50, 80 } +Color_ResizeHandle :: Color { 80, 80, 90, 180 } Color_3D_BG :: Color { 188, 182 , 170, 255 } diff --git a/code/env.odin b/code/env.odin index 2f23161..0fd730f 100644 --- a/code/env.odin +++ b/code/env.odin @@ -177,7 +177,7 @@ State :: struct { config : AppConfig, app_window : AppWindow, - app_ui : UI_State, + app_ui : App_UI_State, monitor_id : i32, monitor_refresh_hz : i32, diff --git a/code/text.odin b/code/text.odin index 239d256..08334a0 100644 --- a/code/text.odin +++ b/code/text.odin @@ -26,12 +26,14 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co px_size := size rl_font := to_rl_Font(font, px_size ) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) rl.DrawTextCodepoints( rl_font, raw_data(runes), cast(i32) len(runes), position = transmute(rl.Vector2) pos, fontSize = px_size, spacing = 0.0, tint = color ); + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) } draw_text_screenspace :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) @@ -51,7 +53,7 @@ draw_text_screenspace :: proc( content : StrRunesPair, pos : Vec2, size : f32, c rl_font := to_rl_Font(font, size ) runes := content.runes - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.TRILINEAR) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) rl.DrawTextCodepoints( rl_font, raw_data(runes), cast(i32) len(runes), position = transmute(rl.Vector2) pos, @@ -83,14 +85,14 @@ ws_view_draw_text_string :: proc( content : string, pos : Vec2, size : f32, colo zoom_adjust := px_size * project.workspace.cam.zoom rl_font := to_rl_Font(font, zoom_adjust ) - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.TRILINEAR) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.BILINEAR) rl.DrawTextCodepoints( rl_font, raw_data(runes), cast(i32) len(runes), position = transmute(rl.Vector2) pos, fontSize = px_size, spacing = 0.0, tint = color ); - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.ANISOTROPIC_16X) } ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) @@ -112,14 +114,14 @@ ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size rl_font := to_rl_Font(font, zoom_adjust ) runes := content.runes - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.TRILINEAR) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.BILINEAR) rl.DrawTextCodepoints( rl_font, raw_data(runes), cast(i32) len(runes), position = transmute(rl.Vector2) pos, fontSize = px_size, spacing = 0.0, tint = color ); - rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.ANISOTROPIC_16X) } // Raylib's equivalent doesn't take a length for the string (making it a pain in the ass) diff --git a/code/tick_render.odin b/code/tick_render.odin index 79e31df..f37ae3b 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -111,6 +111,7 @@ render_mode_2d_workspace :: proc() if root.num_children == 0 { break ImguiRender } + state.ui_context = ui current := root.first for ; current != nil; current = ui_box_tranverse_next( current ) @@ -173,10 +174,10 @@ render_mode_2d_workspace :: proc() // profile_begin("rl.DrawRectangleRoundedLines: padding & content") if equal_range2(computed.content, computed.padding) { - // draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness ) + draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness ) } else { - // draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness ) + draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness ) } // profile_end() @@ -278,7 +279,7 @@ render_mode_screenspace :: proc () ui := & project.workspace.ui - debug_text("Box Count: %v", ui.built_box_count ) + debug_text("Box Count (Workspace): %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 ) @@ -324,6 +325,7 @@ render_app_ui :: proc() { profile("App UI") ui := & state.app_ui + state.ui_context = ui root := ui.root if root.num_children == 0 { break Render_App_UI @@ -423,7 +425,7 @@ render_app_ui :: proc() // rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) // profile_end() - if len(current.text.str) > 0 { + if len(current.text.str) > 0 && style.font.key != 0 { 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 5ed5b79..7b92472 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -200,209 +200,7 @@ update :: proc( delta_time : f64 ) -> b32 ui_graph_build( & state.app_ui ) ui := ui_context - /* - Prototype app menu - This is a menu bar for the app for now inside the same ui as the workspace's UI state - Eventually this will get moved out to its own UI state for the app itself. - */ - if true - { - profile("App Menu Bar") - fmt :: str_fmt_alloc - - @static bar_pos := Vec2{0, 100} - bar_size := vec2( 400, 40 ) - - menu_bar : UI_Widget - { - theme := UI_Style { - flags = { - }, - bg_color = { 0, 0, 0, 30 }, - border_color = { 0, 0, 0, 200 }, - - font = default_font, - 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", { .Mouse_Clickable} ) - 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) - } - } - // Setup Children - settings_btn : UI_Widget - { - ui_parent(menu_bar) - - style := UI_Style { - flags = { - // .Origin_At_Anchor_Center - .Fixed_Height - }, - bg_color = Color_Frame_Disabled, - - font = default_font, - 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 := to_ui_styletheme(style) - theme.disabled.bg_color = Color_Frame_Disabled - 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.active { - bar_pos += input.mouse.delta - } - using move_box - hot := ui_box_from_key(ui.curr_cache, ui.hot) - if hot != nil { - text = to_str_runes_pair(str_fmt_tmp("Hot box: %v %v", hot.label.str, hot.hot_delta)) - style.font = default_font - style.font_size = 12 - style.text_color = Color_White - style.text_alignment = {0, 1} - } - } - - move_settings_spacer := ui_widget("Move-Settings Spacer", {}) - { - 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? - { - hb_space_ratio_move_box := 0.1 - hb_space_ratio_move_settings_spacer := 0.05 - hb_space_ratio_settings_btn := 1.0 - - style := & move_box.box.style - style.anchor.max.x = 0.9 - - style = & move_settings_spacer.box.style - style.anchor.min.x = 0.1 - style.anchor.max.x = 0.8 - - style = & settings_btn.box.style - style.anchor.min.x = 0.2 - style.anchor.max.x = 0.55 - } - } - - @static settings_open := true - if settings_btn.pressed || settings_open - { - profile("Settings Menu") - settings_open = true - - resize_border_width : f32 = 20 - @static pos := Vec2 {0, 0} - @static size := Vec2 { 200, 200 } - if size.x < 200 { - size.x = 200 - } - if size.y < 200 { - size.y = 200 - } - settings_menu := ui_widget("Settings Menu", {.Mouse_Clickable}) - { - using settings_menu - 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, {}) - text = to_str_runes_pair(str_fmt_tmp("Hot Delta: %v", hot_delta)) - style.font = default_font - style.font_size = 12 - style.text_color = Color_White - } - - ui_parent(settings_menu) - ui_theme_via_style({ - bg_color = Color_Transparent, - font = default_font, - text_color = Color_White, - 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.alignment = { 0, 1 } - } - ui_parent(frame_bar) - - if frame_bar.active { - pos += input.mouse.delta - } - - 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} - } - - 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 ) - } - } + ui_app_menu_bar() } //endregion App UI Tick diff --git a/code/ui.odin b/code/ui.odin index f9d9a19..1f5609c 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -291,9 +291,14 @@ ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box) { // Check to make sure parent is present on the screen, if its not don't bother. // If current has children, do them first - if intersects_range2( view_get_bounds(), box.computed.bounds) && box.first != nil + using state := get_state() + if box.first != nil { - return box.first + is_app_ui := ui_context == & app_ui + if is_app_ui || intersects_range2( view_get_bounds(), box.computed.bounds) + { + return box.first + } } if box.next == nil diff --git a/code/ui_signal.odin b/code/ui_signal.odin index 6587109..e16220f 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -21,7 +21,7 @@ UI_Signal :: struct { commit : b8, } -ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal +ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas := true ) -> UI_Signal { // profile(#procedure) ui := get_state().ui_context @@ -199,6 +199,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal // logf("was_active: %v", was_active) // Update style if not in default state + if update_style { // profile("Update style") diff --git a/code/ui_style.odin b/code/ui_style.odin index 509f8b8..20ea86a 100644 --- a/code/ui_style.odin +++ b/code/ui_style.odin @@ -1,5 +1,15 @@ package sectr +UI_LayoutDirectionX :: enum(i32) { + Left_To_Right, + Right_To_Left, +} + +UI_LayoutDirectionY :: enum(i32) { + Top_To_Bottom, + Bottom_To_Top, +} + UI_LayoutSide :: struct { // using _ : struct { top, bottom : UI_Scalar, diff --git a/code/ui_tests.odin b/code/ui_tests.odin index fd9ae6e..8367520 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -202,7 +202,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : } ui_style_theme_set_layout( layout_text ) - line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {}) + line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {.Mouse_Clickable}) if line_hbox.key == ui.hot { diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index 958c0c2..81f25fa 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -7,8 +7,6 @@ UI_Widget :: struct { ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget) { - // profile(#procedure) - widget.box = ui_box_make( flags, label ) widget.signal = ui_signal_from_box( widget.box ) return @@ -16,8 +14,6 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget) ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget) { - // profile(#procedure) - btn_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus } btn.box = ui_box_make( btn_flags | flags, label ) btn.signal = ui_signal_from_box( btn.box ) @@ -32,7 +28,7 @@ attempt to slot them adjacent to each other along the x-axis. The user must provide the direction that the hbox will append entries. How the widgets will be scaled will be based on the individual entires style flags. -All the usual behaviors that the style and box flags do apply when manage by the box widget. +All the usual behaviors that the style and box flags do apply when managed by the box widget. Whether or not the horizontal box will scale the widget's width is if: fixed size or "scale by ratio" flags are not used for the width. The hbox will use the anchor's (range2) ratio.x value to determine the "stretch ratio". @@ -40,18 +36,31 @@ The hbox will use the anchor's (range2) ratio.x value to determine the "stretch Keep in mind the stretch ratio is only respected if no size.min.x value is violated for each of the widgets. */ -ui_hbox_begin :: proc( label : string, flags : UI_BoxFlags = {} -//, direction -) -> (widget : UI_Widget) { - // profile(#procedure) +// Horizontal Widget +UI_HBox :: struct { + using widget : UI_Widget, + direction : UI_LayoutDirectionX, +} - widget.box = ui_box_make( flags, label ) - widget.signal = ui_signal_from_box( widget.box ) +// Boilerplate creation +ui_hbox_begin :: proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) { + // profile(#procedure) + hbox.direction = direction + hbox.box = ui_box_make( flags, label ) + hbox.signal = ui_signal_from_box( hbox.box ) return } -ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget { - hbox_width := hbox.computed.content.max.y - hbox.computed.content.min.y +// Auto-layout children +ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil ) { + // profile(#procedure) + hbox_width : f32 + if width_ref != nil { + hbox_width = width_ref ^ + } + else { + hbox_width = hbox.computed.content.max.x - hbox.computed.content.min.x + } // do layout calculations for the children total_stretch_ratio : f32 = 0.0 @@ -74,20 +83,53 @@ ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget { size_req_children += size.min.x continue } + + total_stretch_ratio += anchor.ratio.x + } + + avail_flex_space := hbox_width - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 ) + { + using child.style + if ! (.Fixed_Width in child.style.flags) { + size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space + } + flags |= {.Fixed_Width} + alignment = {0, 1} + } + + space_used : f32 = 0.0 + switch hbox.direction{ + case .Right_To_Left: + for child := hbox.last; child != nil; child = child.prev { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.style + anchor = range2({0, 0}, {0, 0}) + pos.x = space_used + space_used += size.min.x + } + case .Left_To_Right: + for child := hbox.first; child != nil; child = child.next { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.style + anchor = range2({0, 0}, {0, 0}) + pos.x = space_used + space_used += size.min.x + } } - availble_flexible_space := hbox_width - size_req_children - return hbox } -ui_hbox_auto_end :: proc( vbox : UI_Widget ) { - ui_hbox_end(vbox) +// Auto-layout children and pop parent from parent stack +ui_hbox_end_pop_parent :: proc( hbox : UI_HBox ) { + ui_hbox_end(hbox) ui_parent_pop() } -@(deferred_out = ui_hbox_end) -ui_hbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) { - widget = ui_hbox_begin(label, flags) - ui_parent(widget) +@(deferred_out = ui_hbox_end_pop_parent) +ui_hbox :: #force_inline proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) { + hbox = ui_hbox_begin(direction, label, flags) + ui_parent_push(hbox.widget) return } //endregion Horizontal Box @@ -235,6 +277,14 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, if corner_bl do process_handle_drag( & handle_corner_bl, { -1, -1 }, delta, {1, 1}, pos, size, alignment ) } +ui_spacer :: proc( label : string ) -> (widget : UI_Widget) { + widget.box = ui_box_make( {.Mouse_Clickable}, label ) + widget.signal = ui_signal_from_box( widget.box ) + + widget.style.bg_color = Color_Transparent + return +} + ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget { // profile(#procedure) @@ -278,28 +328,115 @@ ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget } //region Vertical Box -ui_vbox_begin :: proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) { - // profile(#procedure) +/* +Vertical Boxes automatically manage a collection of widgets and +attempt to slot them adjacent to each other along the y-axis. - widget.box = ui_box_make( flags, label ) - // widget.signal = ui_signal_from_box( widget.box ) +The user must provide the direction that the vbox will append entries. +How the widgets will be scaled will be based on the individual entires style flags. + +All the usual behaviors that the style and box flags do apply when managed by the box widget. +Whether or not the horizontal box will scale the widget's width is if: +fixed size or "scale by ratio" flags are not used for the width. +The hbox will use the anchor's (range2) ratio.y value to determine the "stretch ratio". + +Keep in mind the stretch ratio is only respected if no size.min.y value is violated for each of the widgets. +*/ + +UI_VBox :: struct { + using widget : UI_Widget, + direction : UI_LayoutDirectionY, +} + +// Boilerplate creation +ui_vbox_begin :: proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {} ) -> (vbox : UI_VBox) { + // profile(#procedure) + vbox.direction = direction + vbox.box = ui_box_make( flags, label ) + vbox.signal = ui_signal_from_box( vbox.box ) return } -ui_vbox_end :: proc( hbox : UI_Widget ) -> UI_Widget { + +// Auto-layout children +ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil ) { + // profile(#procedure) + + vbox_height : f32 + if height_ref != nil { + vbox_height = height_ref ^ + } + else { + vbox_height = vbox.computed.bounds.max.y - vbox.computed.bounds.min.y + } + // do layout calculations for the children - return hbox + total_stretch_ratio : f32 = 0.0 + size_req_children : f32 = 0 + for child := vbox.first; child != nil; child = child.next + { + using child + using style.layout + scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in style.flags) + if .Fixed_Height in style.flags + { + if scaled_width_by_height { + width := size.max.x != 0 ? size.max.x : vbox_height + height := width * size.min.y + + size_req_children += height + continue + } + + size_req_children += size.min.y + continue + } + + total_stretch_ratio += anchor.ratio.y + } + + avail_flex_space := vbox_height - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 ) + { + using child.style + if ! (.Fixed_Height in child.style.flags) { + size.min.y = anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space + } + flags |= {.Fixed_Height} + alignment = {0, 0} + } + + space_used : f32 = 0.0 + switch vbox.direction { + case .Top_To_Bottom: + for child := vbox.last; child != nil; child = child.prev { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.style + anchor = range2({0, 0}, {0, 1}) + pos.y = space_used + space_used += size.min.y + } + case .Bottom_To_Top: + for child := vbox.first; child != nil; child = child.next { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.style + anchor = range2({0, 0}, {0, 1}) + pos.y = space_used + space_used += size.min.y + } + } } -ui_vbox_auto_end :: proc( hbox : UI_Widget ) { - ui_vbox_end(hbox) + +// Auto-layout children and pop parent from parent stack +ui_vbox_end_pop_parent :: proc( vbox : UI_VBox ) { + ui_vbox_end(vbox) ui_parent_pop() } -// ui_vbox_append( widget : UI_Widget ) - -@(deferred_out = ui_vbox_auto_end) -ui_vbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) { - widget = ui_vbox_begin(label, flags) - ui_parent_push(widget) +@(deferred_out = ui_vbox_end_pop_parent) +ui_vbox :: #force_inline proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {} ) -> (vbox : UI_VBox) { + vbox = ui_vbox_begin(direction, label, flags) + ui_parent_push(vbox.widget) return } //endregion Vertical Box diff --git a/docs/Persistent b/docs/Persistent new file mode 100644 index 0000000..e69de29 diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 9787847..262c569 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -169,8 +169,8 @@ push-location $path_root # $build_args += $flag_micro_architecture_native # $build_args += $flag_use_separate_modules # $build_args += $flag_thread_count + $CoreCount_Physical - $build_args += $flag_optimize_none - # $build_args += $flag_optimize_minimal + # $build_args += $flag_optimize_none + $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed # $build_args += $falg_optimize_aggressive $build_args += $flag_debug @@ -251,8 +251,8 @@ push-location $path_root # $build_args += $flag_micro_architecture_native # $build_args += $flag_use_separate_modules # $build_args += $flag_thread_count + $CoreCount_Physical - $build_args += $flag_optimize_none - # $build_args += $flag_optimize_minimal + # $build_args += $flag_optimize_none + $build_args += $flag_optimize_minimal # $build_args += $flag_optimize_speed # $build_args += $falg_optimize_aggressive $build_args += $flag_debug diff --git a/toolchain/Odin b/toolchain/Odin index e1b5ccf..e462116 160000 --- a/toolchain/Odin +++ b/toolchain/Odin @@ -1 +1 @@ -Subproject commit e1b5ccf2dc07b6218c8d7c2ec3184594d085af89 +Subproject commit e462116f9428c3a871663eae4363c8d7da35497e