Large refactor of the entire codebase

Saw that layout really should be separated from the style struct, so went ahead and pulled the trigger...
A bunch of other refactors have also been done

* Lifted layout out of style, its not separate in UI_Box and in UI_State there is not a UI_LayoutCombo stack.
* UI_StyleTheme renamed to UI_StyleCombo
* UI_Theme has both UI_StyleCombo & UI_LayoutCombo
* Made files for the "project" related code
* ui_layout_compute moved to its own file, ui_layout now used for layout related data structures and interfacing
* Impovements to horizontal & vertical box impl
* UI_Box now keeps track of how many ancestors it has
This commit is contained in:
Edward R. Gonzalez 2024-05-11 22:38:05 -04:00
parent 6a4f7ac6de
commit 1b32fe916e
22 changed files with 1023 additions and 762 deletions

View File

@ -21,24 +21,34 @@ The dependencies are:
* Odin repo's base, core, and vendor(raylib) libaries
* An ini parser
The client(sectr) module's organization is relatively flat due to the nature of odin's module suste, not allowing for cyclic dependencies across modules, and modules can only be in one directory.
The client(sectr) module's organization is relatively flat due to the nature of odin's compiler, not allowing for cyclic dependencies across modules, and modules can only be in one directory.
This makes it difficult to unflatten, not something organic todo in a prototype...
Even so the notatble groups are:
Even so the notable groups are:
* API : Provides the overarching interface of the app's general behavior. Host uses this to provide the client its necessary data and exection env.
* Has the following definitions: startup, shutdown, reload, tick, clean_frame
* Env : Core Memory & State definition + orchestration
* Grime : Name speaks for itself, stuff not directly related to the target features to iterate upon for the prototype.
* Defining dependency aliases or procedure overload tables, rolling own allocator, data structures, etc.
* Font Provider : Manages fonts.
* When loading fonts, the provider currently uses raylib to generate bitmap glyth sheets for a range of font sizes at once.
* Goal is to eventually render using SDF shaders.
* Input : Standard input pooling and related features. Platform abstracted via raylib for now.
* Input : All human input related features
* Base input features (polling & related) are platform abstracted from raylib
* Input Events
* Parser : AST generation, editing, and serialization. A 1/3 of this prototype will most likely be this alone.
* UI : AST visualzation & editing, backend visualization, project organizationa via workspaces (2d cavnases)
* Project : Encpasulation of user config/state separate from persistent app config/state as a 'project'
* Manages the codebase (program model database)
* Manages workspaces : View compositions of the codebase
* UI : Core graphic user interface framework, AST visualzation & editing, backend visualization
* Will most likely be the bulk of this prototype.
* PIMGUI (Persistent Immediate Mode User Interface)
* Auto-layout with heavy procedural generation of box widgets
* Auto-layout
* Supports heavy procedural generation of box widgets
Due to the nature of the prototype there are 'sub-groups' such as the codebase being its own ordeal as well as the workspace.
They'll be elaborated in their own documentation
There is some unused code in `code/__imgui_raddbg`. Its a partial translation of some data structures from raddbg's ui.
@ -47,3 +57,4 @@ There is some unused code in `code/__imgui_raddbg`. Its a partial translation of
![img](docs/assets/sectr_host_2024-03-09_04-30-27.png)
![img](docs/assets/sectr_host_2024-05-04_12-29-39.png)
![img](docs/assets/Code_2024-05-04_12-55-53.png)
![img](docs/assets/sectr_host_2024-05-11_22-34-15.png)

View File

