diff --git a/SectrPrototype.code-workspace b/SectrPrototype.code-workspace new file mode 100644 index 0000000..639301e --- /dev/null +++ b/SectrPrototype.code-workspace @@ -0,0 +1,20 @@ +{ + "folders": [ + { + "path": "code_virtual_view" + }, + { + "path": "scripts" + } + ], + "settings": { + "autoHide.autoHidePanel": false, + "autoHide.autoHideSideBar": false, + "files.associations": { + "*.rmd": "markdown", + "type_traits": "cpp", + "utf8proc.c": "cpp", + "xtr1common": "cpp" + } + } +} \ No newline at end of file diff --git a/code/__Imgui_raddbg/ui.odin b/code/__Imgui_raddbg/ui.odin deleted file mode 100644 index 22cf6dd..0000000 --- a/code/__Imgui_raddbg/ui.odin +++ /dev/null @@ -1,275 +0,0 @@ -package wip - -// Based off Ryan Fleury's UI Series & Epic's RAD Debugger which directly implements a version of it. -// You will see Note(rjf) these are comments directly from the RAD Debugger codebase by Fleury. -// TODO(Ed) If I can, I would like this to be its own package, but the nature of packages in odin may make this difficult. - -// TODO(Ed) : This is in Raddbg base_types.h, consider moving outside of UI. -Axis2 :: enum i32 { - Invalid = -1, - X = 0, - Y = 1, - Count, -} - -Corner :: enum i32 { - Invalid = -1, - _00, - _01, - _10, - _11, - TopLeft = _00, - TopRight = _01, - BottomLeft = _10, - BottomRight = _11, - Count = 4, -} - -// TODO(Ed) : From Raddbg draw.h, consider if needed or covered by raylib. -DrawBucket :: struct { - // passes : RenderPassList, - stack_gen : u64, - last_cmd_stack_gen : u64, - // DrawBucketStackDeclares -} - -// TODO(Ed) : From Raddbg draw.h, consider if needed or covered by raylib. -DrawFancyRunList :: struct { - placeholder : int -} - -// TODO(Ed) : From Raddbg base_string.h, consider if needed or covered by raylib. -FuzzyMatchRangeList :: struct { - placeholder : int -} - -// TODO(Ed): This is in Raddbg os_gfx.h, consider moving outside of UI. -OS_Cursor :: enum u32 { - Pointer, - IBar, - Left_Right, - Up_Down, - Down_Right, - Up_Right, - Up_Down_left_Right, - Hand_Point, - Disabled, - Count, -} - -Range2 :: struct #raw_union{ - using _ : struct { - min, max : Vec2 - }, - using _ : struct { - p0, p1 : Vec2 - }, - using _ : struct { - x0, y0 : f32, - x1, y1 : f32, - }, -} - -Side :: enum i32 { - Invalid = -1, - Min = 0, - Max = 1, - Count -} - -UI_FocusKind :: enum u32 { - Null, - Off, - On, - Root, - Count, -} - -UI_IconKind :: enum u32 { - Null, - Arrow_Up, - Arrow_Left, - Arrow_Right, - Arrow_Down, - Caret_Up, - Caret_Left, - Caret_Right, - Caret_Down, - Check_Hollow, - Check_Filled, - Count, -} - -UI_IconInfo :: struct { - placehodler : int -} - -UI_Key :: struct { - opaque : [1]u64, -} - -UI_Layout :: struct { - placeholder : int -} - -UI_Nav :: struct { - moved : b32, - new_p : Vec2i -} - -UI_NavDeltaUnit :: enum u32 { - Element, - Chunk, - Whole, - End_Point, - Count, -} - -UI_NavActionFlag :: enum u32 { - Keep_Mark, - Delete, - Copy, - Paste, - Zero_Delta_On_Select, - Pick_Select_Side, - Can_At_Line, - Explicit_Directional, - Replace_And_Commit, -} -UI_NavActionFlags :: bit_set[UI_NavActionFlag; u32] - -UI_NavAction :: struct { - flags : UI_NavActionFlags, - delta : Vec2i, - delta_unit : UI_NavDeltaUnit, - insertion : string, -} - -UI_NavActionNode :: struct { - next : ^ UI_NavActionNode, - last : ^ UI_NavActionNode, - action : UI_NavAction -} - -UI_NavActionList :: struct { - first : ^ UI_NavActionNode, - last : ^ UI_NavActionNode, - count : u64, -} - -UI_NavTextOpFlag :: enum u32 { - Invalid, - Copy, -} -UI_NavTextOpFlags :: bit_set[UI_NavTextOpFlag; u32] - -UI_ScrollPt :: struct { - idx : i64, - offset : f32 -} -UI_ScrollPt2 :: [2]UI_ScrollPt - -UI_Signal :: struct { - box : ^ UI_Box, - - 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, - hovering : b8, - mouse_over : b8, - commit : b8, -} - -UI_SizeKind :: enum u32 { - Null, - Pixels, - Points, - TextContent, - PercentOfParent, - ChildrenSum, - Count, -} - -UI_Size :: struct { - kind : UI_SizeKind, - value : f32, - strictness : f32, -} - -UI_Size2 : struct { - kind : [Axis2.Count]UI_SizeKind, - value : Vec2, - strictness : Vec2, -} - -UI_TextAlign :: enum u32 { - Left, - Center, - Right, - Count -} - -ui_key_null :: proc() -> UI_Key { - return {} -} - -ui_key_from_string :: proc( value : string ) -> UI_Key { - return {} -} - -ui_key_match :: proc( a, b : UI_Key ) -> b32 { - return false -} - -ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) { - return nil -} - -ui_box_equip_display_string :: proc( box : ^ UI_Box, display_string : string ) { - -} - -ui_box_equip_child_layout_axis :: proc( box : ^ UI_Box, axis : Axis2 ) { - -} - -ui_push_parent :: proc( box : ^ UI_Box ) -> (^ UI_Box) { - return nil -} - -ui_pop_parent :: proc() -> (^ UI_Box) { - return nil -} - -ui_signal_from_box :: proc( box : ^ UI_Box ) -> UI_Signal { - return {} -} - -ui_button :: proc( label : string ) -> UI_Signal { - button_flags : UI_BoxFlags = - UI_BoxFlags_Clickable & { - .Draw_Border, - .Draw_Text, - .Draw_Background, - .Focus_Hot, - .Focus_Active, - } - box := ui_box_make( button_flags, label ) - signal := ui_signal_from_box( box ) - return signal -} - -ui_spacer :: proc( label : string = UI_NullLabel ) -> UI_Signal { - box := ui_box_make( UI_BoxFlags_Null, label ) - signal := ui_signal_from_box( box ) - return signal -} diff --git a/code/__Imgui_raddbg/ui_box.odin b/code/__Imgui_raddbg/ui_box.odin deleted file mode 100644 index 447c6fb..0000000 --- a/code/__Imgui_raddbg/ui_box.odin +++ /dev/null @@ -1,172 +0,0 @@ -package wip - -UI_BoxFlag :: enum u64 { - // Note(rjf) : Interaction - Mouse_Clickable, - Keyboard_Clickable, - Click_To_Focus, - Scroll, - View_Scroll_X, - View_Scroll_Y, - View_Clamp_X, - View_Clamp_Y, - Focus_Active, - Focus_Active_Disabled, - Focus_Hot, - Focus_Hot_Disabled, - Default_Focus_Nav_X, - Default_Focus_Nav_Y, - Default_Focus_Edit, - Focus_Nav_Skip, - Disabled, - - // Note(rjf) : Layout - Floating_X, - Floating_Y, - Fixed_Width, - Fixed_Height, - Allow_Overflow_X, - Allow_Overflow_Y, - Skip_View_Off_X, - Skip_View_Off_Y, - - // Note(rjf) : Appearance / Animation - Draw_Drop_Shadow, - Draw_Background_Blur, - Draw_Background, - Draw_Border, - Draw_Side_Top, - Draw_Side_Bottom, - Draw_Side_Left, - Draw_Side_Right, - Draw_Text, - Draw_Text_Fastpath_Codepoint, - Draw_Hot_Effects, - Draw_Overlay, - Draw_Bucket, - Clip, - Animate_Pos_X, - Animate_Pos_Y, - Disable_Text_Trunc, - Disable_ID_String, - Disable_Focus_Viz, - Require_Focus_Background, - Has_Display_String, - Has_Fuzzy_Match_Ranges, - Round_Children_By_Parent, - - Count, -} -UI_BoxFlags :: bit_set[UI_BoxFlag; u64] - -UI_BoxFlags_Null :: UI_BoxFlags {} -UI_BoxFlags_Clickable :: UI_BoxFlags { .Mouse_Clickable, .Keyboard_Clickable } - -UI_NullLabel :: "" - -UI_BoxCustomDrawProc :: #type proc( box : ^ UI_Box, user_data : rawptr ) - -UI_BoxCustomDraw :: struct { - callback : UI_BoxCustomDrawProc, - data : rawptr, -} - -UI_BoxRec :: struct { - next : ^ UI_BoxNode, - push_count : i32, - pop_count : i32, -} - -UI_BoxNode :: struct { - next : ^ UI_BoxNode, - box : ^ UI_Box, -} - -UI_BoxList :: struct { - first : UI_BoxNode, - last : UI_BoxNode, - count : u64, -} - -UI_BoxHashSlot :: struct { - first, last : ^ UI_Box -} - -// Note(Ed) : This is called UI_Widget in the substack series, its called box in raddbg -// This eventually gets renamed by part 4 of the series to UI_Box. -// However, its essentially a UI_Node or UI_BaselineEntity, etc. -// Think of godot's Control nodes I guess. -// TODO(Ed) : We dumped all the fields present within raddbg, review which ones are actually needed. -UI_Box :: struct { - // Note(rjf) : persistent links - hash : struct { - next, prev : ^ UI_Box, - }, - - // Note(rjf) : Per-build links & data - // TODO(ED) : Put this in its own struct? - first, last, prev, next : ^ UI_Box, - num_children : i32, - - // Note(rjf) : Key + generation info - // TODO(ED) : Put this in its own struct? - key : UI_Key, - last_frame_touched_index : u64, - - // Note(rjf) : Per-frame info provided by builders - // TODO(ED) : Put this in its own struct? - flags : UI_BoxFlags, - display_str : string, // raddbg: string - semantic_size : [Axis2.Count]UI_Size, - text_align : UI_TextAlign, - fixed_pos : Vec2, - fixed_size : Vec2, - pref_size : [Axis2.Count]UI_Size, - child_layout_axis : Axis2, - hover_cursor : OS_Cursor, - fastpath_codepoint : u32, - draw_bucket : DrawBucket, // TODO(Ed): Translate to equivalent in raylib if necessary - custom_draw : UI_BoxCustomDraw, - bg_color : Color, - text_color : Color, - border_color : Color, - overlay_color : Color, - font : FontID, - font_size : f32, - corner_radii : [Corner.Count]f32, - blur_size : f32, // TODO(Ed) : You would need to run a custom shader with raylib or have your own rendering backend for this. - transparency : f32, - squish : f32, - text_padding : f32, - - // Note(rjf) : Per-frame artifacts by builders - // TODO(ED) : Put this in its own struct? - display_string_runs : DrawFancyRunList, // TODO(Ed) : Translate to equivalent in raylib if necessary - rect : Range2, - fixed_pos_animated : Vec2, - pos_delta : Vec2, - fuzzy_match_range : FuzzyMatchRangeList, // TODO(Ed) : I'm not sure this is needed - - // Note(rjf) : Computed every frame - // TODO(ED) : Put this in its own struct? - computed_rel_pos : Vec2, // TODO(Ed) : Raddbg doesn't have these, check what they became or if needed - computed_size : Vec2, - - // Note(rjf) : Persistent data - // TODO(ED) : Put this in its own struct? - first_touched_build_id : u64, - last_touched_build_id : u64, - hot_time : f32, - active_time : f32, - disabled_time : f32, - focus_hot_time : f32, - focus_active_time : f32, - focus_active_disabled_time : f32, - view_off : Vec2, - view_off_target : Vec2, - view_bounds : Vec2, - default_nav_focus_hot_key : UI_Key, - default_nav_focus_active_key : UI_Key, - default_nav_focus_next_hot_key : UI_Key, - default_nav_focus_next_active_key : UI_Key, -} diff --git a/code/__Imgui_raddbg/ui_state.odin b/code/__Imgui_raddbg/ui_state.odin deleted file mode 100644 index 7a5ff70..0000000 --- a/code/__Imgui_raddbg/ui_state.odin +++ /dev/null @@ -1,76 +0,0 @@ -package wip - -import "core:os" - -// TODO(Ed) : As with UI_Box, not all of this will be implemented at once, -// we'll need to review this setup for our use case, we will have UI persistent of -// a workspace (global state UI), and one for the workspace itself. -// The UI state use in raddbg seems to be tied to an OS window and has realted things to it. -// You may need to lift the nav actions outside of the UI_State of a workspace, etc... -UI_State :: struct { - arena : ^ Arena, - - build_arenas : [2] ^ Arena, - build_id : u64, - - // Note(rjf) : Box cache - // TODO(ED) : Put this in its own struct? - first_free_box : UI_Box, - box_table_size : u64, - box_table : ^ UI_BoxHashSlot, // TODO(Ed) : Can the cache use HMapZPL? - - // Note(rjf) : Build phase output - // TODO(ED) : Put this in its own struct? - root : ^ UI_Box, - tooltip_root : ^ UI_Box, - ctx_menu_root : ^ UI_Box, - default_nav_root_key : UI_Key, - build_box_count : u64, - last_build_box_count : u64, - ctx_menu_touched_this_frame : b32, - - // Note(rjf) : Build parameters - // icon_info : UI_IconInfo - // window : os.Handle - // events : OS_EventList - nav_actions : UI_NavActionList, - // TODO(Ed) : Do we want to keep tracvk of the cursor pos separately - // incase we do some sequence for tutorials? - mouse : Vec2, - animation_delta : f32, - external_focus_commit : b32, - - // Note(rjf) : User Interaction State - // TODO(ED) : Put this in its own struct? - hot_box_key : UI_Key, - active_box_key : [Side.Count] UI_Key, - clipboard_copy_key : UI_Key, - time_since_last_click : [Side.Count] f32, - last_click_key : [Side.Count] UI_Key, - drag_start_mouse : Vec2, - drag_state_arena : ^ Arena, - drag_state_data : string, - string_hover_arena : ^ Arena, - string_hover_string : string, - string_hover_fancy_runs : DrawFancyRunList, - string_hover_begin_us : u64, - string_hover_build_index : u64, - last_time_mouse_moved_us : u64, - - // Note(rjf) : Tooltip State - // TODO(ED) : Put this in its own struct? - tool_tip_open_time : f32, - tool_tip_open : b32, - - // Note(rjf) : Context menu state - // TODO(ED) : Put this in its own struct? - ctx_menu_anchor_key : UI_Key, - next_ctx_menu_anchor_key : UI_Key, - ctx_menu_anchor_box_last_pos : Vec2, - cxt_menu_anchor_off : Vec2, - ctx_menu_open : b32, - next_ctx_menu_open : b32, - ctx_menu_open_time : f32, - ctx_menu_key : UI_Key, - ctx_menu_changed : b32, -} diff --git a/code/api.odin b/code/engine_api.odin similarity index 98% rename from code/api.odin rename to code/engine_api.odin index 5b7abc4..326dc80 100644 --- a/code/api.odin +++ b/code/engine_api.odin @@ -129,7 +129,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem resolution_height = 600 refresh_rate = 0 - cam_min_zoom = 0.25 + cam_min_zoom = 0.10 cam_max_zoom = 30.0 cam_zoom_mode = .Smooth cam_zoom_smooth_snappiness = 4.0 @@ -198,7 +198,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem menu_bar.pos = Vec2(app_window.extent) * { -1, 1 } menu_bar.size = {200, 40} - settings_menu.min_size = {200, 200} + settings_menu.min_size = {250, 200} } // Demo project setup @@ -338,6 +338,10 @@ tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32 rl.PollInputEvents() + debug.draw_ui_box_bounds_points = false + debug.draw_UI_padding_bounds = false + debug.draw_ui_content_bounds = false + should_close = update( host_delta_time ) render() diff --git a/code/logger.odin b/code/engine_logger.odin similarity index 98% rename from code/logger.odin rename to code/engine_logger.odin index 5605fbf..0edec33 100644 --- a/code/logger.odin +++ b/code/engine_logger.odin @@ -118,7 +118,7 @@ logger_interface :: proc( } log :: proc( msg : string, level := LogLevel.Info, loc := #caller_location ) { - core_log.log( level, msg, location = loc ) + core_log.logf( level, msg, location = loc ) } logf :: proc( fmt : string, args : ..any, level := LogLevel.Info, loc := #caller_location ) { diff --git a/code/render.odin b/code/engine_render.odin similarity index 100% rename from code/render.odin rename to code/engine_render.odin diff --git a/code/replay.odin b/code/engine_replay.odin similarity index 100% rename from code/replay.odin rename to code/engine_replay.odin diff --git a/code/app_startup.odin b/code/engine_startup.odin similarity index 100% rename from code/app_startup.odin rename to code/engine_startup.odin diff --git a/code/env.odin b/code/env_env.odin similarity index 98% rename from code/env.odin rename to code/env_env.odin index 78887bd..3af391c 100644 --- a/code/env.odin +++ b/code/env_env.odin @@ -10,6 +10,8 @@ import rl "vendor:raylib" Str_App_State := "App State" +#region("Memory") + Memory_App : Memory Memory_Base_Address_Persistent :: Terabyte * 1 @@ -133,6 +135,10 @@ MemoryConfig :: struct { commit_initial_filebuffer : uint, } +#endregion("Memory") + +#region("State") + // ALl nobs available for this application AppConfig :: struct { using memory : MemoryConfig, @@ -249,3 +255,5 @@ get_state :: #force_inline proc "contextless" () -> ^ State { // get_frametime :: #force_inline proc "contextless" () -> FrameTime { // return get_state().frametime // } + +#endregion("State") diff --git a/code/env_scratch.odin b/code/env_scratch.odin index 196a2b8..f389dc2 100644 --- a/code/env_scratch.odin +++ b/code/env_scratch.odin @@ -17,8 +17,6 @@ DebugData :: struct { // UI Vis draw_ui_box_bounds_points : bool, - draw_ui_margin_bounds : bool, - draw_ui_anchor_bounds : bool, draw_UI_padding_bounds : bool, draw_ui_content_bounds : bool, diff --git a/code/assert.odin b/code/grime_assert.odin similarity index 100% rename from code/assert.odin rename to code/grime_assert.odin diff --git a/code/grime_filesystem.odin b/code/grime_filesystem.odin index 2016336..3b73ae3 100644 --- a/code/grime_filesystem.odin +++ b/code/grime_filesystem.odin @@ -5,6 +5,8 @@ import "core:fmt" import "core:os" import "core:runtime" +// Test + file_copy_sync :: proc( path_src, path_dst: string, allocator := context.temp_allocator ) -> b32 { file_size : i64 diff --git a/code/grime.odin b/code/grime_grime.odin similarity index 100% rename from code/grime.odin rename to code/grime_grime.odin diff --git a/code/grime_linked_list.odin b/code/grime_linked_list.odin index ef2653a..a3a9565 100644 --- a/code/grime_linked_list.odin +++ b/code/grime_linked_list.odin @@ -97,6 +97,21 @@ dll_push_back :: proc "contextless" ( current_ptr : ^(^ ($ TypeCurr)), node : ^$ node.next = nil } +dll_pn_pop :: proc "contextless" ( node : ^$Type ) +{ + if node == nil { + return + } + if node.prev != nil { + node.prev.next = nil + node.prev = nil + } + if node.next != nil { + node.next.prev = nil + node.next = nil + } +} + dll_pop_back :: #force_inline proc "contextless" ( current_ptr : ^(^ ($ Type)) ) { to_remove : ^Type = (current_ptr ^) @@ -146,7 +161,27 @@ dll_full_insert_raw :: proc "contextless" ( null : ^($ Type), parent, pos, node } } -dll_full_push_back :: proc "contextless" ( null : ^($ Type), parent, node : ^ Type ) { +dll_full_pop :: proc "contextless" ( node, parent : ^$Type ) { + if node == nil { + return + } + if parent.first == node { + parent.first = node.next + } + if parent.last == node { + parent.last = node.prev + } + if node.prev != nil { + node.prev.next = nil + node.prev = nil + } + if node.next != nil { + node.next.prev = nil + node.next = nil + } +} + +dll_full_push_back :: proc "contextless" ( parent, node : ^ $Type, null : ^Type ) { dll_full_insert_raw( null, parent, parent.last, node ) } diff --git a/code/string_format.odin b/code/grime_string_format.odin similarity index 100% rename from code/string_format.odin rename to code/grime_string_format.odin diff --git a/code/input.odin b/code/input_input.odin similarity index 100% rename from code/input.odin rename to code/input_input.odin diff --git a/code/math.odin b/code/math_math.odin similarity index 100% rename from code/math.odin rename to code/math_math.odin diff --git a/code/project.odin b/code/project_project.odin similarity index 100% rename from code/project.odin rename to code/project_project.odin diff --git a/code/serialize.odin b/code/project_serialize.odin similarity index 100% rename from code/serialize.odin rename to code/project_serialize.odin diff --git a/code/serialize_manual_unmarshal.odin b/code/project_serialize_manual_unmarshal.odin similarity index 100% rename from code/serialize_manual_unmarshal.odin rename to code/project_serialize_manual_unmarshal.odin diff --git a/code/text.odin b/code/text.odin index 4fb49e4..db881af 100644 --- a/code/text.odin +++ b/code/text.odin @@ -85,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.BILINEAR) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.ANISOTROPIC_4X) 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.ANISOTROPIC_16X) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) } ws_view_draw_text_StrRunesPair :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) @@ -114,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.BILINEAR) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.ANISOTROPIC_4X) 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.ANISOTROPIC_16X) + rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT) } // 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 a5cae05..6c9454d 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -175,10 +175,10 @@ render_mode_2d_workspace :: proc() line_thickness := 1 * cam_zoom_ratio // profile_begin("rl.DrawRectangleRoundedLines: padding & content") - if equal_range2(computed.content, computed.padding) { + if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { draw_rectangle_lines( rect_padding, current, Color_Debug_UI_Padding_Bounds, line_thickness ) } - else { + else if debug.draw_ui_content_bounds { draw_rectangle_lines( rect_content, current, Color_Debug_UI_Content_Bounds, line_thickness ) } // profile_end() @@ -186,6 +186,8 @@ render_mode_2d_workspace :: proc() point_radius := 3 * cam_zoom_ratio // profile_begin("circles") + if debug.draw_ui_box_bounds_points + { // center := Vec2 { // render_bounds.p0.x + computed_size.x * 0.5, // render_bounds.p0.y - computed_size.y * 0.5, @@ -194,6 +196,7 @@ render_mode_2d_workspace :: proc() rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) + } // profile_end() if len(current.text.str) > 0 { @@ -333,6 +336,8 @@ render_screen_ui :: proc() break Render_App_UI } + // Sort roots children by top-level order + current := root.first for ; current != nil; current = ui_box_tranverse_next( current ) { @@ -385,10 +390,10 @@ render_screen_ui :: proc() line_thickness : f32 = 1 // profile_begin("rl.DrawRectangleRoundedLines: padding & content") - if equal_range2(computed.content, computed.padding) { + if debug.draw_UI_padding_bounds && equal_range2(computed.content, computed.padding) { draw_rectangle_lines( rect_padding, current, Color_Debug_UI_Padding_Bounds, line_thickness ) } - else { + else if debug.draw_ui_content_bounds { draw_rectangle_lines( rect_content, current, Color_Debug_UI_Content_Bounds, line_thickness ) } // profile_end() @@ -418,6 +423,8 @@ render_screen_ui :: proc() point_radius : f32 = 3 // profile_begin("circles") + if debug.draw_ui_box_bounds_points + { // center := Vec2 { // render_bounds.p0.x + computed_size.x * 0.5, // render_bounds.p0.y - computed_size.y * 0.5, @@ -426,6 +433,7 @@ render_screen_ui :: proc() rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red ) rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue ) + } // profile_end() if len(current.text.str) > 0 && style.font.key != 0 { diff --git a/code/tick_update.odin b/code/tick_update.odin index 145b33b..a902e15 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -151,12 +151,16 @@ update :: proc( delta_time : f64 ) -> b32 // TODO(Ed): This should be per workspace view { // profile("Camera Manual Nav") - digital_move_speed : f32 = 200.0 + digital_move_speed : f32 = 1000.0 if workspace.zoom_target == 0.0 { workspace.zoom_target = cam.zoom } + config.cam_max_zoom = 30 + config.cam_zoom_sensitivity_digital = 0.04 + config.cam_min_zoom = 0.04 + config.cam_zoom_mode = .Digital switch config.cam_zoom_mode { case .Smooth: @@ -175,8 +179,8 @@ update :: proc( delta_time : f64 ) -> b32 } move_velocity : Vec2 = { - + cast(f32) i32(debug_actions.cam_move_left) - cast(f32) i32(debug_actions.cam_move_right), - + cast(f32) i32(debug_actions.cam_move_up) - cast(f32) i32(debug_actions.cam_move_down), + - cast(f32) i32(debug_actions.cam_move_left) + cast(f32) i32(debug_actions.cam_move_right), + - cast(f32) i32(debug_actions.cam_move_up) + cast(f32) i32(debug_actions.cam_move_down), } move_velocity *= digital_move_speed * f32(delta_time) cam.target += move_velocity @@ -206,11 +210,12 @@ 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, } default_layout := UI_Layout { flags = frame_style_flags, anchor = {}, - alignment = { 0.0, 0.0 }, + alignment = { 0.5, 0.5 }, font_size = 30, text_alignment = { 0.0, 0.0 }, // corner_radii = { 0.2, 0.2, 0.2, 0.2 }, @@ -235,7 +240,7 @@ update :: proc( delta_time : f64 ) -> b32 // test_draggable() // test_text_box() // test_parenting( & default_layout, & frame_style_default ) - test_whitespace_ast( & default_layout, & frame_style_default ) + // test_whitespace_ast( & default_layout, & frame_style_default ) } //endregion Workspace Imgui Tick diff --git a/code/ui_layout.odin b/code/ui_layout.odin index f6db43f..a9dadcc 100644 --- a/code/ui_layout.odin +++ b/code/ui_layout.odin @@ -143,5 +143,143 @@ ui_set_layout :: #force_inline proc( layout : UI_Layout, preset : UI_StylePreset Widget Layout Ops */ -ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_LayoutDirectionX, width_ref : ^f32 ) { +ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_LayoutDirectionX, width_ref : ^f32 ) +{ + container_width : f32 + if width_ref != nil { + container_width = width_ref ^ + } + else { + container_width = container.computed.content.max.x - container.computed.content.min.x + } + + // do layout calculations for the children + total_stretch_ratio : f32 = 0.0 + size_req_children : f32 = 0 + for child := container.first; child != nil; child = child.next + { + using child.layout + scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) + if .Fixed_Width in flags + { + if scaled_width_by_height { + height := size.max.y != 0 ? size.max.y : container_width + width := height * size.min.x + + size_req_children += width + continue + } + + size_req_children += size.min.x + continue + } + + total_stretch_ratio += anchor.ratio.x + } + + avail_flex_space := container_width - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 ) + { + using child.layout + if ! (.Fixed_Width in flags) { + size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space + } + flags |= {.Fixed_Width} + } + + space_used : f32 = 0.0 + switch direction{ + case .Right_To_Left: + for child := container.last; child != nil; child = child.prev { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.layout + anchor = range2({0, 0}, {0, 0}) + alignment = { 0, 1 }// - hbox.layout.alignment + pos.x = space_used + space_used += size.min.x + size.min.y = container.computed.content.max.y - container.computed.content.min.y + } + case .Left_To_Right: + for child := container.first; child != nil; child = child.next { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.layout + anchor = range2({0, 0}, {0, 0}) + alignment = { 0, 1 } + pos.x = space_used + space_used += size.min.x + size.min.y = container.computed.content.max.y - container.computed.content.min.y + } + } +} + +ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_LayoutDirectionY, height_ref : ^f32 ) +{ + container_height : f32 + if height_ref != nil { + container_height = height_ref ^ + } + else { + container_height = container.computed.content.max.y - container.computed.content.min.y + } + + // do layout calculations for the children + total_stretch_ratio : f32 = 0.0 + size_req_children : f32 = 0 + for child := container.first; child != nil; child = child.next + { + using child.layout + scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) + if .Fixed_Height in flags + { + if scaled_width_by_height { + width := size.max.x != 0 ? size.max.x : container_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 := container_height - size_req_children + + allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 ) + { + using child.layout + if ! (.Fixed_Height in 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 direction { + case .Top_To_Bottom: + for child := container.last; child != nil; child = child.prev { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.layout + anchor = range2({0, 0}, {0, 1}) + // alignment = {0, 1} + pos.y = space_used + space_used += size.min.y + size.min.x = container.computed.content.max.x - container.computed.content.min.x + } + case .Bottom_To_Top: + for child := container.first; child != nil; child = child.next { + allocate_space(child, total_stretch_ratio, avail_flex_space) + using child.layout + anchor = range2({0, 0}, {0, 1}) + // alignment = {0, 1} + pos.y = space_used + space_used += size.min.y + size.min.x = container.computed.content.max.x - container.computed.content.min.x + } + } } diff --git a/code/ui_layout_compute.odin b/code/ui_layout_compute.odin index 0e032bb..f6c9050 100644 --- a/code/ui_layout_compute.odin +++ b/code/ui_layout_compute.odin @@ -2,6 +2,178 @@ package sectr // Note(Ed): This is naturally pretty expensive +ui_box_compute_layout :: proc( box : ^UI_Box ) +{ + profile("Layout Box") + state := get_state() + ui := state.ui_context + + parent := box.parent + // if parent != ui.root && ! parent.computed.fresh { + // ui_box_compute_layout( parent ) + // } + + style := box.style + layout := & box.layout + + // These are used to choose via multiplication weather to apply + // position & size constraints of the parent. + // The parent's unadjusted content bounds however are enforced for position, + // they cannot be ignored. The user may bypass them by doing the + // relative offset math vs world/screen space if they desire. + fixed_pos_x : f32 = cast(f32) int(.Fixed_Position_X in layout.flags) + fixed_pos_y : f32 = cast(f32) int(.Fixed_Position_Y in layout.flags) + fixed_width : f32 = cast(f32) int(.Fixed_Width in layout.flags) + fixed_height : f32 = cast(f32) int(.Fixed_Height in layout.flags) + + size_to_text : bool = .Size_To_Text in layout.flags + + computed := & box.computed + + parent_content := parent.computed.content + parent_content_size := parent_content.max - parent_content.min + parent_center := parent_content.min + parent_content_size * 0.5 + + /* + If fixed position (X or Y): + * Ignore Margins + * Ignore Anchors + + If clampped position (X or Y): + * Positon cannot exceed the anchors/margins bounds. + + If fixed size (X or Y): + * Ignore Parent constraints (can only be clipped) + + If auto-sized: + * Enforce parent size constraint of bounds relative to + where the adjusted content bounds are after applying margins & anchors. + The 'side' conflicting with the bounds will end at that bound side instead of clipping. + + If size.min is not 0: + * Ignore parent constraints if the bounds go below that value. + + If size.max is 0: + * Allow the child box to spread to entire adjusted content bounds, otherwise clampped to max size. + */ + + // 1. Anchors + anchor := & layout.anchor + anchored_bounds := range2( + parent_content.min + parent_content_size * anchor.min, + parent_content.max - parent_content_size * anchor.max, + ) + // anchored_bounds_origin := (anchored_bounds.min + anchored_bounds.max) * 0.5 + + // 2. Apply Margins + margins := range2( + { layout.margins.left, layout.margins.bottom }, + { layout.margins.right, layout.margins.top }, + ) + margined_bounds := range2( + anchored_bounds.min + margins.min, + anchored_bounds.max - margins.max, + ) + margined_bounds_origin := (margined_bounds.min + margined_bounds.max) * 0.5 + margined_size := margined_bounds.max - margined_bounds.min + + // 3. Enforce Min/Max Size Constraints + adjusted_max_size_x := layout.size.max.x > 0 ? min( margined_size.x, layout.size.max.x ) : margined_size.x + adjusted_max_size_y := layout.size.max.y > 0 ? min( margined_size.y, layout.size.max.y ) : margined_size.y + + adjusted_size : Vec2 + adjusted_size.x = max( adjusted_max_size_x, layout.size.min.x) + adjusted_size.y = max( adjusted_max_size_y, layout.size.min.y) + + if .Fixed_Width in layout.flags { + adjusted_size.x = layout.size.min.x + } + if .Fixed_Height in layout.flags { + adjusted_size.y = layout.size.min.y + } + + text_size : Vec2 + if layout.font_size == computed.text_size.y { + text_size = computed.text_size + } + else { + text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 ) + } + + if size_to_text { + adjusted_size = text_size + } + + // 5. Determine relative position + + origin_center := margined_bounds_origin + origin_top_left := Vec2 { margined_bounds.min.x, margined_bounds.max.y } + + origin := .Origin_At_Anchor_Center in layout.flags ? origin_center : origin_top_left + + rel_pos := origin + layout.pos + + if .Fixed_Position_X in layout.flags { + rel_pos.x = origin.x + layout.pos.x + } + if .Fixed_Position_Y in layout.flags { + rel_pos.y = origin.y + layout.pos.y + } + + vec2_one := Vec2 { 1, 1 } + + // 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), + ) + + // Determine Padding's outer bounds + border_offset := Vec2 { layout.border_width, layout.border_width } + + padding_bounds := range2( + bounds.min + border_offset, + bounds.min - border_offset, + ) + + // 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, + ) + + // computed.anchors = anchored_bounds + // computed.margins = margined_bounds + computed.bounds = bounds + computed.padding = padding_bounds + computed.content = content_bounds + + if len(box.text.str) > 0 + { + content_size := content_bounds.max - content_bounds.min + text_pos : Vec2 + text_pos = content_bounds.min + { 0, text_size.y } + text_pos.x += ( content_size.x - text_size.x ) * layout.text_alignment.x + text_pos.y += ( content_size.y - text_size.y ) * layout.text_alignment.y + + computed.text_size = text_size + computed.text_pos = { text_pos.x, text_pos.y } + } + computed.fresh = true +} + +ui_box_compute_layout_children :: proc( box : ^UI_Box ) +{ + for current := box.first; current != nil; current = ui_box_tranverse_next( current ) + { + if current == box do return + if current.computed.fresh do continue + ui_box_compute_layout( current ) + } +} + ui_compute_layout :: proc( ui : ^UI_State ) { profile(#procedure) @@ -12,169 +184,16 @@ ui_compute_layout :: proc( ui : ^UI_State ) computed := & root.computed style := root.style layout := & root.layout - computed.bounds.min = layout.pos - computed.bounds.max = layout.size.min + // if ui == & state.screen_ui { + computed.bounds.min = transmute(Vec2) state.app_window.extent * -1 + computed.bounds.max = transmute(Vec2) state.app_window.extent + // } computed.content = computed.bounds } - current := root.first - for ; current != nil; + for current := root.first; current != nil; current = ui_box_tranverse_next( current ) { - // if current.computed.fresh do return - - // TODO(Ed): Lift this to ui_box_compute_layout - // profile("Layout Box") - style := current.style - layout := & current.layout - - // These are used to choose via multiplication weather to apply - // position & size constraints of the parent. - // The parent's unadjusted content bounds however are enforced for position, - // they cannot be ignored. The user may bypass them by doing the - // relative offset math vs world/screen space if they desire. - fixed_pos_x : f32 = cast(f32) int(.Fixed_Position_X in layout.flags) - fixed_pos_y : f32 = cast(f32) int(.Fixed_Position_Y in layout.flags) - fixed_width : f32 = cast(f32) int(.Fixed_Width in layout.flags) - fixed_height : f32 = cast(f32) int(.Fixed_Height in layout.flags) - - size_to_text : bool = .Size_To_Text in layout.flags - - parent := current.parent - computed := & current.computed - - parent_content := parent.computed.content - parent_content_size := parent_content.max - parent_content.min - parent_center := parent_content.min + parent_content_size * 0.5 - - /* - If fixed position (X or Y): - * Ignore Margins - * Ignore Anchors - - If clampped position (X or Y): - * Positon cannot exceed the anchors/margins bounds. - - If fixed size (X or Y): - * Ignore Parent constraints (can only be clipped) - - If auto-sized: - * Enforce parent size constraint of bounds relative to - where the adjusted content bounds are after applying margins & anchors. - The 'side' conflicting with the bounds will end at that bound side instead of clipping. - - If size.min is not 0: - * Ignore parent constraints if the bounds go below that value. - - If size.max is 0: - * Allow the child box to spread to entire adjusted content bounds, otherwise clampped to max size. - */ - - // 1. Anchors - anchor := & layout.anchor - anchored_bounds := range2( - parent_content.min + parent_content_size * anchor.min, - parent_content.max - parent_content_size * anchor.max, - ) - // anchored_bounds_origin := (anchored_bounds.min + anchored_bounds.max) * 0.5 - - // 2. Apply Margins - margins := range2( - { layout.margins.left, layout.margins.bottom }, - { layout.margins.right, layout.margins.top }, - ) - margined_bounds := range2( - anchored_bounds.min + margins.min, - anchored_bounds.max - margins.max, - ) - margined_bounds_origin := (margined_bounds.min + margined_bounds.max) * 0.5 - margined_size := margined_bounds.max - margined_bounds.min - - // 3. Enforce Min/Max Size Constraints - adjusted_max_size_x := layout.size.max.x > 0 ? min( margined_size.x, layout.size.max.x ) : margined_size.x - adjusted_max_size_y := layout.size.max.y > 0 ? min( margined_size.y, layout.size.max.y ) : margined_size.y - - adjusted_size : Vec2 - adjusted_size.x = max( adjusted_max_size_x, layout.size.min.x) - adjusted_size.y = max( adjusted_max_size_y, layout.size.min.y) - - if .Fixed_Width in layout.flags { - adjusted_size.x = layout.size.min.x - } - if .Fixed_Height in layout.flags { - adjusted_size.y = layout.size.min.y - } - - text_size : Vec2 - if 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, layout.font_size, 0 ) - } - - if size_to_text { - adjusted_size = text_size - } - - // 5. Determine relative position - - origin_center := margined_bounds_origin - origin_top_left := Vec2 { margined_bounds.min.x, margined_bounds.max.y } - - origin := .Origin_At_Anchor_Center in layout.flags ? origin_center : origin_top_left - - rel_pos := origin + layout.pos - - if .Fixed_Position_X in layout.flags { - rel_pos.x = origin.x + layout.pos.x - } - if .Fixed_Position_Y in layout.flags { - rel_pos.y = origin.y + layout.pos.y - } - - vec2_one := Vec2 { 1, 1 } - - // 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), - ) - - // Determine Padding's outer bounds - border_offset := Vec2 { layout.border_width, layout.border_width } - - padding_bounds := range2( - bounds.min + border_offset, - bounds.min - border_offset, - ) - - // 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, - ) - - // computed.anchors = anchored_bounds - // computed.margins = margined_bounds - computed.bounds = bounds - computed.padding = padding_bounds - computed.content = content_bounds - - if len(current.text.str) > 0 - { - content_size := content_bounds.max - content_bounds.min - text_pos : Vec2 - text_pos = content_bounds.min + { 0, text_size.y } - text_pos.x += ( content_size.x - text_size.x ) * layout.text_alignment.x - text_pos.y += ( content_size.y - text_size.y ) * layout.text_alignment.y - - computed.text_size = text_size - computed.text_pos = { text_pos.x, text_pos.y } - } - computed.fresh = true - - current = ui_box_tranverse_next( current ) + if current.computed.fresh do continue + ui_box_compute_layout( current ) } } diff --git a/code/ui_screen.odin b/code/ui_screen.odin index 9671c8d..d9f3a87 100644 --- a/code/ui_screen.odin +++ b/code/ui_screen.odin @@ -17,6 +17,7 @@ UI_ScreenState :: struct pos, size, min_size : Vec2, container : UI_VBox, is_open : b32, + is_maximized : b32, }, } @@ -41,11 +42,12 @@ ui_screen_menu_bar :: proc() { using menu_bar ui_layout( UI_Layout { - // flags = {.Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height}, - anchor = {}, - alignment = { 0, 1 }, + flags = {.Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, .Origin_At_Anchor_Center}, + // anchor = range2({0.5, 0.5}, {0.5, 0.5} ), + alignment = { 0.5, 0.5 }, border_width = 1.0, font_size = 12, + // pos = {}, pos = menu_bar.pos, size = range2( menu_bar.size, {}), }) @@ -56,12 +58,13 @@ ui_screen_menu_bar :: proc() text_color = Color_White, }) // ui_hbox( & container, .Left_To_Right, "App Menu Bar", { .Mouse_Clickable} ) - container = ui_hbox_begin( .Left_To_Right, "Menu Bar" ) - ui_parent(container) + container = ui_hbox( .Left_To_Right, "Menu Bar" ) + // ui_parent(container) ui_layout( UI_Layout { flags = {}, anchor = {}, + text_alignment = {0.5, 0.5}, border_width = 1.0, font_size = 12, }) @@ -101,7 +104,7 @@ ui_screen_menu_bar :: proc() spacer = ui_spacer("Menu Bar: End Spacer") spacer.layout.anchor.ratio.x = 1.0 - ui_hbox_end( container) + // ui_hbox_end( container) } } @@ -118,18 +121,21 @@ ui_screen_settings_menu :: proc() container = ui_vbox_begin( .Top_To_Bottom, "Settings Menu", {.Mouse_Clickable}) { - using container - // flags = {} - layout.flags = { .Origin_At_Anchor_Center } - layout.pos = pos - layout.alignment = { 0.5, 0.5 } - layout.size = range2( size, {}) - style.bg_color = Color_BG_Panel_Translucent - } - ui_parent(container) - { + { + using container + // flags = {} + layout.flags = { .Fixed_Width, .Fixed_Height, .Origin_At_Anchor_Center } + layout.pos = pos + layout.alignment = { 0.5, 0.5 } + // layout.alignment = {} + layout.size = range2( size, {}) + style.bg_color = Color_BG_Panel_Translucent + } + ui_parent(container) + ui_layout( UI_Layout { font_size = 16, + alignment = {0, 1} }) ui_style( UI_Style { bg_color = Color_Transparent, @@ -153,9 +159,25 @@ ui_screen_settings_menu :: proc() } ui_style(ui_style_peek()) - theme := ui_style_ref() - theme.default.bg_color = Color_GreyRed - theme.hot. bg_color = Color_Red + style := ui_style_ref() + style.default.bg_color = Color_Black + style.hot.bg_color = Color_Frame_Hover + maximize_btn := ui_button("Settings Menu: Maximize Btn") + { + using maximize_btn + layout.flags = {.Fixed_Width} + layout.size.min = {50, 0} + layout.text_alignment = {0.5, 0.5} + layout.anchor.ratio.x = 1.0 + if maximize_btn.pressed { + settings_menu.is_maximized = ~settings_menu.is_maximized + } + if settings_menu.is_maximized do text = str_intern("min") + else do text = str_intern("max") + } + + style.default.bg_color = Color_GreyRed + style.hot. bg_color = Color_Red close_btn := ui_button("Settings Menu: Close Btn") { using close_btn @@ -169,7 +191,7 @@ ui_screen_settings_menu :: proc() } } - ui_hbox_end(frame_bar, & size.x) + ui_hbox_end(frame_bar) } if frame_bar.active { pos += input.mouse.delta @@ -178,8 +200,13 @@ ui_screen_settings_menu :: proc() spacer := ui_spacer("Settings Menu: Spacer") spacer.layout.anchor.ratio.y = 1.0 - ui_vbox_end(container, & size.y) + ui_vbox_end(container, compute_layout = false ) + } + if settings_menu.is_maximized { + using settings_menu.container + layout.flags = {.Origin_At_Anchor_Center } + layout.pos = {} } - ui_resizable_handles( & container, & pos, & size ) + ui_resizable_handles( & container, & pos, & size) } diff --git a/code/ui_signal.odin b/code/ui_signal.odin index 9c3d108..ea3a951 100644 --- a/code/ui_signal.odin +++ b/code/ui_signal.odin @@ -36,7 +36,6 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas signal.cursor_pos = ui_cursor_pos() signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds ) - // TODO(Ed): We eventually need to setup a sorted root based on last active history UnderCheck: { if ! signal.cursor_over do break UnderCheck @@ -45,10 +44,10 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas if last_root == nil do break UnderCheck top_ancestor := ui_top_ancestor(box) - if top_ancestor.parent_index != last_root.num_children - 1 + if top_ancestor.parent_index < last_root.parent_index { for curr := last_root.last; curr != nil && curr.key != box.key; curr = curr.prev { - if pos_within_range2( signal.cursor_pos, curr.computed.bounds ) && curr.parent_index > top_ancestor.parent_index { + if pos_within_range2( signal.cursor_pos, curr.computed.bounds ) { signal.cursor_over = false } } @@ -72,7 +71,10 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas if mouse_clickable && signal.cursor_over && left_pressed && was_hot { - //TODO(Ed): We need to add the reorder of top-level widgets based on this interaction + top_ancestor := ui_top_ancestor(box) + + dll_full_pop(top_ancestor, top_ancestor.parent) + dll_full_push_back( top_ancestor.parent, top_ancestor, nil ) // runtime.debug_trap() // ui.hot = box.key @@ -212,7 +214,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas box.style_delta = 0 } box.layout = ui_layout_peek().hot - box.style = ui_style_peek().hot + box.style = ui_style_peek().hot } if is_active { @@ -221,7 +223,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas box.style_delta = 0 } box.layout = ui_layout_peek().active - box.style = ui_style_peek().active + box.style = ui_style_peek().active } if is_disabled { @@ -230,7 +232,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas box.style_delta = 0 } box.layout = ui_layout_peek().disabled - box.style = ui_style_peek().disabled + box.style = ui_style_peek().disabled } if ! is_disabled && ! is_active && ! is_hot { @@ -242,7 +244,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas box.style_delta += frame_delta } box.layout = ui_layout_peek().default - box.style = ui_style_peek().default + box.style = ui_style_peek().default } } diff --git a/code/ui_tests.odin b/code/ui_tests.odin index 0c58f34..abdcb2a 100644 --- a/code/ui_tests.odin +++ b/code/ui_tests.odin @@ -25,16 +25,22 @@ test_draggable :: proc() ui := ui_context draggable_layout := UI_Layout { - anchor = {}, + flags = { + .Fixed_Position_X, .Fixed_Position_Y, + .Fixed_Width, .Fixed_Height, + .Origin_At_Anchor_Center, + }, // alignment = { 0.0, 0.5 }, - alignment = { 0.5, 0.5 }, + alignment = { 0.5, 0 }, text_alignment = { 0.0, 0.0 }, // alignment = { 1.0, 1.0 }, - // corner_radii = { 0.3, 0.3, 0.3, 0.3 }, pos = { 0, 0 }, size = range2({ 200, 200 }, {}), } ui_layout( draggable_layout ) + ui_style( UI_Style { + corner_radii = { 0.3, 0.3, 0.3, 0.3 }, + }) draggable := ui_widget( "Draggable Box!", UI_BoxFlags { .Mouse_Clickable, .Mouse_Resizable } ) if draggable.first_frame { @@ -72,8 +78,9 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S parent_layout.padding = { 5, 10, 5, 5 } parent_layout.pos = { 0, 0 } parent_layout.flags = { - // .Fixed_Position_X, .Fixed_Position_Y, + .Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height, + .Origin_At_Anchor_Center } ui_layout(parent_layout) @@ -155,7 +162,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : text_layout.flags = { .Origin_At_Anchor_Center, .Fixed_Position_X, .Fixed_Position_Y, - // .Fixed_Width, .Fixed_Height, + .Fixed_Width, .Fixed_Height, } text_layout.text_alignment = { 0.0, 0.5 } text_layout.alignment = { 0.0, 1.0 } diff --git a/code/ui.odin b/code/ui_ui.odin similarity index 79% rename from code/ui.odin rename to code/ui_ui.odin index 369f7d3..40d346b 100644 --- a/code/ui.odin +++ b/code/ui_ui.odin @@ -110,11 +110,11 @@ UI_Box :: struct { text : StrRunesPair, // Regenerated per frame. - using links : DLL_NodeFull( UI_Box ), // first, last, prev, next - parent : ^UI_Box, - num_children : i16, - ancestors : i16, - parent_index : i16, + using links : DLL_NodeFull( UI_Box ), // first, last, prev, next + parent : ^UI_Box, + num_children : i32, + ancestors : i32, + parent_index : i32, flags : UI_BoxFlags, computed : UI_Computed, @@ -131,6 +131,7 @@ UI_Box :: struct { active_delta : f32, disabled_delta : f32, style_delta : f32, + // root_order_id : i16, // prev_computed : UI_Computed, // prev_style : UI_Style,v @@ -143,7 +144,7 @@ UI_Layout_Stack_Size :: 512 UI_Style_Stack_Size :: 512 UI_Parent_Stack_Size :: 512 // UI_Built_Boxes_Array_Size :: 8 -UI_Built_Boxes_Array_Size :: 4 * Kilobyte +UI_Built_Boxes_Array_Size :: 16 * Kilobyte UI_State :: struct { // TODO(Ed) : Use these @@ -158,6 +159,9 @@ UI_State :: struct { null_box : ^UI_Box, // Ryan had this, I don't know why yet. root : ^UI_Box, + // Children of the root node are unique in that they have their order preserved per frame + // This is to support overlapping frames + // So long as their parent-index is non-negative they'll be rendered // Do we need to recompute the layout? layout_dirty : b32, @@ -195,9 +199,9 @@ ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_rese verify( allocation_error == AllocatorError.None, "Failed to allocate box cache" ) cache = box_cache } - ui.curr_cache = (& ui.caches[1]) ui.prev_cache = (& ui.caches[0]) + log("ui_startup completed") } @@ -242,14 +246,16 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) set_result : ^ UI_Box set_error : AllocatorError - if prev_box != nil { + if prev_box != nil + { // Previous history was found, copy over previous state. set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, (prev_box ^) ) } else { box : UI_Box - box.key = key - box.label = str_intern( label ) + box.key = key + box.label = str_intern( label ) + // set_result, set_error = zpl_hmap_set( prev_cache, cast(u64) key, box ) set_result, set_error = zpl_hmap_set( curr_cache, cast(u64) key, box ) } @@ -259,23 +265,54 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box) curr_box.first_frame = prev_box == nil } - curr_box.flags = flags + curr_box.flags = flags // Clear non-persistent data curr_box.computed.fresh = false - curr_box.parent = nil curr_box.links = {} curr_box.num_children = 0 + curr_box.parent = nil + // curr_box.ancestors = 0 + curr_box.parent_index = -1 // If there is a parent, setup the relevant references parent := stack_peek( & parent_stack ) + if curr_box.ancestors == 0 && prev_box != nil + { + set_error : AllocatorError + if prev_box.first != nil { + curr_box.first, set_error = zpl_hmap_set( curr_cache, cast(u64) prev_box.first.key, prev_box.first ^ ) + verify( set_error == AllocatorError.None, "Failed to set zpl_hmap due to allocator error" ) + } + if prev_box.last != nil { + curr_box.last, set_error = zpl_hmap_set( curr_cache, cast(u64) prev_box.last.key, prev_box.last ^ ) + verify( set_error == AllocatorError.None, "Failed to set zpl_hmap due to allocator error" ) + } + } if parent != nil { - dll_full_push_back( null_box, parent, curr_box ) - curr_box.parent_index = parent.num_children - parent.num_children += 1 - curr_box.parent = parent - curr_box.ancestors = parent.ancestors + 1 + if curr_box.ancestors != 1 + { + dll_full_push_back( parent, curr_box, null_box ) + curr_box.parent_index = parent.num_children + parent.num_children += 1 + curr_box.parent = parent + curr_box.ancestors = parent.ancestors + 1 + } + else if prev_box != nil + { + // Order was previously restored, restore linkage + if prev_box.prev != nil { + curr_box.prev = zpl_hmap_get( curr_cache, cast(u64) prev_box.prev.key ) + } + if prev_box.next != nil { + curr_box.next = zpl_hmap_get( curr_cache, cast(u64) prev_box.next.key ) + } + curr_box.parent = ui.root + curr_box.ancestors = 1 + curr_box.parent_index = ui.root.num_children + parent.num_children += 1 + } } ui.built_box_count += 1 @@ -314,7 +351,7 @@ ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 { return screen_to_ws_view_pos( input.mouse.pos ) } else { - return input.mouse.pos + return input.mouse.pos } } @@ -327,9 +364,19 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) { profile(#procedure) + state := get_state() get_state().ui_context = ui using get_state().ui_context + if root != nil + { + // Set all top-level widgets to a negative index + // This will be used for prunning the rooted_children order + // for box := ui.root.first; box != nil; box = box.next { + // box.parent_index = -1 + // } + } + temp := prev_cache prev_cache = curr_cache curr_cache = temp @@ -342,6 +389,9 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} ) ui.built_box_count = 0 root = ui_box_make( {}, "root#001" ) + if ui == & state.screen_ui { + root.layout.size = range2(Vec2(state.app_window.extent) * 2, {}) + } ui_parent_push(root) } diff --git a/code/ui_widgets.odin b/code/ui_widgets.odin index f30a2b1..81e0e06 100644 --- a/code/ui_widgets.odin +++ b/code/ui_widgets.odin @@ -1,5 +1,7 @@ package sectr +import lalg "core:math/linalg" + UI_Widget :: struct { using box : ^UI_Box, using signal : UI_Signal, @@ -20,7 +22,7 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge return } -//region Horizontal Box +#region("Horizontal Box") /* Horizontal Boxes automatically manage a collection of widgets and attempt to slot them adjacent to each other along the x-axis. @@ -52,82 +54,11 @@ ui_hbox_begin :: proc( direction : UI_LayoutDirectionX, label : string, flags : } // Auto-layout children -ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil ) +ui_hbox_end :: proc( hbox : UI_HBox, width_ref : ^f32 = nil, compute_layout := true ) { // profile(#procedure) - - // ui_box_compute_layout(hox.widget) - - // ui_layout_children_horizontally( & hbox.box, hbox.direction, width_ref ) - { - 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 - size_req_children : f32 = 0 - for child := hbox.first; child != nil; child = child.next - { - using child.layout - scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) - if .Fixed_Width in flags - { - if scaled_width_by_height { - height := size.max.y != 0 ? size.max.y : hbox_width - width := height * size.min.x - - size_req_children += width - continue - } - - 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.layout - if ! (.Fixed_Width in flags) { - size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space - } - flags |= {.Fixed_Width} - } - - 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.layout - anchor = range2({0, 0}, {0, 0}) - alignment = { 0, 1 }// - hbox.layout.alignment - pos.x = space_used - space_used += size.min.x - size.min.y = hbox.computed.content.max.y - hbox.computed.content.min.y - } - case .Left_To_Right: - for child := hbox.first; child != nil; child = child.next { - allocate_space(child, total_stretch_ratio, avail_flex_space) - using child.layout - anchor = range2({0, 0}, {0, 0}) - alignment = { 0, 1 } - pos.x = space_used - space_used += size.min.x - size.min.y = hbox.computed.content.max.y - hbox.computed.content.min.y - } - } - } - + if compute_layout do ui_box_compute_layout(hbox.box) + ui_layout_children_horizontally( hbox.box, hbox.direction, width_ref ) } @(deferred_out = ui_hbox_end_auto) @@ -139,18 +70,33 @@ ui_hbox :: #force_inline proc( direction : UI_LayoutDirectionX, label : string, // Auto-layout children and pop parent from parent stack ui_hbox_end_auto :: proc( hbox : UI_HBox ) { - ui_parent_pop() ui_hbox_end(hbox) + ui_parent_pop() } -//endregion Horizontal Box +#endregion("Horizontal Box") -// 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, size : ^Vec2, - handle_width : f32 = 15, - handle_color_non_default : Color = Color_ResizeHandle, - handle_color_default : Color = Color_Transparent, +#region("Resizable") +// Parameterized widget def for ui_resizable_handles +UI_Resizable :: struct { + using widget : UI_Widget, + handle_width : f32, + color_non_default : Color, + color_default : Color, + left : bool, + right : bool, + top : bool, + bottom : bool, + corner_tr : bool, + corner_tl : bool, + corner_br : bool, + corner_bl : bool, + compute_layout : bool +} + +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, left := true, right := true, top := true, @@ -158,7 +104,67 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, corner_tr := true, corner_tl := true, corner_br := true, - corner_bl := true, ) + corner_bl := true, + compute_layout := true ) -> (resizable : UI_Resizable) +{ + 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 + return +} + +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, + left, + right, + top, + bottom, + corner_tr, + corner_tl, + corner_br, + corner_bl, + compute_layout) +} + +ui_resizable_begin_auto :: proc() { + +} + +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, + left := true, + right := true, + top := true, + bottom := true, + corner_tr := true, + corner_tl := true, + corner_br := true, + corner_bl := true, + compute_layout := true) { profile(#procedure) handle_left : UI_Widget @@ -284,6 +290,9 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, delta := get_state().input.mouse.delta alignment := & parent.layout.alignment + + if compute_layout do ui_box_compute_layout_children(parent) + 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 ) @@ -293,6 +302,7 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, 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 ) } +#endregion("Resizable") ui_spacer :: proc( label : string ) -> (widget : UI_Widget) { widget.box = ui_box_make( {.Mouse_Clickable}, label ) @@ -302,6 +312,8 @@ ui_spacer :: proc( label : string ) -> (widget : UI_Widget) { return } +#region("Text") + ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget { // profile(#procedure) @@ -343,8 +355,9 @@ ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget box.text = tab_str return { box, signal } } +#endregion("Text") -//region Vertical Box +#region("Vertical Box") /* Vertical Boxes automatically manage a collection of widgets and attempt to slot them adjacent to each other along the y-axis. @@ -375,77 +388,10 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirectionY, label : string, flags : } // Auto-layout children -ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil ) { +ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil, compute_layout := true ) { // profile(#procedure) - - // ui_box_compute_layout(vbox) - - // ui_layout_children_vertically( & hbox.box, hbox.direction, width_ref ) - { - 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 - total_stretch_ratio : f32 = 0.0 - size_req_children : f32 = 0 - for child := vbox.first; child != nil; child = child.next - { - using child.layout - scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags) - if .Fixed_Height in 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.layout - if ! (.Fixed_Height in 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.layout - 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.layout - anchor = range2({0, 0}, {0, 1}) - pos.y = space_used - space_used += size.min.y - } - } - } + if compute_layout do ui_box_compute_layout(vbox) + ui_layout_children_vertically( vbox.box, vbox.direction, height_ref ) } // Auto-layout children and pop parent from parent stack @@ -460,4 +406,4 @@ ui_vbox :: #force_inline proc( direction : UI_LayoutDirectionY, label : string, ui_parent_push(vbox.widget) return } -//endregion Vertical Box +#endregion("Vertical Box") diff --git a/ols.json b/ols.json index d789d58..bbefa3f 100644 --- a/ols.json +++ b/ols.json @@ -7,12 +7,12 @@ } ], "odin_command": "C:/projects/SectrPrototype/toolchain/Odin/odin.exe", - "enable_document_symbols": true, - "enable_fake_methods": false, + "enable_document_symbols": false, + "enable_fake_methods": true, "enable_format": false, "enable_hover": true, - "enable_semantic_tokens": false, + "enable_semantic_tokens": true, "enable_snippets": false, - "enable_references": false, + "enable_references": true, "thread_pool_count": 10 } diff --git a/scripts/build.ps1 b/scripts/build.ps1 index d62909b..7954386 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/scripts/setup_virtual_code_view.ps1 b/scripts/setup_virtual_code_view.ps1 index 928aeb0..17a5776 100644 --- a/scripts/setup_virtual_code_view.ps1 +++ b/scripts/setup_virtual_code_view.ps1 @@ -3,21 +3,9 @@ cls write-host "Build.ps1" -$incremental_checks = Join-Path $PSScriptRoot 'helpers/incremental_checks.ps1' -. $incremental_checks -write-host 'incremental_checks.ps1 imported' - -$ini_parser = join-path $PSScriptRoot 'helpers/ini.ps1' -. $ini_parser -write-host 'ini.ps1 imported' - $path_root = git rev-parse --show-toplevel $path_code = join-path $path_root 'code' -$path_build = join-path $path_root 'build' $path_scripts = join-path $path_root 'scripts' -$path_thirdparty = join-path $path_root 'thirdparty' -$path_toolchain = join-path $path_root 'toolchain' -$path_odin = join-path $path_toolchain 'odin' $path_virtual_view = join-path $path_root 'code_virtual_view'