made intial impl for UI_TextInputBox, used it with min and max cam zoom settings ui

This commit is contained in:
Edward R. Gonzalez 2024-11-29 23:17:27 -05:00
parent f6ba5b2638
commit 533da2cdfb
7 changed files with 273 additions and 11 deletions

View File

@ -21,6 +21,8 @@ UI_ScreenState :: struct
settings_menu : struct
{
container : UI_Widget,
min_zoom_inputbox : UI_TextInputBox,
max_zoom_inputbox : UI_TextInputBox,
cfg_drop_down : UI_DropDown,
pos, size, min_size : Vec2,
is_open : b32,
@ -28,6 +30,14 @@ UI_ScreenState :: struct
},
}
ui_screen_reload :: proc() {
using state := get_state()
using screen_ui.settings_menu
min_zoom_inputbox.input_str.backing = persistent_slab_allocator()
max_zoom_inputbox.input_str.backing = persistent_slab_allocator()
}
ui_screen_tick :: proc() {
profile("Screenspace Imgui")
@ -263,6 +273,10 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
@static last_invalid_input_time : Time
if input_box.active
{
if ! input_box.was_active {
last_invalid_input_time._nsec = 0
}
if input_box.pressed {
editor_cursor_pos = i32(value_str.num)
}
@ -275,10 +289,6 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
editor_cursor_pos = min(i32(value_str.num), editor_cursor_pos + 1)
}
if ! input_box.was_active {
last_invalid_input_time._nsec = 0
}
iter_obj := iterator( & input_events.key_events ); iter := & iter_obj
for event := iter_next( iter ); event != nil; event = iter_next( iter )
{
@ -297,14 +307,12 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
}
}
// append( & value_str, input_events.codes_pressed )
for code in to_slice(input_events.codes_pressed)
{
if value_str.num == 0 && code == '0' {
last_invalid_input_time = time_now()
continue
}
if value_str.num >= max_value_length {
last_invalid_input_time = time_now()
continue
@ -397,6 +405,39 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
layout.margins.left = 10
layout.font_size = 12
}
min_zoom_config: {
using min_zoom_inputbox
digits_only = true
disallow_leading_zeros = false
disallow_decimal = false
digit_min = 0.01
digit_max = 9999
max_length = 5
}
ui_text_input_box( & min_zoom_inputbox, "settings_menu.cam_min_zoom.input_box", allocator = persistent_slab_allocator() )
{
using min_zoom_inputbox
layout.flags = {.Fixed_Width}
layout.margins.left = 5
layout.padding.right = 5
layout.size.min.x = 80
style.corner_radii = { 3, 3, 3, 3 }
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.cam_min_zoom = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.cam_min_zoom)))
}
}
}
Max_Zoom:
@ -415,6 +456,39 @@ ui_screen_settings_menu :: proc( captures : rawptr = nil ) -> ( should_raise : b
layout.margins.left = 10
layout.font_size = 12
}
max_zoom_config: {
using max_zoom_inputbox
digits_only = true
disallow_leading_zeros = false
disallow_decimal = false
digit_min = 0.01
digit_max = 9999
max_length = 5
ui_text_input_box( & max_zoom_inputbox, "settings_menu.cam_max_zoom.input_box", allocator = persistent_slab_allocator() )
{
using max_zoom_inputbox
layout.flags = {.Fixed_Width}
layout.margins.left = 5
layout.padding.right = 5
layout.size.min.x = 80
style.corner_radii = { 3, 3, 3, 3 }
if was_active
{
value, success := parse_f32(to_string(array_to_slice(input_str)))
if success {
value = clamp(value, 0.001, 9999.0)
config.cam_max_zoom = value
}
}
else
{
clear( input_str )
append( & input_str, to_runes(str_fmt("%v", config.cam_max_zoom)))
}
}
}
}
}
}

View File

@ -456,3 +456,13 @@ theme_text :: proc() -> UI_Theme
}
return theme
}
theme_text_input_box :: proc() -> UI_Theme
{
@static theme : UI_Theme
@static loaded : b32 = false
if ! loaded
{
}
return theme
}

View File

@ -153,7 +153,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
color_theme = App_Thm_Dusk
font_size_canvas_scalar = 2.0
font_size_canvas_scalar = 1.0
}
Desired_OS_Scheduler_MS :: 1