@ -133,6 +133,7 @@ MemoryConfig :: struct {
commit_initial_filebuffer : uint,
}
// ALl nobs available for this application
AppConfig :: struct {
using memory : MemoryConfig,
@ -154,6 +155,39 @@ AppConfig :: struct {
ui_resize_border_width : f32,
}
AppWindow :: struct {
extent : Extents2, // Window half-size
dpi_scale : f32, // Dots per inch scale (provided by raylib via glfw)
ppcm : f32, // Dots per centimetre
}
FontData :: struct {
provider : FontProviderData,
// TODO(Ed): We can have font constants here I guess but eventually
// I rather have fonts configurable for a 'theme' combo
// So that way which IDs are picked depends on runtime
firacode : FontID,
squidgy_slimes : FontID,
rec_mono_semicasual_reg : FontID,
default_font : FontID,
}
FrameTime :: struct {
sleep_is_granular : b32,
delta_seconds : f64,
delta_ms : f64,
delta_ns : Duration,
target_ms : f64,
elapsed_ms : f64,
avg_ms : f64,
fps_avg : f64,
}
// Global Singleton stored in the persistent virtual arena, the first allocated data.
// Use get_state() to conviently retrieve at any point for the program's lifetime
State :: struct {
default_slab_policy : SlabPolicy,
persistent_slab : Slab,
@ -165,8 +199,6 @@ State :: struct {
string_cache : StringCache,
font_provider_data : FontProviderData,
input_data : [2]InputState,
input_prev : ^InputState,
input : ^InputState,
@ -182,6 +214,7 @@ State :: struct {
monitor_id : i32,
monitor_refresh_hz : i32,
// using frametime : FrameTime,
sleep_is_granular : b32,
frametime_delta_seconds : f64,
@ -192,6 +225,9 @@ State :: struct {
frametime_avg_ms : f64,
fps_avg : f64,
// fonts : FontData,
font_provider_data : FontProviderData,
font_firacode : FontID,
font_squidgy_slimes : FontID,
font_rec_mono_semicasual_reg : FontID,
@ -206,90 +242,10 @@ State :: struct {
cam_context : Camera,
}
get_state :: proc "contextless" () -> ^ State {
get_state :: #force_inline proc "contextless" () -> ^ State {
return cast( ^ State ) Memory_App.persistent.reserve_start
}
AppWindow :: struct {
extent : Extents2, // Window half-size
dpi_scale : f32, // Dots per inch scale (provided by raylib via glfw)
ppcm : f32, // Dots per centimetre
}
// PMDB
CodeBase :: struct {
placeholder : int,
}
ProjectConfig :: struct {
placeholder : int,
}
Project :: struct {
path : StrRunesPair,
name : StrRunesPair,
config : ProjectConfig,
codebase : CodeBase,
// TODO(Ed) : Support multiple workspaces
workspace : Workspace,
}
Frame :: struct
{
pos : Vec2,
size : Vec2,
ui : ^UI_Box,
}
Workspace :: struct {
name : StrRunesPair,
cam : Camera,
zoom_target : f32,
frames : Array(Frame),
test_frame : Frame,
// TODO(Ed) : The workspace is mainly a 'UI' conceptually...
ui : UI_State,
}
DebugData :: struct {
square_size : i32,
square_pos : rl.Vector2,
draw_debug_text_y : f32,
cursor_locked : b32,
cursor_unlock_pos : Vec2, // Raylib changes the mose position on lock, we want restore the position the user would be in on screen
mouse_vis : b32,
last_mouse_pos : Vec2,
// 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,
// Test First
frame_2_created : b32,
// Test Draggable
draggable_box_pos : Vec2,
draggable_box_size : Vec2,
box_original_size : Vec2,
// Test parsing
path_lorem : string,
lorem_content : []byte,
lorem_parse : PWS_ParseResult,
// Test 3d Viewport
cam_vp : rl.Camera3D,
viewport_rt : rl.RenderTexture,
}
// get_frametime :: #force_inline proc "contextless" () -> FrameTime {
// return get_state().frametime
// }

41
code/env_scratch.odin Normal file
View File

@ -0,0 +1,41 @@
package sectr
// Scratch space
import rl "vendor:raylib"
DebugData :: struct {
square_size : i32,
square_pos : rl.Vector2,
draw_debug_text_y : f32,
cursor_locked : b32,
cursor_unlock_pos : Vec2, // Raylib changes the mose position on lock, we want restore the position the user would be in on screen
mouse_vis : b32,
last_mouse_pos : Vec2,
// 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,
// Test First
frame_2_created : b32,
// Test Draggable
draggable_box_pos : Vec2,
draggable_box_size : Vec2,
box_original_size : Vec2,
// Test parsing
path_lorem : string,
lorem_content : []byte,
lorem_parse : PWS_ParseResult,
// Test 3d Viewport
cam_vp : rl.Camera3D,
viewport_rt : rl.RenderTexture,
}

View File

@ -268,9 +268,30 @@ to_writer :: proc {
str_builder_to_writer,
}
ui_set_layout :: proc {
ui_style_set_layout,
ui_style_theme_set_layout,
ui_layout_push :: proc {
ui_layout_push_layout,
ui_layout_push_theme,
}
ui_layout :: proc {
ui_layout_via_layout,
ui_layout_via_combo,
}
ui_style_push :: proc {
ui_style_push_style,
ui_style_push_combo,
}
ui_style :: proc {
ui_style_via_style,
ui_style_via_combo,
}
ui_theme :: proc {
ui_theme_via_layout_style,
ui_theme_via_combos,
ui_theme_via_theme,
}
wedge :: proc {

30
code/grime_ptr.odin Normal file
View File

@ -0,0 +1,30 @@
package sectr
// Provides an alternative syntax for pointers
Ptr :: struct( $ Type : typeid ) {
v : Type,
}
exmaple_ptr :: proc()
{
a, b : int
var : ^Ptr(int)
reg : ^int
a = 1
b = 1
var = &{a}
var.v = 2
var = &{b}
var.v = 3
a = 1
b = 1
reg = (& a)
(reg^) = 2
reg = (& b)
(reg^) = 3
}

26
code/project.odin Normal file
View File

@ -0,0 +1,26 @@
package sectr
/*
Project: Encapsulation of all things a user can do separate from the core app behavior
that is managed independetly of it.
*/
// PMDB
CodeBase :: struct {
placeholder : int,
}
ProjectConfig :: struct {
placeholder : int,
}
Project :: struct {
path : StrRunesPair,
name : StrRunesPair,
config : ProjectConfig,
codebase : CodeBase,
// TODO(Ed) : Support multiple workspaces
workspace : Workspace,
}

View File

@ -0,0 +1,38 @@
/*
Workspace : A canvas for compositoning a view for the codebase along with notes.
Each workspace viewport supports both a canvas composition of code frames
or frame tiling towards the application's screenspace.
*/
package sectr
Workspace :: struct {
name : StrRunesPair,
cam : Camera,
zoom_target : f32,
frames : Array(Frame),
test_frame : Frame,
// TODO(Ed) : The workspace is mainly a 'UI' conceptually...
ui : UI_State,
}
// Top level widgets for the workspace
Frame :: struct {
pos : Vec2,
size : Vec2,
ui : UI_Widget,
}
CodeFrame :: struct {
readonly : b32, // Should this frame allow editing?
}
NoteFrame :: struct {
}

View File

@ -4,18 +4,20 @@ import "core:fmt"
import rl "vendor:raylib"
draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRounded( rect, style.layout.corner_radii[0], 9, style.bg_color )
draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_Box ) {
using box
if style.corner_radii[0] > 0 {
rl.DrawRectangleRounded( rect, style.corner_radii[0], 9, style.bg_color )
}
else {
rl.DrawRectangleRec( rect, style.bg_color )
}
}
draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style, color : Color, thickness : f32 ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRoundedLines( rect, style.layout.corner_radii[0], 9, thickness, color )
draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, box : ^UI_Box, color : Color, thickness : f32 ) {
using box
if style.corner_radii[0] > 0 {
rl.DrawRectangleRoundedLines( rect, style.corner_radii[0], 9, thickness, color )
}
else {
rl.DrawRectangleLinesEx( rect, thickness, color )
@ -119,6 +121,7 @@ render_mode_2d_workspace :: proc()
// profile("Box")
parent := current.parent
layout := current.layout
style := current.style
computed := & current.computed
@ -128,18 +131,17 @@ render_mode_2d_workspace :: proc()
continue
}
// TODO(Ed) : Render Borders
// profile_begin("Calculating Raylib rectangles")
render_anchors := range2(
ws_view_to_render_pos(computed.anchors.min),
ws_view_to_render_pos(computed.anchors.max),
)
render_margins := range2(
ws_view_to_render_pos(computed.margins.min),
ws_view_to_render_pos(computed.margins.max),
)
// render_anchors := range2(
// ws_view_to_render_pos(computed.anchors.min),
// ws_view_to_render_pos(computed.anchors.max),
// )
// render_margins := range2(
// ws_view_to_render_pos(computed.margins.min),
// ws_view_to_render_pos(computed.margins.max),
// )
render_bounds := range2(
ws_view_to_render_pos(computed.bounds.min),
ws_view_to_render_pos(computed.bounds.max),
@ -153,8 +155,8 @@ render_mode_2d_workspace :: proc()
ws_view_to_render_pos(computed.content.max),
)
rect_anchors := range2_to_rl_rect( render_anchors )
rect_margins := range2_to_rl_rect( render_margins )
// rect_anchors := range2_to_rl_rect( render_anchors )
// rect_margins := range2_to_rl_rect( render_margins )
rect_bounds := range2_to_rl_rect( render_bounds )
rect_padding := range2_to_rl_rect( render_padding )
rect_content := range2_to_rl_rect( render_content )
@ -163,10 +165,10 @@ render_mode_2d_workspace :: proc()
// profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )")
if style.bg_color.a != 0
{
draw_rectangle( rect_bounds, style )
draw_rectangle( rect_bounds, current )
}
if style.border_width > 0 {
draw_rectangle_lines( rect_bounds, style, style.border_color, style.border_width )
if layout.border_width > 0 {
draw_rectangle_lines( rect_bounds, current, style.border_color, layout.border_width )
}
// profile_end()
@ -174,10 +176,10 @@ render_mode_2d_workspace :: proc()
// profile_begin("rl.DrawRectangleRoundedLines: padding & content")
if equal_range2(computed.content, computed.padding) {
draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness )
draw_rectangle_lines( rect_padding, current, Color_Debug_UI_Padding_Bounds, line_thickness )
}
else {
draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness )
draw_rectangle_lines( rect_content, current, Color_Debug_UI_Content_Bounds, line_thickness )
}
// profile_end()
@ -195,7 +197,7 @@ render_mode_2d_workspace :: proc()
// profile_end()
if len(current.text.str) > 0 {
ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), style.layout.font_size, style.text_color )
ws_view_draw_text( current.text, ws_view_to_render_pos(computed.text_pos * {1, -1}), layout.font_size, style.text_color )
}
}
}
@ -338,18 +340,19 @@ render_screen_ui :: proc()
parent := current.parent
style := current.style
layout := current.layout
computed := & current.computed
computed_size := computed.bounds.p1 - computed.bounds.p0
render_anchors := range2(
screen_to_render_pos(computed.anchors.min),
screen_to_render_pos(computed.anchors.max),
)
render_margins := range2(
screen_to_render_pos(computed.margins.min),
screen_to_render_pos(computed.margins.max),
)
// render_anchors := range2(
// screen_to_render_pos(computed.anchors.min),
// screen_to_render_pos(computed.anchors.max),
// )
// render_margins := range2(
// screen_to_render_pos(computed.margins.min),
// screen_to_render_pos(computed.margins.max),
// )
render_bounds := range2(
screen_to_render_pos(computed.bounds.min),
screen_to_render_pos(computed.bounds.max),
@ -362,8 +365,8 @@ render_screen_ui :: proc()
screen_to_render_pos(computed.content.min),
screen_to_render_pos(computed.content.max),
)
rect_anchors := range2_to_rl_rect( render_anchors )
rect_margins := range2_to_rl_rect( render_margins )
// rect_anchors := range2_to_rl_rect( render_anchors )
// rect_margins := range2_to_rl_rect( render_margins )
rect_bounds := range2_to_rl_rect( render_bounds )
rect_padding := range2_to_rl_rect( render_padding )
rect_content := range2_to_rl_rect( render_content )
@ -372,10 +375,10 @@ render_screen_ui :: proc()
// profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )")
if style.bg_color.a != 0
{
draw_rectangle( rect_bounds, style )
draw_rectangle( rect_bounds, current )
}
if style.border_width > 0 {
draw_rectangle_lines( rect_bounds, style, style.border_color, style.border_width )
if layout.border_width > 0 {
draw_rectangle_lines( rect_bounds, current, style.border_color, layout.border_width )
}
// profile_end()
@ -383,34 +386,34 @@ render_screen_ui :: proc()
// profile_begin("rl.DrawRectangleRoundedLines: padding & content")
if equal_range2(computed.content, computed.padding) {
// draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness )
draw_rectangle_lines( rect_padding, current, Color_Debug_UI_Padding_Bounds, line_thickness )
}
else {
// draw_rectangle_lines( rect_content, style, Color_Debug_UI_Content_Bounds, line_thickness )
draw_rectangle_lines( rect_content, current, Color_Debug_UI_Content_Bounds, line_thickness )
}
// profile_end()
if .Mouse_Resizable in current.flags
{
// profile("Resize Bounds")
resize_border_width := cast(f32) get_state().config.ui_resize_border_width
resize_percent_width := computed_size * (resize_border_width * 1.0/ 200.0)
resize_border_non_range := add(current.computed.bounds, range2(
{ resize_percent_width.x, -resize_percent_width.x },
{ -resize_percent_width.x, resize_percent_width.x }))
// if .Mouse_Resizable in current.flags
// {
// // profile("Resize Bounds")
// resize_border_width := cast(f32) get_state().config.ui_resize_border_width
// resize_percent_width := computed_size * (resize_border_width * 1.0/ 200.0)
// resize_border_non_range := add(current.computed.bounds, range2(
// { resize_percent_width.x, -resize_percent_width.x },
// { -resize_percent_width.x, resize_percent_width.x }))
render_resize := range2(
resize_border_non_range.min,
resize_border_non_range.max,
)
rect_resize := rl.Rectangle {
render_resize.min.x,
render_resize.min.y,
render_resize.max.x - render_resize.min.x,
render_resize.max.y - render_resize.min.y,
}
draw_rectangle_lines( rect_padding, style, Color_Red, line_thickness )
}
// render_resize := range2(
// resize_border_non_range.min,
// resize_border_non_range.max,
// )
// rect_resize := rl.Rectangle {
// render_resize.min.x,
// render_resize.min.y,
// render_resize.max.x - render_resize.min.x,
// render_resize.max.y - render_resize.min.y,
// }
// draw_rectangle_lines( rect_padding, current, Color_Red, line_thickness )
// }
point_radius : f32 = 3
@ -421,12 +424,12 @@ render_screen_ui :: proc()
// }
// rl.DrawCircleV( center, point_radius, Color_White )
// rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red )
// rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue )
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 {
draw_text_screenspace( current.text, screen_to_render_pos(computed.text_pos), style.layout.font_size, style.text_color )
draw_text_screenspace( current.text, screen_to_render_pos(computed.text_pos), layout.font_size, style.text_color )
}
}
}

