Cleanup and setup of drop down widget

Got an initial variant of the drop down widget, not completely set on it..
I put some more time to figuring out how I'm going to be ideomatically constructing the widgets. screen.odin I think its getting pretty close to what it will be like.
I'm ready to start lifting the input box. I'll be adding the constraints when I lift it.

Added the option to toggle the debug text in screenspace
Added the fixes from the ui_layout_children_horizontally for margins to ui_layout_children_vertically

Known issue:
There is a bug with test_whitespace that forced me todo a null check on a box. Not sure why.
It needs to be redone anyway.. (compose it with the h/vboxes instead)

There is some sublime files added in, started to use it.
This commit is contained in:
2024-05-20 22:05:52 -04:00
parent cdfc3d65bb
commit e5be246d30
23 changed files with 23871 additions and 411 deletions

View File

@ -113,8 +113,7 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
curr_box.links = {}
curr_box.num_children = 0
// If there is a parent, setup the relevant references
parent := stack_peek( & parent_stack )
parent := ui_parent_peek()
if parent != nil
{
dll_full_push_back( parent, curr_box, nil )

View File

@ -160,10 +160,10 @@ ui_layout_peek :: #force_inline proc() -> UI_LayoutCombo { return stack_peek( &
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_push_combo :: #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) }
@(deferred_none = ui_layout_pop) ui_layout_scope_via_layout :: #force_inline proc( layout : UI_Layout ) { ui_layout_push( layout) }
@(deferred_none = ui_layout_pop) ui_layout_scope_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 }

View File

@ -274,6 +274,8 @@ ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_
ui_parent_push :: #force_inline proc( ui : ^ UI_Box ) { stack_push( & ui_context().parent_stack, ui ) }
ui_parent_pop :: #force_inline proc() { stack_pop( & get_state().ui_context.parent_stack ) }
ui_parent_peek :: #force_inline proc() -> ^UI_Box { return stack_peek( & ui_context().parent_stack )}
@(deferred_none = ui_parent_pop)
ui_parent :: #force_inline proc( ui : ^UI_Box) { ui_parent_push( ui ) }

View File

@ -66,7 +66,7 @@ ui_style_push_style :: #force_inline proc( style : UI_Style ) { push( & get
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 ) }
@(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_pop) ui_style_scope_via_style :: #force_inline proc( style : UI_Style ) { ui_style_push( style) }
@(deferred_none = ui_style_pop) ui_style_scope_via_combo :: #force_inline proc( combo : UI_StyleCombo ) { ui_style_push( combo) }
ui_style_set :: #force_inline proc ( style : UI_Style, preset : UI_StylePreset ) { stack_peek_ref( & get_state().ui_context.style_combo_stack ).array[preset] = style }

View File

@ -10,28 +10,36 @@ UI_Theme :: struct {
style : UI_StyleCombo,
}
ui_theme_push_via_proc :: #force_inline proc( procedure : #type proc() -> UI_Theme ) { ui_theme_push(procedure()) }
ui_theme_push_via_theme ::#force_inline proc( theme : UI_Theme ) {
ui_layout_push( theme.layout )
ui_style_push( theme.style )
}
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 ) {
ui_theme_scope_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 ) {
ui_theme_scope_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 ) {
ui_theme_scope_via_proc :: #force_inline proc( procedure : #type proc() -> UI_Theme ) { ui_theme_push(procedure()) }
@(deferred_none = ui_theme_pop)
ui_theme_scope_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_theme_push(theme)
}

View File

