From fdad60014ddc7cadff032e79725fbdde1b6359ab Mon Sep 17 00:00:00 2001 From: Loggableim <160679982+Loggableim@users.noreply.github.com> Date: Wed, 22 Jul 2026 09:03:54 +0200 Subject: [PATCH] Fix locale-safe UI scale input --- base/sources/iron_ui.c | 34 ++++++++++++++++++++++++++---- paint/sources/config.c | 10 +++++++++ paint/sources/functions.h | 1 + paint/sources/ui/box_preferences.c | 3 ++- paint/tests/test_issue_2082.py | 30 ++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 paint/tests/test_issue_2082.py diff --git a/base/sources/iron_ui.c b/base/sources/iron_ui.c index b3ddd395e4..19ffaa4a55 100644 --- a/base/sources/iron_ui.c +++ b/base/sources/iron_ui.c @@ -2339,6 +2339,31 @@ int ui_combo(ui_handle_t *handle, string_array_t *texts, char *label, bool show_ return handle->i; } +static char *ui_normalize_numeric_text(char *text) { + static char normalized[1024]; + bool comma = false; + bool digits = false; + bool invalid = false; + for (int i = 0; text[i] != 0 && i < (int)sizeof(normalized) - 1; ++i) { + char c = text[i]; + if (c >= '0' && c <= '9') { + digits = true; + } + else if (c == ',') { + if (comma) { + invalid = true; + } + comma = true; + } + else if (c != ' ' && c != '\t' && !((c == '+' || c == '-') && i == 0)) { + invalid = true; + } + normalized[i] = c == ',' ? '.' : c; + normalized[i + 1] = 0; + } + return comma && digits && !invalid ? normalized : text; +} + float ui_slider(ui_handle_t *handle, char *text, float from, float to, bool filled, float precision, bool display_value, int align, bool text_edit) { static char temp[1024]; if (!ui_is_visible(UI_ELEMENT_H())) { @@ -2399,15 +2424,16 @@ float ui_slider(ui_handle_t *handle, char *text, float from, float to, bool fill } if (current->submit_text_handle == handle) { ui_submit_text_edit(); + char *numeric_text = ui_normalize_numeric_text(handle->text); #ifdef WITH_EVAL - if (handle->text[0] == '.') { - handle->text = string("0%s", handle->text); + if (numeric_text[0] == '.') { + numeric_text = string("0%s", numeric_text); } - minic_ctx_t *_ctx = minic_eval(string("float main() { return %s; }", handle->text)); + minic_ctx_t *_ctx = minic_eval(string("float main() { return %s; }", numeric_text)); handle->f = minic_ctx_result(_ctx); minic_ctx_free(_ctx); #else - handle->f = atof(handle->text); + handle->f = atof(numeric_text); #endif handle->changed = current->changed = true; } diff --git a/paint/sources/config.c b/paint/sources/config.c index 51b30d1175..d5b8613708 100644 --- a/paint/sources/config.c +++ b/paint/sources/config.c @@ -8,6 +8,13 @@ typedef struct version { bool config_loaded = false; +f32 config_validate_window_scale(f32 scale) { + if (!(scale >= 1.0 && scale <= 4.0)) { + return 1.0; + } + return scale; +} + void config_load() { char *path = ""; if (path_is_protected()) { @@ -36,6 +43,7 @@ void config_load() { gc_unroot(g_config); g_config = json_parse(config_string); gc_root(g_config); + g_config->window_scale = config_validate_window_scale(g_config->window_scale); } } } @@ -44,6 +52,7 @@ void config_save() { if (g_config->workspace == WORKSPACE_PLAYER) { return; } + g_config->window_scale = config_validate_window_scale(g_config->window_scale); // Use system application data folder // when running from protected path like "Program Files" @@ -395,6 +404,7 @@ void config_import_from(config_t *from) { gc_root(g_config); g_config->sha = string_copy(_sha); g_config->version = string_copy(_version); + g_config->window_scale = config_validate_window_scale(g_config->window_scale); gc_unroot(ui_children); ui_children = any_map_create(); // Reset ui handles gc_root(ui_children); diff --git a/paint/sources/functions.h b/paint/sources/functions.h index 9094357a90..207cf52f91 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -96,6 +96,7 @@ void render_envsphere(); void render_pathsphere(); void config_load(); void config_save(); +f32 config_validate_window_scale(f32 scale); void config_init(); void config_init_layout(); char *config_get_sha(); diff --git a/paint/sources/ui/box_preferences.c b/paint/sources/ui/box_preferences.c index e42b8491c9..ad1ab06219 100644 --- a/paint/sources/ui/box_preferences.c +++ b/paint/sources/ui/box_preferences.c @@ -9,7 +9,8 @@ ui_handle_t *_box_preferences_h; i32 _box_preferences_i; void box_preferences_set_scale() { - f32 scale = g_config->window_scale; + g_config->window_scale = config_validate_window_scale(g_config->window_scale); + f32 scale = g_config->window_scale; ui_set_scale(scale); ui_header_h = math_floor(ui_header_default_h * scale); g_config->layout->buffer[LAYOUT_SIZE_STATUS_H] = math_floor(ui_statusbar_default_h * scale); diff --git a/paint/tests/test_issue_2082.py b/paint/tests/test_issue_2082.py new file mode 100644 index 0000000000..a41c40dcbd --- /dev/null +++ b/paint/tests/test_issue_2082.py @@ -0,0 +1,30 @@ +"""Regression checks for locale-safe UI scale input.""" + +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +def read(*parts: str) -> str: + return ROOT.joinpath(*parts).read_text(encoding="utf-8") + + +def test_slider_normalizes_a_decimal_comma_before_evaluation() -> None: + ui = read("base", "sources", "iron_ui.c") + assert "ui_normalize_numeric_text" in ui + assert "ui_normalize_numeric_text(handle->text)" in ui + + +def test_window_scale_is_validated_when_loaded_applied_and_saved() -> None: + config = read("paint", "sources", "config.c") + preferences = read("paint", "sources", "ui", "box_preferences.c") + assert "f32 config_validate_window_scale" in config + assert config.count("config_validate_window_scale") >= 4 + assert "g_config->window_scale = config_validate_window_scale(g_config->window_scale);" in preferences + + +if __name__ == "__main__": + test_slider_normalizes_a_decimal_comma_before_evaluation() + test_window_scale_is_validated_when_loaded_applied_and_saved() + print("2 regression checks passed")