View File

@ -175,8 +175,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
@ -203,13 +203,14 @@ update :: proc( delta_time : f64 ) -> b32
ui_graph_build( & state.project.workspace.ui )
ui := ui_context
frame_style_flags : UI_StyleFlags = {
frame_style_flags : UI_LayoutFlags = {
.Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,
}
default_layout := UI_Layout {
flags = frame_style_flags,
anchor = {},
alignment = { 0., 0.0 },
alignment = { 0.0, 0.0 },
font_size = 30,
text_alignment = { 0.0, 0.0 },
// corner_radii = { 0.2, 0.2, 0.2, 0.2 },
@ -217,27 +218,24 @@ update :: proc( delta_time : f64 ) -> b32
size = range2( { 1000, 1000 }, {}),
// padding = { 20, 20, 20, 20 }
}
ui_layout( default_layout )
frame_style_default := UI_Style {
flags = frame_style_flags,
bg_color = Color_BG_TextBox,
font = default_font,
text_color = Color_White,
layout = default_layout,
}
frame_theme := to_ui_styletheme(frame_style_default)
frame_theme := to_ui_style_combo(frame_style_default)
frame_theme.disabled.bg_color = Color_Frame_Disabled
frame_theme.hot. bg_color = Color_Frame_Hover
frame_theme.active. bg_color = Color_Frame_Select
ui_style_theme( frame_theme )
ui_style( frame_theme )
config.ui_resize_border_width = 2.5
// test_hover_n_click()
// 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

@ -76,19 +76,6 @@ UI_BoxFlag :: enum u64 {
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y }
// The UI_Box's actual positioning and sizing
// There is an excess of rectangles here for debug puproses.
UI_Computed :: struct {
fresh : b32, // If the auto-layout has been computed for the current frame
anchors : Range2, // Bounds for anchors within parent
margins : Range2, // Bounds for margins within parent
bounds : Range2, // Bounds for box itself
padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one)
content : Range2, // Bounds for content (text or children)
text_pos : Vec2, // Position of text within content
text_size : Vec2, // Size of text within content
}
UI_Cursor :: struct {
placeholder : int,
}
@ -125,10 +112,16 @@ UI_Box :: struct {
// Regenerated per frame.
using links : DLL_NodeFull( UI_Box ), // first, last, prev, next
parent : ^UI_Box,
num_children : i32,
num_children : i16,
ancestors : i16,
parent_index : i16,
flags : UI_BoxFlags,
computed : UI_Computed,
prev_layout : UI_Layout,
layout : UI_Layout,
flags : UI_BoxFlags,
computed : UI_Computed,
prev_style : UI_Style,
style : UI_Style,
@ -139,8 +132,6 @@ UI_Box :: struct {
disabled_delta : f32,
style_delta : f32,
parent_index : i32,
// prev_computed : UI_Computed,
// prev_style : UI_Style,v
// mouse : UI_InteractState,
@ -172,8 +163,9 @@ UI_State :: struct {
layout_dirty : b32,
// TODO(Ed) : Look into using a build arena like Ryan does for these possibly (and thus have a linked-list stack)
theme_stack : StackFixed( UI_StyleTheme, UI_Style_Stack_Size ),
parent_stack : StackFixed( ^UI_Box, UI_Parent_Stack_Size ),
layout_combo_stack : StackFixed( UI_LayoutCombo, UI_Style_Stack_Size ),
style_combo_stack : StackFixed( UI_StyleCombo, UI_Style_Stack_Size ),
parent_stack : StackFixed( ^UI_Box, UI_Parent_Stack_Size ),
// flag_stack : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ),
hot : UI_Key,
@ -192,7 +184,7 @@ UI_State :: struct {
last_pressed_key_us : [MouseBtn.count] f32,
}
ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator )
ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_reserve_size : u64 */ )
{
ui := ui
ui^ = {}
@ -283,6 +275,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
curr_box.parent_index = parent.num_children
parent.num_children += 1
curr_box.parent = parent
curr_box.ancestors = parent.ancestors + 1
}
ui.built_box_count += 1
@ -416,3 +409,5 @@ ui_top_ancestor :: #force_inline proc "contextless" ( box : ^UI_Box ) -> (^UI_Bo
for ; ancestor.parent != root; ancestor = ancestor.parent {}
return ancestor
}
ui_context :: #force_inline proc() -> ^UI_State { return get_state().ui_context }

View File

