Dragging! & basic proportional box resize frm cursor distance to box pos

Still need to add resize via 'pulling' to stretch the box out from a side or 2 sides diagonally.

Also some general clenaup of code
This commit is contained in:
2024-03-08 03:34:21 -05:00
parent 90478bec94
commit 191d5076ea
15 changed files with 278 additions and 79 deletions

View File

@ -80,6 +80,23 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
input = & input_data[1]
input_prev = & input_data[0]
// Configuration Load
{
using config
resolution_width = 1000
resolution_height = 600
refresh_rate = 0
cam_min_zoom = 0.25
cam_max_zoom = 10.0
cam_zoom_mode = .Smooth
cam_zoom_smooth_snappiness = 4.0
cam_zoom_sensitivity_digital = 0.2
cam_zoom_sensitivity_smooth = 4.0
ui_resize_border_width = 20
}
// rl.Odin_SetMalloc( RL_MALLOC )
rl.SetConfigFlags( {
@ -119,15 +136,6 @@ startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^V
path_firacode := strings.concatenate( { Path_Assets, "FiraCode-Regular.ttf" }, frame_allocator() )
font_firacode = font_load( path_firacode, 24.0, "FiraCode" )
// font_data, read_succeded : = os.read_entire_file( path_rec_mono_semicasual_reg )
// verify( read_succeded, fmt.tprintf("Failed to read font file for: %v", path_rec_mono_semicasual_reg) )
// cstr := strings.clone_to_cstring( path_rec_mono_semicasual_reg )
// font_rec_mono_semicasual_reg = rl.LoadFontEx( cstr, cast(i32) points_to_pixels(24.0), nil, 0 )
// delete( cstr)
// rl.GuiSetFont( font_rec_mono_semicasual_reg ) // TODO(Ed) : Does this do anything?
default_font = font_firacode
log( "Default font loaded" )
}
@ -198,8 +206,8 @@ reload :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VA
// Thankfully persistent dynamic allocations are rare, and thus we know exactly which ones they are.
font_provider_data := & get_state().font_provider_data
// font_provide_data.font_cache.hashes.allocator = slab_allocator()
// font_provide_data.font_cache.entries.allocator = slab_allocator()
font_provider_data.font_cache.hashes.allocator = general_slab_allocator()
font_provider_data.font_cache.entries.allocator = general_slab_allocator()
ui_reload( & get_state().project.workspace.ui, cache_allocator = general_slab_allocator() )
@ -217,7 +225,9 @@ tick :: proc( delta_time : f64, delta_ns : Duration ) -> b32
context.allocator = frame_allocator()
context.temp_allocator = transient_allocator()
get_state().frametime_delta_ns = delta_ns
state := get_state()
state.frametime_delta_ns = delta_ns
state.frametime_delta_seconds = delta_time
result := update( delta_time )
render()

View File

@ -10,10 +10,10 @@ Color_White :: rl.WHITE
Color_Transparent :: Color { 0, 0, 0, 0 }
Color_BG :: Color { 41, 41, 45, 255 }
Color_BG_TextBox :: Color { 32, 32, 32, 255 }
Color_BG_TextBox :: Color { 32, 32, 32, 180 }
Color_BG_TextBox_Green :: Color { 102, 102, 110, 255 }
Color_Frame_Disabled :: Color { 22, 22, 22, 120 }
Color_Frame_Hover :: Color { 122, 122, 125, 255 }
Color_Frame_Select :: Color { 188, 188, 188, 255 }
Color_Frame_Hover :: Color { 122, 122, 125, 200 }
Color_Frame_Select :: Color { 188, 188, 188, 220 }
Color_GreyRed :: Color { 220, 100, 100, 125 }
Color_White_A125 :: Color { 255, 255, 255, 125 }

View File

@ -118,8 +118,15 @@ AppConfig :: struct {
resolution_width : uint,
resolution_height : uint,
refresh_rate : uint,
min_zoom : uint,
max_zoom : uint,
cam_min_zoom : f32,
cam_max_zoom : f32,
cam_zoom_mode : CameraZoomMode,
cam_zoom_smooth_snappiness : f32,
cam_zoom_sensitivity_smooth : f32,
cam_zoom_sensitivity_digital : f32,
ui_resize_border_width : uint,
}
State :: struct {
@ -144,7 +151,8 @@ State :: struct {
engine_refresh_hz : i32,
engine_refresh_target : i32,
frametime_delta_ns : Duration,
frametime_delta_seconds : f64,
frametime_delta_ns : Duration,
font_firacode : FontID,
font_squidgy_slimes : FontID,
@ -190,7 +198,8 @@ Project :: struct {
Workspace :: struct {
name : string,
cam : Camera,
cam : Camera,
zoom_target : f32,
// TODO(Ed) : The workspace is mainly a 'UI' conceptually...
ui : UI_State,
@ -207,7 +216,15 @@ DebugData :: struct {
mouse_vis : b32,
last_mouse_pos : Vec2,
zoom_target : f32,
// Test First
frame_2_created : b32,
// Test Draggable
draggable_box_pos : Vec2,
draggable_box_size : Vec2,
box_original_size : Vec2,
box_resize_started : b32,
ui_drag_delta : Vec2,
ui_drag_start : Vec2,
}

View File

@ -47,7 +47,10 @@ FontGlyphsRender :: struct {
FontDef :: struct {
path_file : string,
data : [] u8,
// TODO(Ed) : you may have to store font data in the future if we render on demand
// data : []u8,
default_size : i32,
size_table : [Font_Largest_Px_Size / 2] FontGlyphsRender,
}
@ -62,8 +65,7 @@ font_provider_startup :: proc()
font_provider_data := & get_state().font_provider_data; using font_provider_data
font_cache_alloc_error : AllocatorError
font_cache, font_cache_alloc_error = zpl_hmap_init_reserve( FontDef, general_slab_allocator(), 8 )
font_cache, font_cache_alloc_error = zpl_hmap_init_reserve( FontDef, general_slab_allocator(), 2 )
verify( font_cache_alloc_error == AllocatorError.None, "Failed to allocate font_cache" )
log("font_cache created")
@ -94,7 +96,7 @@ font_load :: proc( path_file : string,
{
font_provider_data := & get_state().font_provider_data; using font_provider_data
font_data, read_succeded : = os.read_entire_file( path_file, general_slab_allocator() )
font_data, read_succeded : = os.read_entire_file( path_file, context.temp_allocator )
verify( b32(read_succeded), str_fmt_tmp("Failed to read font file for: %v", path_file) )
font_data_size := cast(i32) len(font_data)
@ -117,7 +119,7 @@ font_load :: proc( path_file : string,
verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" )
def.path_file = path_file
def.data = font_data
// def.data = font_data
def.default_size = i32(points_to_pixels(default_size))
// TODO(Ed): this is slow & eats quite a bit of memory early on. Setup a more on demand load for a specific size.

View File

@ -80,6 +80,10 @@ OS_Type :: type_of(ODIN_OS)
// Proc Name Overloads Alias table
// This has to be done on a per-module basis.
add :: proc {
add_range2,
}
cm_to_pixels :: proc {
f32_cm_to_pixels,
vec2_cm_to_pixels,

View File

@ -212,7 +212,8 @@ zpl_hmap_set :: proc( using self : ^ HMapZPL( $ Type), key : u64, value : Type )
entries.data[id].value = value
if zpl_hmap_full( self ) {
return & entries.data[id].value, zpl_hmap_grow( self )
alloc_error := zpl_hmap_grow( self )
return & entries.data[id].value, alloc_error
}
return & entries.data[id].value, AllocatorError.None

View File

@ -280,6 +280,8 @@ main :: proc()
verify( sectr_api.lib_version != 0, "Failed to initially load the sectr module" )
}
free_all( context.temp_allocator )
running = true;
sectr_api = sectr_api
sectr_api.startup(

View File

@ -273,6 +273,12 @@ MouseState :: struct {
vertical_wheel, horizontal_wheel : AnalogAxis
}
mouse_world_delta :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
cam := & state.project.workspace.cam
return { input.mouse.delta.x, -input.mouse.delta.y } * ( 1 / cam.zoom )
}
InputState :: struct {
keyboard : KeyboardState,
mouse : MouseState

View File

@ -33,6 +33,19 @@ Range2 :: struct #raw_union{
},
}
range2 :: #force_inline proc "contextless" ( a, b : Vec2 ) -> Range2 {
result := Range2 { pts = { a, b } }
return result
}
Rect :: struct {
top_left, bottom_right : Vec2
}
add_range2 :: #force_inline proc "contextless" ( a, b : Range2 ) -> Range2 {
result := Range2 { pts = {
a.p0 + b.p0,
a.p1 + b.p1,
}}
return result
}

View File

@ -95,6 +95,11 @@ range2_pixels_to_cm :: proc( range : Range2 ) -> Range2 {
Camera :: rl.Camera2D
CameraZoomMode :: enum u32 {
Digital,
Smooth,
}
// TODO(Ed) : I'm not sure making the size and extent types distinct has made things easier or more difficult in Odin..
// The lack of operator overloads is going to make any sort of nice typesystem
// for doing lots of math or phyiscs more error prone or filled with proc wrappers
@ -160,7 +165,7 @@ view_get_corners :: proc() -> BoundsCorners2 {
return { top_left, top_right, bottom_left, bottom_right }
}
screen_to_world :: proc(pos: Vec2) -> Vec2 {
screen_to_world :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
state := get_state(); using state
cam := & project.workspace.cam
result := Vec2 { cam.target.x, -cam.target.y} + Vec2 { pos.x, -pos.y } * (1 / cam.zoom)

View File

@ -60,12 +60,20 @@ render :: proc()
}
}
debug_text("Zoom Target: %v", project.workspace.zoom_target)
if debug.mouse_vis {
debug_text( "Mouse Vertical Wheel: %v", input.mouse.vertical_wheel )
debug_text( "Mouse Position (Screen): %v", input.mouse.pos )
debug_text("Mouse Position (World): %v", screen_to_world(input.mouse.pos) )
cursor_pos := transmute(Vec2) state.app_window.extent + input.mouse.pos
rl.DrawCircleV( cursor_pos, 10, Color_White_A125 )
}
debug_text( "ui_drag_start : %v", debug.ui_drag_start )
debug_text( "ui_drag_delta : %v", debug.ui_drag_delta )
debug_text( "Draggable Box Pos: %v", debug.draggable_box_pos )
debug.draw_debug_text_y = 50
}
//endregion Render Screenspace
@ -81,6 +89,8 @@ render_mode_2d :: proc()
rl.BeginMode2D( project.workspace.cam )
debug_draw_text_world( "This is text in world space", { 0, 200 }, 16.0 )
ImguiRender:
{
ui := & state.project.workspace.ui
@ -118,7 +128,6 @@ render_mode_2d :: proc()
}
//endregion Imgui Render
debug_draw_text_world( "This is text in world space", { 0, 200 }, 16.0 )
if debug.mouse_vis {
cursor_world_pos := screen_to_world(input.mouse.pos)

View File

@ -2,6 +2,7 @@ package sectr
import "base:runtime"
import "core:math"
import "core:math/linalg"
import rl "vendor:raylib"
@ -56,10 +57,16 @@ poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState )
cam_mouse_pan = mouse.right.ended_down && ! pressed(mouse.right)
}
frametime_delta32 :: #force_inline proc "contextless" () -> f32 {
return cast(f32) get_state().frametime_delta_seconds
}
update :: proc( delta_time : f64 ) -> b32
{
state := get_state(); using state
replay := & Memory_App.replay
workspace := & project.workspace
cam := & workspace.cam
if rl.IsWindowResized() {
window := & state.app_window
@ -138,25 +145,30 @@ update :: proc( delta_time : f64 ) -> b32
//region Camera Manual Nav
{
cam := & project.workspace.cam
digital_move_speed : f32 = 200.0
// zoom_sensitivity : f32 = 0.2 // Digital
zoom_sensitivity : f32 = 4.0 // Smooth
if debug.zoom_target == 0.0 {
debug.zoom_target = cam.zoom
if workspace.zoom_target == 0.0 {
workspace.zoom_target = cam.zoom
}
// Adjust zoom_target based on input, not the actual zoom
zoom_delta := input.mouse.vertical_wheel * zoom_sensitivity
debug.zoom_target *= 1 + zoom_delta * f32(delta_time)
debug.zoom_target = clamp(debug.zoom_target, 0.25, 10.0)
config.cam_zoom_smooth_snappiness = 10.0
config.cam_zoom_mode = .Smooth
switch config.cam_zoom_mode
{
case .Smooth:
zoom_delta := input.mouse.vertical_wheel * config.cam_zoom_sensitivity_smooth
workspace.zoom_target *= 1 + zoom_delta * f32(delta_time)
workspace.zoom_target = clamp(workspace.zoom_target, 0.25, 10.0)
// Linearly interpolate cam.zoom towards zoom_target
lerp_factor := cast(f32) 4.0 // Adjust this value to control the interpolation speed
cam.zoom += (debug.zoom_target - cam.zoom) * lerp_factor * f32(delta_time)
cam.zoom = clamp(cam.zoom, 0.25, 10.0) // Ensure cam.zoom stays within bounds
// Linearly interpolate cam.zoom towards zoom_target
lerp_factor := config.cam_zoom_smooth_snappiness // Adjust this value to control the interpolation speed
cam.zoom += (workspace.zoom_target - cam.zoom) * lerp_factor * f32(delta_time)
cam.zoom = clamp(cam.zoom, 0.25, 10.0) // Ensure cam.zoom stays within bounds
case .Digital:
zoom_delta := input.mouse.vertical_wheel * config.cam_zoom_sensitivity_digital
workspace.zoom_target = clamp(workspace.zoom_target + zoom_delta, 0.25, 10.0)
cam.zoom = workspace.zoom_target
}
move_velocity : Vec2 = {
- cast(f32) i32(debug_actions.cam_move_left) + cast(f32) i32(debug_actions.cam_move_right),
@ -168,7 +180,7 @@ update :: proc( delta_time : f64 ) -> b32
if debug_actions.cam_mouse_pan
{
if is_within_screenspace(input.mouse.pos) {
pan_velocity := input.mouse.delta * (1/cam.zoom)
pan_velocity := input.mouse.delta * ( 1 / cam.zoom )
cam.target -= pan_velocity
}
}
@ -208,7 +220,7 @@ update :: proc( delta_time : f64 ) -> b32
}}
ui_style_theme( frame_theme )
first_layout := UI_Layout {
default_layout := UI_Layout {
anchor = {},
// alignment = { 0.0, 0.0 },
alignment = { 0.5, 0.5 },
@ -216,26 +228,71 @@ update :: proc( delta_time : f64 ) -> b32
pos = { 0, 0 },
size = { 200, 200 },
}
ui_set_layout( first_layout )
ui_set_layout( default_layout )
// First Demo
when true
Test_HoverNClick :: false
Test_Draggable :: true
when Test_HoverNClick
{
first_flags : UI_BoxFlags = { .Mouse_Clickable, .Focusable, .Click_To_Focus }
first_box := ui_box_make( first_flags, "FIRST BOX BOIS" )
first_box := ui_box_make( first_flags, "FIRST BOX!" )
signal := ui_signal_from_box( first_box )
if signal.left_clicked || debug.frame_2_created {
second_layout := first_layout
second_layout := default_layout
second_layout.pos = { 250, 0 }
ui_set_layout( second_layout )
second_box := ui_box_make( first_flags, "SECOND BOX BOIS" )
second_box := ui_box_make( first_flags, "SECOND BOX!" )
signal := ui_signal_from_box( second_box )
debug.frame_2_created = true
}
}
config.ui_resize_border_width = 50
when Test_Draggable
{
draggable_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus }
draggable_box := ui_box_make( draggable_flags, "Draggable Box!" )
signal := ui_signal_from_box( draggable_box )
if draggable_box.first_frame {
debug.draggable_box_pos = draggable_box.style.layout.pos
debug.draggable_box_size = draggable_box.style.layout.size
}
// Dragging
if signal.dragging {
debug.draggable_box_pos += mouse_world_delta()
}
// Resize
if signal.resizing
{
if ! debug.box_resize_started {
debug.box_original_size = debug.draggable_box_size
}
center := debug.draggable_box_pos
original_distance := linalg.distance(ui_context.cursor_active_start, center)
cursor_distance := linalg.distance(signal.cursor_pos, center)
scale_factor := cursor_distance * (1 / original_distance)
debug.draggable_box_size = debug.box_original_size * scale_factor
}
debug.box_resize_started = cast(b32) signal.resizing
if workspace.ui.hot_resizable || workspace.ui.active_resizing {
draggable_box.style.bg_color = Color_Blue
}
// Note(Ed): Don't necessarily need a layout if its simple...
draggable_box.style.pos = debug.draggable_box_pos
draggable_box.style.layout.size = debug.draggable_box_size
}
}
//endregion Imgui Tick

View File

@ -162,6 +162,7 @@ UI_Signal :: struct {
pressed : b8,
released : b8,
dragging : b8,
resizing : b8,
hovering : b8,
cursor_over : b8,
commit : b8,
@ -202,7 +203,7 @@ UI_Style :: struct {
cursor : UI_Cursor,
layout : UI_Layout,
using layout : UI_Layout,
transition_time : f32,
}
@ -228,16 +229,20 @@ UI_Box :: struct {
// Regenerated per frame.
using links : DLL_NodeFull( UI_Box ), // first, last, prev, next
parent : ^ UI_Box,
parent : ^UI_Box,
num_children : i32,
flags : UI_BoxFlags,
computed : UI_Computed,
theme : UI_StyleTheme,
style : ^ UI_Style,
style : ^UI_Style,
// Persistent Data
style_delta : f32,
first_frame : b8,
hot_delta : f32,
active_delta : f32,
style_delta : f32,
// prev_computed : UI_Computed,
// prev_style : UI_Style,v
mouse : UI_InteractState,
@ -248,7 +253,7 @@ UI_Box :: struct {
UI_Layout_Stack_Size :: 512
UI_Style_Stack_Size :: 512
UI_Parent_Stack_Size :: 1024
UI_Built_Boxes_Array_Size :: 128
UI_Built_Boxes_Array_Size :: 1024
UI_State :: struct {
// TODO(Ed) : Use these
@ -258,11 +263,11 @@ UI_State :: struct {
built_box_count : i32,
caches : [2] HMapZPL( UI_Box ),
prev_cache : ^ HMapZPL( UI_Box ),
curr_cache : ^ HMapZPL( UI_Box ),
prev_cache : ^HMapZPL( UI_Box ),
curr_cache : ^HMapZPL( UI_Box ),
null_box : ^ UI_Box, // Ryan had this, I don't know why yet.
root : ^ UI_Box,
null_box : ^UI_Box, // Ryan had this, I don't know why yet.
root : ^UI_Box,
// Do we need to recompute the layout?
layout_dirty : b32,
@ -272,13 +277,17 @@ UI_State :: struct {
parent_stack : StackFixed( ^UI_Box, UI_Parent_Stack_Size ),
// flag_stack : Stack( UI_BoxFlags, UI_BoxFlags_Stack_Size ),
hot : UI_Key,
active_mouse : [MouseBtn.count] UI_Key,
active : UI_Key,
hot : UI_Key,
active_mouse : [MouseBtn.count] UI_Key,
active : UI_Key,
hot_resizable : b32,
active_resizing : b32, // Locks the user into a resizing state for the active box until they release the active key
clipboard_copy : UI_Key,
last_clicked : UI_Key,
drag_start_mouse : Vec2,
cursor_active_start : Vec2,
// cursor_resize_start : Vec2,
// drag_state_arena : ^ Arena,
// drag_state data : string,
@ -347,6 +356,8 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
verify( set_error == AllocatorError.None, "Failed to set zpl_hmap due to allocator error" )
curr_box = set_result
curr_box.first_frame = prev_box == nil
}
// TODO(Ed) : Review this when we learn layouts more...
@ -396,6 +407,21 @@ ui_box_tranverse_next :: proc( box : ^ UI_Box ) -> (^ UI_Box) {
return box.next
}
ui_cursor_pos :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
if ui_context == & state.project.workspace.ui {
return screen_to_world( input.mouse.pos )
}
else {
return input.mouse.pos
}
}
ui_drag_delta :: #force_inline proc "contextless" () -> Vec2 {
using state := get_state()
return ui_cursor_pos() - state.ui_context.cursor_active_start
}
ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
{
get_state().ui_context = ui
@ -457,15 +483,21 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
ui := get_state().ui_context
input := get_state().input
frame_delta := frametime_delta32()
signal := UI_Signal { box = box }
if ui == & get_state().project.workspace.ui {
signal.cursor_pos = screen_to_world( input.mouse.pos )
}
else {
signal.cursor_pos = input.mouse.pos
}
signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds )
// Cursor Collision
signal.cursor_pos = ui_cursor_pos()
signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds )
resize_border_width := cast(f32) get_state().config.ui_resize_border_width
resize_border_non_range := add(box.computed.bounds, range2(
{ resize_border_width, -resize_border_width },
{ -resize_border_width, resize_border_width }))
within_resize_range := cast(b8) ! pos_within_range2( signal.cursor_pos, resize_border_non_range )
within_resize_range &= signal.cursor_over
left_pressed := pressed( input.mouse.left )
left_released := released( input.mouse.left )
@ -478,7 +510,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
ui.hot = box.key
ui.active = box.key
ui.active_mouse[MouseBtn.Left] = box.key
ui.drag_start_mouse = signal.cursor_pos
ui.cursor_active_start = signal.cursor_pos
ui.last_pressed_key = box.key
signal.pressed = true
@ -487,18 +519,24 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
if mouse_clickable && signal.cursor_over && left_released
{
ui.active = UI_Key(0)
box.active_delta = 0
ui.active = UI_Key(0)
ui.active_mouse[MouseBtn.Left] = UI_Key(0)
signal.released = true
signal.left_clicked = true
ui.last_clicked = box.key
}
if mouse_clickable && ! signal.cursor_over && left_released {
if mouse_clickable && ! signal.cursor_over && left_released
{
box.hot_delta = 0
ui.hot = UI_Key(0)
ui.active = UI_Key(0)
ui.active_mouse[MouseBtn.Left] = UI_Key(0)
signal.released = true
signal.left_clicked = false
}
@ -523,22 +561,45 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
}
is_hot := ui.hot == box.key
is_active := ui.active == box.key
if signal.cursor_over &&
ui.hot == UI_Key(0) || ui.hot == box.key &&
ui.active == UI_Key(0) || ui.active == box.key
ui.hot == UI_Key(0) || is_hot &&
ui.active == UI_Key(0) || is_active
{
ui.hot = box.key
is_hot = true
}
if ! is_active {
ui.hot_resizable = cast(b32) within_resize_range
}
signal.resizing = cast(b8) is_active && (within_resize_range || ui.active_resizing)
if is_hot {
box.hot_delta += frame_delta
}
if is_active {
box.active_delta += frame_delta
}
ui.active_resizing = cast(b32) is_active && signal.resizing
signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_resizing)
style_preset := UI_StylePreset.Default
// box.style = stack_peek( & ui.them_stack ).default
if box.key == ui.hot {
style_preset = UI_StylePreset.Hovered
// box.stye = stack_peek( & ui.theme_stack ).hovered
}
if box.key == ui.active {
style_preset = UI_StylePreset.Focused
// box.stye = stack_peek( & ui.theme_stack ).focused
}
if UI_BoxFlag.Disabled in box.flags {
style_preset = UI_StylePreset.Disabled
// box.style = stack_peek( & ui.theme.stack ).disabled
}
box.style = & box.theme.array[style_preset]