View File

@ -261,7 +261,7 @@ render_mode_screenspace :: proc( screen_extent : Extents2, screen_ui : ^UI_State
}
if true {
state.config.font_size_canvas_scalar = 1.5
state.config.font_size_canvas_scalar = 2.0
zoom_adjust_size := 16 * state.project.workspace.cam.zoom
over_sample := f32(state.config.font_size_canvas_scalar)
debug_text("font_size_canvas_scalar: %v", config.font_size_canvas_scalar)

View File

@ -270,7 +270,16 @@ update :: proc( delta_time : f64 ) -> b32
}
if target_index != current_index {
workspace.zoom_target = Digial_Zoom_Snap_Levels[target_index]
proposed_target := Digial_Zoom_Snap_Levels[target_index]
if proposed_target < config.cam_min_zoom {
workspace.zoom_target = Digial_Zoom_Snap_Levels[find_closest_zoom_index(config.cam_min_zoom, Digial_Zoom_Snap_Levels)]
}
else if proposed_target > config.cam_max_zoom {
workspace.zoom_target = Digial_Zoom_Snap_Levels[find_closest_zoom_index(config.cam_max_zoom, Digial_Zoom_Snap_Levels)]
}
else {
workspace.zoom_target = proposed_target
}
}
}
@ -341,7 +350,7 @@ update :: proc( delta_time : f64 ) -> b32
// test_draggable()
// test_text_box()
// test_parenting( & default_layout, & frame_style_default )
// test_whitespace_ast( & default_layout, & frame_style_default )
test_whitespace_ast( & default_layout, & frame_style_default )
}
//endregion Workspace Imgui Tick

View File