@ -3,182 +3,145 @@ package sectr
import "core:math"
import "core:math/linalg"
// Note(Ed): This is naturally pretty expensive
// The UI_Box's actual positioning and sizing
// There is an excess of rectangles here for debug puproses.
UI_Computed :: struct {
fresh : b32, // If the auto-layout has been computed for the current frame
// anchors : Range2, // Bounds for anchors within parent
// margins : Range2, // Bounds for margins within parent
bounds : Range2, // Bounds for box itself
padding : Range2, // Bounds for padding's starting bounds (will be offset by border if there is one)
content : Range2, // Bounds for content (text or children)
text_pos : Vec2, // Position of text within content
text_size : Vec2, // Size of text within content
}
ui_compute_layout :: proc( ui : ^UI_State )
{
profile(#procedure)
state := get_state()
UI_LayoutDirectionX :: enum(i32) {
Left_To_Right,
Right_To_Left,
}
root := ui.root
{
computed := & root.computed
style := root.style
layout := & style.layout
computed.bounds.min = layout.pos
computed.bounds.max = layout.size.min
computed.content = computed.bounds
}
UI_LayoutDirectionY :: enum(i32) {
Top_To_Bottom,
Bottom_To_Top,
}
current := root.first
for ; current != nil;
{
// if current.computed.fresh do return
UI_LayoutSide :: struct {
// using _ : struct {
top, bottom : UI_Scalar,
left, right : UI_Scalar,
// }
}
// TODO(Ed): Lift this to ui_box_compute_layout
// profile("Layout Box")
style := current.style
UI_LayoutFlag :: enum u32 {
// 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 style.flags)
fixed_pos_y : f32 = cast(f32) int(.Fixed_Position_Y in style.flags)
fixed_width : f32 = cast(f32) int(.Fixed_Width in style.flags)
fixed_height : f32 = cast(f32) int(.Fixed_Height in style.flags)
// Will perform scissor pass on children to their parent's bounds
// (Specified in the parent)
Clip_Children_To_Bounds,
size_to_text : bool = .Size_To_Text in style.flags
// Enforces the box will always remain in a specific position relative to the parent.
// Overriding the anchors and margins.
Fixed_Position_X,
Fixed_Position_Y,
parent := current.parent
computed := & current.computed
// Enforces box will always be within the bounds of the parent box.
Clamp_Position_X,
Clamp_Position_Y,
parent_content := parent.computed.content
parent_content_size := parent_content.max - parent_content.min
parent_center := parent_content.min + parent_content_size * 0.5
// Enroces the widget will maintain its size reguardless of any constraints
// Will override parent constraints (use the size.min.xy to specify the width & height)
Fixed_Width,
Fixed_Height,
layout := & style.layout
// TODO(Ed): Implement this!
// Enforces the widget will have a width specified as a ratio of its height (use the size.min/max.x to specify the scalar)
// If you wish for the width to stay fixed couple with the Fixed_Width flag
Scale_Width_By_Height_Ratio,
// Enforces the widget will have a height specified as a ratio of its width (use the size.min/max.y to specify the scalar)
// If you wish for the height to stay fixed couple with the Fixed_Height flag
Scale_Height_By_Width_Ratio,
/*
If fixed position (X or Y):
* Ignore Margins
* Ignore Anchors
// Sets the (0, 0) position of the child box to the parents anchor's center (post-margins bounds)
// By Default, the origin is at the top left of the anchor's bounds
Origin_At_Anchor_Center,
If clampped position (X or Y):
* Positon cannot exceed the anchors/margins bounds.
// TODO(Ed): Implement this!
// For this to work, the children must have a minimum size set & their size overall must be greater than the parent's minimum size
Size_To_Content,
If fixed size (X or Y):
* Ignore Parent constraints (can only be clipped)
// Will size the box to its text.
Size_To_Text,
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.
// TODO(Ed): Implement this!
Text_Wrap,
If size.min is not 0:
* Ignore parent constraints if the bounds go below that value.
Count,
}
UI_LayoutFlags :: bit_set[UI_LayoutFlag; u32]
If size.max is 0:
* Allow the child box to spread to entire adjusted content bounds, otherwise clampped to max size.
*/
// Used within UI_Box, provides the layout (spacial constraints & specification) of the widget and
UI_Layout :: struct {
flags : UI_LayoutFlags,
anchor : Range2,
alignment : Vec2,
text_alignment : Vec2,
// 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
font_size : f32,
// 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
margins : UI_LayoutSide,
padding : UI_LayoutSide,
// 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
border_width : UI_Scalar,
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)
// Position in relative coordinate space.
// If the box's flags has Fixed_Position, then this will be its aboslute position in the relative coordinate space
pos : Vec2,
size : Range2,
if .Fixed_Width in style.flags {
adjusted_size.x = layout.size.min.x
}
if .Fixed_Height in style.flags {
adjusted_size.y = layout.size.min.y
}
// TODO(Ed) : Should thsi just always be WS_Pos for workspace UI?
// (We can union either varient and just know based on checking if its the screenspace UI)
// If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos.
// tile_pos : WS_Pos,
text_size : Vec2
if style.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, style.layout.font_size, 0 )
}
transition_time : f32,
}
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 style.flags ? origin_center : origin_top_left
rel_pos := origin + layout.pos
if .Fixed_Position_X in style.flags {
rel_pos.x = origin.x + layout.pos.x
}
if .Fixed_Position_Y in style.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 )
UI_LayoutCombo :: struct #raw_union {
array : [UI_StylePreset.Count] UI_Layout,
using layouts : struct {
default, disabled, hot, active : UI_Layout,
}
}
to_ui_layout_side :: #force_inline proc( pixels : f32 ) -> UI_LayoutSide { return { pixels, pixels, pixels, pixels } }
to_ui_layout_combo :: #force_inline proc( layout : UI_Layout ) -> UI_LayoutCombo { return { layouts = {layout, layout, layout, layout} } }
/*
Layout Interface
Layout for UI_Boxes in the state graph is stored on a per-graph UI_State basis in the fixed sized stack called layout_combo_stack.
The following provides a convient way to manipulate this stack from the assuption of the program's state.ui_context
The following procedure overloads are available from grime.odin:
* ui_layout
* ui_layout_push
*/
ui_layout_peek :: #force_inline proc() -> UI_LayoutCombo { return stack_peek( & get_state().ui_context.layout_combo_stack) }
ui_layout_ref :: #force_inline proc() -> ^UI_LayoutCombo { return stack_peek_ref( & get_state().ui_context.layout_combo_stack) }
ui_layout_push_layout :: #force_inline proc( layout : UI_Layout ) { push( & get_state().ui_context.layout_combo_stack, to_ui_layout_combo(layout)) }
ui_layout_push_theme :: #force_inline proc( combo : UI_LayoutCombo ) { push( & get_state().ui_context.layout_combo_stack, combo ) }
ui_layout_pop :: #force_inline proc() { pop( & get_state().ui_context.layout_combo_stack ) }
@(deferred_none = ui_layout_pop) ui_layout_via_layout :: #force_inline proc( layout : UI_Layout ) { ui_layout_push( layout) }
@(deferred_none = ui_layout_pop) ui_layout_via_combo :: #force_inline proc( combo : UI_LayoutCombo ) { ui_layout_push( combo) }
ui_set_layout :: #force_inline proc( layout : UI_Layout, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.layout_combo_stack).array[preset] = layout }
/*
Widget Layout Ops
*/
ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_LayoutDirectionX, width_ref : ^f32 ) {
}

180
code/ui_layout_compute.odin Normal file
View File

