Progress on setting up app's UI and horizontal/vertical box widgets

This commit is contained in:
Edward R. Gonzalez 2024-05-08 02:26:39 -04:00
parent e282397bf0
commit b8e8e7c88a
17 changed files with 577 additions and 246 deletions

View File

@ -190,6 +190,9 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
log( "Default font loaded" )
}
// Setup the app ui state
ui_startup( & app_ui, cache_allocator = persistent_slab_allocator() )
// Demo project setup
{
using project
@ -274,6 +277,7 @@ reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem,
context.allocator = persistent_allocator()
context.temp_allocator = transient_allocator()
Memory_App.state = get_state()
using state
// Procedure Addresses are not preserved on hot-reload. They must be restored for persistent data.

View File

@ -10,6 +10,7 @@ import rl "vendor:raylib"
Str_App_State := "App State"
Memory_App : Memory
Memory_Base_Address_Persistent :: Terabyte * 1
@ -177,6 +178,7 @@ State :: struct {
config : AppConfig,
app_window : AppWindow,
app_ui : UI_State,
monitor_id : i32,
monitor_refresh_hz : i32,
@ -203,8 +205,7 @@ State :: struct {
}
get_state :: proc "contextless" () -> ^ State {
// return cast( ^ State ) Memory_App.persistent.reserve_start
return Memory_App.state
return cast( ^ State ) Memory_App.persistent.reserve_start
}
AppWindow :: struct {
@ -223,8 +224,8 @@ ProjectConfig :: struct {
}
Project :: struct {
path : StringCached,
name : StringCached,
path : StrRunesPair,
name : StrRunesPair,
config : ProjectConfig,
codebase : CodeBase,
@ -242,7 +243,7 @@ Frame :: struct
}
Workspace :: struct {
name : StringCached,
name : StrRunesPair,
cam : Camera,
zoom_target : f32,

View File

@ -20,14 +20,14 @@ StringKey :: distinct u64
RunesCached :: []rune
// TODO(Ed): Should this just track the key instead? (by default)
StringCached :: struct {
StrRunesPair :: struct {
str : string,
runes : []rune,
}
StringCache :: struct {
slab : Slab,
table : HMapZPL(StringCached),
table : HMapZPL(StrRunesPair),
}
str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
@ -61,7 +61,7 @@ str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
cache.slab, alloc_error = slab_init( & policy, allocator = persistent_allocator(), dbg_name = dbg_name )
verify(alloc_error == .None, "Failed to initialize the string cache" )
cache.table, alloc_error = zpl_hmap_init_reserve( StringCached, persistent_allocator(), 1 * Megabyte, dbg_name )
cache.table, alloc_error = zpl_hmap_init_reserve( StrRunesPair, persistent_allocator(), 1 * Megabyte, dbg_name )
return
}
@ -69,7 +69,7 @@ str_cache_init :: proc( /*allocator : Allocator*/ ) -> ( cache : StringCache ) {
// cache : ^StringCache,
str_intern :: proc(
content : string
) -> StringCached
) -> StrRunesPair
{
// profile(#procedure)
cache := & get_state().string_cache
@ -97,8 +97,8 @@ str_intern :: proc(
slab_validate_pools( get_state().persistent_slab )
// result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } )
result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } )
// result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) byte_slice(str_mem, length), runes } )
result, alloc_error = zpl_hmap_set( & cache.table, key, StrRunesPair { transmute(string) str_mem, runes } )
verify( alloc_error == .None, "String cache had a backing allocator error" )
slab_validate_pools( get_state().persistent_slab )
@ -108,7 +108,7 @@ str_intern :: proc(
return (result ^)
}
// runes_intern :: proc( content : []rune ) -> StringCached
// runes_intern :: proc( content : []rune ) -> StrRunesPair
// {
// cache := get_state().string_cache
// }

View File

@ -7,7 +7,7 @@ rune16 :: distinct u16
// Exposing the alloc_error
@(require_results)
string_to_runes :: proc ( content : string, allocator := context.allocator) -> (runes : []rune, alloc_error : AllocatorError) {
string_to_runes :: proc ( content : string, allocator := context.allocator) -> (runes : []rune, alloc_error : AllocatorError) #optional_allocator_error {
num := str_rune_count(content)
runes, alloc_error = make([]rune, num, allocator)

View File

@ -77,6 +77,13 @@ Range2 :: struct #raw_union {
x0, y0 : f32,
x1, y1 : f32,
},
using side : struct {
left, bottom : f32,
right, top : f32,
},
ratio : struct {
x, y : f32,
},
// TODO(Ed) : Test these
array : [4]f32,

View File

@ -17,6 +17,8 @@ Rotor2 :: struct {
rotor2_to_complex64 :: #force_inline proc( rotor : Rotor2 ) -> complex64 { return transmute(complex64) rotor; }
vec2 :: #force_inline proc "contextless" ( x, y : f32 ) -> Vec2 { return {x, y} }
dot_vec2 :: proc "contextless" ( a, b : Vec2 ) -> (s : f32) {
x := a.x * b.x
y := a.y + b.y

View File

@ -63,7 +63,7 @@ PWS_LexResult :: struct {
PWS_Token :: struct {
type : PWS_TokenType,
line, column : u32,
content : StringCached,
content : StrRunesPair,
}
PWS_AST_Type :: enum u32 {
@ -80,7 +80,7 @@ PWS_AST :: struct {
type : PWS_AST_Type,
line, column : u32,
content : StringCached,
content : StrRunesPair,
}
PWS_ParseError :: struct {

View File

@ -138,6 +138,14 @@ screen_size :: proc "contextless" () -> AreaSize {
return transmute(AreaSize) ( extent * 2.0 )
}
screen_get_bounds :: #force_inline proc "contextless" () -> Range2 {
state := get_state(); using state
screen_extent := state.app_window.extent
bottom_left := Vec2 { -screen_extent.x, -screen_extent.y}
top_right := Vec2 { screen_extent.x, screen_extent.y}
return range2( bottom_left, top_right )
}
screen_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
state := get_state(); using state
screen_extent := state.app_window.extent

View File

@ -66,7 +66,7 @@ draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.C
rl.SetTextureFilter(rl_font.texture, rl.TextureFilter.POINT)
}
draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
draw_text_string_cached :: proc( content : StrRunesPair, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state

View File

@ -23,17 +23,42 @@ render :: proc()
render_mode_screenspace :: proc ()
{
profile("Render Screenspace")
state := get_state(); using state
replay := & Memory_App.replay
cam := & project.workspace.cam
win_extent := state.app_window.extent
//region App UI
Render_App_UI:
{
profile("App UI")
ui := & state.app_ui
root := ui.root
if root.num_children == 0 {
break Render_App_UI
}
current := root.first
for ; current != nil; current = ui_box_tranverse_next( current )
{
// profile("Box")
parent := current.parent
style := current.style
computed := & current.computed
computed_size := computed.bounds.p1 - computed.bounds.p0
}
}
//endregion App UI
screen_top_left : Vec2 = {
-win_extent.x + cam.target.x,
-win_extent.y + cam.target.y,
}
profile("Render Screenspace")
fps_msg := str_fmt_tmp( "FPS: %f", fps_avg)
fps_msg_width := measure_text_size( fps_msg, default_font, 16.0, 0.0 ).x
fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 }
@ -230,16 +255,19 @@ render_mode_2d :: proc()
{
draw_rectangle( rect_bounds, style )
}
if style.border_width > 0 {
draw_rectangle_lines( rect_bounds, style, style.border_color, style.border_width )
}
// profile_end()
line_thickness := 1 * cam_zoom_ratio
// 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, style, 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, style, Color_Debug_UI_Content_Bounds, line_thickness )
}
// profile_end()
@ -291,7 +319,7 @@ render_mode_2d :: proc()
rl.DrawCircleV( world_to_screen_pos(cursor_world_pos), 5, Color_GreyRed )
}
// rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White )
rl.DrawCircleV( { 0, 0 }, 1 * cam_zoom_ratio, Color_White )
rl.EndMode2D()
}

View File

@ -191,9 +191,9 @@ update :: proc( delta_time : f64 ) -> b32
}
//endregion 2D Camera Manual Nav
//region Imgui Tick
//region WorkspaceImgui Tick
{
profile("Imgui Tick")
profile("Workspace Imgui")
// Creates the root box node, set its as the first parent.
ui_graph_build( & state.project.workspace.ui )
@ -238,235 +238,181 @@ update :: proc( delta_time : f64 ) -> b32
config.ui_resize_border_width = 2.5
// test_draggable()
// test_text_box()
// test_parenting( & default_layout, & frame_style_default )
// test_whitespace_ast( & default_layout, & frame_style_default )
// test_parenting()
if false
{
// frame := ui_widget( "Frame", {} )
// ui_parent(frame)
parent_layout := default_layout
parent_layout.size = range2( { 300, 300 }, {} )
parent_layout.alignment = { 0.5, 0.5 }
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 = {
// .Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,
}
ui_theme_via_style(parent_theme)
parent := ui_widget( "Parent", { .Mouse_Clickable, .Mouse_Resizable })
ui_parent(parent)
{
if parent.first_frame {
debug.draggable_box_pos = parent.style.layout.pos
debug.draggable_box_size = parent.style.layout.size.min
}
if parent.dragging {
debug.draggable_box_pos += mouse_world_delta()
}
if parent.resizing
{
og_layout := ui_context.active_start_style.layout
center := debug.draggable_box_pos
original_distance := linalg.distance(ui.active_start_signal.cursor_pos, center)
cursor_distance := linalg.distance(parent.cursor_pos, center)
scale_factor := cursor_distance * (1 / original_distance)
debug.draggable_box_size = og_layout.size.min * scale_factor
}
if (ui.hot == parent.key) && (ui.hot_resizable || ui.active_start_signal.resizing) {
parent.style.bg_color = Color_Blue
}
parent.style.layout.pos = debug.draggable_box_pos
parent.style.layout.size.min = debug.draggable_box_size
}
child_layout := default_layout
child_layout.size = range2({ 0, 0 }, { 0, 0 })
child_layout.alignment = { 0.5, 0.5 }
child_layout.margins = { 20, 20, 20, 20 }
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 = {
// .Fixed_Width, .Fixed_Height,
.Origin_At_Anchor_Center
}
child_theme.layout = child_layout
ui_theme_via_style(child_theme)
child := ui_widget( "Child", { .Mouse_Clickable })
}
// Whitespace AST test
/*
Prototype app menu
This is a menu bar for the app for now inside the same ui as the workspace's UI state
Eventually this will get moved out to its own UI state for the app itself.
*/
if true
{
profile("Whitespace AST test")
fmt :: str_fmt_alloc
text_style := frame_style_default
text_style.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 }
@static bar_pos := Vec2 {}
bar_size := vec2( 400, 40 )
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
alloc_error : AllocatorError; success : bool
// debug.lorem_content, success = os.read_entire_file( debug.path_lorem, frame_allocator() )
// debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, frame_slab_allocator() )
// verify( alloc_error == .None, "Faield to parse due to allocation failure" )
text_space := str_intern( " " )
text_tab := str_intern( "\t")
// index := 0
widgets : Array(UI_Widget)
// widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 8 )
widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 4 * Kilobyte )
widgets_ptr := & widgets
label_id := 0
line_id := 0
for line in array_to_slice_num( debug.lorem_parse.lines )
menu_bar : UI_Widget
{
if line_id == 0 {
line_id += 1
continue
theme := UI_Style {
flags = {
},
bg_color = { 0, 0, 0, 30 },
border_color = { 0, 0, 0, 200 },
font = default_font,
font_size = 12,
text_color = Color_White,
layout = UI_Layout {
anchor = {},
border_width = 1.0 * (1.0/cam.zoom),
pos = screen_to_world(bar_pos),
size = range2( bar_size * (1.0/cam.zoom), {}),
// padding = { 10, 10, 10, 10 },
},
}
ui_theme_via_style(theme)
menu_bar = ui_widget("App Menu Bar", UI_BoxFlags {} )
}
// Setup Children
settings_btn : UI_Widget
{
ui_parent(menu_bar)
ui_style_theme_set_layout( layout_text )
line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {})
style := UI_Style {
flags = {
// .Origin_At_Anchor_Center
.Fixed_Height
},
bg_color = Color_Frame_Disabled,
if line_hbox.key == ui.hot
{
line_hbox.text = StringCached {}
ui_parent(line_hbox)
font = default_font,
font_size = 18 * (1.0/cam.zoom),
text_color = Color_White,
chunk_layout := layout_text
chunk_layout.alignment = { 0.0, 1.0 }
chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 })
chunk_layout.pos = {}
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 )
head := line.first
for ; head != nil;
{
ui_style_theme_set_layout( chunk_layout )
widget : UI_Widget
#partial switch head.type
{
case .Visible:
label := str_intern( str_fmt_alloc( "%v %v", head.content.str, label_id ))
widget = ui_text( label.str, head.content )
label_id += 1
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Spaces:
label := str_intern( str_fmt_alloc( "%v %v", "space", label_id ))
widget = ui_text_spaces( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// TODO(Ed): VIRTUAL WHITESPACE
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Tabs:
label := str_intern( str_fmt_alloc( "%v %v", "tab", label_id ))
widget = ui_text_tabs( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
}
array_append( widgets_ptr, widget )
head = head.next
layout = UI_Layout {
anchor = range2( {0, 0}, {0.0, 0} ),
alignment = { 0.0, 1.0 },
text_alignment = { 0.5, 0.5 },
pos = { 0, 0 },
size = range2( {25, bar_size.y} * (1.0/cam.zoom), {0, 0})
}
line_hbox.style.size.min.x = chunk_layout.pos.x
}
else
theme := UI_StyleTheme { styles = {
style,
style,
style,
style,
}}
theme.disabled.bg_color = Color_Frame_Disabled
theme.hot.bg_color = Color_White
theme.active.bg_color = Color_Frame_Select
ui_style_theme(theme)
move_box : UI_Widget
{
builder_backing : [16 * Kilobyte] byte
builder := str.builder_from_bytes( builder_backing[:] )
line_hbox.style.flags |= { .Size_To_Text }
head := line.first.next
for ; head != nil;
{
str.write_string( & builder, head.content.str )
head = head.next
move_box = ui_button("Move Box")
if move_box.dragging {
// bar_pos += mouse_world_delta()
bar_pos += state.input.mouse.delta
}
line_hbox.text = str_intern( to_string( builder ) )
// if len(line_hbox.text.str) == 0 {
// line_hbox.text = str_intern( " " )
// }
}
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
}
else {
layout_text.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
move_settings_spacer := ui_widget("Move-Settings Spacer", {})
move_settings_spacer.text = str_intern("")
move_settings_spacer.style.font_size = 10 * (1.0/cam.zoom)
move_settings_spacer.style.bg_color = Color_Transparent
// settings_btn : UI_Widget
{
settings_btn = ui_button("Settings Btn")
settings_btn.text = str_intern("Settings")
settings_btn.style.flags = {
.Scale_Width_By_Height_Ratio,
}
}
line_id += 1
// HBox layout calculation?
{
hb_space_ratio_move_box := 0.1
hb_space_ratio_move_settings_spacer := 0.05
hb_space_ratio_settings_btn := 1.0
style := & move_box.box.style
style.anchor.max.x = 0.9
style = & move_settings_spacer.box.style
style.anchor.min.x = 0.1
style.anchor.max.x = 0.8
style = & settings_btn.box.style
style.anchor.min.x = 0.2
style.anchor.max.x = 0.55
}
}
label_id += 1 // Dummy action
@static settings_open := false
if settings_btn.left_clicked || settings_open
{
settings_open = true
@static pos := Vec2 {0, 0}
settings_menu := ui_widget("Settings Menu", { .Mouse_Clickable, .Focusable, .Click_To_Focus })
settings_menu.style.pos = screen_to_world(pos)
settings_menu.style.size = range2( {600, 800} * (1/cam.zoom), {})
settings_menu.style.text_alignment = {0, 0.0}
settings_menu.style.alignment = { 0.5, 0.5 }
settings_menu.style.bg_color = Color_Transparent
settings_menu.style.border_width = 1.0 * (1/cam.zoom)
settings_menu.style.border_color = Color_Blue
// settings_menu.style.padding = { 10, 10, 10, 10 }
settings_menu.text = { fmt("%v", pos), {} }
settings_menu.text.runes = to_runes(settings_menu.text.str)
settings_menu.style.font_size = 16 * (1/cam.zoom)
// pos.x += frametime_delta32() * 100
if settings_menu.dragging {
pos += state.input.mouse.delta
// pos.x += frametime_delta32() * 1
}
ui_parent(settings_menu)
frame_bar := ui_widget("Settings Menu: Frame Bar", {})
{
using frame_bar
// style.bg_color = Color_Red
style.flags = {}
style.alignment = { 0, 1 }
style.size = {}
style.anchor = range2( {0, 0.95}, {0, 0} )
// Close button
{
}
}
}
}
}
//endregion Imgui Tick
//endregion Workspace Imgui Tick
//region App Screenspace Imgui Tick
{
profile("App Screenspace Imgui")
ui_graph_build( & state.app_ui )
ui := ui_context
/*
Prototype app menu
TODO(Ed): Move it to here
*/
}
//endregion App Screenspace Imgui Tick
debug.last_mouse_pos = input.mouse.pos

View File

@ -179,10 +179,18 @@ UI_StyleFlag :: enum u32 {
Clamp_Position_Y,
// Enroces the widget will maintain its size reguardless of any constraints
// Will override parent 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,
@ -244,8 +252,8 @@ UI_Box :: struct {
// Cache ID
key : UI_Key,
// label : string,
label : StringCached,
text : StringCached,
label : StrRunesPair,
text : StrRunesPair,
// Regenerated per frame.
using links : DLL_NodeFull( UI_Box ), // first, last, prev, next
@ -574,3 +582,14 @@ ui_style_theme_set_layout :: proc ( layout : UI_Layout ) {
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 :: proc( layout : UI_Layout ) {
ui_style_theme_layout_push(layout)
}

View File

@ -153,8 +153,8 @@ ui_compute_layout :: proc()
// Determine Content Bounds
content_bounds := range2(
bounds.min + { layout.padding.left, layout.padding.bottom },
bounds.max - { layout.padding.right, layout.padding.top },
bounds.min + { layout.padding.left, layout.padding.bottom } + border_offset,
bounds.max - { layout.padding.right, layout.padding.top } - border_offset,
)
computed.anchors = anchored_bounds

View File

@ -1,6 +1,7 @@
package sectr
import "core:math/linalg"
import str "core:strings"
test_hover_n_click :: proc()
{
@ -67,6 +68,75 @@ test_draggable :: proc()
draggable.style.layout.size.min = debug.draggable_box_size
}
test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_Style )
{
state := get_state(); using state
ui := ui_context
// frame := ui_widget( "Frame", {} )
// ui_parent(frame)
parent_layout := default_layout ^
parent_layout.size = range2( { 300, 300 }, {} )
parent_layout.alignment = { 0.5, 0.5 }
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 = {
// .Fixed_Position_X, .Fixed_Position_Y,
.Fixed_Width, .Fixed_Height,
}
ui_theme_via_style(parent_theme)
parent := ui_widget( "Parent", { .Mouse_Clickable, .Mouse_Resizable })
ui_parent(parent)
{
if parent.first_frame {
debug.draggable_box_pos = parent.style.layout.pos
debug.draggable_box_size = parent.style.layout.size.min
}
if parent.dragging {
debug.draggable_box_pos += mouse_world_delta()
}
if parent.resizing
{
og_layout := ui_context.active_start_style.layout
center := debug.draggable_box_pos
original_distance := linalg.distance(ui.active_start_signal.cursor_pos, center)
cursor_distance := linalg.distance(parent.cursor_pos, center)
scale_factor := cursor_distance * (1 / original_distance)
debug.draggable_box_size = og_layout.size.min * scale_factor
}
if (ui.hot == parent.key) && (ui.hot_resizable || ui.active_start_signal.resizing) {
parent.style.bg_color = Color_Blue
}
parent.style.layout.pos = debug.draggable_box_pos
parent.style.layout.size.min = debug.draggable_box_size
}
child_layout := default_layout ^
child_layout.size = range2({ 0, 0 }, { 0, 0 })
child_layout.alignment = { 0.5, 0.5 }
child_layout.margins = { 20, 20, 20, 20 }
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 = {
// .Fixed_Width, .Fixed_Height,
.Origin_At_Anchor_Center
}
child_theme.layout = child_layout
ui_theme_via_style(child_theme)
child := ui_widget( "Child", { .Mouse_Clickable })
}
test_text_box :: proc()
{
state := get_state(); using state
@ -95,3 +165,164 @@ test_text_box :: proc()
text_box.style.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 )
{
profile("Whitespace AST test")
state := get_state(); using state
ui := ui_context
text_style := frame_style_default ^
text_style.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
alloc_error : AllocatorError; success : bool
// debug.lorem_content, success = os.read_entire_file( debug.path_lorem, frame_allocator() )
// debug.lorem_parse, alloc_error = pws_parser_parse( transmute(string) debug.lorem_content, frame_slab_allocator() )
// verify( alloc_error == .None, "Faield to parse due to allocation failure" )
text_space := str_intern( " " )
text_tab := str_intern( "\t")
// index := 0
widgets : Array(UI_Widget)
// widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 8 )
widgets, alloc_error = array_init_reserve( UI_Widget, frame_slab_allocator(), 4 * Kilobyte )
widgets_ptr := & widgets
label_id := 0
line_id := 0
for line in array_to_slice_num( debug.lorem_parse.lines )
{
if line_id == 0 {
line_id += 1
continue
}
ui_style_theme_set_layout( layout_text )
line_hbox := ui_widget(str_fmt_alloc( "line %v", line_id ), {})
if line_hbox.key == ui.hot
{
line_hbox.text = StrRunesPair {}
ui_parent(line_hbox)
chunk_layout := layout_text
chunk_layout.alignment = { 0.0, 1.0 }
chunk_layout.anchor = range2({ 0.0, 0 }, { 0.0, 0 })
chunk_layout.pos = {}
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 )
head := line.first
for ; head != nil;
{
ui_style_theme_set_layout( chunk_layout )
widget : UI_Widget
#partial switch head.type
{
case .Visible:
label := str_intern( str_fmt_alloc( "%v %v", head.content.str, label_id ))
widget = ui_text( label.str, head.content )
label_id += 1
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Spaces:
label := str_intern( str_fmt_alloc( "%v %v", "space", label_id ))
widget = ui_text_spaces( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// TODO(Ed): VIRTUAL WHITESPACE
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
case .Tabs:
label := str_intern( str_fmt_alloc( "%v %v", "tab", label_id ))
widget = ui_text_tabs( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
chunk_layout.pos.x += size_range2( widget.computed.bounds ).x
}
array_append( widgets_ptr, widget )
head = head.next
}
line_hbox.style.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 }
head := line.first.next
for ; head != nil;
{
str.write_string( & builder, head.content.str )
head = head.next
}
line_hbox.text = str_intern( to_string( builder ) )
// if len(line_hbox.text.str) == 0 {
// line_hbox.text = str_intern( " " )
// }
}
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
}
else {
layout_text.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
}
line_id += 1
}
label_id += 1 // Dummy action
}

View File

@ -5,7 +5,6 @@ UI_Widget :: struct {
using signal : UI_Signal,
}
ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
{
// profile(#procedure)
@ -15,7 +14,6 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
return
}
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
{
// profile(#procedure)
@ -26,8 +24,76 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge
return
}
//region Horizontal Box
/*
Horizontal Boxes automatically manage a collection of widgets and
attempt to slot them adjacent to each other along the x-axis.
ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {} ) -> UI_Widget
The user must provide the direction that the hbox will append entries.
How the widgets will be scaled will be based on the individual entires style flags.
All the usual behaviors that the style and box flags do apply when manage by the box widget.
Whether or not the horizontal box will scale the widget's width is if:
fixed size or "scale by ratio" flags are not used for the width.
The hbox will use the anchor's (range2) ratio.x value to determine the "stretch ratio".
Keep in mind the stretch ratio is only respected if no size.min.x value is violated for each of the widgets.
*/
ui_hbox_begin :: proc( label : string, flags : UI_BoxFlags = {}
//, direction
) -> (widget : UI_Widget) {
// profile(#procedure)
widget.box = ui_box_make( flags, label )
widget.signal = ui_signal_from_box( widget.box )
return
}
ui_hbox_end :: proc( hbox : UI_Widget ) -> UI_Widget {
hbox_width := hbox.computed.content.max.y - hbox.computed.content.min.y
// 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
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
size_req_children += width
continue
}
size_req_children += size.min.x
continue
}
}
availble_flexible_space := hbox_width - size_req_children
return hbox
}
ui_hbox_auto_end :: proc( vbox : UI_Widget ) {
ui_hbox_end(vbox)
ui_parent_pop()
}
@(deferred_out = ui_hbox_end)
ui_hbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
widget = ui_hbox_begin(label, flags)
ui_parent(widget)
return
}
//endregion Horizontal Box
ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
state := get_state(); using state
@ -39,7 +105,6 @@ ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {
return { box, signal }
}
ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
@ -55,7 +120,6 @@ ui_text_spaces :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
return { box, signal }
}
ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
@ -70,3 +134,28 @@ ui_text_tabs :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
box.text = tab_str
return { box, signal }
}
ui_vbox_begin :: proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
// profile(#procedure)
widget.box = ui_box_make( flags, label )
// widget.signal = ui_signal_from_box( widget.box )
return
}
ui_vbox_end :: proc( hbox : UI_Widget ) -> UI_Widget {
// do layout calculations for the children
return hbox
}
ui_vbox_auto_end :: proc( hbox : UI_Widget ) {
ui_vbox_end(hbox)
ui_parent_pop()
}
// ui_vbox_append( widget : UI_Widget )
@(deferred_out = ui_vbox_auto_end)
ui_vbox :: #force_inline proc( label : string, flags : UI_BoxFlags = {} ) -> (widget : UI_Widget) {
widget = ui_vbox_begin(label, flags)
ui_parent_push(widget)
return
}

View File

@ -14,12 +14,8 @@
"path": "C:/projects/SectrPrototype/code"
},
{
"name": "ini",
"path": "C:/projects/SectrPrototype/thirdparty/ini"
},
{
"name": "backtrace",
"path": "C:/projects/SectrPrototype/thirdparty/backtrace"
"name": "thirdparty",
"path": "C:/projects/SectrPrototype/thirdparty"
}
],
"odin_command": "C:/projects/SectrPrototype/toolchain/Odin/odin.exe",

@ -1 +1 @@
Subproject commit 647d7ed9e3e07b8b248d3b56eaa8fa60b451e1c9
Subproject commit 373733fb2b410cd51b4d674b09f5ed9e38677c99