WIP: Stuff related to working on the settings menu + more
Moved core ui to its own folder. Worked on theming (proper light and dark theme) Began to work on the scroll box widget and input box constructions I added back a script for flattening the codebase: gen_flattened_codebase.ps1
This commit is contained in:
7
code/sectr/ui/core/Readme.md
Normal file
7
code/sectr/ui/core/Readme.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Plans
|
||||
|
||||
Eventually want to generalize this core UI as its own library.
|
||||
This will keep track of here whats needed for it to work wihtout the rest of this codebase.
|
||||
|
||||
* Provide UI input state in its own data stucture at the beginning of `ui_build_graph`:
|
||||
*
|
@ -15,6 +15,10 @@ UI_BoxFlag :: enum u64
|
||||
UI_BoxFlags :: bit_set[UI_BoxFlag; u64]
|
||||
// UI_BoxFlag_Scroll :: UI_BoxFlags { .Scroll_X, .Scroll_Y }
|
||||
|
||||
UI_NavLinks :: struct {
|
||||
left, right, up, down : ^UI_Box,
|
||||
}
|
||||
|
||||
UI_RenderBoxInfo :: struct {
|
||||
using computed : UI_Computed,
|
||||
using style : UI_Style,
|
||||
@ -26,12 +30,15 @@ UI_RenderBoxInfo :: struct {
|
||||
UI_Box :: struct {
|
||||
// Cache ID
|
||||
key : UI_Key,
|
||||
// label : string,
|
||||
label : StrRunesPair,
|
||||
text : StrRunesPair,
|
||||
|
||||
// Regenerated per frame.
|
||||
|
||||
nav : UI_NavLinks,
|
||||
// signal_callback : #type proc(),
|
||||
|
||||
|
||||
// first, last : The first and last child of this box
|
||||
// prev, next : The adjacent neighboring boxes who are children of to the same parent
|
||||
using links : DLL_NodeFull( UI_Box ),
|
||||
@ -129,7 +136,7 @@ ui_box_tranverse_next :: proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box)
|
||||
{
|
||||
// Check to make sure parent is present on the screen, if its not don't bother.
|
||||
is_app_ui := ui_context == & screen_ui
|
||||
if intersects_range2( view_get_bounds(), box.computed.bounds)
|
||||
if intersects_range2( ui_view_bounds(), box.computed.bounds)
|
||||
{
|
||||
return box.first
|
||||
}
|
@ -16,13 +16,17 @@ LayoutAlign_OriginTL_Bottom :: Vec2{0.5, 1}
|
||||
LayoutAlign_OriginTL_BottomLeft :: Vec2{ 0, 1}
|
||||
LayoutAlign_OriginTL_BottomRight :: Vec2{ 1, 1}
|
||||
|
||||
// LayoutAlign_OriginTL_
|
||||
|
||||
Layout_OriginCenter_Centered :: Vec2{0.5, 0.5}
|
||||
|
||||
|
||||
|
||||
|
||||
UI_Align_Presets_Struct :: struct {
|
||||
origin_tl_centered : Vec2,
|
||||
text_centered : Vec2,
|
||||
}
|
||||
UI_Align_Presets :: UI_Align_Presets_Struct {
|
||||
origin_tl_centered = {0.5, 0.5},
|
||||
text_centered = {0.5, 0.5},
|
||||
}
|
||||
|
||||
|
||||
// The UI_Box's actual positioning and sizing
|
@ -85,9 +85,16 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
|
||||
if .Scale_Width_By_Height_Ratio in layout.flags {
|
||||
adjusted_size.x = adjusted_size.y * layout.size.min.x
|
||||
}
|
||||
else if .Fixed_Width in layout.flags {
|
||||
adjusted_size.x = layout.size.min.x
|
||||
}
|
||||
|
||||
if .Scale_Height_By_Width_Ratio in layout.flags {
|
||||
adjusted_size.y = adjusted_size.x * layout.size.min.y
|
||||
}
|
||||
else if .Fixed_Height in layout.flags {
|
||||
adjusted_size.y = layout.size.min.y
|
||||
}
|
||||
|
||||
if .Size_To_Content in layout.flags {
|
||||
// Preemtively traverse the children of this parent and have them compute their layout.
|
||||
@ -97,14 +104,6 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
|
||||
//ui_compute_children_bounding_area(box)
|
||||
}
|
||||
|
||||
// TODO(Ed): Should this force override all of the previous auto-sizing possible?
|
||||
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
|
||||
}
|
||||
|
||||
// 5. Determine relative position
|
||||
|
||||
origin_center := margined_bounds_origin
|
||||
@ -186,34 +185,3 @@ ui_box_compute_layout_children :: proc( box : ^UI_Box )
|
||||
}
|
||||
}
|
||||
|
||||
ui_core_compute_layout :: proc( ui : ^UI_State )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state()
|
||||
|
||||
root := ui.root
|
||||
{
|
||||
computed := & root.computed
|
||||
style := root.style
|
||||
layout := & root.layout
|
||||
if ui == & state.screen_ui {
|
||||
computed.bounds.min = transmute(Vec2) state.app_window.extent * -1
|
||||
computed.bounds.max = transmute(Vec2) state.app_window.extent
|
||||
}
|
||||
computed.content = computed.bounds
|
||||
}
|
||||
|
||||
for current := root.first; current != nil; current = ui_box_tranverse_next( current )
|
||||
{
|
||||
if ! current.computed.fresh {
|
||||
ui_box_compute_layout( current )
|
||||
}
|
||||
array_append( & ui.render_queue, UI_RenderBoxInfo {
|
||||
current.computed,
|
||||
current.style,
|
||||
current.text,
|
||||
current.layout.font_size,
|
||||
current.layout.border_width,
|
||||
})
|
||||
}
|
||||
}
|
@ -70,13 +70,13 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
|
||||
|
||||
mouse_clickable := UI_BoxFlag.Mouse_Clickable in box.flags
|
||||
keyboard_clickable := UI_BoxFlag.Keyboard_Clickable in box.flags
|
||||
is_focusable := .Focusable in box.flags
|
||||
|
||||
was_hot := (box.hot_delta > 0)
|
||||
was_active := (ui.active == box.key) && (box.active_delta > 0)
|
||||
was_hot := (box.hot_delta > 0)
|
||||
was_disabled := box.disabled_delta > 0
|
||||
// if was_hot {
|
||||
// runtime.debug_trap()
|
||||
// }
|
||||
|
||||
is_focused_locked := is_focusable ? was_active : false
|
||||
|
||||
// Check to see if this box is active
|
||||
if mouse_clickable && signal.cursor_over && left_pressed && was_hot
|
||||
@ -93,14 +93,14 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
|
||||
// TODO(Ed) : Support double-click detection
|
||||
}
|
||||
|
||||
if mouse_clickable && ! signal.cursor_over && left_released
|
||||
if ! is_focused_locked && was_active && mouse_clickable && ! signal.cursor_over && left_released
|
||||
{
|
||||
box.active_delta = 0
|
||||
|
||||
ui.active = UI_Key(0)
|
||||
ui.active_mouse[MouseBtn.Left] = UI_Key(0)
|
||||
|
||||
signal.released = true
|
||||
signal.released = true
|
||||
}
|
||||
|
||||
if keyboard_clickable
|
||||
@ -109,6 +109,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
|
||||
}
|
||||
|
||||
// TODO(Ed): Should panning and scrolling get supported here? (problably not...)
|
||||
// We can just report the scroll amount in the signal and have the scrollbox widget handle it
|
||||
// TODO(Ed) : Add scrolling support
|
||||
// if UI_BoxFlag.Scroll_X in box.flags {
|
||||
|
||||
@ -123,39 +124,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
|
||||
// if UI_BoxFlag.Pan_Y in box.flags {
|
||||
// }
|
||||
|
||||
is_disabled := UI_BoxFlag.Disabled in box.flags
|
||||
is_hot := ui.hot == box.key
|
||||
is_active := ui.active == box.key
|
||||
|
||||
// TODO(Ed): It should be able to enter hot without mouse_clickable
|
||||
if mouse_clickable && signal.cursor_over && ! is_disabled
|
||||
{
|
||||
hot_vacant := ui.hot == UI_Key(0)
|
||||
active_vacant := ui.active == UI_Key(0)
|
||||
// (active_vacant is_active)
|
||||
if signal.cursor_over && active_vacant
|
||||
{
|
||||
if ! hot_vacant {
|
||||
prev := ui_box_from_key( ui.curr_cache, ui.hot )
|
||||
prev.hot_delta = 0
|
||||
}
|
||||
// prev_hot := zpl_hmap_get( ui.prev_cache, u64(ui.hot) )
|
||||
// prev_hot_label := prev_hot != nil ? prev_hot.label.str : ""
|
||||
// log( str_fmt_tmp("Detected HOT via CURSOR OVER: %v is_hot: %v is_active: %v prev_hot: %v", box.label.str, is_hot, is_active, prev_hot_label ))
|
||||
ui.hot = box.key
|
||||
is_hot = true
|
||||
|
||||
ui.hot_start_style = box.style
|
||||
}
|
||||
}
|
||||
else if ! signal.cursor_over && was_hot
|
||||
{
|
||||
ui.hot = UI_Key(0)
|
||||
is_hot = false
|
||||
box.hot_delta = 0
|
||||
}
|
||||
|
||||
if mouse_clickable && signal.cursor_over && left_released
|
||||
if ! is_focused_locked && was_active && mouse_clickable && signal.cursor_over && left_released
|
||||
{
|
||||
box.active_delta = 0
|
||||
|
||||
@ -169,6 +138,39 @@ ui_signal_from_box :: proc ( box : ^ UI_Box, update_style := true, update_deltas
|
||||
ui.last_clicked = box.key
|
||||
}
|
||||
}
|
||||
|
||||
is_disabled := UI_BoxFlag.Disabled in box.flags
|
||||
is_hot := ui.hot == box.key
|
||||
is_active := ui.active == box.key
|
||||
|
||||
// TODO(Ed): It should be able to enter hot without mouse_clickable
|
||||
if mouse_clickable && signal.cursor_over && ! is_disabled
|
||||
{
|
||||
hot_vacant := ui.hot == UI_Key(0)
|
||||
// active_vacant := ui.active == UI_Key(0)
|
||||
if signal.cursor_over //&& active_vacant
|
||||
{
|
||||
if ! hot_vacant {
|
||||
prev := ui_box_from_key( ui.curr_cache, ui.hot )
|
||||
prev.hot_delta = 0
|
||||
}
|
||||
// prev_hot := zpl_hmap_get( ui.prev_cache, u64(ui.hot) )
|
||||
// prev_hot_label := prev_hot != nil ? prev_hot.label.str : ""
|
||||
// log( str_fmt_tmp("Detected HOT via CURSOR OVER: %v is_hot: %v is_active: %v prev_hot: %v", box.label.str, is_hot, is_active, prev_hot_label ))
|
||||
ui.hot = box.key
|
||||
is_hot = true
|
||||
|
||||
ui.hot_start_style = box.style
|
||||
signal.hot = true
|
||||
}
|
||||
}
|
||||
else if ! signal.cursor_over && was_hot
|
||||
{
|
||||
ui.hot = UI_Key(0)
|
||||
is_hot = false
|
||||
box.hot_delta = 0
|
||||
}
|
||||
|
||||
// profile_end()
|
||||
|
||||
// State Deltas update
|
@ -206,14 +206,55 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
|
||||
ui_parent_push(root)
|
||||
}
|
||||
|
||||
ui_core_compute_layout :: proc( ui : ^UI_State )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state()
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
ui_graph_build_end :: proc( ui : ^UI_State )
|
||||
{
|
||||
profile(#procedure)
|
||||
state := get_state()
|
||||
|
||||
ui_parent_pop() // Should be ui_context.root
|
||||
|
||||
// Regenerate the computed layout if dirty
|
||||
ui_compute_layout( ui )
|
||||
Post_Build_Graph_Traversal:
|
||||
{
|
||||
root := ui.root
|
||||
{
|
||||
computed := & root.computed
|
||||
style := root.style
|
||||
layout := & root.layout
|
||||
if ui == & state.screen_ui {
|
||||
computed.bounds.min = transmute(Vec2) state.app_window.extent * -1
|
||||
computed.bounds.max = transmute(Vec2) state.app_window.extent
|
||||
}
|
||||
computed.content = computed.bounds
|
||||
}
|
||||
for current := root.first; current != nil; current = ui_box_tranverse_next( current )
|
||||
{
|
||||
|
||||
|
||||
if ! current.computed.fresh {
|
||||
ui_box_compute_layout( current )
|
||||
}
|
||||
|
||||
// Enqueue for rendering
|
||||
array_append( & ui.render_queue, UI_RenderBoxInfo {
|
||||
current.computed,
|
||||
current.style,
|
||||
current.text,
|
||||
current.layout.font_size,
|
||||
current.layout.border_width,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get_state().ui_context = nil
|
||||
}
|
||||
@ -259,4 +300,14 @@ ui_top_ancestor :: #force_inline proc "contextless" ( box : ^UI_Box ) -> (^UI_Bo
|
||||
return ancestor
|
||||
}
|
||||
|
||||
ui_view_bounds :: #force_inline proc "contextless" () -> (range : Range2) {
|
||||
using state := get_state();
|
||||
if ui_context == & screen_ui {
|
||||
return screen_get_bounds()
|
||||
}
|
||||
else {
|
||||
return view_get_bounds()
|
||||
}
|
||||
}
|
||||
|
||||
ui_context :: #force_inline proc() -> ^UI_State { return get_state().ui_context }
|
@ -117,20 +117,6 @@ ui_floating_build :: proc()
|
||||
}
|
||||
lookup.queued = true
|
||||
dll_full_push_back(floating, lookup, nil )
|
||||
// if first == nil {
|
||||
// first = lookup
|
||||
// last = lookup
|
||||
// continue
|
||||
// }
|
||||
// if first == last {
|
||||
// last = lookup
|
||||
// last.prev = first
|
||||
// first.next = last
|
||||
// continue
|
||||
// }
|
||||
// last.next = lookup
|
||||
// lookup.prev = last
|
||||
// last = lookup
|
||||
}
|
||||
array_clear(build_queue)
|
||||
|
||||
@ -140,28 +126,7 @@ ui_floating_build :: proc()
|
||||
if ! entry.queued
|
||||
{
|
||||
ensure(false, "There should be no queue failures yet")
|
||||
|
||||
if entry == first
|
||||
{
|
||||
first = entry.next
|
||||
entry.next = nil
|
||||
continue
|
||||
}
|
||||
if entry == last
|
||||
{
|
||||
last = last.prev
|
||||
last.prev = nil
|
||||
entry.prev = nil
|
||||
continue
|
||||
}
|
||||
|
||||
left := entry.prev
|
||||
right := entry.next
|
||||
|
||||
left.next = right
|
||||
right.prev = left
|
||||
entry.prev = nil
|
||||
entry.next = nil
|
||||
dll_full_pop(to_raise, floating)
|
||||
}
|
||||
|
||||
if entry.builder( entry.captures ) && entry != last && to_raise == nil
|
||||
|
@ -1,5 +1,7 @@
|
||||
package sectr
|
||||
|
||||
import "base:runtime"
|
||||
|
||||
/*
|
||||
Widget Layout Ops
|
||||
*/
|
||||
@ -13,6 +15,7 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
|
||||
else {
|
||||
container_width = container.computed.content.max.x - container.computed.content.min.x
|
||||
}
|
||||
container_height := container.computed.content.max.y - container.computed.content.min.y
|
||||
|
||||
// do layout calculations for the children
|
||||
total_stretch_ratio : f32 = 0.0
|
||||
@ -21,16 +24,13 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
|
||||
{
|
||||
using child.layout
|
||||
scaled_width_by_height : b32 = b32(.Scale_Width_By_Height_Ratio in flags)
|
||||
if .Scale_Width_By_Height_Ratio in flags
|
||||
{
|
||||
size_req_children += size.min.x * container_height
|
||||
continue
|
||||
}
|
||||
if .Fixed_Width in flags
|
||||
{
|
||||
if scaled_width_by_height {
|
||||
height := size.max.y != 0 ? size.max.y : container_width
|
||||
width := height * size.min.x
|
||||
|
||||
size_req_children += width
|
||||
continue
|
||||
}
|
||||
|
||||
size_req_children += size.min.x
|
||||
continue
|
||||
}
|
||||
@ -40,32 +40,45 @@ ui_layout_children_horizontally :: proc( container : ^UI_Box, direction : UI_Lay
|
||||
|
||||
avail_flex_space := container_width - 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_height : f32 ) -> (space_allocated : f32)
|
||||
{
|
||||
using child.layout
|
||||
if ! (.Fixed_Width in flags) {
|
||||
size.min.x = anchor.ratio.x * (1 / total_stretch_ratio) * avail_flex_space - child.layout.margins.left - child.layout.margins.right
|
||||
if .Scale_Width_By_Height_Ratio in flags {
|
||||
size.min.y = container_height
|
||||
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
|
||||
}
|
||||
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
|
||||
flags |= {.Fixed_Width}
|
||||
return
|
||||
}
|
||||
|
||||
space_used : f32 = 0.0
|
||||
switch direction{
|
||||
case .Right_To_Left:
|
||||
for child := container.last; child != nil; child = child.prev {
|
||||
allocate_space(child, total_stretch_ratio, avail_flex_space)
|
||||
using child.layout
|
||||
anchor = range2({0, 0}, {0, 0})
|
||||
pos.x = space_used
|
||||
space_used += size.min.x + child.layout.margins.left + child.layout.margins.right
|
||||
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
|
||||
}
|
||||
case .Left_To_Right:
|
||||
for child := container.first; child != nil; child = child.next {
|
||||
allocate_space(child, total_stretch_ratio, avail_flex_space)
|
||||
using child.layout
|
||||
anchor = range2({0, 0}, {0, 0})
|
||||
pos.x = space_used
|
||||
space_used += size.min.x + child.layout.margins.left + child.layout.margins.right
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -123,9 +136,8 @@ ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_Layou
|
||||
allocate_space(child, total_stretch_ratio, avail_flex_space)
|
||||
using child.layout
|
||||
anchor = range2({0,0}, {0, 0})
|
||||
// alignment = {0, 0}
|
||||
pos.y = -space_used
|
||||
space_used += size.min.y
|
||||
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
|
||||
}
|
||||
case .Top_To_Bottom:
|
||||
@ -133,9 +145,8 @@ ui_layout_children_vertically :: proc( container : ^UI_Box, direction : UI_Layou
|
||||
allocate_space(child, total_stretch_ratio, avail_flex_space)
|
||||
using child.layout
|
||||
anchor = range2({0, 0}, {0, 0})
|
||||
// alignment = {0, 0}
|
||||
pos.y = -space_used
|
||||
space_used += size.min.y
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
|
||||
|
||||
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
|
||||
{
|
||||
btn_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus }
|
||||
btn_flags := UI_BoxFlags { .Mouse_Clickable }
|
||||
btn.box = ui_box_make( btn_flags | flags, label )
|
||||
btn.signal = ui_signal_from_box( btn.box )
|
||||
return
|
||||
@ -197,6 +197,7 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
|
||||
}
|
||||
else
|
||||
{
|
||||
app_color := app_color_theme()
|
||||
layout := UI_Layout {
|
||||
flags = flags,
|
||||
anchor = range2({},{}),
|
||||
@ -215,7 +216,7 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
|
||||
corner_radii = {5, 0, 0, 0},
|
||||
blur_size = 0,
|
||||
font = get_state().default_font,
|
||||
text_color = Color_ThmDark_Text_Default,
|
||||
text_color = app_color.text_default,
|
||||
cursor = {},
|
||||
}
|
||||
layout_combo = to_ui_layout_combo(layout)
|
||||
@ -223,12 +224,12 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
|
||||
{
|
||||
using layout_combo.hot
|
||||
using style_combo.hot
|
||||
bg_color = Color_ThmDark_ResizeHandle_Hot
|
||||
bg_color = app_color.resize_hndl_hot
|
||||
}
|
||||
{
|
||||
using layout_combo.active
|
||||
using style_combo.active
|
||||
bg_color = Color_ThmDark_ResizeHandle_Active
|
||||
bg_color = app_color.resize_hndl_active
|
||||
}
|
||||
}
|
||||
theme := UI_Theme {
|
||||
@ -364,7 +365,7 @@ ui_resizable_handles :: proc( parent : ^UI_Widget, pos : ^Vec2, size : ^Vec2,
|
||||
}
|
||||
was_dragging = true
|
||||
}
|
||||
else if released && was_dragging
|
||||
else if released// && was_dragging
|
||||
{
|
||||
// This needed to be added as for some reason, this was getting called in screen_ui even when we were resizing with a handle in a worksapce
|
||||
if active_context != ui do return false
|
||||
|
Reference in New Issue
Block a user