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.
This commit is contained in:
Edward R. Gonzalez 2024-02-22 21:19:29 -05:00
parent 9cc0855c03
commit 7332644515
15 changed files with 862 additions and 288 deletions

275
code/__Imgui_raddbg/ui.odin Normal file
View File

@ -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
}

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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 )
}
}
}

5
code/app_startup.odin Normal file
View File

@ -0,0 +1,5 @@
package sectr

View File

@ -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 {

View File

@ -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
}

View File

@ -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 )
}
}
}

View File

@ -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 )

View File

@ -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 )
}
}

View File

@ -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 {

View File

@ -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 )

View File

@ -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

View File

@ -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
}

5
code/ui_proto.odin Normal file
View File

@ -0,0 +1,5 @@
package sectr