@ -1,6 +1,7 @@
package sectr
import "base:runtime"
import lalg "core:math/linalg"
/*
Widget Layout Ops
@ -35,6 +36,7 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
continue
}
size_req_children += size.min.x
total_stretch_ratio += anchor.ratio.x
}
@ -48,14 +50,15 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
space_allocated = size.min.x * container_height
}
else if ! (.Fixed_Width in flags) {
size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space
space_allocated = size.min.x
potential_size := anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space
space_allocated = lalg.max(potential_size, size.min.x)
size.min.x = space_allocated
}
else {
space_allocated = size.min.x
}
space_allocated -= child.layout.margins.left - child.layout.margins.right
size.min.x -= child.layout.margins.left - child.layout.margins.right
space_allocated -= margins.left - margins.right
size.min.x -= margins.left - margins.right
flags |= {.Fixed_Width}
return
}
@ -67,7 +70,6 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
using child.layout
child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height)
anchor = range2({0, 0}, {0, 0})
width : f32
pos.x = space_used
space_used += child_width + child.layout.margins.left + child.layout.margins.right
}
@ -76,7 +78,6 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
using child.layout
child_width := allocate_space(child, total_stretch_ratio, avail_flex_space, container_height)
anchor = range2({0, 0}, {0, 0})
width : f32
pos.x = space_used
space_used += child_width + child.layout.margins.left + child.layout.margins.right
}
@ -92,6 +93,7 @@ ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_Layou
else {
container_height = container.computed.content.max.y - container.computed.content.min.y
}
container_width := container.computed.content.max.x - container.computed.content.min.x
// do layout calculations for the children
total_stretch_ratio : f32 = 0.0
@ -99,33 +101,42 @@ ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_Layou
for child := container.first; child != nil; child = child.next
{
using child.layout
scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags)
scaled_height_by_width : b32 = b32(.Scale_Height_By_Width_Ratio in flags)
if scaled_height_by_width {
size_req_children += size.min.y * container_width
continue
}
if .Fixed_Height in flags
{
if scaled_width_by_height {
width := size.max.x != 0 ? size.max.x : container_height
height := width * size.min.y
size_req_children += height
continue
}
size_req_children += size.min.y
continue
}
size_req_children += size.min.y
total_stretch_ratio += anchor.ratio.y
}
avail_flex_space := container_height - size_req_children
allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space : f32 )
allocate_space :: proc( child : ^UI_Box, total_stretch_ratio, avail_flex_space, container_width : f32 ) -> (space_allocated : f32)
{
using child.layout
if ! (.Fixed_Height in flags) {
size.min.y = (anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space)
if .Scale_Height_By_Width_Ratio in flags {
size.min.x = container_width
space_allocated = size.min.y * container_width
}
if ! (.Fixed_Height in flags) {
potential_size := (anchor.ratio.y * (1 / total_stretch_ratio) * avail_flex_space)
space_allocated = lalg.max(potential_size, size.min.y)
size.min.y = space_allocated
}
else {
space_allocated = size.min.y
}
space_allocated -= margins.top - margins.bottom
size.min.y -= margins.top - margins.bottom
flags |= {.Fixed_Height}
return
}
space_used : f32 = 0.0
@ -133,21 +144,19 @@ ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_Layou
{
case .Bottom_To_Top:
for child := container.last; child != nil; child = child.prev {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.layout
anchor = range2({0,0}, {0, 0})
pos.y = -space_used
space_used += size.min.y + child.layout.margins.top + child.layout.margins.bottom
size.min.x = container.computed.content.max.x + container.computed.content.min.x
child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width)
anchor = range2({0,0}, {0, 0})
pos.y = -space_used
space_used += child_height + child.layout.margins.top + child.layout.margins.bottom
}
case .Top_To_Bottom:
for child := container.first; child != nil; child = child.next {
allocate_space(child, total_stretch_ratio, avail_flex_space)
using child.layout
child_height := allocate_space(child, total_stretch_ratio, avail_flex_space, container_width)
anchor = range2({0, 0}, {0, 0})
pos.y = -space_used
space_used += size.min.y + child.layout.margins.top + child.layout.margins.bottom
size.min.x = container.computed.content.max.x - container.computed.content.min.x
space_used += child_height + child.layout.margins.top + child.layout.margins.bottom
}
}
}

View File

@ -8,6 +8,7 @@ test_hover_n_click :: proc()
state := get_state(); using state
first_btn := ui_button( "FIRST BOX!" )
first_btn.layout.size.min = {1000, 1000}
if first_btn.left_clicked || debug.frame_2_created {
debug.frame_2_created = true
@ -37,8 +38,8 @@ test_draggable :: proc()
pos = { 0, 0 },
size = range2({ 200, 200 }, {}),
}
ui_layout( draggable_layout )
ui_style( UI_Style {
scope( draggable_layout )
scope( UI_Style {
corner_radii = { 0.3, 0.3, 0.3, 0.3 },
})
@ -82,10 +83,10 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
.Fixed_Width, .Fixed_Height,
.Origin_At_Anchor_Center
}
ui_layout(parent_layout)
scope(parent_layout)
parent_style := frame_style_default ^
ui_style(parent_style)
scope(parent_style)
parent := ui_widget( "Parent", { .Mouse_Clickable })
ui_parent_push(parent)
@ -107,7 +108,7 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
child_layout := default_layout ^
child_layout.size = range2({ 100, 100 }, { 0, 0 })
child_layout.alignment = { 0.0, 0.0 }
child_layout.alignment = { 1.0, 0.0 }
// 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 })
@ -119,7 +120,7 @@ test_parenting :: proc( default_layout : ^UI_Layout, frame_style_default : ^UI_S
child_style := frame_style_default ^
child_style.bg_color = Color_GreyRed
ui_theme(child_layout, child_style)
scope(child_layout, child_style)
child := ui_widget( "Child", { .Mouse_Clickable })
ui_parent_pop()
}
@ -174,7 +175,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
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 )
scope( text_layout, text_style )
alloc_error : AllocatorError; success : bool
// debug.lorem_content, success = os.read_entire_file( debug.path_lorem, frame_allocator() )
@ -216,7 +217,7 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
chunk_layout.flags = { .Fixed_Position_X, .Size_To_Text }
chunk_style := text_style
ui_theme( to_ui_layout_combo(chunk_layout), to_ui_style_combo(chunk_style) )
scope( chunk_layout, chunk_style )
head := line.first
for ; head != nil;
@ -289,7 +290,10 @@ test_whitespace_ast :: proc( default_layout : ^UI_Layout, frame_style_default :
text_layout.pos.y += size_range2(line_hbox.computed.bounds).y
}
else {
text_layout.pos.y += size_range2( (& widgets.data[ widgets.num - 1 ]).computed.bounds ).y
widget := & widgets.data[ widgets.num - 1 ]
if widget.box != nil {
text_layout.pos.y += size_range2( widget.computed.bounds ).y
}
}
line_id += 1

View File

@ -3,6 +3,9 @@ package sectr
import "base:runtime"
import lalg "core:math/linalg"
// Problably cursed way to setup a 'scope' for a widget
ui_build :: #force_inline proc( captures : $Type, $maker : #type proc(captures : Type) -> $ReturnType ) -> ReturnType { return maker(captures) }
UI_Widget :: struct {
using box : ^UI_Box,
using signal : UI_Signal,
@ -23,6 +26,80 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge
return
}
#region("Drop Down")
/* TODO(Ed): Don't feel very good about the abstraction...
*/
UI_DropDown :: struct {
btn : UI_Widget,
title : UI_Widget,
vbox : UI_VBox,
is_open : bool,
}
@(deferred_out = ui_drop_down_end_auto)
ui_drop_down :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrRunesPair,
direction := UI_LayoutDirectionY.Top_To_Bottom,
btn_flags := UI_BoxFlags{},
vb_flags := UI_BoxFlags{},
vb_compute_layout := true,
btn_theme : ^UI_Theme = nil,
title_theme : ^UI_Theme = nil
) -> (deferred : ^UI_DropDown)
{
deferred = drop_down
ui_drop_down_begin(drop_down, label, title_text, direction, btn_flags, vb_flags, btn_theme, title_theme)
if ! drop_down.is_open do return
ui_parent_push(drop_down.vbox)
return
}
// Its assumed that the drop down has a vertical box parent already pushed
ui_drop_down_begin :: proc( drop_down : ^UI_DropDown, label : string, title_text : StrRunesPair,
direction := UI_LayoutDirectionY.Top_To_Bottom,
btn_flags := UI_BoxFlags{},
vb_flags := UI_BoxFlags{},
btn_theme : ^UI_Theme = nil,
title_theme : ^UI_Theme = nil,
vb_compute_layout := true )
{
using drop_down
if btn_theme == nil do push(theme_drop_down_btn)
else do push(btn_theme ^)
defer ui_theme_pop()
btn = ui_button( str_intern_fmt("%s.btn", label).str );
{
btn.layout.padding.left = 4
ui_parent(btn)
if title_theme == nil do push(theme_text)
else do push(title_theme ^)
defer ui_theme_pop()
title = ui_text( str_intern_fmt("%s.btn.title", label).str, title_text)
}
if btn.pressed {
is_open = !is_open
}
if is_open == false do return
scope(theme_transparent)
vbox = ui_vbox_begin( direction, str_intern_fmt("%v : vbox", label).str, compute_layout = vb_compute_layout )
vbox.layout.anchor.ratio.y = 1.0
}
ui_drop_down_end :: proc( drop_down : ^UI_DropDown ) {
if ! drop_down.is_open do return
ui_vbox_end(drop_down.vbox)
}
ui_drop_down_end_auto :: proc( drop_down : ^UI_DropDown) {
if ! drop_down.is_open do return
ui_vbox_end(drop_down.vbox, compute_layout = true)
ui_parent_pop()
}
#endregion("Drop Down")
#region("Horizontal Box")
/*
Horizontal Boxes automatically manage a collection of widgets and
@ -39,13 +116,11 @@ The hbox will use the anchor's (range2) ratio.x value to determine the "stretch
Keep in mind the stretch ratio is only respected if no size.min.x value is violated for each of the widgets.
*/
// Horizontal Widget
UI_HBox :: struct {
using widget : UI_Widget,
direction : UI_LayoutDirectionX,
}
// Boilerplate creation
ui_hbox_begin :: proc( direction : UI_LayoutDirectionX, label : string, flags : UI_BoxFlags = {} ) -> (hbox : UI_HBox) {
// profile(#procedure)
hbox.direction = direction
@ -171,7 +246,6 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
handle_corner_tl : UI_Widget
handle_corner_br : UI_Widget
handle_corner_bl : UI_Widget
ui_parent(parent)
@(deferred_none = ui_theme_pop)
theme_handle :: proc( base : ^UI_Theme, margins, size : Vec2, flags : UI_LayoutFlags = {})
@ -247,50 +321,52 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
}
context.user_ptr = & parent.label
#region("Handle & Corner construction")
theme_handle( theme, {handle_width, 0}, {handle_width,0})
if left {
handle_left = ui_widget(name("resize_handle_left"), flags )
handle_left.layout.anchor.left = 0
handle_left.layout.anchor.right = 1
handle_left.layout.alignment = {1, 0}
Handle_Construction:
{
ui_parent(parent)
theme_handle( theme, {handle_width, 0}, {handle_width,0})
if left {
handle_left = ui_widget(name("resize_handle_left"), flags )
handle_left.layout.anchor.left = 0
handle_left.layout.anchor.right = 1
handle_left.layout.alignment = {1, 0}
}
if right {
handle_right = ui_widget(name("resize_handle_right"), flags )
handle_right.layout.anchor.left = 1
}
theme_handle( theme, {0, handle_width}, {0, handle_width})
if top {
handle_top = ui_widget(name("resize_handle_top"), flags )
handle_top.layout.anchor.bottom = 1
handle_top.layout.alignment = {0, -1}
}
if bottom {
handle_bottom = ui_widget("resize_handle_bottom", flags)
handle_bottom.layout.anchor.top = 1
handle_bottom.layout.alignment = { 0, 0 }
}
theme_handle( theme, {0,0}, {handle_width, handle_width}, {.Fixed_Width, .Fixed_Height} )
if corner_tl {
handle_corner_tl = ui_widget(name("corner_top_left"), flags)
handle_corner_tl.layout.alignment = {1, -1}
}
if corner_tr {
handle_corner_tr = ui_widget(name("corner_top_right"), flags)
handle_corner_tr.layout.anchor = range2({1, 0}, {})
handle_corner_tr.layout.alignment = {0, -1}
}
if corner_bl {
handle_corner_bl = ui_widget("corner_bottom_left", flags)
handle_corner_bl.layout.anchor = range2({}, {0, 1})
handle_corner_bl.layout.alignment = { 1, 0 }
}
if corner_br {
handle_corner_br = ui_widget("corner_bottom_right", flags)
handle_corner_br.layout.anchor = range2({1, 0}, {0, 1})
handle_corner_br.layout.alignment = {0, 0}
}
}
if right {
handle_right = ui_widget(name("resize_handle_right"), flags )
handle_right.layout.anchor.left = 1
}
theme_handle( theme, {0, handle_width}, {0, handle_width})
if top {
handle_top = ui_widget(name("resize_handle_top"), flags )
handle_top.layout.anchor.bottom = 1
handle_top.layout.alignment = {0, -1}
}
if bottom {
handle_bottom = ui_widget("resize_handle_bottom", flags)
handle_bottom.layout.anchor.top = 1
handle_bottom.layout.alignment = { 0, 0 }
}
theme_handle( theme, {0,0}, {handle_width, handle_width}, {.Fixed_Width, .Fixed_Height} )
if corner_tl {
handle_corner_tl = ui_widget(name("corner_top_left"), flags)
handle_corner_tl.layout.alignment = {1, -1}
}
if corner_tr {
handle_corner_tr = ui_widget(name("corner_top_right"), flags)
handle_corner_tr.layout.anchor = range2({1, 0}, {})
handle_corner_tr.layout.alignment = {0, -1}
}
if corner_bl {
handle_corner_bl = ui_widget("corner_bottom_left", flags)
handle_corner_bl.layout.anchor = range2({}, {0, 1})
handle_corner_bl.layout.alignment = { 1, 0 }
}
if corner_br {
handle_corner_br = ui_widget("corner_bottom_right", flags)
handle_corner_br.layout.anchor = range2({1, 0}, {0, 1})
handle_corner_br.layout.alignment = {0, 0}
}
#endregion("Handle & Corner construction")
process_handle_drag :: #force_inline proc ( handle : ^UI_Widget,
direction : Vec2,
@ -319,8 +395,6 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
shift_changed := (left_shift_held != prev_left_shift_held)
need_to_change_alignment_and_pos := pressed || shift_changed
if active
{
if pressed
@ -379,8 +453,6 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
was_dragging = false
start_size = 0
}
// text = active_context.root.label
// style.text_color = Color_White
prev_left_shift_held = handle.left_shift_held
prev_alignment = align_adjsutment
@ -433,14 +505,13 @@ UI_ScrollBox :: struct {
}
ui_scroll_box :: proc( label : string, flags : UI_BoxFlags ) -> (scroll_box : UI_ScrollBox) {
fatal("NOT IMPLEMENTED")
return
}
// ui_scrollable_view( )
#region("Text")
ui_text :: proc( label : string, content : StrRunesPair, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
@ -510,7 +581,6 @@ UI_VBox :: struct {
direction : UI_LayoutDirectionY,
}
// Boilerplate creation
ui_vbox_begin :: proc( direction : UI_LayoutDirectionY, label : string, flags : UI_BoxFlags = {}, compute_layout := false ) -> (vbox : UI_VBox) {
// profile(#procedure)
vbox.direction = direction