@ -0,0 +1,180 @@
package sectr
// Note(Ed): This is naturally pretty expensive
ui_compute_layout :: proc( ui : ^UI_State )
{
profile(#procedure)
state := get_state()
root := ui.root
{
computed := & root.computed
style := root.style
layout := & root.layout
computed.bounds.min = layout.pos
computed.bounds.max = layout.size.min
computed.content = computed.bounds
}
current := root.first
for ; current != nil;
{
// 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 )
}
}

View File

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

View File

@ -10,13 +10,13 @@ UI_ScreenState :: struct
settings_btn : struct
{
using widget : UI_Widget,
is_open : b32,
}
},
settings_menu : struct
{
pos, size, min_size : Vec2,
container : UI_VBox,
is_open : b32,
},
}
@ -40,38 +40,39 @@ ui_screen_menu_bar :: proc()
using screen_ui
{
using menu_bar
ui_theme_via_style({
flags = {},
ui_layout( UI_Layout {
// flags = {.Fixed_Position_X, .Fixed_Position_Y, .Fixed_Width, .Fixed_Height},
anchor = {},
alignment = { 0, 1 },
border_width = 1.0,
font_size = 12,
pos = menu_bar.pos,
size = range2( menu_bar.size, {}),
})
ui_style( UI_Style {
bg_color = { 0, 0, 0, 30 },
border_color = { 0, 0, 0, 200 },
font = default_font,
text_color = Color_White,
layout = {
anchor = {},
alignment = { 0, 1 },
border_width = 1.0,
font_size = 12,
pos = menu_bar.pos,
size = range2( menu_bar.size, {}),
},
})
container = ui_hbox( .Left_To_Right, "App Menu Bar", { .Mouse_Clickable} )
// ui_hbox( & container, .Left_To_Right, "App Menu Bar", { .Mouse_Clickable} )
container = ui_hbox_begin( .Left_To_Right, "Menu Bar" )
ui_parent(container)
theme := to_ui_styletheme({
ui_layout( UI_Layout {
flags = {},
anchor = {},
border_width = 1.0,
font_size = 12,
})
style_theme := to_ui_style_combo({
bg_color = Color_Frame_Disabled,
font = default_font,
text_color = Color_White,
layout = {
anchor = range2( {0, 0}, {0, 0} ),
alignment = { 0.0, 0.0 },
font_size = 18,
text_alignment = { 0.5, 0.5 },
size = range2({25, 0}, {0, 0})
}
})
theme.hot.bg_color = Color_Blue
theme.active.bg_color = Color_Frame_Select
ui_style_theme(theme)
style_theme.hot.bg_color = Color_Blue
style_theme.active.bg_color = Color_Frame_Select
ui_style(style_theme)
move_box := ui_button("Move Box");
{
@ -79,28 +80,28 @@ ui_screen_menu_bar :: proc()
if active {
menu_bar.pos += input.mouse.delta
}
style.anchor.ratio.x = 0.2
layout.anchor.ratio.x = 0.2
}
spacer := ui_spacer("Menu Bar: Move Spacer")
spacer.style.flags |= {.Fixed_Width}
spacer.style.size.min.x = 50
// spacer.style.bg_color = Color_Red
spacer.layout.flags |= {.Fixed_Width}
spacer.layout.size.min.x = 50
settings_btn.widget = ui_button("Settings Btn")
settings_btn.text = str_intern("Settings")
settings_btn.style.flags = {
settings_btn.layout.flags = {
// .Scale_Width_By_Height_Ratio,
.Fixed_Width
}
settings_btn.style.size.min.x = 100
settings_btn.layout.size.min.x = 100
if settings_btn.pressed {
settings_btn.is_open = true
settings_menu.is_open = true
}
spacer = ui_spacer("Menu Bar: End Spacer")
spacer.style.anchor.ratio.x = 1.0
// spacer.style.bg_color = Color_Red
spacer.layout.anchor.ratio.x = 1.0
ui_hbox_end( container)
}
}
@ -109,7 +110,7 @@ ui_screen_settings_menu :: proc()
profile("Settings Menu")
using state := get_state()
using state.screen_ui
if menu_bar.settings_btn.pressed || ! menu_bar.settings_btn.is_open do return
if ! settings_menu.is_open do return
using settings_menu
if size.x < min_size.x do size.x = min_size.x
@ -118,66 +119,66 @@ ui_screen_settings_menu :: proc()
container = ui_vbox_begin( .Top_To_Bottom, "Settings Menu", {.Mouse_Clickable})
{
using container
style.flags = {
// .Origin_At_Anchor_Center
}
style.pos = pos
style.alignment = { 0.5, 0.5 }
style.bg_color = Color_BG_Panel_Translucent
style.size = range2( size, {})
// 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)
{
ui_theme_via_style({
ui_layout( UI_Layout {
font_size = 16,
})
ui_style( UI_Style {
bg_color = Color_Transparent,
font = default_font,
text_color = Color_White,
layout = { font_size = 16 },
})
ui_style_theme_ref().hot.bg_color = Color_Blue
ui_style_ref().hot.bg_color = Color_Blue
frame_bar := ui_hbox_begin(.Left_To_Right, "Settings Menu: Frame Bar", { .Mouse_Clickable, .Focusable, .Click_To_Focus })
{
frame_bar.style.bg_color = Color_BG_Panel
frame_bar.style.flags = {.Fixed_Height}
frame_bar.style.alignment = { 0, 0 }
frame_bar.style.size.min.y = 50
if frame_bar.active {
pos += input.mouse.delta
}
frame_bar.style.bg_color = Color_BG_Panel
frame_bar.layout.flags = {.Fixed_Height}
frame_bar.layout.size.min.y = 50
ui_parent(frame_bar)
title := ui_text("Settings Menu: Title", str_intern("Settings Menu"), {.Disabled})
{
using title
style.margins = { 0, 0, 15, 0}
style.text_alignment = {0 , 0.5}
style.anchor.ratio.x = 1.0
layout.margins = { 0, 0, 15, 0}
layout.text_alignment = {0 , 0.5}
layout.anchor.ratio.x = 1.0
}
ui_style_theme(ui_style_theme_peek())
theme := ui_style_theme_ref()
ui_style(ui_style_peek())
theme := ui_style_ref()
theme.default.bg_color = Color_GreyRed
theme.hot. bg_color = Color_Red
close_btn := ui_button("Settings Menu: Close Btn")
{
using close_btn
text = str_intern("close")
style.flags = {.Fixed_Width}
style.size.min = {50, 0}
style.text_alignment = {0.5, 0.5}
style.anchor.ratio.x = 1.0
layout.flags = {.Fixed_Width}
layout.size.min = {50, 0}
layout.text_alignment = {0.5, 0.5}
layout.anchor.ratio.x = 1.0
if close_btn.pressed {
menu_bar.settings_btn.is_open = false
settings_menu.is_open = false
}
}
ui_hbox_end(frame_bar)//, & size.x)
ui_hbox_end(frame_bar, & size.x)
}
if frame_bar.active {
pos += input.mouse.delta
}
spacer := ui_spacer("Settings Menu: Spacer")
spacer.style.anchor.ratio.y = 1.0
spacer.layout.anchor.ratio.y = 1.0
ui_vbox_end(container)//, & size.y)
ui_vbox_end(container, & size.y)
}
ui_resizable_handles( & container, & pos, & size )

View File

@ -211,7 +211,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
box.prev_style = box.style
box.style_delta = 0
}
box.style = stack_peek( & ui.theme_stack ).hot
box.layout = ui_layout_peek().hot
box.style = ui_style_peek().hot
}
if is_active
{
@ -219,7 +220,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
box.prev_style = box.style
box.style_delta = 0
}
box.style = stack_peek( & ui.theme_stack ).active
box.layout = ui_layout_peek().active
box.style = ui_style_peek().active
}
if is_disabled
{
@ -227,7 +229,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
box.prev_style = box.style
box.style_delta = 0
}
box.style = stack_peek( & ui.theme_stack ).disabled
box.layout = ui_layout_peek().disabled
box.style = ui_style_peek().disabled
}
if ! is_disabled && ! is_active && ! is_hot {
@ -238,7 +241,8 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
else {
box.style_delta += frame_delta
}
box.style = stack_peek( & ui.theme_stack ).default
box.layout = ui_layout_peek().default
box.style = ui_style_peek().default
}
}

View File

@ -1,95 +1,12 @@
package sectr
UI_LayoutDirectionX :: enum(i32) {
Left_To_Right,
Right_To_Left,
UI_TextAlign :: enum u32 {
Left,
Center,
Right,
Count
}
UI_LayoutDirectionY :: enum(i32) {
Top_To_Bottom,
Bottom_To_Top,
}
UI_LayoutSide :: struct {
// using _ : struct {
top, bottom : UI_Scalar,
left, right : UI_Scalar,
// }
}
// Desiered constraints on the UI_Box.
UI_Layout :: struct {
anchor : Range2,
alignment : Vec2,
text_alignment : Vec2,
font_size : f32,
margins : UI_LayoutSide,
padding : UI_LayoutSide,
// TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend)
corner_radii : [Corner.Count]f32,
border_width : UI_Scalar,
// Position in relative coordinate space.
// If the box's flags has Fixed_Position, then this will be its aboslute position in the relative coordinate space
pos : Vec2,
size : Range2,
// TODO(Ed) : Should thsi just always be WS_Pos for workspace UI?
// (We can union either varient and just know based on checking if its the screenspace UI)
// If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos.
// tile_pos : WS_Pos,
}
// TODO(Ed): Change this to layout flags? Everything so far is just related to auto-layout
UI_StyleFlag :: enum u32 {
// Will perform scissor pass on children to their parent's bounds
// (Specified in the parent)
Clip_Children_To_Bounds,
// Enforces the box will always remain in a specific position relative to the parent.
// Overriding the anchors and margins.
Fixed_Position_X,
Fixed_Position_Y,
// Enforces box will always be within the bounds of the parent box.
Clamp_Position_X,
Clamp_Position_Y,
// Enroces the widget will maintain its size reguardless of any constraints
// Will override parent constraints (use the size.min.xy to specify the width & height)
Fixed_Width,
Fixed_Height,
// TODO(Ed): Implement this!
// Enforces the widget will have a width specified as a ratio of its height (use the size.min/max.x to specify the scalar)
// If you wish for the width to stay fixed couple with the Fixed_Width flag
Scale_Width_By_Height_Ratio,
// Enforces the widget will have a height specified as a ratio of its width (use the size.min/max.y to specify the scalar)
// If you wish for the height to stay fixed couple with the Fixed_Height flag
Scale_Height_By_Width_Ratio,
// Sets the (0, 0) position of the child box to the parents anchor's center (post-margins bounds)
// By Default, the origin is at the top left of the anchor's bounds
Origin_At_Anchor_Center,
// TODO(Ed): Implement this!
// For this to work, the children must have a minimum size set & their size overall must be greater than the parent's minimum size
Size_To_Content,
// Will size the box to its text.
Size_To_Text,
// TODO(Ed): Implement this!
Text_Wrap,
Count,
}
UI_StyleFlags :: bit_set[UI_StyleFlag; u32]
UI_StylePreset :: enum u32 {
Default,
Disabled,
@ -99,79 +16,61 @@ UI_StylePreset :: enum u32 {
}
UI_Style :: struct {
flags : UI_StyleFlags,
bg_color : Color,
border_color : Color,
// TODO(Ed): We cannot support individual corners unless we add it to raylib (or finally change the rendering backend)
corner_radii : [Corner.Count]f32,
// TODO(Ed) : Add support for this eventually
blur_size : f32,
// TODO(Ed): Add support for textures
// texture : Texture2,
// TODO(Ed): Add support for custom shader
// shader : UI_Shader,
font : FontID,
// TODO(Ed): Should this get moved to the layout struct? Techncially font-size is mainly
text_color : Color,
// TODO(Ed) : Support setting the cursor state
cursor : UI_Cursor,
// TODO(Ed): Should layout be separate from style?
// It technically entirely makes up style flags
// and the only thing shared I guess is the font determining the measured text size + transition time
// Technically we can provide a separate transition time option just for layout...
using layout : UI_Layout,
// Used with style, prev_style, and style_delta to produce a simple interpolated animation
// Applied in the layout pass & the rendering pass for their associated fields.
transition_time : f32,
}
UI_StyleTheme :: struct #raw_union {
UI_StyleCombo :: struct #raw_union {
array : [UI_StylePreset.Count] UI_Style,
using styles : struct {
default, disabled, hot, active : UI_Style,
}
}
UI_TextAlign :: enum u32 {
Left,
Center,
Right,
Count
}
to_ui_style_combo :: #force_inline proc( style : UI_Style ) -> UI_StyleCombo { return { styles = {style, style, style, style} } }
to_ui_layoutside :: #force_inline proc( pixels : f32 ) -> UI_LayoutSide { return { pixels, pixels, pixels, pixels } }
/*
Style Interface
to_ui_styletheme :: #force_inline proc( style : UI_Style ) -> UI_StyleTheme { return { styles = {style, style, style, style} } }
Style for UI_Boxes in the state graph is stored on a per-graph UI_State basis in the fixed sized stack called style_combo_stack.
The following provides a convient way to manipulate this stack from the assuption of the program's state.ui_context
ui_style_peek :: #force_inline proc( box_state : UI_StylePreset ) -> UI_Style { return stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] }
ui_style_ref :: #force_inline proc( box_state : UI_StylePreset ) -> (^ UI_Style) { return & stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] }
The following procedure overloads are available from grime.odin :
* ui_style
* ui_style_push
*/
ui_style_set :: #force_inline proc ( style : UI_Style, box_state : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.theme_stack ).array[box_state] = style }
ui_style_peek :: #force_inline proc() -> UI_StyleCombo { return stack_peek( & get_state().ui_context.style_combo_stack ) }
ui_style_ref :: #force_inline proc() -> (^ UI_StyleCombo) { return stack_peek_ref( & get_state().ui_context.style_combo_stack ) }
ui_style_set_layout :: #force_inline proc ( layout : UI_Layout, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.theme_stack ).array[preset].layout = layout }
ui_style_push_style :: #force_inline proc( style : UI_Style ) { push( & get_state().ui_context.style_combo_stack, to_ui_style_combo(style)) }
ui_style_push_combo :: #force_inline proc( combo : UI_StyleCombo ) { push( & get_state().ui_context.style_combo_stack, combo ) }
ui_style_pop :: #force_inline proc() { pop( & get_state().ui_context.style_combo_stack ) }
ui_style_theme_push :: #force_inline proc( preset : UI_StyleTheme ) { push( & get_state().ui_context.theme_stack, preset ) }
ui_style_theme_pop :: #force_inline proc() { pop( & get_state().ui_context.theme_stack ) }
@(deferred_none = ui_style_pop) ui_style_via_style :: #force_inline proc( style : UI_Style ) { ui_style_push( style) }
@(deferred_none = ui_style_pop) ui_style_via_combo :: #force_inline proc( combo : UI_StyleCombo ) { ui_style_push( combo) }
@(deferred_none = ui_style_theme_pop)
ui_style_theme :: #force_inline proc( preset : UI_StyleTheme ) { ui_style_theme_push( preset ) }
ui_style_theme_peek :: #force_inline proc() -> UI_StyleTheme { return stack_peek( & get_state().ui_context.theme_stack ) }
ui_style_theme_ref :: #force_inline proc() -> (^ UI_StyleTheme) { return stack_peek_ref( & get_state().ui_context.theme_stack ) }
@(deferred_none = ui_style_theme_pop)
ui_theme_via_style :: #force_inline proc ( style : UI_Style ) { ui_style_theme_push( UI_StyleTheme { styles = { style, style, style, style } }) }
ui_style_theme_set_layout :: proc ( layout : UI_Layout ) {
for & preset in stack_peek_ref( & get_state().ui_context.theme_stack ).array {
preset.layout = layout
}
}
ui_style_theme_layout_push :: proc ( layout : UI_Layout ) {
ui := get_state().ui_context
ui_style_theme_push( stack_peek( & ui.theme_stack) )
ui_style_theme_set_layout(layout)
}
@(deferred_none = ui_style_theme_pop)
ui_style_theme_layout :: #force_inline proc( layout : UI_Layout ) { ui_style_theme_layout_push(layout) }
ui_style_set :: #force_inline proc ( style : UI_Style, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.style_combo_stack ).array[preset] = style }

View File

@ -1,3 +0,0 @@
package sectr
ui_theme_btn_default : proc( ) -> UI_StyleTheme

View File

@ -11,9 +11,9 @@ test_hover_n_click :: proc()
if first_btn.left_clicked || debug.frame_2_created {
debug.frame_2_created = true
second_layout := first_btn.style.layout
second_layout := first_btn.layout
second_layout.pos = { 250, 0 }
ui_set_layout( second_layout )
ui_layout( second_layout )
second_box := ui_button( "SECOND BOX!")
}
@ -34,12 +34,12 @@ test_draggable :: proc()
pos = { 0, 0 },
size = range2({ 200, 200 }, {}),
}
ui_style_theme_set_layout( draggable_layout )
ui_layout( draggable_layout )
draggable := ui_widget( "Draggable Box!", UI_BoxFlags { .Mouse_Clickable, .Mouse_Resizable } )
if draggable.first_frame {
debug.draggable_box_pos = draggable.style.layout.pos + { 0, -100 }
debug.draggable_box_size = draggable.style.layout.size.min
debug.draggable_box_pos = draggable.layout.pos + { 0, -100 }
debug.draggable_box_size = draggable.layout.size.min
}
// Dragging
@ -51,8 +51,8 @@ test_draggable :: proc()
draggable.style.bg_color = Color_Blue
}
draggable.style.layout.pos = debug.draggable_box_pos
draggable.style.layout.size.min = debug.draggable_box_size
draggable.layout.pos = debug.draggable_box_pos
draggable.layout.size.min = debug.draggable_box_size
draggable.text = { str_fmt_alloc("%v", debug.draggable_box_pos), {} }
draggable.text.runes = to_runes(draggable.text.str)
@ -71,21 +71,21 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
parent_layout.margins = { 100, 100, 100, 100 }
parent_layout.padding = { 5, 10, 5, 5 }
parent_layout.pos = { 0, 0 }
parent_theme := frame_style_default ^
parent_theme.layout = parent_layout
parent_theme.flags = {
parent_layout.flags = {
// .Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,
}
ui_theme_via_style(parent_theme)
ui_layout(parent_layout)
parent_style := frame_style_default ^
ui_style(parent_style)
parent := ui_widget( "Parent", { .Mouse_Clickable, .Mouse_Resizable })
ui_parent(parent)
ui_parent_push(parent)
{
if parent.first_frame {
debug.draggable_box_pos = parent.style.layout.pos
debug.draggable_box_size = parent.style.layout.size.min
debug.draggable_box_pos = parent.layout.pos
debug.draggable_box_size = parent.layout.size.min
}
if parent.active {
debug.draggable_box_pos += mouse_world_delta()
@ -93,8 +93,8 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
if (ui.hot == parent.key) {
parent.style.bg_color = Color_Blue
}
parent.style.layout.pos = debug.draggable_box_pos
parent.style.layout.size.min = debug.draggable_box_size
parent.layout.pos = debug.draggable_box_pos
parent.layout.size.min = debug.draggable_box_size
}
child_layout := default_layout ^
@ -104,16 +104,16 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
child_layout.padding = { 5, 5, 5, 5 }
child_layout.anchor = range2({ 0.2, 0.1 }, { 0.1, 0.15 })
child_layout.pos = { 0, 0 }
child_theme := frame_style_default ^
child_theme.bg_color = Color_GreyRed
child_theme.flags = {
child_layout.flags = {
// .Fixed_Width, .Fixed_Height,
.Origin_At_Anchor_Center
}
child_theme.layout = child_layout
ui_theme_via_style(child_theme)
child_style := frame_style_default ^
child_style.bg_color = Color_GreyRed
ui_theme(child_layout, child_style)
child := ui_widget( "Child", { .Mouse_Clickable })
ui_parent_pop()
}
test_text_box :: proc()
@ -122,27 +122,27 @@ test_text_box :: proc()
ui := ui_context
@static pos : Vec2
style := ui_style_peek( .Default )
style.text_alignment = { 1.0, 1.0 }
layout := ui_layout_peek().default
layout.text_alignment = { 1.0, 1.0 }
// style.flags = { .Size_To_Text }
style.padding = { 10, 10, 10, 10 }
style.layout.font_size = 32
ui_style_theme( { styles = { style, style, style, style, }} )
layout.padding = { 10, 10, 10, 10 }
layout.font_size = 32
ui_layout( layout)
text := str_intern( "Lorem ipsum dolor sit amet")
text_box := ui_text("TEXT BOX!", text, flags = { .Mouse_Clickable })
if text_box.first_frame {
pos = text_box.style.layout.pos
pos = text_box.layout.pos
}
if text_box.active {
pos += mouse_world_delta()
}
text_box.style.layout.pos = pos
text_box.layout.pos = pos
text_box.style.size.min = { text_box.computed.text_size.x * 1.5, text_box.computed.text_size.y * 3 }
text_box.layout.size.min = { text_box.computed.text_size.x * 1.5, text_box.computed.text_size.y * 3 }
}
test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )
@ -151,30 +151,22 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
state := get_state(); using state
ui := ui_context
text_style := frame_style_default ^
text_style.flags = {
text_layout := default_layout^
text_layout.flags = {
.Origin_At_Anchor_Center,
.Fixed_Position_X, .Fixed_Position_Y,
// .Fixed_Width, .Fixed_Height,
}
text_style.text_alignment = { 0.0, 0.5 }
text_style.alignment = { 0.0, 1.0 }
text_style.size.min = { 1600, 30 }
text_theme := UI_StyleTheme { styles = {
text_style,
text_style,
text_style,
text_style,
}}
text_theme.default.bg_color = Color_Transparent
text_theme.disabled.bg_color = Color_Frame_Disabled
text_theme.hot.bg_color = Color_Frame_Hover
text_theme.active.bg_color = Color_Frame_Select
ui_style_theme( text_theme )
layout_text := text_style.layout
text_layout.text_alignment = { 0.0, 0.5 }
text_layout.alignment = { 0.0, 1.0 }
text_layout.size.min = { 1600, 30 }
text_style := frame_style_default ^
text_style_combo := to_ui_style_combo(text_style)
text_style_combo.default.bg_color = Color_Transparent
text_style_combo.disabled.bg_color = Color_Frame_Disabled
text_style_combo.hot.bg_color = Color_Frame_Hover
text_style_combo.active.bg_color = Color_Frame_Select
ui_theme( text_layout, text_style )
alloc_error : AllocatorError; success : bool
// debug.lorem_content, success = os.read_entire_file( debug.path_lorem, frame_allocator() )
@ -201,7 +193,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
continue
}
ui_style_theme_set_layout( layout_text )
ui_layout( text_layout )
line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {.Mouse_Clickable})
if line_hbox.key == ui.hot
@ -209,27 +201,19 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
line_hbox.text = StrRunesPair {}
ui_parent(line_hbox)
chunk_layout := layout_text
chunk_layout := text_layout
chunk_layout.alignment = { 0.0, 1.0 }
chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 })
chunk_layout.pos = {}
chunk_layout.flags = { .Fixed_Position_X, .Size_To_Text }
chunk_style := text_style
chunk_style.flags = { .Fixed_Position_X, .Size_To_Text }
chunk_style.layout = chunk_layout
chunk_theme := UI_StyleTheme { styles = {
chunk_style,
chunk_style,
chunk_style,
chunk_style,
}}
ui_style_theme( chunk_theme )
ui_theme( to_ui_layout_combo(chunk_layout), to_ui_style_combo(chunk_style) )
head := line.first
for ; head != nil;
{
ui_style_theme_set_layout( chunk_layout )
ui_layout( chunk_layout )
widget : UI_Widget
#partial switch head.type
@ -269,14 +253,14 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
head = head.next
}
line_hbox.style.size.min.x = chunk_layout.pos.x
line_hbox.layout.size.min.x = chunk_layout.pos.x
}
else
{
builder_backing : [16 * Kilobyte] byte
builder := str.builder_from_bytes( builder_backing[:] )
line_hbox.style.flags |= { .Size_To_Text }
line_hbox.layout.flags |= { .Size_To_Text }
head := line.first.next
for ; head != nil;
@ -293,11 +277,11 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
if len(line_hbox.text.str) > 0 {
array_append( widgets_ptr, line_hbox )
layout_text.pos.x = text_style.layout.pos.x
layout_text.pos.y += size_range2(line_hbox.computed.bounds).y
text_layout.pos.x = text_layout.pos.x
text_layout.pos.y += size_range2(line_hbox.computed.bounds).y
}
else {
layout_text.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
text_layout.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
}
line_id += 1

54
code/ui_theme.odin Normal file
View File

@ -0,0 +1,54 @@
package sectr
UI_ThemePtr :: struct {
layout : ^UI_LayoutCombo,
style : ^UI_StyleCombo,
}
UI_Theme :: struct {
layout : UI_LayoutCombo,
style : UI_StyleCombo,
}
ui_theme_pop :: #force_inline proc() {
ui_layout_pop()
ui_style_pop()
}
@(deferred_none = ui_theme_pop)
ui_theme_via_layout_style :: #force_inline proc( layout : UI_Layout, style : UI_Style ) {
using ui := get_state().ui_context
ui_layout_push( layout )
ui_style_push( style )
}
@(deferred_none = ui_theme_pop)
ui_theme_via_combos :: #force_inline proc( layout : UI_LayoutCombo, style : UI_StyleCombo ) {
using ui := get_state().ui_context
ui_layout_push( layout )
ui_style_push( style )
}
@(deferred_none = ui_theme_pop)
ui_theme_via_theme :: #force_inline proc( theme : UI_Theme ) {
using ui := get_state().ui_context
ui_layout_push( theme.layout )
ui_style_push( theme.style )
}
/*
UI Themes: Comprise of UI_Box's layout & style
Provides presets for themes and their interface for manipulating the combo stacks in UI_State in pairs
*/
UI_Theme_Btn_Default :: UI_Theme {
}
@(deferred_none = ui_layout_pop)
ui_theme_btn_default :: #force_inline proc() {
ui_layout_push(UI_Theme_Btn_Default.layout)
ui_style_push(UI_Theme_Btn_Default.style)
}

