made intial impl for UI_TextInputBox, used it with min and max cam zoom settings ui
This commit is contained in:
parent
f6ba5b2638
commit
533da2cdfb
@ -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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user