From 7332644515bfd43430595b5b9e8da06965333bd5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Thu, 22 Feb 2024 21:19:29 -0500 Subject: [PATCH] Preparing skeleton for proper imgui support. I originally wanted to reference Ryan's UI series along with the RAD Debugger codebase, but that ended up being too convoluted of a route. Instead, I moved on to just doing a deep dive on imgui content I could find to learn from and associated libraries available. I collected my notes so far in this repo [IMGUI_Notes](https://github.com/Ed94/IMGUI_Notes). For now I have the base scaffolding datatype wise for the prototype ui. --- code/__Imgui_raddbg/ui.odin | 275 ++++++++++++++++++++ code/__Imgui_raddbg/ui_box.odin | 172 +++++++++++++ code/__Imgui_raddbg/ui_state.odin | 76 ++++++ code/api.odin | 5 + code/app_startup.odin | 5 + code/env.odin | 17 +- code/font_provider.odin | 3 +- code/grime.odin | 101 ++++++++ code/grime_hashtable.odin | 2 + code/host/memory_windows.odin | 11 + code/space.odin | 4 +- code/tick_render.odin | 25 +- code/tick_update.odin | 45 ++-- code/ui.odin | 404 +++++++++++++----------------- code/ui_proto.odin | 5 + 15 files changed, 862 insertions(+), 288 deletions(-) create mode 100644 code/__Imgui_raddbg/ui.odin create mode 100644 code/__Imgui_raddbg/ui_box.odin create mode 100644 code/__Imgui_raddbg/ui_state.odin create mode 100644 code/app_startup.odin create mode 100644 code/ui_proto.odin diff --git a/code/__Imgui_raddbg/ui.odin b/code/__Imgui_raddbg/ui.odin new file mode 100644 index 0000000..6e1c0cb --- /dev/null +++ b/code/__Imgui_raddbg/ui.odin @@ -0,0 +1,275 @@ +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 new file mode 100644 index 0000000..447c6fb --- /dev/null +++ b/code/__Imgui_raddbg/ui_box.odin @@ -0,0 +1,172 @@ +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 new file mode 100644 index 0000000..11541f4 --- /dev/null +++ b/code/__Imgui_raddbg/ui_state.odin @@ -0,0 +1,76 @@ +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 HashTable? + + // 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/api.odin index 58812b6..591aa49 100644 --- a/code/api.odin +++ b/code/api.odin @@ -1,6 +1,7 @@ package sectr import "base:runtime" +import c "core:c/libc" import "core:dynlib" import "core:fmt" import "core:mem" @@ -71,6 +72,8 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ input = & input_data[1] input_prev = & input_data[0] + // rl.Odin_SetMalloc( RL_MALLOC ) + rl.SetConfigFlags( { rl.ConfigFlag.WINDOW_RESIZABLE /*, rl.ConfigFlag.WINDOW_TOPMOST*/ } ) // Rough setup of window with rl stuff @@ -145,6 +148,8 @@ startup :: proc( live_mem : virtual.Arena, snapshot_mem : []u8, host_logger : ^ frame_2.color = Color_BG_TextBox_Green box_set_size( & frame_2, { 60, 100 } * CM_Per_Point ) + + } } } diff --git a/code/app_startup.odin b/code/app_startup.odin new file mode 100644 index 0000000..599978d --- /dev/null +++ b/code/app_startup.odin @@ -0,0 +1,5 @@ +package sectr + + + + diff --git a/code/env.odin b/code/env.odin index b417dca..cca0ea3 100644 --- a/code/env.odin +++ b/code/env.odin @@ -132,12 +132,24 @@ AppWindow :: struct { ppcm : f32, // Dots per centimetre } +// PMDB +CodeBase :: struct { + placeholder : int, +} + +ProjectConfig :: struct { + placeholder : int, +} + Project :: struct { path : string, name : string, + config : ProjectConfig, + codebase : CodeBase, + // TODO(Ed) : Support multiple workspaces - workspace : Workspace + workspace : Workspace, } Workspace :: struct { @@ -146,6 +158,9 @@ Workspace :: struct { cam : Camera, frame_1 : Box2, frame_2 : Box2, + + // TODO(Ed) : The workspace is mainly a 'UI' conceptually... + ui : UI_State, } DebugData :: struct { diff --git a/code/font_provider.odin b/code/font_provider.odin index badd041..8adf76e 100644 --- a/code/font_provider.odin +++ b/code/font_provider.odin @@ -9,7 +9,7 @@ import "core:os" import rl "vendor:raylib" Font_Arena_Size :: 32 * Megabyte -Font_Largest_Px_Size :: 96 +Font_Largest_Px_Size :: 32 // Font_Default :: "" Font_Default :: 0 @@ -56,6 +56,7 @@ FontProviderData :: struct { //TODO(Ed) : There is an issue with hot-reload and map allocations that I can't figure out right now.. // font_cache : ^ map [FontID](FontDef), + // font_cache : HashTable(FontDef), font_cache : [10] FontDef, open_id : i32 } diff --git a/code/grime.odin b/code/grime.odin index 2d0c917..8d7af28 100644 --- a/code/grime.odin +++ b/code/grime.odin @@ -3,8 +3,11 @@ package sectr // At least its less than C/C++ ... import "base:builtin" +import "base:runtime" +import c "core:c/libc" import "core:mem" import "core:mem/virtual" +import "core:os" import "core:path/filepath" Byte :: 1 @@ -49,3 +52,101 @@ get_bounds :: proc { box_get_bounds, view_get_bounds, } + + + +// TODO(Ed) : This is extremely jank, Raylib requires a 'heap' allocator with the way it works. +// We do not have persistent segmented in such a way for this. Eventually we might just want to segment vmem and just shove a heap allocator on a segment of it. + +when false { +RL_MALLOC :: proc "c" ( size : c.size_t ) -> rawptr +{ + allocator : Allocator + when Use_TrackingAllocator { + allocator = Allocator { + data = & memory.persistent.tracker, + procedure = mem.tracking_allocator_proc, + } + } + else { + allocator = Allocator { + data = & memory.persistent, + procedure = mem.arena_allocator_proc, + } + } + result, error_code := allocator.procedure( allocator.data, mem.Allocator_Mode.Alloc_Non_Zeroed, cast(int) size, mem.DEFAULT_ALIGNMENT, nil, 0, auto_cast {} ) + if error_code != AllocatorError.None { + runtime.debug_trap() + os.exit( -1 ) + } + return raw_data(result) +} + +RL_CALLOC :: proc "c" ( count : c.size_t, size : c.size_t ) -> rawptr +{ + allocator : Allocator + when Use_TrackingAllocator { + allocator = Allocator { + data = & memory.persistent.tracker, + procedure = mem.tracking_allocator_proc, + } + } + else { + allocator = Allocator { + data = & memory.persistent, + procedure = mem.arena_allocator_proc, + } + } + result, error_code := allocator.procedure( allocator.data, mem.Allocator_Mode.Alloc, cast(int) size, mem.DEFAULT_ALIGNMENT, nil, 0, auto_cast {} ) + if error_code != AllocatorError.None { + runtime.debug_trap() + os.exit( -1 ) + } + return raw_data(result) +} + +RL_REALLOC :: proc "c" ( block : rawptr, size : c.size_t ) -> rawptr +{ + allocator : Allocator + when Use_TrackingAllocator { + allocator = Allocator { + data = & memory.persistent.tracker, + procedure = mem.tracking_allocator_proc, + } + } + else { + allocator = Allocator { + data = & memory.persistent, + procedure = mem.arena_allocator_proc, + } + } + result, error_code := allocator.procedure( allocator.data, mem.Allocator_Mode.Resize_Non_Zeroed, cast(int) size, mem.DEFAULT_ALIGNMENT, block, 0, auto_cast {} ) + if error_code != AllocatorError.None { + runtime.debug_trap() + os.exit( -1 ) + } + return raw_data(result) +} + +RL_FREE :: proc "c" ( block : rawptr ) +{ + allocator : Allocator + when Use_TrackingAllocator { + allocator = Allocator { + data = & memory.persistent.tracker, + procedure = mem.tracking_allocator_proc, + } + } + else { + allocator = Allocator { + data = & memory.persistent, + procedure = mem.arena_allocator_proc, + } + } + result, error_code := allocator.procedure( allocator.data, mem.Allocator_Mode.Free, 0, 0, block, 0, auto_cast {} ) + if error_code != AllocatorError.None { + runtime.debug_trap() + os.exit( -1 ) + } +} +} \ No newline at end of file diff --git a/code/grime_hashtable.odin b/code/grime_hashtable.odin index 4f9a035..40b4b18 100644 --- a/code/grime_hashtable.odin +++ b/code/grime_hashtable.odin @@ -3,6 +3,8 @@ // with hot-reloads... package sectr +// Note(Ed) : See core:hash for hasing procs. + // This might be problematic... HT_MapProc :: #type proc( $ Type : typeid, key : u64, value : Type ) HT_MapMutProc :: #type proc( $ Type : typeid, key : u64, value : ^ Type ) diff --git a/code/host/memory_windows.odin b/code/host/memory_windows.odin index 4ba848d..25382df 100644 --- a/code/host/memory_windows.odin +++ b/code/host/memory_windows.odin @@ -129,3 +129,14 @@ arena_init_static :: proc(arena: ^virtual.Arena, base_address : rawptr, // END WINDOWS CHECK WRAP } +else +{ + // Fallback to regular init_static impl for other platforms for now. + + arena_init_static :: proc(arena: ^virtual.Arena, base_address : rawptr, + reserved : uint = virtual.DEFAULT_ARENA_STATIC_RESERVE_SIZE, + commit_size : uint = virtual.DEFAULT_ARENA_STATIC_COMMIT_SIZE + ) -> (err: virtual.Allocator_Error) { + return virtual.arena_init_static( arena, reserved, commit_size ) + } +} diff --git a/code/space.odin b/code/space.odin index e1d27e9..aedcb9b 100644 --- a/code/space.odin +++ b/code/space.odin @@ -2,6 +2,8 @@ package sectr import rl "vendor:raylib" +// TODO(Ed) : Do we want to have distinct types for cm/pixels/points ? This will make mistakes with unit conversion happen less. + // The points to pixels and pixels to points are our only reference to accurately converting // an object from world space to screen-space. // This prototype engine will have all its spacial unit base for distances in centimetres. @@ -16,7 +18,7 @@ DPT_PPCM :: cast(f32) 96.0 * Inches_To_CM // 243.84 pixels per cm when ODIN_OS == OS_Type.Windows { op_default_dpcm :: 72.0 * Inches_To_CM os_default_ppcm :: 96.0 * Inches_To_CM - // 1 inch = 2.54 cm, 96 inch * 2.54 = 243.84 DPC + // 1 inch = 2.54 cm, 96 inch * 2.54 = 243.84 DPCM } cm_to_pixels :: proc { diff --git a/code/tick_render.odin b/code/tick_render.odin index 9ccd00a..f75e3b6 100644 --- a/code/tick_render.odin +++ b/code/tick_render.odin @@ -19,7 +19,7 @@ render :: proc() rl.BeginDrawing() rl.ClearBackground( Color_BG ) render_mode_2d() - // Render Screenspace + //region Render Screenspace { fps_msg := fmt.tprint( "FPS:", rl.GetFPS() ) fps_msg_width := measure_text_size( fps_msg, default_font, 16.0, 0.0 ).x @@ -68,6 +68,7 @@ render :: proc() debug.draw_debug_text_y = 50 } + //endregion Render Screenspace rl.EndDrawing() } @@ -79,27 +80,11 @@ render_mode_2d :: proc() rl.BeginMode2D( project.workspace.cam ) - // debug.frame_1_on_top = true + //region Imgui Render + { - boxes : [2]^Box2 - if debug.frame_1_on_top { - boxes = { & project.workspace.frame_2, & project.workspace.frame_1 } - } - else { - boxes = { & project.workspace.frame_1, & project.workspace.frame_2 } - } - - for box in boxes { - screen_pos := world_to_screen_no_zoom(box.position) - vec2_cm_to_pixels( Vec2(box.extent) ) - size := vec2_cm_to_pixels( transmute(Vec2) box.extent * 2.0 ) - - rect : rl.Rectangle - rect.x = screen_pos.x - rect.y = screen_pos.y - rect.width = size.x - rect.height = size.y - rl.DrawRectangleRec( rect, box.color ) } + //endregion Imgui Render debug_draw_text_world( "This is text in world space", { 0, 0 }, 16.0 ) diff --git a/code/tick_update.odin b/code/tick_update.odin index 85a8e5d..33f2a10 100644 --- a/code/tick_update.odin +++ b/code/tick_update.odin @@ -86,7 +86,7 @@ update :: proc( delta_time : f64 ) -> b32 } } - // Input Replay + //region Input Replay { if debug_actions.record_replay { #partial switch replay.mode { @@ -129,27 +129,28 @@ update :: proc( delta_time : f64 ) -> b32 play_input( replay.active_file, input ) } } + //endregion Input Replay if debug_actions.show_mouse_pos { debug.mouse_vis = !debug.mouse_vis } - // Camera Manual Nav + //region Camera Manual Nav { cam := & project.workspace.cam digital_move_speed : f32 = 200.0 - zoom_sensitivity : f32 = 0.2 // Digital - // zoom_sensitivity : f32 = 2.0 // Smooth + // zoom_sensitivity : f32 = 0.2 // Digital + zoom_sensitivity : f32 = 4.0 // Smooth if debug.zoom_target == 0.0 { debug.zoom_target = cam.zoom } - // Adjust zoom_target based on input, not the actual zoom - zoom_delta := input.mouse.vertical_wheel * zoom_sensitivity - debug.zoom_target *= 1 + zoom_delta //* f32(delta_time) - debug.zoom_target = clamp(debug.zoom_target, 0.25, 10.0) + // Adjust zoom_target based on input, not the actual zoom + zoom_delta := input.mouse.vertical_wheel * zoom_sensitivity + debug.zoom_target *= 1 + zoom_delta * f32(delta_time) + debug.zoom_target = clamp(debug.zoom_target, 0.25, 10.0) // Linearly interpolate cam.zoom towards zoom_target lerp_factor := cast(f32) 4.0 // Adjust this value to control the interpolation speed @@ -171,32 +172,14 @@ update :: proc( delta_time : f64 ) -> b32 } } } + //endregion - boxes : [2]^Box2 - boxes = { & project.workspace.frame_1, & project.workspace.frame_2 } - if debug.frame_1_on_top { - boxes = { & project.workspace.frame_2, & project.workspace.frame_1 } - } - else { - boxes = { & project.workspace.frame_1, & project.workspace.frame_2 } - } - - if debug_actions.mouse_select + //region Imgui Tick { - for box in boxes - { - cursor_pos := screen_to_world( input.mouse.pos ) - if box_is_within( box, cursor_pos ) - { - if box == & project.workspace.frame_1 { - debug.frame_1_on_top = true - } - else { - debug.frame_1_on_top = false - } - } - } + // Layout + } + // endregion debug.last_mouse_pos = input.mouse.pos diff --git a/code/ui.odin b/code/ui.odin index 1c60e4e..77fb069 100644 --- a/code/ui.odin +++ b/code/ui.odin @@ -1,272 +1,208 @@ package sectr -// 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 { - Invalid = -1, - X, - Y, +Axis2 :: enum i32 { + Invalid = -1, + X = 0, + Y = 1, Count, } -UI_FocusKind :: enum u32 { - Null, - Off, - On, - Root, +Corner :: enum i32 { + Invalid = -1, + _00, + _01, + _10, + _11, + TopLeft = _00, + TopRight = _01, + BottomLeft = _10, + BottomRight = _11, + Count = 4, +} + +Range2 :: struct #raw_union{ + using _ : struct { + min, max : Vec2 + }, + using _ : struct { + p0, p1 : Vec2 + }, + using _ : struct { + x0, y0 : f32, + x1, y1 : f32, + }, +} + +Rect :: struct { + top_left, bottom_right : Vec2 +} + +Side :: enum i32 { + Invalid = -1, + Min = 0, + Max = 1, + Count +} + +// Side2 :: enum u32 { +// Top, +// Bottom, +// Left, +// Right, +// Count, +// } + +UI_AnchorPresets :: enum u32 { + Top_Left, + Top_Right, + Bottom_Right, + Bottom_Left, + Center_Left, + Center_Top, + Center_Right, + Center_Bottom, + Center, + Left_Wide, + Top_Wide, + Right_Wide, + Bottom_Wide, + VCenter_Wide, + HCenter_Wide, + Full, 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_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_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_Layout :: struct { - placeholder : int -} - -UI_Key :: struct { - opaque : [1]u64, -} - UI_BoxFlag :: enum u64 { - // Note(rjf) : Interaction + Focusable, + 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_With, 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, + Text_Wrap, Count, } UI_BoxFlags :: bit_set[UI_BoxFlag; u64] -UI_BoxFlags_Null :: UI_BoxFlags {} -UI_BoxFlags_Clickable :: UI_BoxFlags { .Mouse_Clickable, .Keyboard_Clickable } +// The UI_Box's actual positioning and sizing +// There is an excess of rectangles here for debug puproses. +UI_Computed :: struct { + bounds : Range2, + border : Range2, + margin : Range2, + padding : Range2, + content : Range2, +} -UI_NullLabel :: "" +UI_LayoutSide :: struct #raw_union { + using _ : struct { + top, bottom : UI_Scalar2, + left, right : UI_Scalar2, + } +} + +UI_Cursor :: struct { + placeholder : int, +} + +UI_Key :: distinct u64 + +UI_Scalar :: f32 + +// TODO(Ed): I'm not sure if Percentage is needed or if this would over complicate things... +// UI_Scalar :: struct { +// VPixels : f32, +// Percentage : f32, +// } + +UI_ScalarConstraint :: struct { + min, max : UI_Scalar, +} + +UI_Scalar2 :: [Axis2.Count]UI_Scalar + +// Desiered constraints on the UI_Box. +UI_Layout :: struct { + // TODO(Ed) : Make sure this is all we need to represent an anchor. + anchor : Range2, + + border_width : UI_Scalar, + + margins : UI_LayoutSide, + padding : UI_LayoutSide, + + corner_radii : [Corner.Count]f32, + + size : UI_ScalarConstraint, +} + +UI_Style :: struct { + bg_color : Color, + overlay_color : Color, + border_color : Color, + + // blur_size : f32, + + font : FontID, + font_size : f32, + text_color : Color, + text_alignment : UI_TextAlign, + // text_wrap_width_pixels : f32, + // text_wrap_width_percent : f32, + + // cursors : [CursorKind.Count]UI_Cursor, + // active_cursor : ^UI_Cursor, + // hover_cursor : ^ UI_Cursor, +} + +UI_TextAlign :: enum u32 { + Left, + Center, + Right, + Count +} -// 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. UI_Box :: struct { - // Note(rjf) : persistent links - hash : struct { - next, prev : ^ UI_Box, - }, - - // Note(rjf) : Per-build links & data first, last, prev, next : ^ UI_Box, num_children : i32, - // Note(rjf) : Key + generation info - key : UI_Key, - last_frame_touched_index : u64, - - // Note(rjf) : Per-frame info provided by builders flags : UI_BoxFlags, - display_str : string, - semantic_size : [Axis2.Count]UI_Size, + key : UI_Key, + label : string, - // Note(rjf) : Computed every frame - computed_rel_pos : Vec2, - computed_size : Vec2, - //rect : Rng2F32 + computed : UI_Computed, - // Note(rjf) : Persistent data - hot : f32, - active : f32, + layout : UI_Layout, + style : UI_Style, - // bg_color : Color, - // txt_color : Color, + // Persistent Data + hot_time : f32, + active_time : f32, + disabled_time : f32, } -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_State :: struct { + box_cache : HashTable( UI_Box ), + + box_tree_dirty : b32, + root : ^ UI_Box, + + hot : UI_Key, + active : UI_Key, + clipboard_copy_key : UI_Key, + + drag_start_mouse : Vec2, + // drag_state_arena : ^ Arena, + // drag_state data : string, } -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) { +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/ui_proto.odin b/code/ui_proto.odin new file mode 100644 index 0000000..599978d --- /dev/null +++ b/code/ui_proto.odin @@ -0,0 +1,5 @@ +package sectr + + + +