From 23a30ba978723e0f251b5b2a5c675d4ef51d6c81 Mon Sep 17 00:00:00 2001 From: carys-the-weed-cloud <36204825+carys-the-weed-cloud@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:23:24 -0400 Subject: [PATCH 1/2] refactor: Button base code stability tweaks. --- scripts/scr_buttons/scr_buttons.gml | 503 +++++++++++++--------------- 1 file changed, 242 insertions(+), 261 deletions(-) diff --git a/scripts/scr_buttons/scr_buttons.gml b/scripts/scr_buttons/scr_buttons.gml index 7ec050b39..e998c9658 100644 --- a/scripts/scr_buttons/scr_buttons.gml +++ b/scripts/scr_buttons/scr_buttons.gml @@ -6,9 +6,8 @@ global.draw_return_stack = []; #macro UI_CURSOR_BLINK_RATE 500 /// @function add_draw_return_values() -/// @description Saves the current draw state (alpha, font, color, halign, valign) to a global stack. -/// @returns {undefined} /// @category Draw Helpers +/// @description Saves the current draw state (alpha, font, color, halign, valign) to a global stack. function add_draw_return_values() { var _vals = { cur_alpha: draw_get_alpha(), @@ -21,9 +20,8 @@ function add_draw_return_values() { } /// @function pop_draw_return_values() -/// @description Restores the most recent draw state from the global stack and removes it. -/// @returns {undefined} /// @category Draw Helpers +/// @description Restores the most recent draw state from the global stack and removes it. function pop_draw_return_values() { var _array_length = array_length(global.draw_return_stack); if (_array_length > 0) { @@ -38,19 +36,116 @@ function pop_draw_return_values() { } } +/// @function standard_loc_data() +/// @category Draw Helpers +/// @description Acts as an initializer for UI elements positions and size function standard_loc_data() { - x1 = 0; - y1 = 0; - y2 = 0; - x2 = 0; - w = 0; - h = 0; + self.x1 = 0; + self.y1 = 0; + self.y2 = 0; + self.x2 = 0; + self.w = 0; + self.h = 0; +} + +/// @function draw_unit_buttons(position, text, size_mod, colour, halign, font, alpha_mult, bg, bg_color) +/// @category Draw Helpers +/// @description Draws a styled button with text, optional background and hover effects. +/// @param {array} position Either [x, y] or [x1, y1, x2, y2]. +/// @param {string} text Text to display. +/// @param {array} size_mod Text scaling. +/// @param {color} colour Text color. +/// @param {real} _halign Text horizontal alignment. +/// @param {font} font Font resource. +/// @param {real} alpha_mult Alpha multiplier. +/// @param {bool} bg Draw background rectangle. +/// @param {color} bg_color Background color. +/// @returns {array} [x1, y1, x2, y2] bounding box. +function draw_unit_buttons(position, text, size_mod = [1.5, 1.5], colour = c_gray, _halign = fa_center, font = fnt_40k_14b, alpha_mult = 1, bg = false, bg_color = c_black) { + // TODO: fix halign usage + add_draw_return_values(); + + draw_set_font(font); + draw_set_halign(_halign); + draw_set_valign(fa_middle); + + var x2; + var y2; + var _text = string_hash_to_newline(text); + if (array_length(position) > 2) { + x2 = position[2]; + y2 = position[3]; + } else { + var text_width = string_width(_text) * size_mod[0]; + var text_height = string_height(_text) * size_mod[1]; + x2 = position[0] + text_width + (6 * size_mod[0]); + y2 = position[1] + text_height + (6 * size_mod[1]); + } + draw_set_alpha(1 * alpha_mult); + if (bg) { + draw_set_color(bg_color); + draw_rectangle(position[0], position[1], x2, y2, 0); + } + draw_set_color(colour); + draw_text_transformed((position[0] + x2) / 2, (position[1] + y2) / 2, _text, size_mod[0], size_mod[1], 0); + draw_rectangle(position[0], position[1], x2, y2, 1); + draw_set_alpha(0.5 * alpha_mult); + draw_rectangle(position[0] + 1, position[1] + 1, x2 - 1, y2 - 1, 1); + draw_set_alpha(0.25 * alpha_mult); + var mouse_consts = return_mouse_consts(); + if (point_in_rectangle(mouse_consts[0], mouse_consts[1], position[0], position[1], x2, y2)) { + draw_rectangle(position[0], position[1], x2, y2, 0); + } + + pop_draw_return_values(); + + return [position[0], position[1], x2, y2]; +} + +/// @function list_traveler(list, cur_val, move_up_coords, move_down_coords) +/// @category Draw Helpers +/// @description Cycles through values in a list by clicking move-up/down regions. +/// @param {array} list Array of values. +/// @param {any} cur_val Current value. +/// @param {array} move_up_coords Bounding box for up button. +/// @param {array} move_down_coords Bounding box for down button. +/// @returns {any} New value from list. +function list_traveler(list, cur_val, move_up_coords, move_down_coords) { + var _new_val = cur_val; + var _found = false; + for (var i = 0; i < array_length(list); i++) { + if (cur_val == list[i]) { + _found = true; + if (point_and_click(move_up_coords)) { + if (i == 0) { + _new_val = list[0]; + } else { + _new_val = list[i - 1]; + } + } else if (point_and_click(move_down_coords)) { + if (i == array_length(list) - 1) { + _new_val = list[array_length(list) - 1]; + } else { + _new_val = list[i + 1]; + } + } + } + } + // If value not found in list, default to first element + if (!_found && array_length(list) > 0) { + _new_val = list[0]; + } + return _new_val; } // -------------------- // 🟩 UI ELEMENTS // -------------------- +/// @function Box(data) +/// @constructor +/// @category UI +/// @description A simple drawable box function Box(data) constructor { colour = CM_GREEN_COLOR; move_data_to_current_scope(data); @@ -67,38 +162,37 @@ function Box(data) constructor { }; } -/// @function ReactiveString(text, x1, y1, data) +/// @function ReactiveString(text_param, x1_param, y1_param, data) /// @constructor /// @category UI /// @description Represents a reactive text element that can update, draw itself, and respond to hits. -/// @param {string} text The text to display. -/// @param {real} [x1=0] The X position. -/// @param {real} [y1=0] The Y position. -/// @param {struct|bool} [data=false] Optional struct of properties to apply. -/// @returns {ReactiveString} -/// +/// @param {string} text_param The text to display. +/// @param {real} x1_param The X position. +/// @param {real} y1_param The Y position. +/// @param {struct} data Optional struct of properties to apply. /// @example /// var rs = new ReactiveString("Hello", 100, 200); /// rs.draw(); -function ReactiveString(text, x1 = 0, y1 = 0, data = false) constructor { - self.x1 = x1; - self.y1 = y1; - x2 = 0; - y2 = 0; +function ReactiveString(text_param, x1_param = 0, y1_param = 0, data = {}) constructor { + x1 = x1_param; + y1 = y1_param; + text = text_param; + font = fnt_40k_14; + add_draw_return_values(); + draw_set_font(font); + w = string_width(text); + h = string_height(text); + pop_draw_return_values(); + x2 = x1 + w; + y2 = y1 + h; halign = fa_left; valign = fa_top; - - self.text = text; - text_max_width = -1; - font = fnt_40k_14; colour = CM_GREEN_COLOR; tooltip = ""; max_width = -1; - h = 0; - w = 0; + scale = 0; scale_text = false; with_outline = true; - min_scale = 0; allow_line_breaks = true; move_data_to_current_scope(data); @@ -107,9 +201,6 @@ function ReactiveString(text, x1 = 0, y1 = 0, data = false) constructor { move_data_to_current_scope(data); add_draw_return_values(); draw_set_font(font); - draw_set_halign(halign); - draw_set_valign(valign); - if (max_width > -1) { if (!scale_text) { w = string_width_ext(text, -1, max_width); @@ -122,16 +213,18 @@ function ReactiveString(text, x1 = 0, y1 = 0, data = false) constructor { scale = _scale_edits.scale; text = _scale_edits.text; h = string_height(text) * scale; + x2 = x1 + w; + y2 = y1 + h; } } else { w = string_width(text); + h = string_height(text); + x2 = x1 + w; + y2 = y1 + h; } - pop_draw_return_values(); }; - update(); - static hit = function() { return scr_hit(x1, y1, x2, y2); }; @@ -167,27 +260,26 @@ function ReactiveString(text, x1 = 0, y1 = 0, data = false) constructor { /// @constructor /// @category UI /// @description UI element combining a sprite and text with optional tooltip. -/// @param {sprite} icon The sprite asset. -/// @param {string} text The text label. -/// @param {real} [x1=0] X position. -/// @param {real} [y1=0] Y position. -/// @param {struct|bool} [data=false] Optional struct of properties to apply. -/// @returns {LabeledIcon} -function LabeledIcon(icon, text, x1 = 0, y1 = 0, data = false) constructor { - self.x1 = x1; - self.y1 = y1; +/// @param {sprite} icon_param The sprite asset. +/// @param {string} text_param The text label. +/// @param {real} x1_param X position. +/// @param {real} y1_param Y position. +/// @param {struct} data Optional struct of properties to apply. +function LabeledIcon(icon_param, text_param, x1_param = 0, y1_param = 0, data = {}) constructor { + x1 = x1_param; + y1 = y1_param; x2 = 0; y2 = 0; - self.text = text; + text = text_param; text_max_width = -1; font = fnt_40k_14; colour = CM_GREEN_COLOR; text_position = "right"; tooltip = ""; - self.icon = sprite_exists(icon) ? icon : spr_none; - icon_width = sprite_get_width(self.icon); - icon_height = sprite_get_height(self.icon); + icon = sprite_exists(icon_param) ? icon_param : spr_none; + icon_width = sprite_get_width(icon); + icon_height = sprite_get_height(icon); w = icon_width; h = icon_height; @@ -195,19 +287,19 @@ function LabeledIcon(icon, text, x1 = 0, y1 = 0, data = false) constructor { static update = function(data = {}) { move_data_to_current_scope(data); - add_draw_return_values(); - draw_set_font(font); if (text_position == "right") { w = icon_width + 2 + string_width(text); x2 = x1 + w; h = icon_height; y2 = y1 + icon_height; + } else { + w = icon_width; + h = icon_height; + x2 = x1 + icon_width; + y2 = y1 + icon_height; } - pop_draw_return_values(); }; - update(); - static hit = function() { return scr_hit(x1, y1, x2, y2); }; @@ -232,11 +324,14 @@ function LabeledIcon(icon, text, x1 = 0, y1 = 0, data = false) constructor { }; } +/// @function SpriteButton(_sprite, _hover_sprite) +/// @constructor +/// @category UI /// @desc A clickable sprite-based button component that manages its own state and hover logic. /// @param {Asset.GMSprite} _sprite The default sprite to display. /// @param {Asset.GMSprite} _hover_sprite Optional sprite to show when hovered. /// @returns {Struct.SpriteButton} -function SpriteButton(_sprite, _hover_sprite = -1) constructor { +function SpriteButton(_sprite, _hover_sprite = undefined) constructor { sprite = _sprite; hover_sprite = _hover_sprite; @@ -283,68 +378,12 @@ function SpriteButton(_sprite, _hover_sprite = -1) constructor { }; } -/// @function draw_unit_buttons(position, text, size_mod, colour, halign, font, alpha_mult, bg, bg_color) -/// @description Draws a styled button with text, optional background and hover effects. -/// @param {array} position Either [x, y] or [x1, y1, x2, y2]. -/// @param {string} text Text to display. -/// @param {array} [size_mod=[1.5,1.5]] Text scaling. -/// @param {color} [colour=c_gray] Text color. -/// @param {real} [_halign=fa_center] Text horizontal alignment. -/// @param {font} [font=fnt_40k_14b] Font resource. -/// @param {real} [alpha_mult=1] Alpha multiplier. -/// @param {bool} [bg=false] Draw background rectangle. -/// @param {color} [bg_color=c_black] Background color. -/// @returns {array} [x1, y1, x2, y2] bounding box. -function draw_unit_buttons(position, text, size_mod = [1.5, 1.5], colour = c_gray, _halign = fa_center, font = fnt_40k_14b, alpha_mult = 1, bg = false, bg_color = c_black) { - // TODO: fix halign usage - // Store current state of all global vars - add_draw_return_values(); - - draw_set_font(font); - draw_set_halign(fa_center); - draw_set_valign(fa_middle); - - var x2; - var y2; - var _text = string_hash_to_newline(text); - if (array_length(position) > 2) { - var x2 = position[2]; - var y2 = position[3]; - } else { - var text_width = string_width(_text) * size_mod[0]; - var text_height = string_height(_text) * size_mod[1]; - var x2 = position[0] + text_width + (6 * size_mod[0]); - var y2 = position[1] + text_height + (6 * size_mod[1]); - } - draw_set_alpha(1 * alpha_mult); - if (bg) { - draw_set_color(bg_color); - draw_rectangle(position[0], position[1], x2, y2, 0); - } - draw_set_color(colour); - draw_text_transformed((position[0] + x2) / 2, (position[1] + y2) / 2, _text, size_mod[0], size_mod[1], 0); - draw_rectangle(position[0], position[1], x2, y2, 1); - draw_set_alpha(0.5 * alpha_mult); - draw_rectangle(position[0] + 1, position[1] + 1, x2 - 1, y2 - 1, 1); - draw_set_alpha(0.25 * alpha_mult); - var mouse_consts = return_mouse_consts(); - if (point_in_rectangle(mouse_consts[0], mouse_consts[1], position[0], position[1], x2, y2)) { - draw_rectangle(position[0], position[1], x2, y2, 0); - } - - // Reset all global vars to their previous state - pop_draw_return_values(); - - return [position[0], position[1], x2, y2]; -} - /// @function UnitButtonObject(data) /// @constructor /// @category UI /// @description Represents an interactive UI button with styles, tooltips, and binding support. -/// @param {struct|bool} [data=false] Initial property overrides. -/// @returns {UnitButtonObject} -function UnitButtonObject(data = false) constructor { +/// @param {struct} data Initial property overrides. +function UnitButtonObject(data = {}) constructor { standard_loc_data(); h_gap = 4; v_gap = 4; @@ -364,9 +403,9 @@ function UnitButtonObject(data = false) constructor { set_height_width = false; static update_loc = function() { - add_draw_return_values(); - draw_set_font(font); if (label != "") { + add_draw_return_values(); + draw_set_font(font); if (!set_width) { w = string_width(label) + 10; h = string_height(label) + 4; @@ -378,18 +417,17 @@ function UnitButtonObject(data = false) constructor { label = _text_scale.text; } h = string_height(label) + 4; + pop_draw_return_values(); } x2 = x1 + w; y2 = y1 + h; - pop_draw_return_values(); }; + + move_data_to_current_scope(data); + update_loc(); - static update = function(data) { - var _updaters = struct_get_names(data); - var i = 0; - for (i = 0; i < array_length(_updaters); i++) { - self[$ _updaters[i]] = data[$ _updaters[i]]; - } + static update = function(data = {}) { + move_data_to_current_scope(data); if (struct_exists(data, "label") && !struct_exists(data, "set_width")) { set_width = false; w = 0; @@ -399,29 +437,23 @@ function UnitButtonObject(data = false) constructor { } }; - if (data != false) { - update(data); - } - - update_loc(); - static move = function(m_direction, with_gap = false, multiplier = 1) { switch (m_direction) { case "right": - x1 += (w + (with_gap * v_gap)) * multiplier; - x2 += (w + (with_gap * v_gap)) * multiplier; + x1 += (w + (with_gap * h_gap)) * multiplier; + x2 += (w + (with_gap * h_gap)) * multiplier; break; case "left": - x1 -= (w + (with_gap * v_gap)) * multiplier; - x2 -= (w + (with_gap * v_gap)) * multiplier; + x1 -= (w + (with_gap * h_gap)) * multiplier; + x2 -= (w + (with_gap * h_gap)) * multiplier; break; case "down": - y1 += (h + (with_gap * h_gap)) * multiplier; - y2 += (h + (with_gap * h_gap)) * multiplier; + y1 += (h + (with_gap * v_gap)) * multiplier; + y2 += (h + (with_gap * v_gap)) * multiplier; break; case "up": - y1 -= (h + (with_gap * h_gap)) * multiplier; - y2 -= (h + (with_gap * h_gap)) * multiplier; + y1 -= (h + (with_gap * v_gap)) * multiplier; + y2 -= (h + (with_gap * v_gap)) * multiplier; break; } }; @@ -438,7 +470,7 @@ function UnitButtonObject(data = false) constructor { allow_click = false; } update_loc(); - _button_click_area = draw_unit_buttons(w > 0 ? [x1, y1, x2, y2] : [x1, y1], label, [text_scale, text_scale], active ? color : inactive_col,, font, _temp_alpha); + _button_click_area = draw_unit_buttons(w > 0 ? [x1, y1, x2, y2] : [x1, y1], label, [text_scale, text_scale], active ? color : inactive_col, fa_center, font, _temp_alpha); } else if (style == "pixel") { var _widths = [ sprite_get_width(spr_pixel_button_left), @@ -453,15 +485,15 @@ function UnitButtonObject(data = false) constructor { var _width_scale = w / _widths[1]; _widths[1] *= _width_scale; draw_sprite_ext(spr_pixel_button_middle, 0, x1 + _widths[0], y1, _width_scale, height_scale, 0, c_white, 1); - draw_sprite_ext(spr_pixel_button_right, allow_click, x1 + _widths[0] + _widths[1], y1, height_scale, height_scale, 0, c_white, 1); - var _text_position_x = x1 + ((_widths[0] + 2) * height_scale); + draw_sprite_ext(spr_pixel_button_right, 0, x1 + _widths[0] + _widths[1], y1, height_scale, height_scale, 0, c_white, 1); + var _text_position_x = x1 + _widths[0] + 2; _text_position_x += _widths[1] / 2; draw_set_font(font); draw_set_halign(fa_center); draw_set_valign(fa_middle); draw_set_color(color); - draw_text_transformed(_text_position_x, y1 + ((h * height_scale) / 2), label, text_scale, text_scale, 0); + draw_text_transformed(_text_position_x, y1 + h/2, label, text_scale, text_scale, 0); x2 = x1 + array_sum(_widths); y2 = y1 + h; @@ -505,7 +537,7 @@ function UnitButtonObject(data = false) constructor { /// @category UI /// @description Specialized UnitButtonObject requiring requisition points to click. /// @param {real} req Required requisition cost. -/// @returns {PurchaseButton} +/// @returns {bool} function PurchaseButton(req) : UnitButtonObject() constructor { req_value = req; @@ -520,7 +552,7 @@ function PurchaseButton(req) : UnitButtonObject() constructor { if (scr_hit(x1, y1, x2, y2) && tooltip != "") { tooltip_draw(tooltip); } - if (allow_click && _allow_click) { + if (active && allow_click && _allow_click) { var clicked = point_and_click(_but) || keystroke; if (clicked) { if (is_callable(bind_method)) { @@ -545,7 +577,7 @@ function PurchaseButton(req) : UnitButtonObject() constructor { /// @param {real} _h Height of the bar. /// @param {array} _limits Array [min, max]. /// @param {real} _inc Increment step value. -function SliderBar(_x = 0, _y = 0, _w = 100, _h = 16, _limits = [0, 100], _inc = 1) constructor { +function SliderBar(_x, _y, _w = 100, _h = 16, _limits = [0, 100], _inc = 1) constructor { xx = _x; yy = _y; width = _w; @@ -556,20 +588,10 @@ function SliderBar(_x = 0, _y = 0, _w = 100, _h = 16, _limits = [0, 100], _inc = dragging = false; - /// @param {struct} _data Struct containing keys to override. - static update_data = function(_data) { - var _names = struct_get_names(_data); - var _count = array_length(_names); - var _i = 0; - repeat (_count) { - var _key = _names[_i]; - self[$ _key] = _data[$ _key]; - _i++; - } + static update = function(data) { + move_data_to_current_scope(data); }; - /// @description Returns the current value of the slider. - /// @returns {real} static draw = function() { add_draw_return_values(); @@ -611,7 +633,6 @@ function SliderBar(_x = 0, _y = 0, _w = 100, _h = 16, _limits = [0, 100], _inc = draw_rectangle(xx, yy, xx + _knob_pos, yy + height, false); pop_draw_return_values(); - return value; }; } @@ -621,9 +642,8 @@ function SliderBar(_x = 0, _y = 0, _w = 100, _h = 16, _limits = [0, 100], _inc = /// @description Input text area with background and cursor handling. /// @param {real} _x X position. /// @param {real} _y Y position. -/// @param {real} [_max_width=400] Max width of text bar. -/// @param {bool} [_requires_input=false] If true, input is required. -/// @returns {TextBarArea} +/// @param {real} _max_width Max width of text bar. +/// @param {bool} _requires_input If true, input is required. function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) constructor { xx = _x; yy = _y; @@ -631,7 +651,6 @@ function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) construc requires_input = _requires_input; allow_input = false; - draw_col = c_gray; cooloff = 0; current_text = ""; @@ -639,6 +658,8 @@ function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) construc background.draw_top_piece = false; static render_logic = function() { + add_draw_return_values(); + draw_set_valign(fa_middle); draw_set_halign(fa_center); draw_set_alpha(1); @@ -660,6 +681,8 @@ function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) construc draw_text(_cursor_x, _center_y, "|"); } } + + pop_draw_return_values(); }; render_content = method(self, render_logic); @@ -685,7 +708,7 @@ function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) construc if (current_text != "") { _bar_wid = max(max_width, string_width($"' {current_text} '") + _cursor_padding + 20); - draw_set_color(draw_col); + draw_set_color(c_gray); } else { draw_set_color(requires_input ? CM_RED_COLOR : CM_GREEN_COLOR); } @@ -732,11 +755,13 @@ function TextBarArea(_x, _y, _max_width = 400, _requires_input = false) construc }; } +/// @function UIDropdown(_options, _width, _on_change) +/// @constructor +/// @category UI /// @desc A modular UI dropdown component for selecting options from a list. /// @param {Array} _options Array of {label, value} structs. /// @param {real} _width Width of the dropdown. /// @param {Function} _on_change Optional callback invoked with the new value on selection change. -/// @returns {Struct.UIDropdown} function UIDropdown(_options, _width = 180, _on_change = undefined) constructor { options = _options; width = _width; @@ -834,6 +859,8 @@ function UIDropdown(_options, _width = 180, _on_change = undefined) constructor _y + height + _total_h ]; + add_draw_return_values(); + draw_set_alpha(0.95); draw_set_color(c_black); draw_rectangle_array(_list_rect, false); @@ -867,20 +894,20 @@ function UIDropdown(_options, _width = 180, _on_change = undefined) constructor draw_text(_x + 10, _oy + 4, options[i].label); } + pop_draw_return_values(); return _selection; }; } -/// @function MultiSelect(options_array, title, data) +/// @function MultiSelect(options_array, title_param, data) /// @constructor /// @category UI /// @description Multi-option toggle group allowing multiple selections. /// @param {array} options_array Array of option labels. -/// @param {string} title Title string. -/// @param {struct} [data={}] Optional overrides. -/// @returns {MultiSelect} -function MultiSelect(options_array, title, data = {}) constructor { - self.title = title; +/// @param {string} title_param Title string. +/// @param {struct} data Optional overrides. +function MultiSelect(options_array, title_param, data = {}) constructor { + title = title_param; x_gap = 10; y_gap = 5; standard_loc_data(); @@ -897,13 +924,15 @@ function MultiSelect(options_array, title, data = {}) constructor { _next_tog.active = false; array_push(toggles, _next_tog); } - static update = item_data_updater; - - update(data); + + move_data_to_current_scope(data); + + static update = function(data = {}) { + move_data_to_current_scope(data); + }; static draw_toggle = function(index) { var _cur_opt = toggles[index]; - _cur_opt.update(next_draw); if (_cur_opt.clicked() && allow_changes) { changed = true; } @@ -944,9 +973,9 @@ function MultiSelect(options_array, title, data = {}) constructor { }; }; - static draw = function(allow_changes = true) { + static draw = function(allow_changes_param = true) { changed = false; - self.allow_changes = allow_changes; + allow_changes = allow_changes_param; add_draw_return_values(); has_change_method = is_callable(on_change); @@ -1004,30 +1033,18 @@ function MultiSelect(options_array, title, data = {}) constructor { }; } -/// @function item_data_updater(data) -/// @description Utility to copy struct data into `self`. -/// @param {struct} data Data to apply. -/// @returns {undefined} -function item_data_updater(data) { - var _data_presets = struct_get_names(data); - for (var i = 0; i < array_length(_data_presets); i++) { - self[$ _data_presets[i]] = data[$ _data_presets[i]]; - } -} - -/// @function RadioSet(options_array, title, data) +/// @function RadioSet(options_array, title_param, data) /// @constructor /// @category UI /// @description Radio button group allowing only one active selection. /// @param {array} options_array List of option labels. -/// @param {string} [title=""] Title string. -/// @param {struct} [data={}] Optional overrides. -/// @returns {RadioSet} -function RadioSet(options_array, title = "", data = {}) constructor { +/// @param {string} title_param Title string. +/// @param {struct} data Optional overrides. +function RadioSet(options_array, title_param = "", data = {}) constructor { toggles = []; standard_loc_data(); current_selection = 0; - self.title = title; + title = title_param; active_col = CM_GREEN_COLOR; inactive_col = c_gray; allow_changes = true; @@ -1035,12 +1052,13 @@ function RadioSet(options_array, title = "", data = {}) constructor { y_gap = 5; title_font = fnt_40k_14b; draw_title = true; - space_evenly = false; - changed = false; - if (title == "") { draw_title = false; } + space_evenly = false; + changed = false; + x1 = 0; + y1 = 0; max_width = 0; // container width; if 0, use row's natural width max_height = 0; center = false; // when true, center each row horizontally in container @@ -1048,11 +1066,12 @@ function RadioSet(options_array, title = "", data = {}) constructor { for (var i = 0; i < array_length(options_array); i++) { array_push(toggles, new ToggleButton(options_array[i])); } - x2 = 0; - y2 = 0; + + move_data_to_current_scope(data); - move_data_to_current_scope(data, true); - static update = item_data_updater; + static update = function(data = {}) { + move_data_to_current_scope(data); + }; static draw_option = function(_x, _y, index) { var _cur_opt = toggles[index]; @@ -1122,7 +1141,7 @@ function RadioSet(options_array, title = "", data = {}) constructor { btn.x1 += _offset_x; // shift to center btn.update(); btn.draw(); - if (btn.clicked() && allow_changes) { + if (allow_changes && btn.clicked() && idx != current_selection) { current_selection = idx; // <-- no array_index_of needed } } @@ -1159,8 +1178,7 @@ function RadioSet(options_array, title = "", data = {}) constructor { /// @constructor /// @category UI /// @description A toggleable button element with hover and active states. -/// @param {struct} [data={}] Initial properties. -/// @returns {ToggleButton} +/// @param {struct} data Initial properties. function ToggleButton(data = {}) constructor { standard_loc_data(); tooltip = ""; @@ -1180,13 +1198,11 @@ function ToggleButton(data = {}) constructor { //make true to run clicked() within draw sequence clicked_check_default = false; + + move_data_to_current_scope(data); - update = function(data = {}) { - if (is_struct(data)) { - move_data_to_current_scope(data, true); - } - add_draw_return_values(); - draw_set_font(font); + static update = function(data = {}) { + move_data_to_current_scope(data); if (style == "default") { if (w == 0) { w = string_width(str1); @@ -1202,16 +1218,13 @@ function ToggleButton(data = {}) constructor { } x2 = x1 + w; y2 = y1 + h; - pop_draw_return_values(); }; - update(data); - - hover = function() { + static hover = function() { return scr_hit(x1, y1, x2, y2); }; - clicked = function() { + static clicked = function() { if (hover() && mouse_button_clicked()) { active = !active; audio_play_sound(snd_click_small, 10, false); @@ -1221,7 +1234,7 @@ function ToggleButton(data = {}) constructor { } }; - draw = function(is_active = undefined) { + static draw = function(is_active = undefined) { if (is_active != undefined) { self.active = is_active; } @@ -1298,8 +1311,7 @@ function ToggleButton(data = {}) constructor { /// @constructor /// @category UI /// @description A button with separate active/inactive tooltips and click sounds. -/// @param {struct} [data={}] Initial properties. -/// @returns {InteractiveButton} +/// @param {struct} data Initial properties. function InteractiveButton(data = {}) constructor { x1 = 0; y1 = 0; @@ -1316,11 +1328,11 @@ function InteractiveButton(data = {}) constructor { text_halign = fa_left; text_color = c_gray; button_color = c_gray; - var _data_presets = struct_get_names(data); - for (var i = 0; i < array_length(_data_presets); i++) { - self[$ _data_presets[i]] = data[$ _data_presets[i]]; - } - update = function() { + + move_data_to_current_scope(data); + + static update = function(data = {}) { + move_data_to_current_scope(data); if (width == 0) { width = string_width(str1) + 4; } @@ -1331,11 +1343,11 @@ function InteractiveButton(data = {}) constructor { y2 = y1 + height; }; - hover = function() { + static hover = function() { return scr_hit(x1, y1, x2, y2); }; - clicked = function() { + static clicked = function() { if (hover() && mouse_button_clicked()) { if (!active) { audio_play_sound(snd_error, 10, false); @@ -1349,12 +1361,14 @@ function InteractiveButton(data = {}) constructor { } }; - draw = function() { + static draw = function() { var str1_h = string_height(str1); var text_padding = width * 0.03; var text_x = x1 + text_padding; var text_y = y1 + text_padding; var total_alpha; + + add_draw_return_values(); if (text_halign == fa_center) { text_x = x1 + (width / 2); @@ -1386,51 +1400,18 @@ function InteractiveButton(data = {}) constructor { } total_alpha = state_alpha * hover_alpha; - draw_rectangle_color_simple(x1, y1, x1 + width, y1 + str1_h, 1, button_color, total_alpha); + draw_rectangle_color_simple(x1, y1, x1 + width, y1 + height, 1, button_color, total_alpha); draw_set_halign(text_halign); draw_set_valign(fa_top); draw_text_color_simple(text_x, text_y, str1, text_color, total_alpha); - draw_set_alpha(1); - draw_set_halign(fa_left); + + pop_draw_return_values(); }; } -/// @function list_traveler(list, cur_val, move_up_coords, move_down_coords) -/// @description Cycles through values in a list by clicking move-up/down regions. -/// @param {array} list Array of values. -/// @param {any} cur_val Current value. -/// @param {array} move_up_coords Bounding box for up button. -/// @param {array} move_down_coords Bounding box for down button. -/// @returns {any} New value from list. -function list_traveler(list, cur_val, move_up_coords, move_down_coords) { - var _new_val = cur_val; - var _found = false; - for (var i = 0; i < array_length(list); i++) { - if (cur_val == list[i]) { - _found = true; - if (point_and_click(move_up_coords)) { - if (i == array_length(list) - 1) { - _new_val = list[0]; - } else { - _new_val = list[i + 1]; - } - } else if (point_and_click(move_down_coords)) { - if (i == 0) { - _new_val = list[array_length(list) - 1]; - } else { - _new_val = list[i - 1]; - } - } - } - } - // If value not found in list, default to first element - if (!_found && array_length(list) > 0) { - _new_val = list[0]; - } - return _new_val; -} - -/// @function MainMenuButton +/// @function MainMenuButton(_sprite, _sprite_hover, _x, _y, _hot_key, _on_click) +/// @constructor +/// @category UI /// @description A UI button component featuring hover animations, oscillation effects, and Alt-key shortcut support. /// @param {Asset.GMSprite} _sprite The base sprite index for the button. /// @param {Asset.GMSprite} _sprite_hover The additive blend sprite used for hover effects. From 4863706ea114feafce30dd35824931e61a798db2 Mon Sep 17 00:00:00 2001 From: carys-the-weed-cloud <36204825+carys-the-weed-cloud@users.noreply.github.com> Date: Wed, 3 Jun 2026 18:58:10 -0400 Subject: [PATCH 2/2] refactor: Another round of button stability tweaks. --- scripts/scr_buttons/scr_buttons.gml | 32 ++++++++++++++++------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/scripts/scr_buttons/scr_buttons.gml b/scripts/scr_buttons/scr_buttons.gml index e998c9658..e2c651738 100644 --- a/scripts/scr_buttons/scr_buttons.gml +++ b/scripts/scr_buttons/scr_buttons.gml @@ -118,13 +118,13 @@ function list_traveler(list, cur_val, move_up_coords, move_down_coords) { _found = true; if (point_and_click(move_up_coords)) { if (i == 0) { - _new_val = list[0]; + _new_val = list[array_length(list) - 1]; } else { _new_val = list[i - 1]; } } else if (point_and_click(move_down_coords)) { if (i == array_length(list) - 1) { - _new_val = list[array_length(list) - 1]; + _new_val = list[0]; } else { _new_val = list[i + 1]; } @@ -199,7 +199,7 @@ function ReactiveString(text_param, x1_param = 0, y1_param = 0, data = {}) const static update = function(data = {}) { move_data_to_current_scope(data); - add_draw_return_values(); + var temp_font = draw_get_font(); draw_set_font(font); if (max_width > -1) { if (!scale_text) { @@ -222,7 +222,7 @@ function ReactiveString(text_param, x1_param = 0, y1_param = 0, data = {}) const x2 = x1 + w; y2 = y1 + h; } - pop_draw_return_values(); + draw_set_font(temp_font); }; static hit = function() { @@ -268,8 +268,6 @@ function ReactiveString(text_param, x1_param = 0, y1_param = 0, data = {}) const function LabeledIcon(icon_param, text_param, x1_param = 0, y1_param = 0, data = {}) constructor { x1 = x1_param; y1 = y1_param; - x2 = 0; - y2 = 0; text = text_param; text_max_width = -1; @@ -282,15 +280,21 @@ function LabeledIcon(icon_param, text_param, x1_param = 0, y1_param = 0, data = icon_height = sprite_get_height(icon); w = icon_width; h = icon_height; + x2 = x1 + w; + y2 = y1 + icon_height; + temp_font = draw_get_font(); + draw_set_font(font); + text_width = string_width(text) + 2; + draw_set_font(temp_font); move_data_to_current_scope(data); static update = function(data = {}) { move_data_to_current_scope(data); if (text_position == "right") { - w = icon_width + 2 + string_width(text); - x2 = x1 + w; + w = icon_width + text_width; h = icon_height; + x2 = x1 + w; y2 = y1 + icon_height; } else { w = icon_width; @@ -404,8 +408,8 @@ function UnitButtonObject(data = {}) constructor { static update_loc = function() { if (label != "") { - add_draw_return_values(); - draw_set_font(font); + var temp_font = draw_get_font(); + draw_set_font(font); if (!set_width) { w = string_width(label) + 10; h = string_height(label) + 4; @@ -417,7 +421,7 @@ function UnitButtonObject(data = {}) constructor { label = _text_scale.text; } h = string_height(label) + 4; - pop_draw_return_values(); + draw_set_font(temp_font); } x2 = x1 + w; y2 = y1 + h; @@ -543,7 +547,6 @@ function PurchaseButton(req) : UnitButtonObject() constructor { static draw = function(allow_click = true) { add_draw_return_values(); - var _but = draw_unit_buttons([x1, y1, x2, y2], label, [1, 1], color,,, alpha); var _sh = sprite_get_height(spr_requisition); var _scale = (y2 - y1) / _sh; @@ -976,7 +979,6 @@ function MultiSelect(options_array, title_param, data = {}) constructor { static draw = function(allow_changes_param = true) { changed = false; allow_changes = allow_changes_param; - add_draw_return_values(); has_change_method = is_callable(on_change); reset_next_draw(); @@ -992,7 +994,6 @@ function MultiSelect(options_array, title_param, data = {}) constructor { if (changed && has_change_method) { on_change(); } - pop_draw_return_values(); }; static set = function(set_array) { @@ -1203,6 +1204,8 @@ function ToggleButton(data = {}) constructor { static update = function(data = {}) { move_data_to_current_scope(data); + var temp_font = draw_get_font(); + draw_set_font(font); if (style == "default") { if (w == 0) { w = string_width(str1); @@ -1218,6 +1221,7 @@ function ToggleButton(data = {}) constructor { } x2 = x1 + w; y2 = y1 + h; + draw_set_font(temp_font); }; static hover = function() {