From 39bd13d749978a8122f11794b701d147ac7423dc Mon Sep 17 00:00:00 2001 From: luboslenco Date: Mon, 16 Mar 2026 14:28:30 +0100 Subject: [PATCH] ui: text area fixes --- base/sources/iron.h | 21 +- base/sources/iron_ui.c | 785 ++---------------------- base/sources/iron_ui.h | 253 ++++++-- base/sources/iron_ui_ext.c | 950 ++++++++++++++++++++++++++++++ base/sources/iron_ui_nodes.c | 41 +- base/sources/iron_ui_nodes.h | 165 ------ base/sources/plugins/plugin_api.c | 1 - paint/plugins/plugins.c | 5 +- 8 files changed, 1230 insertions(+), 991 deletions(-) create mode 100644 base/sources/iron_ui_ext.c delete mode 100644 base/sources/iron_ui_nodes.h diff --git a/base/sources/iron.h b/base/sources/iron.h index 3c18e61b..aef25f1e 100644 --- a/base/sources/iron.h +++ b/base/sources/iron.h @@ -2,25 +2,24 @@ #pragma clang diagnostic ignored "-Wincompatible-pointer-types" +#include "const_data.h" #include "iron_armpack.h" #include "iron_array.h" #include "iron_draw.h" +#include "iron_eval.h" #include "iron_file.h" -#include "iron_path.h" #include "iron_gc.h" #include "iron_gpu.h" #include "iron_json.h" +#include "iron_lz4.h" #include "iron_map.h" #include "iron_math.h" #include "iron_obj.h" +#include "iron_path.h" #include "iron_string.h" #include "iron_system.h" #include "iron_thread.h" -#include "iron_lz4.h" -#include "iron_eval.h" #include "iron_ui.h" -#include "iron_ui_nodes.h" -#include "const_data.h" #include #include #include @@ -38,8 +37,8 @@ #endif #define ID__(x, y) x ":" #y -#define ID_(x, y) ID__(x, y) -#define __ID__ ID_(__FILE__, __LINE__) +#define ID_(x, y) ID__(x, y) +#define __ID__ ID_(__FILE__, __LINE__) int _argc; char **_argv; @@ -175,15 +174,15 @@ char *iron_get_arg(i32 index) { // ██║██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║██║ ██║ // ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +#include "engine.h" +#include "iron_input.h" #include "iron_math.h" #include "iron_net.h" -#include "iron_tween.h" -#include "iron_input.h" -#include "iron_tilesheet.h" #include "iron_raycast.h" #include "iron_shape.h" #include "iron_sys.h" -#include "engine.h" +#include "iron_tilesheet.h" +#include "iron_tween.h" #include "kong/dir.h" #include #include diff --git a/base/sources/iron_ui.c b/base/sources/iron_ui.c index 8f448eba..b18ec1f6 100644 --- a/base/sources/iron_ui.c +++ b/base/sources/iron_ui.c @@ -1,9 +1,9 @@ #include "iron_ui.h" #include "iron_draw.h" -#include "iron_gc.h" -#include "iron_string.h" #include "iron_file.h" +#include "iron_gc.h" #include "iron_gpu.h" +#include "iron_string.h" #include "iron_system.h" #include #include @@ -17,8 +17,8 @@ static ui_theme_t *theme; static bool ui_key_repeat = true; // Emulate key repeat for non-character keys static bool ui_dynamic_glyph_load = true; // Allow text input fields to push new glyphs into the font atlas static float ui_key_repeat_time = 0.0; -static char ui_text_to_paste[1024]; -static char ui_text_to_copy[1024]; +char ui_text_to_paste[1024]; +char ui_text_to_copy[1024]; static bool ui_combo_first = true; static ui_handle_t *ui_combo_search_handle = NULL; static int touch_hold_x = -1; @@ -214,7 +214,7 @@ void ui_draw_rect(bool fill, float x, float y, float w, float h) { } } else { - int r = current->round_corner_image.width; + int r = current->round_corner_image.width; float strength = 1.0; if (theme->ROUND_CORNERS && current->enabled && r > 0) { draw_scaled_image(¤t->round_corner_image, x, y, r, r); @@ -360,12 +360,6 @@ void ui_draw_string(char *text, float x_offset, float y_offset, int align, bool } if (current->text_coloring == NULL) { - // if (theme->SHADOWS) { - // uint32_t color = draw_get_color(); - // draw_set_color(0x66000000); - // draw_string(text, current->_x + x_offset + 1, current->_y + current->font_offset_y + y_offset + 1); - // draw_set_color(color); - // } draw_string(text, current->_x + x_offset, current->_y + current->font_offset_y + y_offset); } else { @@ -612,6 +606,10 @@ char *ui_extract_line_off(char *str, int line, int *off) { for (i = 0; i < len; ++i) { if (str[i] == '\n') { line_i++; + if (line_i > line) { + i++; + break; + } continue; } if (line_i < line) { @@ -833,8 +831,8 @@ void ui_draw_combo() { } current->input_enabled = true; - int _BUTTON_COL = theme->BUTTON_COL; - int _ELEMENT_OFFSET = theme->ELEMENT_OFFSET; + int _BUTTON_COL = theme->BUTTON_COL; + int _ELEMENT_OFFSET = theme->ELEMENT_OFFSET; bool _SHADOWS = theme->SHADOWS; theme->ELEMENT_OFFSET = 0; theme->SHADOWS = false; @@ -1117,12 +1115,29 @@ void ui_update_text_edit(int align, bool editable, bool live_update) { strcpy(text, current->text_selected); if (current->is_key_pressed) { // Process input if (current->key_code == KEY_CODE_LEFT) { // Move cursor - if (current->cursor_x > 0) { + if (current->is_ctrl_down) { // Jump to previous word + int i = current->cursor_x; + while (i > 0 && text[i - 1] == ' ') + i--; + while (i > 0 && text[i - 1] != ' ') + i--; + current->cursor_x = i; + } + else if (current->cursor_x > 0) { current->cursor_x--; } } else if (current->key_code == KEY_CODE_RIGHT) { - if (current->cursor_x < strlen(text)) { + if (current->is_ctrl_down) { // Jump to next word + int i = current->cursor_x; + int len = strlen(text); + while (i < len && text[i] != ' ') + i++; + while (i < len && text[i] == ' ') + i++; + current->cursor_x = i; + } + else if (current->cursor_x < strlen(text)) { current->cursor_x++; } } @@ -1180,17 +1195,21 @@ void ui_update_text_edit(int align, bool editable, bool live_update) { else if (editable && // Write current->key_code != KEY_CODE_SHIFT && current->key_code != KEY_CODE_CAPS_LOCK && current->key_code != KEY_CODE_CONTROL && current->key_code != KEY_CODE_META && current->key_code != KEY_CODE_ALT && current->key_code != KEY_CODE_UP && - current->key_code != KEY_CODE_DOWN && current->key_char >= 32) { + current->key_code != KEY_CODE_DOWN && current->key_char >= 32 && (!current->is_ctrl_down || current->is_alt_down)) { // AltGr = ctrl+alt ui_remove_chars_at(text, current->highlight_anchor, current->cursor_x - current->highlight_anchor); ui_insert_char_at(text, current->highlight_anchor, current->key_char); current->cursor_x = current->cursor_x + 1 > strlen(text) ? strlen(text) : current->cursor_x + 1; } bool selecting = - current->is_shift_down && (current->key_code == KEY_CODE_LEFT || current->key_code == KEY_CODE_RIGHT || current->key_code == KEY_CODE_SHIFT); + current->is_shift_down && (current->key_code == KEY_CODE_LEFT || current->key_code == KEY_CODE_RIGHT || current->key_code == KEY_CODE_SHIFT || + current->key_code == KEY_CODE_HOME || current->key_code == KEY_CODE_END); // isCtrlDown && isAltDown is the condition for AltGr was pressed // AltGr is part of the German keyboard layout and part of key combinations like AltGr + e -> € - if (!selecting && (!current->is_ctrl_down || (current->is_ctrl_down && current->is_alt_down))) { + bool ctrl_nav = current->is_ctrl_down && !current->is_shift_down && + (current->key_code == KEY_CODE_LEFT || current->key_code == KEY_CODE_RIGHT || current->key_code == KEY_CODE_HOME || + current->key_code == KEY_CODE_END); + if (!selecting && (ctrl_nav || !current->is_ctrl_down || (current->is_ctrl_down && current->is_alt_down))) { current->highlight_anchor = current->cursor_x; } } @@ -1218,7 +1237,7 @@ void ui_update_text_edit(int align, bool editable, bool live_update) { } if (editable && ui_is_cut) { // Cut if (current->highlight_anchor == current->cursor_x) { - text[0] = '\0'; + // No selection - nothing to cut } else if (current->highlight_anchor < current->cursor_x) { ui_remove_chars_at(text, current->highlight_anchor, current->cursor_x - current->highlight_anchor); @@ -1227,6 +1246,7 @@ void ui_update_text_edit(int align, bool editable, bool live_update) { else { ui_remove_chars_at(text, current->cursor_x, current->highlight_anchor - current->cursor_x); } + ui_is_cut = false; } float off = UI_TEXT_OFFSET(); @@ -2646,730 +2666,3 @@ void ui_theme_default(ui_theme_t *t) { t->SHADOWS = true; t->VIEWPORT_COL = 0xff070707; } - -#define MATH_PI 3.14159265358979323846 -static ui_handle_t *wheel_selected_handle = NULL; -static ui_handle_t *gradient_selected_handle = NULL; -static ui_handle_t radio_handle; -static int _ELEMENT_OFFSET = 0; -static int _BUTTON_COL = 0; -static bool _SHADOWS = false; -static int text_area_selection_start = -1; -bool ui_text_area_line_numbers = false; -bool ui_text_area_scroll_past_end = false; -ui_text_coloring_t *ui_text_area_coloring = NULL; - -float ui_dist(float x1, float y1, float x2, float y2) { - float vx = x1 - x2; - float vy = y1 - y2; - return sqrtf(vx * vx + vy * vy); -} - -float ui_fract(float f) { - return f - (int)f; -} - -float ui_mix(float x, float y, float a) { - return x * (1.0 - a) + y * a; -} - -float ui_clamp(float x, float min_val, float max_val) { - return fmin(fmax(x, min_val), max_val); -} - -float ui_step(float edge, float x) { - return x < edge ? 0.0 : 1.0; -} - -static const float kx = 1.0; -static const float ky = 2.0 / 3.0; -static const float kz = 1.0 / 3.0; -static const float kw = 3.0; -void ui_hsv_to_rgb(float cr, float cg, float cb, float *out) { - float px = fabs(ui_fract(cr + kx) * 6.0 - kw); - float py = fabs(ui_fract(cr + ky) * 6.0 - kw); - float pz = fabs(ui_fract(cr + kz) * 6.0 - kw); - out[0] = cb * ui_mix(kx, ui_clamp(px - kx, 0.0, 1.0), cg); - out[1] = cb * ui_mix(kx, ui_clamp(py - kx, 0.0, 1.0), cg); - out[2] = cb * ui_mix(kx, ui_clamp(pz - kx, 0.0, 1.0), cg); -} - -static const float Kx = 0.0; -static const float Ky = -1.0 / 3.0; -static const float Kz = 2.0 / 3.0; -static const float Kw = -1.0; -static const float e = 1.0e-10; -void ui_rgb_to_hsv(float cr, float cg, float cb, float *out) { - float px = ui_mix(cb, cg, ui_step(cb, cg)); - float py = ui_mix(cg, cb, ui_step(cb, cg)); - float pz = ui_mix(Kw, Kx, ui_step(cb, cg)); - float pw = ui_mix(Kz, Ky, ui_step(cb, cg)); - float qx = ui_mix(px, cr, ui_step(px, cr)); - float qy = ui_mix(py, py, ui_step(px, cr)); - float qz = ui_mix(pw, pz, ui_step(px, cr)); - float qw = ui_mix(cr, px, ui_step(px, cr)); - float d = qx - fmin(qw, qy); - out[0] = fabs(qz + (qw - qy) / (6.0 * d + e)); - out[1] = d / (qx + e); - out[2] = qx; -} - -float ui_float_input(ui_handle_t *handle, char *label, int align, float precision) { - char tmp[256]; - handle->text = tmp; - sprintf(handle->text, "%f", round(handle->f * precision) / precision); - char *text = ui_text_input(handle, label, align, true, false); - handle->f = atof(text); - return handle->f; -} - -int ui_inline_radio(ui_handle_t *handle, string_array_t *texts, int align) { - ui_t *current = ui_get_current(); - - if (!ui_is_visible(UI_ELEMENT_H())) { - ui_end_element(); - return handle->i; - } - float step = current->_w / texts->length; - int hovered = -1; - if (ui_get_hover(UI_ELEMENT_H())) { - int ix = current->input_x - current->_x - current->_window_x; - for (int i = 0; i < texts->length; ++i) { - if (ix < i * step + step) { - hovered = i; - break; - } - } - } - if (ui_get_released(UI_ELEMENT_H())) { - handle->i = hovered; - handle->changed = current->changed = true; - } - else { - handle->changed = false; - } - - for (int i = 0; i < texts->length; ++i) { - if (handle->i == i) { - draw_set_color(theme->HIGHLIGHT_COL); - if (!current->enabled) { - ui_fade_color(0.25); - } - ui_draw_rect(true, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H()); - } - else if (hovered == i) { - draw_set_color(theme->BUTTON_COL); - if (!current->enabled) { - ui_fade_color(0.25); - } - ui_draw_rect(false, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H()); - } - draw_set_color(theme->TEXT_COL); // Text - current->_x += step * i; - float _w = current->_w; - current->_w = (int)step; - ui_draw_string(texts->buffer[i], theme->TEXT_OFFSET, 0, align, true); - current->_x -= step * i; - current->_w = _w; - } - ui_end_element(); - return handle->i; -} - -uint8_t ui_color_r(uint32_t color) { - return (color & 0x00ff0000) >> 16; -} - -uint8_t ui_color_g(uint32_t color) { - return (color & 0x0000ff00) >> 8; -} - -uint8_t ui_color_b(uint32_t color) { - return (color & 0x000000ff); -} - -uint8_t ui_color_a(uint32_t color) { - return (color) >> 24; -} - -uint32_t ui_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - return (a << 24) | (r << 16) | (g << 8) | b; -} - -int ui_color_wheel(ui_handle_t *handle, bool alpha, float w, float h, bool color_preview, void (*picker)(void *), void *data) { - ui_t *current = ui_get_current(); - if (w < 0) { - w = current->_w; - } - - float r = ui_color_r(handle->color) / 255.0f; - float g = ui_color_g(handle->color) / 255.0f; - float b = ui_color_b(handle->color) / 255.0f; - if (fabs(handle->red - r) > 0.01 || fabs(handle->green - g) > 0.01 || fabs(handle->blue - b) > 0.01) { - handle->red = r; - handle->green = g; - handle->blue = b; - ui_rgb_to_hsv(r, g, b, &handle->hue); - } - - // Wheel - float px = current->_x; - float py = current->_y; - bool scroll = current->current_window != NULL ? current->current_window->scroll_enabled : false; - if (!scroll) { - w -= UI_SCROLL_W(); - px += UI_SCROLL_W() / 2.0; - } - float _x = current->_x; - float _y = current->_y; - float _w = current->_w; - current->_w = (int)(28.0 * UI_SCALE()); - if (picker != NULL && ui_button("P", UI_ALIGN_CENTER, "")) { - (*picker)(data); - current->changed = false; - handle->changed = false; - return handle->color; - } - current->_x = _x; - current->_y = _y; - current->_w = _w; - - uint32_t col = ui_color(round(handle->val * 255.0f), round(handle->val * 255.0f), round(handle->val * 255.0f), 255); - float wheel_h = current->ops->color_wheel->height * (w / current->ops->color_wheel->width); - ui_image(current->ops->color_wheel, col, wheel_h - 2); - - // Picker - float ph = current->_y - py; - float ox = px + w / 2.0; - float oy = py + ph / 2.0; - float cw = w * 0.7; - float cwh = cw / 2.0; - float cx = ox; - float cy = oy + handle->sat * cwh; // Sat is distance from center - float grad_tx = px + 0.897 * w; - float grad_ty = oy - cwh; - float grad_w = 0.0777 * w; - float grad_h = cw; - // Rotate around origin by hue - float theta = handle->hue * (MATH_PI * 2.0); - float cx2 = cos(theta) * (cx - ox) - sin(theta) * (cy - oy) + ox; - float cy2 = sin(theta) * (cx - ox) + cos(theta) * (cy - oy) + oy; - cx = cx2; - cy = cy2; - - current->_x = px - (scroll ? 0 : UI_SCROLL_W() / 2.0); - current->_y = py; - ui_image(current->ops->black_white_gradient, 0xffffffff, wheel_h); - - draw_set_color(0xff000000); - draw_filled_rect(cx - 3.0 * UI_SCALE(), cy - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE()); - draw_set_color(0xffffffff); - draw_filled_rect(cx - 2.0 * UI_SCALE(), cy - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE()); - - draw_set_color(0xff000000); - draw_filled_rect(grad_tx + grad_w / 2.0 - 3.0 * UI_SCALE(), grad_ty + (1.0 - handle->val) * grad_h - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE()); - draw_set_color(0xffffffff); - draw_filled_rect(grad_tx + grad_w / 2.0 - 2.0 * UI_SCALE(), grad_ty + (1.0 - handle->val) * grad_h - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE()); - - float a = ui_color_a(handle->color) / 255.0f; - if (alpha) { - ui_handle_t *alpha_handle = ui_nest(handle, 1); - if (alpha_handle->init) { - alpha_handle->f = round(a * 100.0) / 100.0; - } - a = ui_slider(alpha_handle, "Alpha", 0.0, 1.0, true, 100, true, UI_ALIGN_LEFT, true); - if (alpha_handle->changed) { - handle->changed = current->changed = true; - } - } - - // Mouse picking for color wheel - float gx = ox + current->_window_x; - float gy = oy + current->_window_y; - if (current->input_started && ui_input_in_rect(gx - cwh, gy - cwh, cw, cw)) { - wheel_selected_handle = handle; - } - if (current->input_released && wheel_selected_handle != NULL) { - wheel_selected_handle = NULL; - handle->changed = current->changed = true; - } - if (current->input_down && wheel_selected_handle == handle) { - handle->sat = fmin(ui_dist(gx, gy, current->input_x, current->input_y), cwh) / cwh; - float angle = atan2(current->input_x - gx, current->input_y - gy); - if (angle < 0) { - angle = MATH_PI + (MATH_PI - fabs(angle)); - } - angle = MATH_PI * 2.0 - angle; - handle->hue = angle / (MATH_PI * 2.0); - handle->changed = current->changed = true; - } - // Mouse picking for val - if (current->input_started && ui_input_in_rect(grad_tx + current->_window_x, grad_ty + current->_window_y, grad_w, grad_h)) { - gradient_selected_handle = handle; - } - if (current->input_released && gradient_selected_handle != NULL) { - gradient_selected_handle = NULL; - handle->changed = current->changed = true; - } - if (current->input_down && gradient_selected_handle == handle) { - handle->val = fmax(0.01, fmin(1.0, 1.0 - (current->input_y - grad_ty - current->_window_y) / grad_h)); - handle->changed = current->changed = true; - } - - // Save as rgb - ui_hsv_to_rgb(handle->hue, handle->sat, handle->val, &handle->red); - handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); - - if (color_preview) { - ui_text("", UI_ALIGN_RIGHT, handle->color); - } - - char *strings[] = {"RGB", "HSV", "Hex"}; - string_array_t car; - car.buffer = strings; - car.length = 3; - int pos = ui_inline_radio(&radio_handle, &car, UI_ALIGN_LEFT); - - ui_handle_t *h0 = ui_nest(ui_nest(handle, 0), 0); - ui_handle_t *h1 = ui_nest(ui_nest(handle, 0), 1); - ui_handle_t *h2 = ui_nest(ui_nest(handle, 0), 2); - if (pos == 0) { - h0->f = handle->red; - h1->f = handle->green; - h2->f = handle->blue; - handle->red = ui_slider(h0, "R", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - handle->green = ui_slider(h1, "G", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - handle->blue = ui_slider(h2, "B", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - ui_rgb_to_hsv(handle->red, handle->green, handle->blue, &handle->hue); - handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); - } - else if (pos == 1) { - h0->f = handle->hue; - h1->f = handle->sat; - h2->f = handle->val; - handle->hue = ui_slider(h0, "H", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - handle->sat = ui_slider(h1, "S", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - handle->val = ui_slider(h2, "V", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); - ui_hsv_to_rgb(handle->hue, handle->sat, handle->val, &handle->red); - handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); - } - else if (pos == 2) { - char tmp[16]; - handle->text = tmp; - sprintf(handle->text, "%x", handle->color); - char *hex_code = ui_text_input(handle, "#", UI_ALIGN_LEFT, true, false); - if (strlen(hex_code) >= 1 && hex_code[0] == '#') { // Allow # at the beginning - hex_code = strcpy(hex_code, hex_code + 1); - } - if (strlen(hex_code) == 3) { // 3 digit CSS style values like fa0 --> ffaa00 - hex_code[5] = hex_code[2]; - hex_code[4] = hex_code[2]; - hex_code[3] = hex_code[1]; - hex_code[2] = hex_code[1]; - hex_code[1] = hex_code[0]; - hex_code[6] = '\0'; - } - if (strlen(hex_code) == 4) { // 4 digit CSS style values - hex_code[7] = hex_code[3]; - hex_code[6] = hex_code[3]; - hex_code[5] = hex_code[2]; - hex_code[4] = hex_code[2]; - hex_code[3] = hex_code[1]; - hex_code[2] = hex_code[1]; - hex_code[1] = hex_code[0]; - hex_code[8] = '\0'; - } - if (strlen(hex_code) == 6) { // Make the alpha channel optional - hex_code[7] = hex_code[5]; - hex_code[6] = hex_code[4]; - hex_code[5] = hex_code[3]; - hex_code[4] = hex_code[2]; - hex_code[3] = hex_code[1]; - hex_code[2] = hex_code[0]; - hex_code[0] = 'f'; - hex_code[1] = 'f'; - } -#ifdef _WIN32 - handle->color = _strtoi64(hex_code, NULL, 16); -#else - handle->color = strtol(hex_code, NULL, 16); -#endif - } - if (h0->changed || h1->changed || h2->changed) { - handle->changed = current->changed = true; - } - - // Do not close if user clicks - if (current->input_released && ui_input_in_rect(current->_window_x + px, current->_window_y + py, w, h < 0 ? (current->_y - py) : h) && - current->input_released) { - current->changed = true; - } - - return handle->color; -} - -static void scroll_align(ui_t *current, ui_handle_t *handle) { - // Scroll down - if ((handle->i + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset > current->_h - current->window_header_h) { - current->current_window->scroll_offset -= UI_ELEMENT_H(); - } - // Scroll up - else if ((handle->i + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset < current->window_header_h) { - current->current_window->scroll_offset += UI_ELEMENT_H(); - } -} - -static char *right_align_number(char *s, int number, int length) { - sprintf(s, "%d", number); - while (strlen(s) < length) { - for (int i = strlen(s) + 1; i > 0; --i) { - s[i] = s[i - 1]; - } - s[0] = ' '; - } - return s; -} - -static void handle_line_select(ui_t *current, ui_handle_t *handle) { - if (current->is_shift_down) { - current->highlight_anchor = 0; - if (text_area_selection_start == -1) { - text_area_selection_start = handle->i; - } - } - else - text_area_selection_start = -1; -} - -static int ui_word_count(char *str) { - if (str == NULL || str[0] == '\0') { - return 0; - } - int i = 0; - int count = 1; - while (str[i] != '\0') { - if (str[i] == ' ' || str[i] == '\n') { - count++; - } - i++; - } - return count; -} - -static char temp[128]; - -static char *ui_extract_word(char *str, int word) { - int pos = 0; - int len = strlen(str); - int word_i = 0; - for (int i = 0; i < len; ++i) { - if (str[i] == ' ' || str[i] == '\n') { - word_i++; - continue; - } - if (word_i < word) { - continue; - } - if (word_i > word) { - break; - } - temp[pos++] = str[i]; - } - temp[pos] = 0; - return temp; -} - -static int ui_line_pos(char *str, int line) { - int i = 0; - int current_line = 0; - while (str[i] != '\0' && current_line < line) { - if (str[i] == '\n') { - current_line++; - } - i++; - } - return i; -} - -static char *lines_buffer = NULL; -static int lines_size = 0; - -void ui_text_area_word_wrap(char *lines, ui_handle_t *handle, bool selected) { - bool cursor_set = false; - int cursor_pos = current->cursor_x; - for (int i = 0; i < handle->i; ++i) { - cursor_pos += strlen(ui_extract_line(lines, i)) + 1; // + '\n' - } - bool anchor_set = false; - int anchor_pos = current->highlight_anchor; - for (int i = 0; i < handle->i; ++i) { - anchor_pos += strlen(ui_extract_line(lines, i)) + 1; - } - int word_count = ui_word_count(lines); - char line[1024]; - line[0] = '\0'; - char new_lines[4096]; - new_lines[0] = '\0'; - - for (int i = 0; i < word_count; ++i) { - char *w = ui_extract_word(lines, i); - float spacew = draw_string_width(current->ops->font, current->font_size, " "); - float wordw = spacew + draw_string_width(current->ops->font, current->font_size, w); - float linew = wordw + draw_string_width(current->ops->font, current->font_size, line); - if (linew > current->_w - 10 && linew > wordw) { - if (new_lines[0] != '\0') { - strcat(new_lines, "\n"); - } - strcat(new_lines, line); - line[0] = '\0'; - } - - if (line[0] == '\0') { - strcpy(line, w); - } - else { - strcat(line, " "); - strcat(line, w); - } - - int new_line_count = new_lines[0] == '\0' ? 0 : ui_line_count(new_lines); - int lines_len = new_line_count; - for (int i = 0; i < new_line_count; ++i) { - lines_len += strlen(ui_extract_line(new_lines, i)); - } - - if (selected && !cursor_set && cursor_pos <= lines_len + strlen(line)) { - cursor_set = true; - handle->i = new_line_count; - current->cursor_x = cursor_pos - lines_len; - } - if (selected && !anchor_set && anchor_pos <= lines_len + strlen(line)) { - anchor_set = true; - current->highlight_anchor = anchor_pos - lines_len; - } - } - if (new_lines[0] != '\0') { - strcat(new_lines, "\n"); - } - strcat(new_lines, line); - if (selected) { - strcpy(handle->text, ui_extract_line(new_lines, handle->i)); - strcpy(current->text_selected, handle->text); - } - strcpy(lines, new_lines); -} - -void ui_text_area_draw_line_numbers(int line_count) { - float _y = current->_y; - int _TEXT_COL = theme->TEXT_COL; - theme->TEXT_COL = theme->HOVER_COL; - int max_length = ceil(log(line_count + 0.5) / log(10)); // Express log_10 with natural log - char s[64]; - for (int i = 0; i < line_count; ++i) { - ui_text(right_align_number(&s[0], i + 1, max_length), UI_ALIGN_LEFT, 0x00000000); - current->_y -= UI_ELEMENT_OFFSET(); - } - theme->TEXT_COL = _TEXT_COL; - current->_y = _y; - - sprintf(s, "%d", line_count); - float numbers_w = (strlen(s) * 16 + 4) * UI_SCALE(); - current->_x += numbers_w; - current->_w -= numbers_w - UI_SCROLL_W(); -} - -char *ui_text_area(ui_handle_t *handle, int align, bool editable, char *label, bool word_wrap) { - ui_t *current = ui_get_current(); - handle->text = string_replace_all(handle->text, "\t", " "); - bool selected = current->text_selected_handle == handle; // Text being edited - - int text_size = strlen(handle->text) + 1 + 1024; - if (lines_size < text_size) { - if (lines_buffer != NULL) { - free(lines_buffer); - } - lines_buffer = malloc(text_size); - lines_size = text_size; - } - - char *lines = lines_buffer; - strcpy(lines, handle->text); - int line_count = ui_line_count(lines); - bool show_label = (line_count == 1 && lines[0] == '\0'); - bool key_pressed = selected && current->is_key_pressed; - current->highlight_on_select = false; - current->tab_switch_enabled = false; - if (word_wrap && handle->text[0] != '\0') { - ui_text_area_word_wrap(lines, handle, selected); - } - if (ui_text_area_line_numbers) { - ui_text_area_draw_line_numbers(line_count); - } - int cursor_start_x = current->cursor_x; - - draw_set_color(theme->SEPARATOR_COL); // Background - ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, - line_count * UI_ELEMENT_H() - current->button_offset_y * 2); - - ui_text_coloring_t *_text_coloring = current->text_coloring; - current->text_coloring = ui_text_area_coloring; - - if (current->input_started) { - text_area_selection_start = -1; - } - - int lines_off = 0; - for (int i = 0; i < line_count; ++i) { // Draw lines - char *line = ui_extract_line_off(lines, 0, &lines_off); - // Text input - if ((!selected && ui_get_hover(UI_ELEMENT_H())) || (selected && i == handle->i)) { - handle->i = i; // Set active line - strcpy(handle->text, line); - current->submit_text_handle = NULL; - ui_text_input(handle, show_label ? label : "", align, editable, false); - if (selected && current->key_code != KEY_CODE_RETURN && current->key_code != KEY_CODE_ESCAPE && - strcmp(line, current->text_selected) != 0) { // Edit text - int line_pos = ui_line_pos(lines, i); - ui_remove_chars_at(lines, line_pos, strlen(line)); - ui_insert_chars_at(lines, line_pos, current->text_selected); - } - } - // Text - else { - if (show_label) { - int TEXT_COL = theme->TEXT_COL; - theme->TEXT_COL = theme->LABEL_COL; - ui_text(label, UI_ALIGN_RIGHT, 0x00000000); - theme->TEXT_COL = TEXT_COL; - } - else { - // Multi-line selection highlight - if (text_area_selection_start > -1 && - ((i >= text_area_selection_start && i < handle->i) || (i <= text_area_selection_start && i > handle->i))) { - int line_height = UI_ELEMENT_H(); - int cursor_height = line_height - current->button_offset_y * 3.0; - int linew = draw_string_width(current->ops->font, current->font_size, line); - draw_set_color(theme->HOVER_COL + 0x00202020); - draw_filled_rect(current->_x + UI_ELEMENT_OFFSET() * 2.0, current->_y + current->button_offset_y * 1.5, linew, cursor_height); - } - ui_text(line, align, 0x00000000); - } - } - current->_y -= UI_ELEMENT_OFFSET(); - } - current->_y += UI_ELEMENT_OFFSET(); - current->text_coloring = _text_coloring; - - if (ui_text_area_scroll_past_end) { - current->_y += current->_h - current->window_header_h - UI_ELEMENT_H() - UI_ELEMENT_OFFSET(); - } - - if (key_pressed) { - // Move cursor vertically - if (current->key_code == KEY_CODE_DOWN && handle->i < line_count - 1) { - handle_line_select(current, handle); - handle->i++; - scroll_align(current, handle); - } - if (current->key_code == KEY_CODE_UP && handle->i > 0) { - handle_line_select(current, handle); - handle->i--; - scroll_align(current, handle); - } - // New line - if (editable && current->key_code == KEY_CODE_RETURN && !word_wrap) { - handle->i++; - ui_insert_char_at(lines, ui_line_pos(lines, handle->i - 1) + current->cursor_x, '\n'); - ui_start_text_edit(handle, UI_ALIGN_LEFT); - current->cursor_x = current->highlight_anchor = 0; - scroll_align(current, handle); - } - // Delete line - if (editable && current->key_code == KEY_CODE_BACKSPACE && cursor_start_x == 0 && handle->i > 0) { - handle->i--; - current->cursor_x = current->highlight_anchor = strlen(ui_extract_line(lines, handle->i)); - ui_remove_chars_at(lines, ui_line_pos(lines, handle->i + 1) - 1, 1); // Remove '\n' of the previous line - scroll_align(current, handle); - } - strcpy(current->text_selected, ui_extract_line(lines, handle->i)); - } - - current->highlight_on_select = true; - current->tab_switch_enabled = true; - handle->text = string_copy(lines); - return handle->text; -} - -float UI_MENUBAR_H() { - ui_t *current = ui_get_current(); - return UI_BUTTON_H() * 1.1 + 2.0 + current->button_offset_y; -} - -void ui_begin_menu() { - ui_t *current = ui_get_current(); - _ELEMENT_OFFSET = theme->ELEMENT_OFFSET; - _BUTTON_COL = theme->BUTTON_COL; - _SHADOWS = theme->SHADOWS; - theme->ELEMENT_OFFSET = 0; - theme->BUTTON_COL = theme->SEPARATOR_COL; - theme->SHADOWS = false; - draw_set_color(theme->SEPARATOR_COL); - draw_filled_rect(0, 0, current->_window_w, UI_MENUBAR_H()); -} - -void ui_end_menu() { - ui_t *current = ui_get_current(); - theme->ELEMENT_OFFSET = _ELEMENT_OFFSET; - theme->BUTTON_COL = _BUTTON_COL; - theme->SHADOWS = _SHADOWS; -} - -bool ui_menubar_button(char *text) { - ui_t *current = ui_get_current(); - current->_w = draw_string_width(current->ops->font, current->font_size, text) + 25.0 * UI_SCALE(); - return ui_button(text, UI_ALIGN_CENTER, ""); -} - -const char *ui_theme_keys[] = {"WINDOW_BG_COL", "HOVER_COL", "BUTTON_COL", "PRESSED_COL", "TEXT_COL", "LABEL_COL", - "SEPARATOR_COL", "HIGHLIGHT_COL", "FONT_SIZE", "ELEMENT_W", "ELEMENT_H", "ELEMENT_OFFSET", "ARROW_SIZE", - "BUTTON_H", "CHECK_SIZE", "CHECK_SELECT_SIZE", "SCROLL_W", "SCROLL_MINI_W", "TEXT_OFFSET", "TAB_W", - "FILL_WINDOW_BG", "FILL_BUTTON_BG", "LINK_STYLE", "FULL_TABS", "ROUND_CORNERS", "SHADOWS", "VIEWPORT_COL"}; - -int ui_theme_keys_count = sizeof(ui_theme_keys) / sizeof(ui_theme_keys[0]); - -//// - -f32 ui_MENUBAR_H(ui_t *ui) { - f32 button_offset_y = (ui->ops->theme->ELEMENT_H * UI_SCALE() - ui->ops->theme->BUTTON_H * UI_SCALE()) / (float)2; - return ui->ops->theme->BUTTON_H * UI_SCALE() * 1.1 + 2 + button_offset_y; -} - -extern any_map_t *ui_children; - -ui_handle_t *ui_handle(char *s) { - ui_handle_t *h = any_map_get(ui_children, s); - if (h == NULL) { - h = ui_handle_create(); - any_map_set(ui_children, s, h); - return h; - } - h->init = false; - return h; -} - -ui_t *ui_create(ui_options_t *ops) { - ui_t *raw = GC_ALLOC_INIT(ui_t, {0}); - ui_init(raw, ops); - return raw; -} - -ui_theme_t *ui_theme_create() { - ui_theme_t *raw = GC_ALLOC_INIT(ui_theme_t, {0}); - ui_theme_default(raw); - return raw; -} - -void ui_set_font(ui_t *ui, draw_font_t *font) { - draw_font_init(font); - ui->ops->font = font; -} diff --git a/base/sources/iron_ui.h b/base/sources/iron_ui.h index dbfead1e..f8b9397d 100644 --- a/base/sources/iron_ui.h +++ b/base/sources/iron_ui.h @@ -117,10 +117,10 @@ typedef struct ui_text_extract { } ui_text_extract_t; typedef struct ui_coloring { - uint32_t color; + uint32_t color; string_array_t *start; - char *end; - bool separated; + char *end; + bool separated; } ui_coloring_t; typedef struct ui_coloring_array { @@ -193,7 +193,8 @@ typedef struct ui { float input_started_time; int cursor_x; // Text input int highlight_anchor; - f32_array_t *ratios; // Splitting rows + int cursor_sticky_x; // Remember column for vertical navigation + f32_array_t *ratios; // Splitting rows int current_ratio; float x_before_split; int w_before_split; @@ -236,43 +237,43 @@ typedef struct ui { float restore_x; float restore_y; - ui_handle_t *text_selected_handle; - char text_selected[1024]; - ui_handle_t *submit_text_handle; - char text_to_submit[1024]; - bool tab_pressed; - ui_handle_t *tab_pressed_handle; - ui_handle_t *combo_selected_handle; - ui_handle_t *combo_selected_window; - int combo_selected_align; + ui_handle_t *text_selected_handle; + char text_selected[1024]; + ui_handle_t *submit_text_handle; + char text_to_submit[1024]; + bool tab_pressed; + ui_handle_t *tab_pressed_handle; + ui_handle_t *combo_selected_handle; + ui_handle_t *combo_selected_window; + int combo_selected_align; string_array_t *combo_selected_texts; - char *combo_selected_label; - int combo_selected_x; - int combo_selected_y; - int combo_selected_w; - int combo_selected_texts_filtered; - bool combo_search_bar; - ui_handle_t *submit_combo_handle; - int combo_to_submit; - int combo_initial_value; - char tooltip_text[512]; - gpu_texture_t *tooltip_img; - int tooltip_img_max_width; - float tooltip_x; - float tooltip_y; - bool tooltip_shown; - bool tooltip_wait; - double tooltip_time; - char tab_names[16][256]; - uint32_t tab_colors[16]; - bool tab_enabled[16]; - int tab_count; // Number of tab calls since window begin - ui_handle_t *tab_handle; - float tab_scroll; - bool tab_vertical; - bool tab_align_right; - bool sticky; - bool scissor; + char *combo_selected_label; + int combo_selected_x; + int combo_selected_y; + int combo_selected_w; + int combo_selected_texts_filtered; + bool combo_search_bar; + ui_handle_t *submit_combo_handle; + int combo_to_submit; + int combo_initial_value; + char tooltip_text[512]; + gpu_texture_t *tooltip_img; + int tooltip_img_max_width; + float tooltip_x; + float tooltip_y; + bool tooltip_shown; + bool tooltip_wait; + double tooltip_time; + char tab_names[16][256]; + uint32_t tab_colors[16]; + bool tab_enabled[16]; + int tab_count; // Number of tab calls since window begin + ui_handle_t *tab_handle; + float tab_scroll; + bool tab_vertical; + bool tab_align_right; + bool sticky; + bool scissor; bool elements_baked; gpu_texture_t check_select_image; @@ -346,6 +347,7 @@ void ui_fill(float x, float y, float w, float h, uint32_t color); void ui_rect(float x, float y, float w, float h, uint32_t color, float strength); int ui_line_count(char *str); char *ui_extract_line(char *str, int line); +char *ui_extract_line_off(char *str, int line, int *off); bool ui_is_visible(float elem_h); void ui_end_element(); void ui_end_element_of_size(float element_size); @@ -378,11 +380,13 @@ float UI_HEADER_DRAG_H(); float UI_FLASH_SPEED(); float UI_TOOLTIP_DELAY(); -extern bool ui_touch_control; +extern bool ui_touch_control; extern float ui_touch_speed; -extern bool ui_is_cut; -extern bool ui_is_copy; -extern bool ui_is_paste; +extern bool ui_is_cut; +extern bool ui_is_copy; +extern bool ui_is_paste; +extern char ui_text_to_paste[1024]; +extern char ui_text_to_copy[1024]; extern void (*ui_on_border_hover)(ui_handle_t *, int); extern void (*ui_on_tab_drop)(ui_handle_t *, int, ui_handle_t *, int); extern const char *ui_theme_keys[]; @@ -407,3 +411,164 @@ uint32_t ui_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a); extern bool ui_text_area_line_numbers; extern bool ui_text_area_scroll_past_end; extern ui_text_coloring_t *ui_text_area_coloring; + +typedef struct ui_canvas_control { + float pan_x; + float pan_y; + float zoom; + bool controls_down; +} ui_canvas_control_t; + +typedef struct ui_node_socket { + int id; + int node_id; + char *name; + char *type; + uint32_t color; + f32_array_t *default_value; + float min; + float max; + float precision; + int display; +} ui_node_socket_t; + +typedef struct ui_node_button { + char *name; + char *type; + int output; + f32_array_t *default_value; + u8_array_t *data; + float min; + float max; + float precision; + float height; +} ui_node_button_t; + +typedef struct ui_node_socket_array { + ui_node_socket_t **buffer; + int length; + int capacity; +} ui_node_socket_array_t; + +typedef struct ui_node_button_array { + ui_node_button_t **buffer; + int length; + int capacity; +} ui_node_button_array_t; + +typedef enum ui_node_flag { + UI_NODE_FLAG_NONE = 0, + UI_NODE_FLAG_COLLAPSED = 1, + UI_NODE_FLAG_PREVIEW = 2, +} ui_node_flag_t; + +typedef struct ui_node { + int id; + char *name; + char *type; + float x; + float y; + uint32_t color; + ui_node_socket_array_t *inputs; + ui_node_socket_array_t *outputs; + ui_node_button_array_t *buttons; + float width; + int flags; // ui_node_flag_t +} ui_node_t; + +typedef struct ui_node_link { + int id; + int from_id; + int from_socket; + int to_id; + int to_socket; +} ui_node_link_t; + +typedef struct ui_node_array { + ui_node_t **buffer; + int length; + int capacity; +} ui_node_array_t; + +typedef struct ui_node_link_array { + ui_node_link_t **buffer; + int length; + int capacity; +} ui_node_link_array_t; + +typedef struct ui_node_canvas { + char *name; + ui_node_array_t *nodes; + ui_node_link_array_t *links; +} ui_node_canvas_t; + +typedef struct ui_nodes { + bool nodes_drag; + i32_array_t *nodes_selected_id; + float pan_x; + float pan_y; + float zoom; + int uiw; + int uih; + bool _input_started; + void (*color_picker_callback)(uint32_t); + void *color_picker_callback_data; + float scale_factor; + float ELEMENT_H; + bool dragged; + ui_node_t *move_on_top; + int link_drag_id; + bool is_new_link; + int snap_from_id; + int snap_to_id; + int snap_socket; + float snap_x; + float snap_y; + ui_handle_t *handle; +} ui_nodes_t; + +void ui_nodes_init(ui_nodes_t *nodes); +void ui_node_canvas(ui_nodes_t *nodes, ui_node_canvas_t *canvas); +void ui_nodes_rgba_popup(ui_handle_t *nhandle, float *val, int x, int y); + +void ui_remove_node(ui_node_t *n, ui_node_canvas_t *canvas); + +float UI_NODES_SCALE(); +float UI_NODES_PAN_X(); +float UI_NODES_PAN_Y(); +extern char *ui_clipboard; +extern string_array_t *ui_nodes_exclude_remove; +extern bool ui_nodes_socket_released; +extern string_array_t *(*ui_nodes_enum_texts)(char *); +extern gpu_texture_t *(*ui_nodes_preview_image)(ui_node_t *); +extern void (*ui_nodes_on_custom_button)(int, char *); +extern ui_canvas_control_t *(*ui_nodes_on_canvas_control)(void); +extern void (*ui_nodes_on_canvas_released)(void); +extern void (*ui_nodes_on_socket_released)(int); +extern void (*ui_nodes_on_link_drag)(int, bool); +extern void (*ui_nodes_on_node_remove)(ui_node_t *); +extern void (*ui_nodes_on_node_changed)(ui_node_t *); +extern bool ui_nodes_grid_snap; + +void ui_node_canvas_encode(ui_node_canvas_t *canvas); +uint32_t ui_node_canvas_encoded_size(ui_node_canvas_t *canvas); +char *ui_node_canvas_to_json(ui_node_canvas_t *canvas); + +float UI_NODE_X(ui_node_t *node); +float UI_NODE_Y(ui_node_t *node); +float UI_NODE_W(ui_node_t *node); +float UI_NODE_H(ui_node_canvas_t *canvas, ui_node_t *node); +float UI_OUTPUT_Y(ui_node_t *node, int pos); +float UI_INPUT_Y(ui_node_canvas_t *canvas, ui_node_t *node, int pos); +float UI_OUTPUTS_H(ui_node_t *node, int length); +float UI_BUTTONS_H(ui_node_t *node); +float UI_LINE_H(); +float UI_NODES_PAN_X(); +float UI_NODES_PAN_Y(); + +float ui_p(float f); +int ui_get_socket_id(ui_node_array_t *nodes); +ui_node_link_t *ui_get_link(ui_node_link_array_t *links, int id); +int ui_next_link_id(ui_node_link_array_t *links); +ui_node_t *ui_get_node(ui_node_array_t *nodes, int id); +int ui_next_node_id(ui_node_array_t *nodes); diff --git a/base/sources/iron_ui_ext.c b/base/sources/iron_ui_ext.c new file mode 100644 index 00000000..cd20f366 --- /dev/null +++ b/base/sources/iron_ui_ext.c @@ -0,0 +1,950 @@ +#include "iron_draw.h" +#include "iron_gc.h" +#include "iron_string.h" +#include "iron_system.h" +#include "iron_ui.h" +#include +#include +#include +#include + +static ui_handle_t *wheel_selected_handle = NULL; +static ui_handle_t *gradient_selected_handle = NULL; +static ui_handle_t radio_handle; +static int _ELEMENT_OFFSET = 0; +static int _BUTTON_COL = 0; +static bool _SHADOWS = false; +static int text_area_selection_start = -1; +static int text_area_selection_start_col = 0; +bool ui_text_area_line_numbers = false; +bool ui_text_area_scroll_past_end = false; +ui_text_coloring_t *ui_text_area_coloring = NULL; + +float ui_dist(float x1, float y1, float x2, float y2) { + float vx = x1 - x2; + float vy = y1 - y2; + return sqrtf(vx * vx + vy * vy); +} + +float ui_fract(float f) { + return f - (int)f; +} + +float ui_mix(float x, float y, float a) { + return x * (1.0 - a) + y * a; +} + +float ui_clamp(float x, float min_val, float max_val) { + return fmin(fmax(x, min_val), max_val); +} + +float ui_step(float edge, float x) { + return x < edge ? 0.0 : 1.0; +} + +static const float kx = 1.0; +static const float ky = 2.0 / 3.0; +static const float kz = 1.0 / 3.0; +static const float kw = 3.0; +void ui_hsv_to_rgb(float cr, float cg, float cb, float *out) { + float px = fabs(ui_fract(cr + kx) * 6.0 - kw); + float py = fabs(ui_fract(cr + ky) * 6.0 - kw); + float pz = fabs(ui_fract(cr + kz) * 6.0 - kw); + out[0] = cb * ui_mix(kx, ui_clamp(px - kx, 0.0, 1.0), cg); + out[1] = cb * ui_mix(kx, ui_clamp(py - kx, 0.0, 1.0), cg); + out[2] = cb * ui_mix(kx, ui_clamp(pz - kx, 0.0, 1.0), cg); +} + +static const float Kx = 0.0; +static const float Ky = -1.0 / 3.0; +static const float Kz = 2.0 / 3.0; +static const float Kw = -1.0; +static const float e = 1.0e-10; +void ui_rgb_to_hsv(float cr, float cg, float cb, float *out) { + float px = ui_mix(cb, cg, ui_step(cb, cg)); + float py = ui_mix(cg, cb, ui_step(cb, cg)); + float pz = ui_mix(Kw, Kx, ui_step(cb, cg)); + float pw = ui_mix(Kz, Ky, ui_step(cb, cg)); + float qx = ui_mix(px, cr, ui_step(px, cr)); + float qy = ui_mix(py, py, ui_step(px, cr)); + float qz = ui_mix(pw, pz, ui_step(px, cr)); + float qw = ui_mix(cr, px, ui_step(px, cr)); + float d = qx - fmin(qw, qy); + out[0] = fabs(qz + (qw - qy) / (6.0 * d + e)); + out[1] = d / (qx + e); + out[2] = qx; +} + +float ui_float_input(ui_handle_t *handle, char *label, int align, float precision) { + char tmp[256]; + handle->text = tmp; + sprintf(handle->text, "%f", round(handle->f * precision) / precision); + char *text = ui_text_input(handle, label, align, true, false); + handle->f = atof(text); + return handle->f; +} + +int ui_inline_radio(ui_handle_t *handle, string_array_t *texts, int align) { + ui_t *current = ui_get_current(); + + if (!ui_is_visible(UI_ELEMENT_H())) { + ui_end_element(); + return handle->i; + } + float step = current->_w / texts->length; + int hovered = -1; + if (ui_get_hover(UI_ELEMENT_H())) { + int ix = current->input_x - current->_x - current->_window_x; + for (int i = 0; i < texts->length; ++i) { + if (ix < i * step + step) { + hovered = i; + break; + } + } + } + if (ui_get_released(UI_ELEMENT_H())) { + handle->i = hovered; + handle->changed = current->changed = true; + } + else { + handle->changed = false; + } + + ui_theme_t *theme = ui_get_current()->ops->theme; + for (int i = 0; i < texts->length; ++i) { + if (handle->i == i) { + draw_set_color(theme->HIGHLIGHT_COL); + if (!current->enabled) { + ui_fade_color(0.25); + } + ui_draw_rect(true, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H()); + } + else if (hovered == i) { + draw_set_color(theme->BUTTON_COL); + if (!current->enabled) { + ui_fade_color(0.25); + } + ui_draw_rect(false, current->_x + step * i, current->_y + current->button_offset_y, step, UI_BUTTON_H()); + } + draw_set_color(theme->TEXT_COL); // Text + current->_x += step * i; + float _w = current->_w; + current->_w = (int)step; + ui_draw_string(texts->buffer[i], theme->TEXT_OFFSET, 0, align, true); + current->_x -= step * i; + current->_w = _w; + } + ui_end_element(); + return handle->i; +} + +uint8_t ui_color_r(uint32_t color) { + return (color & 0x00ff0000) >> 16; +} + +uint8_t ui_color_g(uint32_t color) { + return (color & 0x0000ff00) >> 8; +} + +uint8_t ui_color_b(uint32_t color) { + return (color & 0x000000ff); +} + +uint8_t ui_color_a(uint32_t color) { + return (color) >> 24; +} + +uint32_t ui_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + return (a << 24) | (r << 16) | (g << 8) | b; +} + +int ui_color_wheel(ui_handle_t *handle, bool alpha, float w, float h, bool color_preview, void (*picker)(void *), void *data) { + ui_t *current = ui_get_current(); + if (w < 0) { + w = current->_w; + } + + float r = ui_color_r(handle->color) / 255.0f; + float g = ui_color_g(handle->color) / 255.0f; + float b = ui_color_b(handle->color) / 255.0f; + if (fabs(handle->red - r) > 0.01 || fabs(handle->green - g) > 0.01 || fabs(handle->blue - b) > 0.01) { + handle->red = r; + handle->green = g; + handle->blue = b; + ui_rgb_to_hsv(r, g, b, &handle->hue); + } + + // Wheel + float px = current->_x; + float py = current->_y; + bool scroll = current->current_window != NULL ? current->current_window->scroll_enabled : false; + if (!scroll) { + w -= UI_SCROLL_W(); + px += UI_SCROLL_W() / 2.0; + } + float _x = current->_x; + float _y = current->_y; + float _w = current->_w; + current->_w = (int)(28.0 * UI_SCALE()); + if (picker != NULL && ui_button("P", UI_ALIGN_CENTER, "")) { + (*picker)(data); + current->changed = false; + handle->changed = false; + return handle->color; + } + current->_x = _x; + current->_y = _y; + current->_w = _w; + + ui_theme_t *theme = current->ops->theme; + uint32_t col = ui_color(round(handle->val * 255.0f), round(handle->val * 255.0f), round(handle->val * 255.0f), 255); + float wheel_h = current->ops->color_wheel->height * (w / current->ops->color_wheel->width); + ui_image(current->ops->color_wheel, col, wheel_h - 2); + + // Picker + float ph = current->_y - py; + float ox = px + w / 2.0; + float oy = py + ph / 2.0; + float cw = w * 0.7; + float cwh = cw / 2.0; + float cx = ox; + float cy = oy + handle->sat * cwh; // Sat is distance from center + float grad_tx = px + 0.897 * w; + float grad_ty = oy - cwh; + float grad_w = 0.0777 * w; + float grad_h = cw; + // Rotate around origin by hue + float theta = handle->hue * (IRON_PI * 2.0); + float cx2 = cos(theta) * (cx - ox) - sin(theta) * (cy - oy) + ox; + float cy2 = sin(theta) * (cx - ox) + cos(theta) * (cy - oy) + oy; + cx = cx2; + cy = cy2; + + current->_x = px - (scroll ? 0 : UI_SCROLL_W() / 2.0); + current->_y = py; + ui_image(current->ops->black_white_gradient, 0xffffffff, wheel_h); + + draw_set_color(0xff000000); + draw_filled_rect(cx - 3.0 * UI_SCALE(), cy - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE()); + draw_set_color(0xffffffff); + draw_filled_rect(cx - 2.0 * UI_SCALE(), cy - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE()); + + draw_set_color(0xff000000); + draw_filled_rect(grad_tx + grad_w / 2.0 - 3.0 * UI_SCALE(), grad_ty + (1.0 - handle->val) * grad_h - 3.0 * UI_SCALE(), 6.0 * UI_SCALE(), 6.0 * UI_SCALE()); + draw_set_color(0xffffffff); + draw_filled_rect(grad_tx + grad_w / 2.0 - 2.0 * UI_SCALE(), grad_ty + (1.0 - handle->val) * grad_h - 2.0 * UI_SCALE(), 4.0 * UI_SCALE(), 4.0 * UI_SCALE()); + + float a = ui_color_a(handle->color) / 255.0f; + if (alpha) { + ui_handle_t *alpha_handle = ui_nest(handle, 1); + if (alpha_handle->init) { + alpha_handle->f = round(a * 100.0) / 100.0; + } + a = ui_slider(alpha_handle, "Alpha", 0.0, 1.0, true, 100, true, UI_ALIGN_LEFT, true); + if (alpha_handle->changed) { + handle->changed = current->changed = true; + } + } + + // Mouse picking for color wheel + float gx = ox + current->_window_x; + float gy = oy + current->_window_y; + if (current->input_started && ui_input_in_rect(gx - cwh, gy - cwh, cw, cw)) { + wheel_selected_handle = handle; + } + if (current->input_released && wheel_selected_handle != NULL) { + wheel_selected_handle = NULL; + handle->changed = current->changed = true; + } + if (current->input_down && wheel_selected_handle == handle) { + handle->sat = fmin(ui_dist(gx, gy, current->input_x, current->input_y), cwh) / cwh; + float angle = atan2(current->input_x - gx, current->input_y - gy); + if (angle < 0) { + angle = IRON_PI + (IRON_PI - fabs(angle)); + } + angle = IRON_PI * 2.0 - angle; + handle->hue = angle / (IRON_PI * 2.0); + handle->changed = current->changed = true; + } + // Mouse picking for val + if (current->input_started && ui_input_in_rect(grad_tx + current->_window_x, grad_ty + current->_window_y, grad_w, grad_h)) { + gradient_selected_handle = handle; + } + if (current->input_released && gradient_selected_handle != NULL) { + gradient_selected_handle = NULL; + handle->changed = current->changed = true; + } + if (current->input_down && gradient_selected_handle == handle) { + handle->val = fmax(0.01, fmin(1.0, 1.0 - (current->input_y - grad_ty - current->_window_y) / grad_h)); + handle->changed = current->changed = true; + } + + // Save as rgb + ui_hsv_to_rgb(handle->hue, handle->sat, handle->val, &handle->red); + handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); + + if (color_preview) { + ui_text("", UI_ALIGN_RIGHT, handle->color); + } + + char *strings[] = {"RGB", "HSV", "Hex"}; + string_array_t car; + car.buffer = strings; + car.length = 3; + int pos = ui_inline_radio(&radio_handle, &car, UI_ALIGN_LEFT); + + ui_handle_t *h0 = ui_nest(ui_nest(handle, 0), 0); + ui_handle_t *h1 = ui_nest(ui_nest(handle, 0), 1); + ui_handle_t *h2 = ui_nest(ui_nest(handle, 0), 2); + if (pos == 0) { + h0->f = handle->red; + h1->f = handle->green; + h2->f = handle->blue; + handle->red = ui_slider(h0, "R", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + handle->green = ui_slider(h1, "G", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + handle->blue = ui_slider(h2, "B", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + ui_rgb_to_hsv(handle->red, handle->green, handle->blue, &handle->hue); + handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); + } + else if (pos == 1) { + h0->f = handle->hue; + h1->f = handle->sat; + h2->f = handle->val; + handle->hue = ui_slider(h0, "H", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + handle->sat = ui_slider(h1, "S", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + handle->val = ui_slider(h2, "V", 0, 1, true, 100, true, UI_ALIGN_LEFT, true); + ui_hsv_to_rgb(handle->hue, handle->sat, handle->val, &handle->red); + handle->color = ui_color(round(handle->red * 255.0), round(handle->green * 255.0), round(handle->blue * 255.0), round(a * 255.0)); + } + else if (pos == 2) { + char tmp[16]; + handle->text = tmp; + sprintf(handle->text, "%x", handle->color); + char *hex_code = ui_text_input(handle, "#", UI_ALIGN_LEFT, true, false); + if (strlen(hex_code) >= 1 && hex_code[0] == '#') { // Allow # at the beginning + hex_code = strcpy(hex_code, hex_code + 1); + } + if (strlen(hex_code) == 3) { // 3 digit CSS style values like fa0 --> ffaa00 + hex_code[5] = hex_code[2]; + hex_code[4] = hex_code[2]; + hex_code[3] = hex_code[1]; + hex_code[2] = hex_code[1]; + hex_code[1] = hex_code[0]; + hex_code[6] = '\0'; + } + if (strlen(hex_code) == 4) { // 4 digit CSS style values + hex_code[7] = hex_code[3]; + hex_code[6] = hex_code[3]; + hex_code[5] = hex_code[2]; + hex_code[4] = hex_code[2]; + hex_code[3] = hex_code[1]; + hex_code[2] = hex_code[1]; + hex_code[1] = hex_code[0]; + hex_code[8] = '\0'; + } + if (strlen(hex_code) == 6) { // Make the alpha channel optional + hex_code[7] = hex_code[5]; + hex_code[6] = hex_code[4]; + hex_code[5] = hex_code[3]; + hex_code[4] = hex_code[2]; + hex_code[3] = hex_code[1]; + hex_code[2] = hex_code[0]; + hex_code[0] = 'f'; + hex_code[1] = 'f'; + } +#ifdef _WIN32 + handle->color = _strtoi64(hex_code, NULL, 16); +#else + handle->color = strtol(hex_code, NULL, 16); +#endif + } + if (h0->changed || h1->changed || h2->changed) { + handle->changed = current->changed = true; + } + + // Do not close if user clicks + if (current->input_released && ui_input_in_rect(current->_window_x + px, current->_window_y + py, w, h < 0 ? (current->_y - py) : h) && + current->input_released) { + current->changed = true; + } + + return handle->color; +} + +static void scroll_align(ui_t *current, ui_handle_t *handle) { + // Scroll down + if ((handle->i + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset > current->_h - current->window_header_h) { + current->current_window->scroll_offset -= UI_ELEMENT_H(); + } + // Scroll up + else if ((handle->i + 1) * UI_ELEMENT_H() + current->current_window->scroll_offset < current->window_header_h) { + current->current_window->scroll_offset += UI_ELEMENT_H(); + } +} + +static char *right_align_number(char *s, int number, int length) { + sprintf(s, "%d", number); + while (strlen(s) < length) { + for (int i = strlen(s) + 1; i > 0; --i) { + s[i] = s[i - 1]; + } + s[0] = ' '; + } + return s; +} + +static void handle_line_select(ui_t *current, ui_handle_t *handle) { + if (current->is_shift_down) { + if (text_area_selection_start == -1) { + text_area_selection_start = handle->i; + text_area_selection_start_col = current->cursor_x; + } + current->highlight_anchor = 0; + } + else + text_area_selection_start = -1; +} + +static int ui_word_count(char *str) { + if (str == NULL || str[0] == '\0') { + return 0; + } + int i = 0; + int count = 1; + while (str[i] != '\0') { + if (str[i] == ' ' || str[i] == '\n') { + count++; + } + i++; + } + return count; +} + +static char temp[128]; + +static char *ui_extract_word(char *str, int word) { + int pos = 0; + int len = strlen(str); + int word_i = 0; + for (int i = 0; i < len; ++i) { + if (str[i] == ' ' || str[i] == '\n') { + word_i++; + continue; + } + if (word_i < word) { + continue; + } + if (word_i > word) { + break; + } + temp[pos++] = str[i]; + } + temp[pos] = 0; + return temp; +} + +static int ui_line_pos(char *str, int line) { + int i = 0; + int current_line = 0; + while (str[i] != '\0' && current_line < line) { + if (str[i] == '\n') { + current_line++; + } + i++; + } + return i; +} + +static char *lines_buffer = NULL; +static int lines_size = 0; + +void ui_text_area_word_wrap(char *lines, ui_handle_t *handle, bool selected) { + ui_t *current = ui_get_current(); + bool cursor_set = false; + int cursor_pos = current->cursor_x; + for (int i = 0; i < handle->i; ++i) { + cursor_pos += strlen(ui_extract_line(lines, i)) + 1; // + '\n' + } + bool anchor_set = false; + int anchor_pos = current->highlight_anchor; + for (int i = 0; i < handle->i; ++i) { + anchor_pos += strlen(ui_extract_line(lines, i)) + 1; + } + int word_count = ui_word_count(lines); + char line[1024]; + line[0] = '\0'; + char new_lines[4096]; + new_lines[0] = '\0'; + + for (int i = 0; i < word_count; ++i) { + char *w = ui_extract_word(lines, i); + float spacew = draw_string_width(current->ops->font, current->font_size, " "); + float wordw = spacew + draw_string_width(current->ops->font, current->font_size, w); + float linew = wordw + draw_string_width(current->ops->font, current->font_size, line); + if (linew > current->_w - 10 && linew > wordw) { + if (new_lines[0] != '\0') { + strcat(new_lines, "\n"); + } + strcat(new_lines, line); + line[0] = '\0'; + } + + if (line[0] == '\0') { + strcpy(line, w); + } + else { + strcat(line, " "); + strcat(line, w); + } + + int new_line_count = new_lines[0] == '\0' ? 0 : ui_line_count(new_lines); + int lines_len = new_line_count; + for (int i = 0; i < new_line_count; ++i) { + lines_len += strlen(ui_extract_line(new_lines, i)); + } + + if (selected && !cursor_set && cursor_pos <= lines_len + strlen(line)) { + cursor_set = true; + handle->i = new_line_count; + current->cursor_x = cursor_pos - lines_len; + } + if (selected && !anchor_set && anchor_pos <= lines_len + strlen(line)) { + anchor_set = true; + current->highlight_anchor = anchor_pos - lines_len; + } + } + if (new_lines[0] != '\0') { + strcat(new_lines, "\n"); + } + strcat(new_lines, line); + if (selected) { + strcpy(handle->text, ui_extract_line(new_lines, handle->i)); + strcpy(current->text_selected, handle->text); + } + strcpy(lines, new_lines); +} + +void ui_text_area_draw_line_numbers(int line_count) { + ui_t *current = ui_get_current(); + ui_theme_t *theme = current->ops->theme; + float _y = current->_y; + int _TEXT_COL = theme->TEXT_COL; + theme->TEXT_COL = theme->HOVER_COL; + int max_length = ceil(log(line_count + 0.5) / log(10)); // Express log_10 with natural log + char s[64]; + for (int i = 0; i < line_count; ++i) { + ui_text(right_align_number(&s[0], i + 1, max_length), UI_ALIGN_LEFT, 0x00000000); + current->_y -= UI_ELEMENT_OFFSET(); + } + theme->TEXT_COL = _TEXT_COL; + current->_y = _y; + + sprintf(s, "%d", line_count); + float numbers_w = (strlen(s) * 16 + 4) * UI_SCALE(); + current->_x += numbers_w; + current->_w -= numbers_w - UI_SCROLL_W(); +} + +char *ui_text_area(ui_handle_t *handle, int align, bool editable, char *label, bool word_wrap) { + ui_t *current = ui_get_current(); + handle->text = string_replace_all(handle->text, "\t", " "); + bool selected = current->text_selected_handle == handle; // Text being edited + + int text_size = strlen(handle->text) + 1 + 1024; + if (lines_size < text_size) { + if (lines_buffer != NULL) { + free(lines_buffer); + } + lines_buffer = malloc(text_size); + lines_size = text_size; + } + + char *lines = lines_buffer; + strcpy(lines, handle->text); + int line_count = ui_line_count(lines); + bool show_label = (line_count == 1 && lines[0] == '\0'); + bool key_pressed = selected && current->is_key_pressed; + current->highlight_on_select = false; + current->tab_switch_enabled = false; + if (word_wrap && handle->text[0] != '\0') { + ui_text_area_word_wrap(lines, handle, selected); + } + if (ui_text_area_line_numbers) { + ui_text_area_draw_line_numbers(line_count); + } + int cursor_start_x = current->cursor_x; + int active_line_len = selected ? (int)strlen(ui_extract_line(lines, handle->i)) : 0; + + ui_theme_t *theme = current->ops->theme; + draw_set_color(theme->SEPARATOR_COL); // Background + ui_draw_rect(true, current->_x + current->button_offset_y, current->_y + current->button_offset_y, current->_w - current->button_offset_y * 2, + line_count * UI_ELEMENT_H() - current->button_offset_y * 2); + + ui_text_coloring_t *_text_coloring = current->text_coloring; + current->text_coloring = ui_text_area_coloring; + + if (current->input_started) { + text_area_selection_start = -1; + } + + int lines_off = 0; + int edit_line_pos = -1; + int edit_line_old_len = 0; + char edit_new_text[1024]; + edit_new_text[0] = '\0'; + for (int i = 0; i < line_count; ++i) { // Draw lines + char *line = ui_extract_line_off(lines, 0, &lines_off); + // Text input + if ((!selected && ui_get_hover(UI_ELEMENT_H())) || (selected && i == handle->i)) { + handle->i = i; // Set active line + strcpy(handle->text, line); + current->submit_text_handle = NULL; + // Suppress cut / paste / select-all in ui_update_text_edit for multi-line handling + bool _is_cut = ui_is_cut; + bool _is_paste = ui_is_paste; + bool _is_a_down = current->is_a_down; + int _key_char = current->key_char; + int _key_code = current->key_code; + int _highlight_anchor = current->highlight_anchor; + bool _is_key_pressed = current->is_key_pressed; + bool paste_is_multiline = ui_is_paste && strchr(ui_text_to_paste, '\n') != NULL; + if ((text_area_selection_start != -1 && text_area_selection_start != i) || paste_is_multiline) { + ui_is_cut = false; + ui_is_paste = false; + // Suppress editing keys for multi-line selection, keep navigation keys active + if (current->key_code == KEY_CODE_BACKSPACE || current->key_code == KEY_CODE_DELETE || current->key_code == KEY_CODE_RETURN || + current->key_code == KEY_CODE_ESCAPE) { + current->is_key_pressed = false; + } + // Fix highlight for active line: anchor at end-of-line when selection comes from below + if (text_area_selection_start > i) { + current->highlight_anchor = (int)strlen(line); + } + } + // When back on the start line during an active shift-selection, restore the original column as anchor + if (text_area_selection_start == i && current->is_shift_down) { + current->highlight_anchor = text_area_selection_start_col; + } + if (current->is_ctrl_down && current->is_a_down) { + current->key_char = 0; // Prevent + } + ui_text_input(handle, show_label ? label : "", align, editable, false); + if ((text_area_selection_start != -1 && text_area_selection_start != i) || paste_is_multiline) { + // Restore flags that were suppressed for multi-line handling + ui_is_cut = _is_cut; + ui_is_paste = _is_paste; + current->key_code = _key_code; + current->highlight_anchor = _highlight_anchor; + current->is_key_pressed = _is_key_pressed; + } + current->is_a_down = _is_a_down; + current->key_char = _key_char; + if (selected && current->key_code != KEY_CODE_RETURN && current->key_code != KEY_CODE_ESCAPE && + strcmp(line, current->text_selected) != 0) { // Edit text - defer to after loop + edit_line_pos = ui_line_pos(lines, i); + edit_line_old_len = strlen(line); + strcpy(edit_new_text, current->text_selected); + } + } + // Text + else { + if (show_label) { + int TEXT_COL = theme->TEXT_COL; + theme->TEXT_COL = theme->LABEL_COL; + ui_text(label, UI_ALIGN_RIGHT, 0x00000000); + theme->TEXT_COL = TEXT_COL; + } + else { + // Multi-line selection highlight + if (text_area_selection_start > -1 && + ((i >= text_area_selection_start && i < handle->i) || (i <= text_area_selection_start && i > handle->i))) { + int line_height = UI_ELEMENT_H(); + int cursor_height = line_height - current->button_offset_y * 3.0; + float off = UI_TEXT_OFFSET(); + float hl_x, hl_w; + if (i == text_area_selection_start && i > handle->i) { + // Start line is below active line: highlight from 0 to start col + hl_x = current->_x + off; + hl_w = draw_sub_string_width(current->ops->font, current->font_size, line, 0, text_area_selection_start_col); + } + else if (i == text_area_selection_start && i < handle->i) { + // Start line is above active line: highlight from start col to end + float start_off = draw_sub_string_width(current->ops->font, current->font_size, line, 0, text_area_selection_start_col); + hl_x = current->_x + off + start_off; + hl_w = draw_string_width(current->ops->font, current->font_size, line) - start_off; + } + else { + // Middle lines: highlight full line + hl_x = current->_x + off; + hl_w = draw_string_width(current->ops->font, current->font_size, line); + } + if (hl_w > 0) { + draw_set_color(theme->HOVER_COL + 0x00202020); + draw_filled_rect(hl_x, current->_y + current->button_offset_y * 1.5, hl_w, cursor_height); + } + } + ui_text(line, align, 0x00000000); + } + } + current->_y -= UI_ELEMENT_OFFSET(); + } + current->_y += UI_ELEMENT_OFFSET(); + current->text_coloring = _text_coloring; + + if (edit_line_pos >= 0) { // Apply edit after loop to avoid flickering + ui_remove_chars_at(lines, edit_line_pos, edit_line_old_len); + ui_insert_chars_at(lines, edit_line_pos, edit_new_text); + } + + // Multi-line paste with no selection + bool paste_is_multiline_out = ui_is_paste && strchr(ui_text_to_paste, '\n') != NULL; + if (selected && paste_is_multiline_out && (text_area_selection_start == -1 || text_area_selection_start == handle->i)) { + int pos = ui_line_pos(lines, handle->i) + current->cursor_x; + ui_insert_chars_at(lines, pos, ui_text_to_paste); + int paste_lines = ui_line_count(ui_text_to_paste) - 1; + handle->i += paste_lines; + char *last_pasted_line = ui_extract_line(ui_text_to_paste, paste_lines); + current->cursor_x = current->highlight_anchor = (int)strlen(last_pasted_line); + text_area_selection_start = -1; + ui_text_to_paste[0] = '\0'; + ui_is_paste = false; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + } + + // Multi-line copy/cut/paste + if (selected && text_area_selection_start != -1 && text_area_selection_start != handle->i) { + // Determine ordered selection bounds + int sel_top_line, sel_bot_line, sel_top_col, sel_bot_col; + if (text_area_selection_start < handle->i) { + sel_top_line = text_area_selection_start; + sel_bot_line = handle->i; + sel_top_col = text_area_selection_start_col; + sel_bot_col = current->cursor_x; + } + else { + sel_top_line = handle->i; + sel_bot_line = text_area_selection_start; + sel_top_col = current->cursor_x; + sel_bot_col = text_area_selection_start_col; + } + + if (ui_is_copy || ui_is_cut) { + // Build the selected text spanning multiple lines + ui_text_to_copy[0] = '\0'; + for (int i = sel_top_line; i <= sel_bot_line; ++i) { + char *ln = ui_extract_line(lines, i); + int col_start = (i == sel_top_line) ? sel_top_col : 0; + int col_end = (i == sel_bot_line) ? sel_bot_col : (int)strlen(ln); + int len = col_end - col_start; + if (len > 0) { + strncat(ui_text_to_copy, ln + col_start, len); + } + if (i < sel_bot_line) { + strcat(ui_text_to_copy, "\n"); + } + } + } + if (editable && (ui_is_cut || current->key_code == KEY_CODE_BACKSPACE || current->key_code == KEY_CODE_DELETE)) { + // Delete from sel_top_col on sel_top_line to sel_bot_col on sel_bot_line + int pos_start = ui_line_pos(lines, sel_top_line) + sel_top_col; + int pos_end = ui_line_pos(lines, sel_bot_line) + sel_bot_col; + ui_remove_chars_at(lines, pos_start, pos_end - pos_start); + handle->i = sel_top_line; + current->cursor_x = current->highlight_anchor = sel_top_col; + text_area_selection_start = -1; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + } + if (editable && ui_is_paste) { + // Delete selected range then insert clipboard + int pos_start = ui_line_pos(lines, sel_top_line) + sel_top_col; + int pos_end = ui_line_pos(lines, sel_bot_line) + sel_bot_col; + ui_remove_chars_at(lines, pos_start, pos_end - pos_start); + ui_insert_chars_at(lines, pos_start, ui_text_to_paste); + // Reposition cursor: count newlines in pasted text + int paste_lines = ui_line_count(ui_text_to_paste) - 1; + handle->i = sel_top_line + paste_lines; + char *last_pasted_line = ui_extract_line(ui_text_to_paste, paste_lines); + current->cursor_x = current->highlight_anchor = sel_top_col + (int)strlen(last_pasted_line); + text_area_selection_start = -1; + ui_text_to_paste[0] = '\0'; + ui_is_paste = false; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + } + } + + if (ui_text_area_scroll_past_end) { + current->_y += current->_h - current->window_header_h - UI_ELEMENT_H() - UI_ELEMENT_OFFSET(); + } + + if (key_pressed) { + // Move cursor vertically + if (current->key_code == KEY_CODE_DOWN && handle->i < line_count - 1) { + handle_line_select(current, handle); + current->cursor_sticky_x = current->cursor_sticky_x > current->cursor_x ? current->cursor_sticky_x : current->cursor_x; + handle->i++; + int next_len = (int)strlen(ui_extract_line(lines, handle->i)); + current->cursor_x = current->cursor_sticky_x < next_len ? current->cursor_sticky_x : next_len; + if (!current->is_shift_down) { + current->highlight_anchor = current->cursor_x; + } + scroll_align(current, handle); + } + else if (current->key_code == KEY_CODE_UP && handle->i > 0) { + handle_line_select(current, handle); + current->cursor_sticky_x = current->cursor_sticky_x > current->cursor_x ? current->cursor_sticky_x : current->cursor_x; + handle->i--; + int prev_len = (int)strlen(ui_extract_line(lines, handle->i)); + current->cursor_x = current->cursor_sticky_x < prev_len ? current->cursor_sticky_x : prev_len; + if (!current->is_shift_down) { + current->highlight_anchor = current->cursor_x; + } + scroll_align(current, handle); + } + else if (current->key_code == KEY_CODE_RIGHT && cursor_start_x == active_line_len && handle->i < line_count - 1 && !current->is_ctrl_down) { + handle_line_select(current, handle); + handle->i++; + current->cursor_x = 0; + current->highlight_anchor = 0; + current->cursor_sticky_x = 0; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + scroll_align(current, handle); + } + else if (current->key_code == KEY_CODE_LEFT && cursor_start_x == 0 && handle->i > 0 && !current->is_ctrl_down) { + handle_line_select(current, handle); + handle->i--; + int prev_len = (int)strlen(ui_extract_line(lines, handle->i)); + current->cursor_x = prev_len; + current->highlight_anchor = prev_len; + current->cursor_sticky_x = prev_len; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + scroll_align(current, handle); + } + else if (current->key_code == KEY_CODE_HOME || current->key_code == KEY_CODE_END) { + text_area_selection_start = -1; // Home/End are single-line operations, cancel multi-line selection + } + else if (current->is_ctrl_down && current->is_a_down) { // Select all lines + text_area_selection_start = 0; + text_area_selection_start_col = 0; + handle->i = line_count - 1; + current->highlight_anchor = 0; + current->cursor_x = (int)strlen(ui_extract_line(lines, handle->i)); + current->cursor_sticky_x = current->cursor_x; + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + strcpy(handle->text, current->text_selected); + scroll_align(current, handle); + } + else { + current->cursor_sticky_x = current->cursor_x; // Reset sticky column on any non-vertical move + if (!current->is_shift_down && !current->is_ctrl_down) { + text_area_selection_start = -1; // Cancel multi-line selection on non-shift, non-ctrl key + } + } + // New line + if (editable && current->key_code == KEY_CODE_RETURN && !word_wrap) { + handle->i++; + ui_insert_char_at(lines, ui_line_pos(lines, handle->i - 1) + current->cursor_x, '\n'); + ui_start_text_edit(handle, UI_ALIGN_LEFT); + current->cursor_x = current->highlight_anchor = 0; + scroll_align(current, handle); + } + // Delete line + if (editable && current->key_code == KEY_CODE_BACKSPACE && cursor_start_x == 0 && handle->i > 0 && text_area_selection_start == -1) { + handle->i--; + current->cursor_x = current->highlight_anchor = strlen(ui_extract_line(lines, handle->i)); + ui_remove_chars_at(lines, ui_line_pos(lines, handle->i + 1) - 1, 1); // Remove '\n' of the previous line + scroll_align(current, handle); + } + // Delete line + if (editable && current->key_code == KEY_CODE_DELETE && handle->i < line_count - 1 && cursor_start_x == active_line_len && + text_area_selection_start == -1) { + current->highlight_anchor = current->cursor_x; + ui_remove_chars_at(lines, ui_line_pos(lines, handle->i + 1) - 1, 1); // Remove '\n' at end of current line + } + strcpy(current->text_selected, ui_extract_line(lines, handle->i)); + } + + current->highlight_on_select = true; + current->tab_switch_enabled = true; + handle->text = string_copy(lines); + return handle->text; +} + +float UI_MENUBAR_H() { + ui_t *current = ui_get_current(); + return UI_BUTTON_H() * 1.1 + 2.0 + current->button_offset_y; +} + +void ui_begin_menu() { + ui_t *current = ui_get_current(); + ui_theme_t *theme = current->ops->theme; + _ELEMENT_OFFSET = theme->ELEMENT_OFFSET; + _BUTTON_COL = theme->BUTTON_COL; + _SHADOWS = theme->SHADOWS; + theme->ELEMENT_OFFSET = 0; + theme->BUTTON_COL = theme->SEPARATOR_COL; + theme->SHADOWS = false; + draw_set_color(theme->SEPARATOR_COL); + draw_filled_rect(0, 0, current->_window_w, UI_MENUBAR_H()); +} + +void ui_end_menu() { + ui_theme_t *theme = ui_get_current()->ops->theme; + theme->ELEMENT_OFFSET = _ELEMENT_OFFSET; + theme->BUTTON_COL = _BUTTON_COL; + theme->SHADOWS = _SHADOWS; +} + +bool ui_menubar_button(char *text) { + ui_t *current = ui_get_current(); + current->_w = draw_string_width(current->ops->font, current->font_size, text) + 25.0 * UI_SCALE(); + return ui_button(text, UI_ALIGN_CENTER, ""); +} + +const char *ui_theme_keys[] = {"WINDOW_BG_COL", "HOVER_COL", "BUTTON_COL", "PRESSED_COL", "TEXT_COL", "LABEL_COL", "SEPARATOR_COL", + "HIGHLIGHT_COL", "FONT_SIZE", "ELEMENT_W", "ELEMENT_H", "ELEMENT_OFFSET", "ARROW_SIZE", "BUTTON_H", + "CHECK_SIZE", "CHECK_SELECT_SIZE", "SCROLL_W", "SCROLL_MINI_W", "TEXT_OFFSET", "TAB_W", "FILL_WINDOW_BG", + "FILL_BUTTON_BG", "LINK_STYLE", "FULL_TABS", "ROUND_CORNERS", "SHADOWS", "VIEWPORT_COL"}; + +int ui_theme_keys_count = sizeof(ui_theme_keys) / sizeof(ui_theme_keys[0]); + +//// + +f32 ui_MENUBAR_H(ui_t *ui) { + f32 button_offset_y = (ui->ops->theme->ELEMENT_H * UI_SCALE() - ui->ops->theme->BUTTON_H * UI_SCALE()) / (float)2; + return ui->ops->theme->BUTTON_H * UI_SCALE() * 1.1 + 2 + button_offset_y; +} + +extern any_map_t *ui_children; + +ui_handle_t *ui_handle(char *s) { + ui_handle_t *h = any_map_get(ui_children, s); + if (h == NULL) { + h = ui_handle_create(); + any_map_set(ui_children, s, h); + return h; + } + h->init = false; + return h; +} + +ui_t *ui_create(ui_options_t *ops) { + ui_t *raw = GC_ALLOC_INIT(ui_t, {0}); + ui_init(raw, ops); + return raw; +} + +ui_theme_t *ui_theme_create() { + ui_theme_t *raw = GC_ALLOC_INIT(ui_theme_t, {0}); + ui_theme_default(raw); + return raw; +} + +void ui_set_font(ui_t *ui, draw_font_t *font) { + draw_font_init(font); + ui->ops->font = font; +} diff --git a/base/sources/iron_ui_nodes.c b/base/sources/iron_ui_nodes.c index 5bce6b15..a1576b11 100644 --- a/base/sources/iron_ui_nodes.c +++ b/base/sources/iron_ui_nodes.c @@ -1,13 +1,12 @@ -#include "iron_ui_nodes.h" +#include "iron_ui.h" #include "iron_armpack.h" #include "iron_array.h" -#include "iron_string.h" #include "iron_draw.h" #include "iron_gc.h" -#include "iron_json.h" -#include "iron_ui.h" #include "iron_gpu.h" +#include "iron_json.h" +#include "iron_string.h" #include "iron_system.h" #include #include @@ -24,10 +23,10 @@ static const int ui_max_buttons = 9; static int ui_node_id = -1; static ui_node_t *node_resize = NULL; -char *ui_clipboard = ""; -string_array_t *ui_nodes_exclude_remove = NULL; // No removal for listed node types -bool ui_nodes_socket_released = false; -string_array_t *(*ui_nodes_enum_texts)(char *) = NULL; // Retrieve combo items for buttons of type ENUM +char *ui_clipboard = ""; +string_array_t *ui_nodes_exclude_remove = NULL; // No removal for listed node types +bool ui_nodes_socket_released = false; +string_array_t *(*ui_nodes_enum_texts)(char *) = NULL; // Retrieve combo items for buttons of type ENUM gpu_texture_t *(*ui_nodes_preview_image)(ui_node_t *) = NULL; // Retrieve preview image void (*ui_nodes_on_custom_button)(int, char *) = NULL; // Call external function ui_canvas_control_t *(*ui_nodes_on_canvas_control)(void) = NULL; @@ -397,14 +396,14 @@ static float ui_nodes_snap(float f) { } static string_array_t enum_ar; -static char enum_label[64]; -static char enum_texts_data[64][64]; -static char *enum_texts[64]; +static char enum_label[64]; +static char enum_texts_data[64][64]; +static char *enum_texts[64]; static string_array_t temp_ar; -static char temp_label[64]; -static char temp_texts_data[64][64]; -static char *temp_texts[64]; +static char temp_label[64]; +static char temp_texts_data[64][64]; +static char *temp_texts[64]; void ui_node_draw_body(ui_node_t *node, ui_node_canvas_t *canvas, float nx, float ny) { ui_t *current = ui_get_current(); @@ -412,7 +411,7 @@ void ui_node_draw_body(ui_node_t *node, ui_node_canvas_t *canvas, float nx, floa float wy = current->_window_y; float w = UI_NODE_W(node); // float h = UI_NODE_H(canvas, node); - float lineh = UI_LINE_H(); + float lineh = UI_LINE_H(); // Outputs for (int i = 0; i < node->outputs->length; ++i) { @@ -533,10 +532,10 @@ void ui_node_draw_body(ui_node_t *node, ui_node_canvas_t *canvas, float nx, floa ui_handle_t *but_handle = ui_nest(nhandle, buti); but_handle->i = ((float *)but->default_value->buffer)[0]; - bool combo_select = current->combo_selected_handle == NULL && ui_get_released(UI_ELEMENT_H()); - char *label = combo_select ? temp_label : enum_label; - char(*texts_data)[64] = combo_select ? temp_texts_data : enum_texts_data; - char **texts = combo_select ? temp_texts : enum_texts; + bool combo_select = current->combo_selected_handle == NULL && ui_get_released(UI_ELEMENT_H()); + char *label = combo_select ? temp_label : enum_label; + char(*texts_data)[64] = combo_select ? temp_texts_data : enum_texts_data; + char **texts = combo_select ? temp_texts : enum_texts; string_array_t *ar = combo_select ? &temp_ar : &enum_ar; int texts_count = 0; @@ -1298,7 +1297,7 @@ void ui_node_canvas(ui_nodes_t *nodes, ui_node_canvas_t *canvas) { for (int j = 0; j < paste_canvas->nodes->buffer[i]->buttons->length; ++j) { ui_node_button_t *but = paste_canvas->nodes->buffer[i]->buttons->buffer[j]; if (but->data != NULL) { - char *s = string_replace_all(but->data, "\\n", "\n"); + char *s = string_replace_all(but->data, "\\n", "\n"); but->data = u8_array_create_from_raw((uint8_t *)s, strlen(s) + 1); } } @@ -1746,7 +1745,7 @@ f32 ui_nodes_INPUT_Y(ui_node_canvas_t *canvas, ui_node_t *node, i32 pos) { return UI_INPUT_Y(canvas, node, pos); } -extern any_map_t *ui_nodes_custom_buttons; +extern any_map_t *ui_nodes_custom_buttons; void nodes_on_custom_button(i32 node_id, char *button_name) { void (*f)(i32) = any_map_get(ui_nodes_custom_buttons, button_name); diff --git a/base/sources/iron_ui_nodes.h b/base/sources/iron_ui_nodes.h deleted file mode 100644 index 5a26f385..00000000 --- a/base/sources/iron_ui_nodes.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -#include "iron_armpack.h" -#include "iron_ui.h" - -typedef struct ui_canvas_control { - float pan_x; - float pan_y; - float zoom; - bool controls_down; -} ui_canvas_control_t; - -typedef struct ui_node_socket { - int id; - int node_id; - char *name; - char *type; - uint32_t color; - f32_array_t *default_value; - float min; - float max; - float precision; - int display; -} ui_node_socket_t; - -typedef struct ui_node_button { - char *name; - char *type; - int output; - f32_array_t *default_value; - u8_array_t *data; - float min; - float max; - float precision; - float height; -} ui_node_button_t; - -typedef struct ui_node_socket_array { - ui_node_socket_t **buffer; - int length; - int capacity; -} ui_node_socket_array_t; - -typedef struct ui_node_button_array { - ui_node_button_t **buffer; - int length; - int capacity; -} ui_node_button_array_t; - -typedef enum ui_node_flag { - UI_NODE_FLAG_NONE = 0, - UI_NODE_FLAG_COLLAPSED = 1, - UI_NODE_FLAG_PREVIEW = 2, -} ui_node_flag_t; - -typedef struct ui_node { - int id; - char *name; - char *type; - float x; - float y; - uint32_t color; - ui_node_socket_array_t *inputs; - ui_node_socket_array_t *outputs; - ui_node_button_array_t *buttons; - float width; - int flags; // ui_node_flag_t -} ui_node_t; - -typedef struct ui_node_link { - int id; - int from_id; - int from_socket; - int to_id; - int to_socket; -} ui_node_link_t; - -typedef struct ui_node_array { - ui_node_t **buffer; - int length; - int capacity; -} ui_node_array_t; - -typedef struct ui_node_link_array { - ui_node_link_t **buffer; - int length; - int capacity; -} ui_node_link_array_t; - -typedef struct ui_node_canvas { - char *name; - ui_node_array_t *nodes; - ui_node_link_array_t *links; -} ui_node_canvas_t; - -typedef struct ui_nodes { - bool nodes_drag; - i32_array_t *nodes_selected_id; - float pan_x; - float pan_y; - float zoom; - int uiw; - int uih; - bool _input_started; - void (*color_picker_callback)(uint32_t); - void *color_picker_callback_data; - float scale_factor; - float ELEMENT_H; - bool dragged; - ui_node_t *move_on_top; - int link_drag_id; - bool is_new_link; - int snap_from_id; - int snap_to_id; - int snap_socket; - float snap_x; - float snap_y; - ui_handle_t *handle; -} ui_nodes_t; - -void ui_nodes_init(ui_nodes_t *nodes); -void ui_node_canvas(ui_nodes_t *nodes, ui_node_canvas_t *canvas); -void ui_nodes_rgba_popup(ui_handle_t *nhandle, float *val, int x, int y); - -void ui_remove_node(ui_node_t *n, ui_node_canvas_t *canvas); - -float UI_NODES_SCALE(); -float UI_NODES_PAN_X(); -float UI_NODES_PAN_Y(); -extern char *ui_clipboard; -extern string_array_t *ui_nodes_exclude_remove; -extern bool ui_nodes_socket_released; -extern string_array_t *(*ui_nodes_enum_texts)(char *); -extern gpu_texture_t *(*ui_nodes_preview_image)(ui_node_t *); -extern void (*ui_nodes_on_custom_button)(int, char *); -extern ui_canvas_control_t *(*ui_nodes_on_canvas_control)(void); -extern void (*ui_nodes_on_canvas_released)(void); -extern void (*ui_nodes_on_socket_released)(int); -extern void (*ui_nodes_on_link_drag)(int, bool); -extern void (*ui_nodes_on_node_remove)(ui_node_t *); -extern void (*ui_nodes_on_node_changed)(ui_node_t *); -extern bool ui_nodes_grid_snap; - -void ui_node_canvas_encode(ui_node_canvas_t *canvas); -uint32_t ui_node_canvas_encoded_size(ui_node_canvas_t *canvas); -char *ui_node_canvas_to_json(ui_node_canvas_t *canvas); - -float UI_NODE_X(ui_node_t *node); -float UI_NODE_Y(ui_node_t *node); -float UI_NODE_W(ui_node_t *node); -float UI_NODE_H(ui_node_canvas_t *canvas, ui_node_t *node); -float UI_OUTPUT_Y(ui_node_t *node, int pos); -float UI_INPUT_Y(ui_node_canvas_t *canvas, ui_node_t *node, int pos); -float UI_OUTPUTS_H(ui_node_t *node, int length); -float UI_BUTTONS_H(ui_node_t *node); -float UI_LINE_H(); -float UI_NODES_PAN_X(); -float UI_NODES_PAN_Y(); - -float ui_p(float f); -int ui_get_socket_id(ui_node_array_t *nodes); -ui_node_link_t *ui_get_link(ui_node_link_array_t *links, int id); -int ui_next_link_id(ui_node_link_array_t *links); -ui_node_t *ui_get_node(ui_node_array_t *nodes, int id); -int ui_next_node_id(ui_node_array_t *nodes); diff --git a/base/sources/plugins/plugin_api.c b/base/sources/plugins/plugin_api.c index 697edcf7..d10288a8 100644 --- a/base/sources/plugins/plugin_api.c +++ b/base/sources/plugins/plugin_api.c @@ -6,7 +6,6 @@ #include "iron_math.h" #include "iron_obj.h" #include "iron_ui.h" -#include "iron_ui_nodes.h" void plugin_embed(); diff --git a/paint/plugins/plugins.c b/paint/plugins/plugins.c index b2bb8422..2c08a19b 100644 --- a/paint/plugins/plugins.c +++ b/paint/plugins/plugins.c @@ -3,7 +3,6 @@ #include "iron_array.h" #include "iron_map.h" #include "iron_ui.h" -#include "iron_ui_nodes.h" void proc_uv_unwrap(void *mesh); FN(proc_uv_unwrap) { @@ -214,7 +213,7 @@ FN(util_mesh_unwrappers_delete) { return JS_UNDEFINED; } -extern any_map_t *import_mesh_importers; +extern any_map_t *import_mesh_importers; extern string_array_t *_path_mesh_formats; string_array_t *path_mesh_formats(void); FN(path_mesh_importers_set) { @@ -236,7 +235,7 @@ FN(path_mesh_importers_delete) { return JS_UNDEFINED; } -extern any_map_t *import_texture_importers; +extern any_map_t *import_texture_importers; extern string_array_t *_path_texture_formats; string_array_t *path_texture_formats(void); FN(path_texture_importers_set) {