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:
parent
6a4f7ac6de
commit
1b32fe916e
21
Readme.md
21
Readme.md
@ -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)
|
||||
|
128
code/env.odin
128
code/env.odin
@ -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
41
code/env_scratch.odin
Normal 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,
|
||||
}
|
@ -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
30
code/grime_ptr.odin
Normal 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
26
code/project.odin
Normal 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,
|
||||
}
|
38
code/project_workspace.odin
Normal file
38
code/project_workspace.odin
Normal 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 {
|
||||
|
||||
}
|
@ -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 )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
37
code/ui.odin
37
code/ui.odin
@ -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 }
|
||||
|
@ -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
180
code/ui_layout_compute.odin
Normal 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 )
|
||||
}
|
||||
}
|
3
code/ui_layout_widget.odin
Normal file
3
code/ui_layout_widget.odin
Normal file
@ -0,0 +1,3 @@
|
||||
package sectr
|
||||
|
||||
|
@ -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 )
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -1,3 +0,0 @@
|
||||
package sectr
|
||||
|
||||
ui_theme_btn_default : proc( ) -> UI_StyleTheme
|
@ -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
54
code/ui_theme.odin
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
BIN
docs/assets/sectr_host_2024-05-11_22-34-15.png
Normal file
BIN
docs/assets/sectr_host_2024-05-11_22-34-15.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 527 KiB |
39
docs/ui_state.md
Normal file
39
docs/ui_state.md
Normal 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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user