@ -131,6 +131,8 @@ UI_State :: struct {
last_pressed_key : [MouseBtn.count] UI_Key,
last_pressed_key_us : [MouseBtn.count] f32,
last_invalid_input_time : Time,
}
#region("Lifetime")
@ -453,7 +455,7 @@ ui_hash_part_from_key_string :: proc ( content : string ) -> string {
ui_key_from_string :: #force_inline proc "contextless" ( value : string ) -> UI_Key
{
// profile(#procedure)
USE_RAD_DEBUGGERS_METHOD :: true
USE_RAD_DEBUGGERS_METHOD :: false
key : UI_Key

View File

@ -550,6 +550,173 @@ ui_text_wrap_panel :: proc( parent : ^UI_Widget )
}
#endregion("Text")
#region("Text Input")
UI_TextInput_Policy :: struct {
disallow_decimal : b32,
disallow_leading_zeros : b32,
digits_only : b32,
digit_min, digit_max : f32,
max_length : u32,
}
// Note(Ed): Currently only supports single-line input (may stay that way).
// This is a precursor to a proper text editor widget
UI_TextInputBox :: struct {
using widget : UI_Widget,
input_str : Array(rune),
editor_cursor_pos : Vec2i,
using policy : UI_TextInput_Policy,
}
ui_text_input_box :: proc( text_input_box : ^UI_TextInputBox, label : string, flags : UI_BoxFlags = {.Mouse_Clickable, .Focusable, .Click_To_Focus}, allocator := context.allocator )
{
state := get_state()
iter_next :: next
input := state.input
input_events := & get_state().input_events
ui := ui_context()
text_input_box.box = ui_box_make( flags, label )
text_input_box.signal = ui_signal_from_box( text_input_box.box )
using text_input_box
// TODO(Ed): This thing needs to be pulling from the theme stack
app_color := app_color_theme()
if active do style.bg_color = app_color.input_box_bg_active
else if hot do style.bg_color = app_color.input_box_bg_hot
else do style.bg_color = app_color.input_box_bg
if input_str.header == nil {
error : AllocatorError
input_str, error = make( Array(rune), Kilo, allocator )
ensure(error == AllocatorError.None, "Failed to allocate array for input_str of input_box")
}
if active
{
// TODO(Ed): Can problably be moved to ui_signal
if ! was_active {
ui.last_invalid_input_time = {}
}
if pressed {
editor_cursor_pos = { i32(input_str.num), 0 }
}
// TODO(Ed): Abstract this to navigation bindings
if btn_pressed(input.keyboard.left) {
editor_cursor_pos.x = max(0, editor_cursor_pos.x - 1)
}
if btn_pressed(input.keyboard.right) {
editor_cursor_pos.x = min(i32(input_str.num), editor_cursor_pos.x + 1)
}
// TODO(Ed): Confirm btn_pressed is working (if not figure out why)
// if btn_pressed(input.keyboard.left) {
// if input_str.num > 0 {
// editor_cursor_pos = max(0, editor_cursor_pos - 1)
// remove_at( value_str, u64(editor_cursor_pos) )
// }
// }
// if btn_pressed(input.keyboard.right) {
// screen_ui.active = 0
// }
iter_obj := iterator( & input_events.key_events ); iter := & iter_obj
for event := iter_next( iter ); event != nil; event = iter_next( iter )
{
if event.frame_id != state.frame do break
if event.key == .backspace && event.type == .Key_Pressed {
if input_str.num > 0 {
editor_cursor_pos.x = max(0, editor_cursor_pos.x - 1)
remove_at( input_str, u64(editor_cursor_pos.x) )
break
}
}
if event.key == .enter && event.type == .Key_Pressed {
ui.active = 0
break
}
}
decimal_detected := false
for code in to_slice(input_events.codes_pressed)
{
accept_digits := ! digits_only || '0' <= code && code <= '9'
accept_decimal := ! disallow_decimal || ! decimal_detected && code =='.'
if disallow_leading_zeros && input_str.num == 0 && code == '0' {
ui.last_invalid_input_time = time_now()
continue
}
if max_length > 0 && input_str.num >= u64(max_length) {
ui.last_invalid_input_time = time_now()
continue
}
if accept_digits || accept_decimal {
append_at( & input_str, code, u64(editor_cursor_pos.x))
editor_cursor_pos.x = min(editor_cursor_pos.x + 1, i32(input_str.num))
}
else {
ui.last_invalid_input_time = time_now()
continue
}
}
clear( input_events.codes_pressed )
invalid_color := RGBA8 { 70, 40, 40, 255}
// Visual feedback - change background color briefly when invalid input occurs
feedback_duration :: 0.2 // seconds
curr_duration := duration_seconds( time_diff( ui.last_invalid_input_time, time_now() ))
if ui.last_invalid_input_time._nsec != 0 && curr_duration < feedback_duration {
style.bg_color = invalid_color
}
}
ui_parent(text_input_box)
name :: proc( label : string ) -> string {
parent_label := (transmute(^string) context.user_ptr) ^
return str_intern(str_fmt("%v: %v", parent_label, label )).str
}
context.user_ptr = & parent.label
// TODO(Ed): Allow for left and center alignment of text
value_txt : UI_Widget; {
scope(theme_text)
value_txt = ui_text(name("input_str"), to_str_runes_pair(array_to_slice(input_str)))
using value_txt
layout.alignment = {0.0, 0.0}
layout.text_alignment = {1.0, 0.5}
layout.anchor.left = 0.0
layout.size.min = cast(Vec2) measure_text_size( text.str, style.font, layout.font_size, 0 )
if active {
ui_parent(value_txt)
ascent, descent, line_gap := get_font_vertical_metrics(style.font, layout.font_size)
cursor_height := ascent - descent
text_before_cursor := to_string(array_to_slice(input_str)[ :editor_cursor_pos.x ])
cursor_x_offset := measure_text_size(text_before_cursor, style.font, layout.font_size, 0).x
text_size := measure_text_size(to_string(array_to_slice(input_str)), style.font, layout.font_size, 0).x
ui_layout( UI_Layout {
flags = { .Fixed_Width },
size = range2({1, 0}, {}),
anchor = range2({1.0, 0},{0.0, 0.0}),
alignment = { 0.0, 0 },
pos = { cursor_x_offset - text_size, 0 }
})
cursor_widget := ui_widget(name("cursor"), {})
cursor_widget.style.bg_color = RGBA8{255, 255, 255, 255}
cursor_widget.layout.anchor.right = 0.0
}
}
}
#endregion("Text Input")
#region("Vertical Box")
/*
Vertical Boxes automatically manage a collection of widgets and