View File

@ -47,92 +47,101 @@ ui_hbox_begin :: proc( direction : UI_LayoutDirectionX, label : string, flags :
// profile(#procedure)
hbox.direction = direction
hbox.box = ui_box_make( flags, label )
hbox.signal = ui_signal_from_box( hbox.box )
hbox.signal = ui_signal_from_box(hbox.box)
return
}
// 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 )
{
// profile(#procedure)
hbox_width : f32
if width_ref != nil {
hbox_width = width_ref ^
}
else {
hbox_width = hbox.computed.content.max.x - hbox.computed.content.min.x
}
// do layout calculations for the children
total_stretch_ratio : f32 = 0.0
size_req_children : f32 = 0
for child := hbox.first; child != nil; child = child.next
// ui_box_compute_layout(hox.widget)
// ui_layout_children_horizontally( & hbox.box, hbox.direction, width_ref )
{
using child
using style.layout
scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in style.flags)
if .Fixed_Width in style.flags
{
if scaled_width_by_height {
height := size.max.y != 0 ? size.max.y : hbox_width
width := height * size.min.x
hbox_width : f32
if width_ref != nil {
hbox_width = width_ref ^
}
else {
hbox_width = hbox.computed.content.max.x - hbox.computed.content.min.x
}
size_req_children += width
// 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
}
size_req_children += size.min.x
continue
total_stretch_ratio += anchor.ratio.x
}
total_stretch_ratio += anchor.ratio.x
}
avail_flex_space := hbox_width - size_req_children
avail_flex_space := hbox_width - size_req_children
allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 )
{
using child.style
if ! (.Fixed_Width in child.style.flags) {
size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space
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
}
}
flags |= {.Fixed_Width}
alignment = {0, 1}
}
space_used : f32 = 0.0
switch hbox.direction{
case .Right_To_Left:
for child := hbox.last; child != nil; child = child.prev {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.style
anchor = range2({0, 0}, {0, 0})
pos.x = space_used
space_used += size.min.x
}
case .Left_To_Right:
for child := hbox.first; child != nil; child = child.next {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.style
anchor = range2({0, 0}, {0, 0})
pos.x = space_used
space_used += size.min.x
}
}
}
@(deferred_out = ui_hbox_end_auto)
ui_hbox :: #force_inline proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) {
hbox = ui_hbox_begin(direction, label, flags)
ui_parent_push(hbox.box)
return
}
// Auto-layout children and pop parent from parent stack
ui_hbox_end_pop_parent :: proc( hbox : UI_HBox ) {
// ui_box_compute_layout(hox.widget)
ui_hbox_end_auto :: proc( hbox : UI_HBox ) {
ui_parent_pop()
ui_hbox_end(hbox)
}
@(deferred_out = ui_hbox_end_pop_parent)
ui_hbox :: #force_inline proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) {
hbox = ui_hbox_begin(direction, label, flags)
ui_parent_push(hbox.widget)
return
}
//endregion Horizontal Box
// Adds resizable handles to a widget
@ -164,23 +173,26 @@ ui_resizable_handles :: proc( parent : ^UI_Widget,
ui_parent(parent)
flags := UI_BoxFlags { .Mouse_Clickable, .Focusable }
style_bar := UI_Style {
layout_bar_width := UI_Layout {
flags = { .Fixed_Width },
size = range2({handle_width, 0}, {}),
bg_color = Color_ResizeHandle,
alignment = {1, 1},
margins = { handle_width, handle_width, 0, 0 },
size = range2({handle_width, 0}, {}),
}
style_bar := UI_Style {
bg_color = Color_ResizeHandle,
corner_radii = { 5, 0, 0, 0 }
}
theme_bar := to_ui_styletheme(style_bar)
theme_bar := to_ui_style_combo(style_bar)
theme_bar.default.bg_color = handle_color_default
theme_bar.default.corner_radii[0] = 0
ui_style_theme(theme_bar)
style_resize_height := style_bar
style_resize_height.flags = {.Fixed_Height}
style_resize_height.size.min = {0, handle_width}
style_resize_height.margins = { 0, 0, handle_width, handle_width }
style_resize_height.alignment = {0, 0}
ui_layout(layout_bar_width)
ui_style(theme_bar)
layout_bar_height := layout_bar_width
layout_bar_height.flags = {.Fixed_Height}
layout_bar_height.size.min = {0, handle_width}
layout_bar_height.margins = { 0, 0, handle_width, handle_width }
layout_bar_height.alignment = {0, 0}
context.user_ptr = & parent.label
name :: proc( ) -> StrRunesPair {
@ -188,47 +200,50 @@ ui_resizable_handles :: proc( parent : ^UI_Widget,
return str_intern(str_fmt_tmp("%v: %v", ))
}
if left do handle_left = ui_widget("Settings Menu: Resize Left Border", flags )
if left do handle_left = ui_widget("Settings Menu: Resize Left Handle", flags )
if right {
handle_right = ui_widget("Settings Menu: Resize Right Border", flags)
handle_right.style.anchor.left = 1
handle_right.style.alignment = { 0, 1 }
handle_right = ui_widget("Settings Menu: Resize Right Handle", flags)
handle_right.layout.anchor.left = 1
handle_right.layout.alignment = { 0, 1 }
}
ui_theme_via_style(style_resize_height)
ui_style_theme_ref().default.bg_color = handle_color_default
ui_layout(layout_bar_height)
ui_style_ref().default.bg_color = handle_color_default
if top do handle_top = ui_widget("Settings Menu: Resize Top Border", flags )
if bottom {
handle_bottom = ui_widget("Settings Menu: Resize Bottom Border", flags)
handle_bottom.style.anchor.top = 1
handle_bottom.style.alignment = { 0, 1 }
using handle_bottom.layout
anchor.top = 1
alignment = { 0, 1 }
}
style_corner := UI_Style {
layout_corner := UI_Layout {
flags = { .Fixed_Width, .Fixed_Height },
size = range2({handle_width, handle_width}, {}),
bg_color = Color_ResizeHandle,
alignment = {1, 0},
size = range2({handle_width, handle_width}, {}),
}
style_corner := UI_Style {
bg_color = Color_ResizeHandle,
corner_radii = { 5, 0, 0, 0 },
}
ui_theme_via_style(style_corner)
ui_style_theme_ref().default.bg_color = handle_color_default
ui_theme(layout_corner, style_corner)
ui_style_ref().default.bg_color = handle_color_default
if corner_tl do handle_corner_tl = ui_widget("Settings Menu: Corner TL", flags)
if corner_tr {
handle_corner_tr = ui_widget("Settings Menu: Corner TR", flags)
handle_corner_tr.style.anchor = range2({1, 0}, {})
handle_corner_tr.style.alignment = {0, 0}
handle_corner_tr.layout.anchor = range2({1, 0}, {})
handle_corner_tr.layout.alignment = {0, 0}
}
if corner_bl {
handle_corner_bl = ui_widget("Settings Menu: Corner BL", flags)
handle_corner_bl.style.anchor = range2({}, {0, 1})
handle_corner_bl.style.alignment = { 1, 1 }
handle_corner_bl.layout.anchor = range2({}, {0, 1})
handle_corner_bl.layout.alignment = { 1, 1 }
}
if corner_br {
handle_corner_br = ui_widget("Settings Menu: Corner BR", flags)
handle_corner_br.style.anchor = range2({1, 0}, {0, 1})
handle_corner_br.style.alignment = {0, 1}
handle_corner_br.layout.anchor = range2({1, 0}, {0, 1})
handle_corner_br.layout.alignment = {0, 1}
}
process_handle_drag :: #force_inline proc ( handle : ^UI_Widget,
@ -268,7 +283,7 @@ ui_resizable_handles :: proc( parent : ^UI_Widget,
}
delta := get_state().input.mouse.delta
alignment := & parent.style.alignment
alignment := & parent.layout.alignment
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 )
@ -363,75 +378,78 @@ ui_vbox_begin :: proc( direction : UI_LayoutDirectionY, label : string, flags :
ui_vbox_end :: proc( vbox : UI_VBox, height_ref : ^f32 = nil ) {
// profile(#procedure)
vbox_height : f32
if height_ref != nil {
vbox_height = height_ref ^
}
else {
vbox_height = vbox.computed.bounds.max.y - vbox.computed.bounds.min.y
}
// ui_box_compute_layout(vbox)
// 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
// ui_layout_children_vertically( & hbox.box, hbox.direction, width_ref )
{
using child
using style.layout
scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in style.flags)
if .Fixed_Height in style.flags
{
if scaled_width_by_height {
width := size.max.x != 0 ? size.max.x : vbox_height
height := width * size.min.y
vbox_height : f32
if height_ref != nil {
vbox_height = height_ref ^
}
else {
vbox_height = vbox.computed.bounds.max.y - vbox.computed.bounds.min.y
}
size_req_children += height
// 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
}
size_req_children += size.min.y
continue
total_stretch_ratio += anchor.ratio.y
}
total_stretch_ratio += anchor.ratio.y
}
avail_flex_space := vbox_height - size_req_children
avail_flex_space := vbox_height - size_req_children
allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 )
{
using child.style
if ! (.Fixed_Height in child.style.flags) {
size.min.y = anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space
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}
}
flags |= {.Fixed_Height}
alignment = {0, 0}
}
space_used : f32 = 0.0
switch vbox.direction {
case .Top_To_Bottom:
for child := vbox.last; child != nil; child = child.prev {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.style
anchor = range2({0, 0}, {0, 1})
pos.y = space_used
space_used += size.min.y
}
case .Bottom_To_Top:
for child := vbox.first; child != nil; child = child.next {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.style
anchor = range2({0, 0}, {0, 1})
pos.y = space_used
space_used += size.min.y
}
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
}
}
}
}
// Auto-layout children and pop parent from parent stack
ui_vbox_end_pop_parent :: proc( vbox : UI_VBox ) {
// ui_box_compute_layout(vbox)
ui_parent_pop()
ui_vbox_end(vbox)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

39
docs/ui_state.md Normal file
View File

@ -0,0 +1,39 @@
# UI
## Ideal UI_Box processing per-frame
Would be done in this order:
Build Graph Start
0. Parent constructed
1. `ui_box_make()`
2. Prepare layout & style
3. Construct & process children
4. Post-children populated processing
5. Auto-layout box
6. Process signal from children & depdendent events
7. `ui_signal_from_box(box)`
8. Process state dependent on signal
9. ... Eventual Render Pass
Issues:
You want to batch auto-layout to be deferred to the end of the construction for the state graph of the frame.
Rendering should be handled outside of the update tick asynchronously (at worst case).
StyleCombo and LayoutCombos are not stored in UI_Box (it would b N * (Style + Layout) per box of memory where N is the number of entries in a combo (right now there is 4) )
A layout must be choosen before auto-layout occurs and rn the convention is that layout & style are choosen at the end of a signal since it depends on the box's state from the signal.
Adjusted order:
0. Parent constructed
1. Prepare layout & style beforehand
2. `ui_box_make()`
3. `ui_signal_from_box(box)`
4. Construct & process children
5. Post-children populated processing
6. ... Build Graph End
7. Auto-Layout Pass
8. Eventual Render Pass