WIP: Persistent order support for rooted boxes (top-most boxes)

I wasn't expecting it to be this to bad to support it...

Also:
* I renamed some of the files to group better with the virtual codebase view
This commit is contained in:
Edward R. Gonzalez 2024-05-13 01:52:55 -04:00
parent f693685d72
commit 595de438af
34 changed files with 667 additions and 933 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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