diff --git a/druid/base/text.lua b/druid/base/text.lua index 1c9e9d3..5396cec 100755 --- a/druid/base/text.lua +++ b/druid/base/text.lua @@ -209,6 +209,8 @@ local function update_text_area_size(self) end +---@param self druid.text +---@param trim_postfix string local function update_text_with_trim(self, trim_postfix) local max_width = self.text_area.x local text_width = self:get_text_size() @@ -231,6 +233,24 @@ local function update_text_with_trim(self, trim_postfix) end end +local function update_text_with_trim_left(self, trim_postfix) + local max_width = self.text_area.x + local text_width = self:get_text_size() + local text_length = utf8.len(self.last_value) + local trim_index = 1 + + if text_width > max_width then + local new_text = self.last_value + while text_width > max_width and trim_index < text_length do + trim_index = trim_index + 1 + new_text = trim_postfix .. utf8.sub(self.last_value, trim_index, text_length) + text_width = self:get_text_size(new_text) + end + + gui.set_text(self.node, new_text) + end +end + local function update_text_with_anchor_shift(self) if self:get_text_size() >= self.text_area.x then @@ -255,6 +275,10 @@ local function update_adjust(self) update_text_with_trim(self, self.style.TRIM_POSTFIX) end + if self.adjust_type == const.TEXT_ADJUST.TRIM_LEFT then + update_text_with_trim_left(self, self.style.TRIM_POSTFIX) + end + if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE_LIMITED then update_text_area_size(self) end @@ -267,6 +291,16 @@ local function update_adjust(self) update_text_area_size(self) update_text_with_anchor_shift(self) end + + if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM then + update_text_area_size(self) + update_text_with_trim(self, self.style.TRIM_POSTFIX) + end + + if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM_LEFT then + update_text_area_size(self) + update_text_with_trim_left(self, self.style.TRIM_POSTFIX) + end end @@ -312,13 +346,13 @@ function M:init(node, value, adjust_type) self.on_set_pivot = Event() self.on_update_text_scale = Event() - self:set_to(value or gui.get_text(self.node)) + self:set_text(value or gui.get_text(self.node)) return self end function M:on_layout_change() - self:set_to(self.last_value) + self:set_text(self.last_value) end @@ -328,13 +362,13 @@ function M:on_message_input(node_id, message) end if message.action == const.MESSAGE_INPUT.TEXT_SET then - Text.set_to(self, message.value) + M.set_text(self, message.value) end end --- Calculate text width with font with respect to trailing space ----@param text|nil string +---@param text string|nil ---@return number Width ---@return number Height function M:get_text_size(text) @@ -421,13 +455,15 @@ end --- Set text area size ---@param size vector3 The new text area size ----@return druid.text Current text instance +---@return druid.text self Current text instance function M:set_size(size) self.start_size = size self.text_area = vmath.vector3(size) self.text_area.x = self.text_area.x * self.start_scale.x self.text_area.y = self.text_area.y * self.start_scale.y update_adjust(self) + + return self end @@ -497,13 +533,15 @@ end --- Set text adjust, refresh the current text visuals, if needed +---Values are: "downscale", "trim", "no_adjust", "downscale_limited", +---"scroll", "scale_then_scroll", "trim_left", "scale_then_trim", "scale_then_trim_left" ---@param adjust_type string|nil See const.TEXT_ADJUST. If pass nil - use current adjust type ---@param minimal_scale number|nil If pass nil - not use minimal scale ----@return druid.text Current text instance +---@return druid.text self Current text instance function M:set_text_adjust(adjust_type, minimal_scale) self.adjust_type = adjust_type self._minimal_scale = minimal_scale - self:set_to(self.last_value) + self:set_text(self.last_value) return self end @@ -520,8 +558,8 @@ end --- Return current text adjust type ----@return number The current text adjust type -function M:get_text_adjust(adjust_type) +---@return string adjust_type The current text adjust type +function M:get_text_adjust() return self.adjust_type end diff --git a/druid/const.lua b/druid/const.lua index 639585f..8bfd3d1 100755 --- a/druid/const.lua +++ b/druid/const.lua @@ -98,6 +98,9 @@ M.SHIFT = { M.TEXT_ADJUST = { DOWNSCALE = "downscale", TRIM = "trim", + TRIM_LEFT = "trim_left", + SCALE_THEN_TRIM = "scale_then_trim", + SCALE_THEN_TRIM_LEFT = "scale_then_trim_left", NO_ADJUST = "no_adjust", DOWNSCALE_LIMITED = "downscale_limited", SCROLL = "scroll", diff --git a/druid/extended/hotkey.lua b/druid/extended/hotkey.lua index 871cb17..8333c5e 100644 --- a/druid/extended/hotkey.lua +++ b/druid/extended/hotkey.lua @@ -74,7 +74,7 @@ end --- Add hotkey for component callback ---@param keys string[]|hash[]|string|hash that have to be pressed before key pressed to activate ---@param callback_argument any|nil The argument to pass into the callback function ----@return Hotkey Current instance +---@return druid.hotkey Current instance function M:add_hotkey(keys, callback_argument) keys = keys or {} if type(keys) == "string" then @@ -86,7 +86,7 @@ function M:add_hotkey(keys, callback_argument) for index = 1, #keys do local key_hash = hash(keys[index]) - if helper.contains(self.style.MODIFICATORS, key_hash) then + if #keys > 1 and helper.contains(self.style.MODIFICATORS, key_hash) then table.insert(modificators, key_hash) else if not key then @@ -114,6 +114,17 @@ function M:add_hotkey(keys, callback_argument) end +function M:is_processing() + for index = 1, #self._hotkeys do + if self._hotkeys[index].is_processing then + return true + end + end + + return false +end + + function M:on_focus_gained() for k, v in pairs(self._modificators) do self._modificators[k] = false @@ -122,32 +133,32 @@ end function M:on_input(action_id, action) - if not action_id or #self._hotkeys == 0 then + if not action_id then return false end - if self._modificators[action_id] ~= nil then - if action.pressed then - self._modificators[action_id] = true - end - if action.released then - self._modificators[action_id] = false - end + if self._modificators[action_id] ~= nil and action.pressed then + self._modificators[action_id] = true end for index = 1, #self._hotkeys do local hotkey = self._hotkeys[index] - if action_id == hotkey.key then + local is_relative_key = helper.contains(self.style.MODIFICATORS, action_id) or action_id == hotkey.key + + if is_relative_key and (action_id == hotkey.key or not hotkey.key) then local is_modificator_ok = true + local is_consume = not not (hotkey.key) -- Check only required modificators pressed - for i = 1, #self.style.MODIFICATORS do - local mod = self.style.MODIFICATORS[i] - if helper.contains(hotkey.modificators, mod) and self._modificators[mod] == false then - is_modificator_ok = false - end - if not helper.contains(hotkey.modificators, mod) and self._modificators[mod] == true then - is_modificator_ok = false + if hotkey.key and #hotkey.modificators > 0 then + for i = 1, #self.style.MODIFICATORS do + local mod = self.style.MODIFICATORS[i] + if helper.contains(hotkey.modificators, mod) and self._modificators[mod] == false then + is_modificator_ok = false + end + if not helper.contains(hotkey.modificators, mod) and self._modificators[mod] == true then + is_modificator_ok = false + end end end @@ -157,23 +168,27 @@ function M:on_input(action_id, action) end if not action.pressed and self._is_process_repeated and action.repeated and is_modificator_ok and hotkey.is_processing then self.on_hotkey_released:trigger(self:get_context(), hotkey.callback_argument) - return true + return is_consume end if action.released and is_modificator_ok and hotkey.is_processing then - hotkey.is_processing = false self.on_hotkey_released:trigger(self:get_context(), hotkey.callback_argument) - return true + hotkey.is_processing = false + return is_consume end end end + if self._modificators[action_id] ~= nil and action.released then + self._modificators[action_id] = false + end + return false end --- If true, the callback will be triggered on action.repeated ---@param is_enabled_repeated bool The flag value ----@return Hotkey +---@return druid.hotkey function M:set_repeat(is_enabled_repeated) self._is_process_repeated = is_enabled_repeated return self diff --git a/druid/extended/layout.lua b/druid/extended/layout.lua index c06752e..c0d1db7 100644 --- a/druid/extended/layout.lua +++ b/druid/extended/layout.lua @@ -68,6 +68,22 @@ function M:update() end +function M:get_entities() + return self.entities +end + + +function M:set_node_index(node, index) + for i = 1, #self.entities do + if self.entities[i] == node then + table.remove(self.entities, i) + table.insert(self.entities, index, node) + break + end + end +end + + ---@param margin_x number|nil ---@param margin_y number|nil ---@return druid.layout @@ -444,15 +460,16 @@ function M:calculate_rows_data() end +---Will reset z value to 0! +local TEMP_VECTOR = vmath.vector3(0, 0, 0) ---@param node node ---@param x number ---@param y number ---@return node function M:set_node_position(node, x, y) - local position = gui.get_position(node) - position.x = x - position.y = y - gui.set_position(node, position) + TEMP_VECTOR.x = x + TEMP_VECTOR.y = y + gui.set_position(node, TEMP_VECTOR) return node end diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index cc30db9..520e76b 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -711,7 +711,7 @@ end local hotkey = require("druid.extended.hotkey") ---Create Hotkey component ---@param keys_array string|string[] Keys for trigger action. Should contains one action key and any amount of modificator keys ----@param callback function The callback function +---@param callback function|nil The callback function ---@param callback_argument any|nil The argument to pass into the callback function ---@return druid.hotkey Hotkey component function M:new_hotkey(keys_array, callback, callback_argument) diff --git a/druid/widget/properties_panel/properties/property_button.lua b/druid/widget/properties_panel/properties/property_button.lua index 27368bf..085a437 100644 --- a/druid/widget/properties_panel/properties/property_button.lua +++ b/druid/widget/properties_panel/properties/property_button.lua @@ -1,6 +1,6 @@ local color = require("druid.color") ----@class property_button: druid.widget +---@class widget.property_button: druid.widget ---@field root node ---@field container druid.container ---@field text_name druid.text @@ -12,6 +12,8 @@ local M = {} function M:init() self.root = self:get_node("root") self.text_name = self.druid:new_text("text_name") + :set_text_adjust("trim_left") + self.selected = self:get_node("selected") gui.set_alpha(self.selected, 0) @@ -33,7 +35,7 @@ end ---@param text string ----@return property_button +---@return widget.property_button function M:set_text_button(text) self.text_button:set_text(text) return self diff --git a/druid/widget/properties_panel/properties/property_checkbox.lua b/druid/widget/properties_panel/properties/property_checkbox.lua index 345e59e..57d82e1 100644 --- a/druid/widget/properties_panel/properties/property_checkbox.lua +++ b/druid/widget/properties_panel/properties/property_checkbox.lua @@ -1,4 +1,4 @@ ----@class property_checkbox: druid.widget +---@class widget.property_checkbox: druid.widget ---@field root node ---@field druid druid_instance ---@field text_name druid.text diff --git a/druid/widget/properties_panel/properties/property_input.gui b/druid/widget/properties_panel/properties/property_input.gui index 59b6101..ac4e786 100644 --- a/druid/widget/properties_panel/properties/property_input.gui +++ b/druid/widget/properties_panel/properties/property_input.gui @@ -36,7 +36,7 @@ nodes { z: 0.49 } type: TYPE_TEXT - text: "Button" + text: "Input" font: "text_bold" id: "text_name" pivot: PIVOT_W diff --git a/druid/widget/properties_panel/properties/property_input.lua b/druid/widget/properties_panel/properties/property_input.lua index ede2073..83c7c0a 100644 --- a/druid/widget/properties_panel/properties/property_input.lua +++ b/druid/widget/properties_panel/properties/property_input.lua @@ -1,4 +1,4 @@ ----@class property_input: druid.widget +---@class widget.property_input: druid.widget ---@field root node ---@field container druid.container ---@field text_name druid.text diff --git a/druid/widget/properties_panel/properties/property_left_right_selector.gui b/druid/widget/properties_panel/properties/property_left_right_selector.gui new file mode 100644 index 0000000..c7d528b --- /dev/null +++ b/druid/widget/properties_panel/properties/property_left_right_selector.gui @@ -0,0 +1,212 @@ +fonts { + name: "text_bold" + font: "/druid/fonts/text_bold.font" +} +textures { + name: "druid" + texture: "/druid/druid.atlas" +} +nodes { + size { + x: 400.0 + y: 40.0 + } + type: TYPE_BOX + texture: "druid/empty" + id: "root" + adjust_mode: ADJUST_MODE_STRETCH + inherit_alpha: true + visible: false +} +nodes { + position { + x: -200.0 + } + scale { + x: 0.5 + y: 0.5 + } + size { + x: 360.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_TEXT + text: "Left Right Selector" + font: "text_bold" + id: "text_name" + pivot: PIVOT_W + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + parent: "root" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +nodes { + position { + x: 200.0 + } + size { + x: 200.0 + y: 40.0 + } + type: TYPE_BOX + id: "E_Anchor" + pivot: PIVOT_E + parent: "root" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO + visible: false +} +nodes { + position { + x: -180.0 + } + size { + x: 40.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_BOX + texture: "druid/rect_round2_width2" + id: "button_left" + parent: "E_Anchor" + inherit_alpha: true + slice9 { + x: 5.0 + y: 5.0 + z: 5.0 + w: 5.0 + } +} +nodes { + rotation { + z: 180.0 + } + color { + x: 0.722 + y: 0.741 + z: 0.761 + } + type: TYPE_BOX + texture: "druid/icon_arrow" + id: "icon_left" + parent: "button_left" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO +} +nodes { + position { + x: -20.0 + } + size { + x: 40.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_BOX + texture: "druid/rect_round2_width2" + id: "button_right" + parent: "E_Anchor" + inherit_alpha: true + slice9 { + x: 5.0 + y: 5.0 + z: 5.0 + w: 5.0 + } +} +nodes { + color { + x: 0.722 + y: 0.741 + z: 0.761 + } + type: TYPE_BOX + texture: "druid/icon_arrow" + id: "icon_right" + parent: "button_right" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO +} +nodes { + position { + x: -100.0 + y: -20.0 + } + size { + x: 120.0 + y: 4.0 + } + color { + x: 0.894 + y: 0.506 + z: 0.333 + } + type: TYPE_BOX + texture: "druid/pixel" + id: "selected" + pivot: PIVOT_S + adjust_mode: ADJUST_MODE_STRETCH + parent: "E_Anchor" + inherit_alpha: true +} +nodes { + position { + x: -100.0 + } + scale { + x: 0.5 + y: 0.5 + } + size { + x: 220.0 + y: 40.0 + } + color { + x: 0.463 + y: 0.475 + z: 0.49 + } + type: TYPE_TEXT + text: "42" + font: "text_bold" + id: "text_value" + outline { + x: 1.0 + y: 1.0 + z: 1.0 + } + shadow { + x: 1.0 + y: 1.0 + z: 1.0 + } + parent: "E_Anchor" + inherit_alpha: true + outline_alpha: 0.0 + shadow_alpha: 0.0 +} +material: "/builtins/materials/gui.material" +adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_left_right_selector.lua b/druid/widget/properties_panel/properties/property_left_right_selector.lua new file mode 100644 index 0000000..b6e5ed7 --- /dev/null +++ b/druid/widget/properties_panel/properties/property_left_right_selector.lua @@ -0,0 +1,204 @@ +local event = require("druid.event") + +---@class widget.property_left_right_selector: druid.widget +---@field root node +---@field druid druid_instance +---@field text_name druid.text +---@field button druid.button +---@field selected node +---@field value string|number +local M = {} + + +function M:init() + self.root = self:get_node("root") + self.selected = self:get_node("selected") + gui.set_alpha(self.selected, 0) + + self.text_name = self.druid:new_text("text_name") + self.text_value = self.druid:new_text("text_value") + self.button_left = self.druid:new_button("button_left", self.on_button_left) + self.button_right = self.druid:new_button("button_right", self.on_button_right) + + self.on_change_value = event.create() + + self.container = self.druid:new_container(self.root) + self.container:add_container("text_name") + self.container:add_container("E_Anchor") +end + + +function M:set_text(text) + self.text_name:set_text(text) + return self +end + + +---Helper to cycle number in range +---@param value number Current value +---@param min number Min range value +---@param max number Max range value +---@param step number Step size +---@param is_loop boolean Is looped +---@return number Cycled value +local function step_number(value, min, max, step, is_loop) + local range = max - min + 1 + if is_loop then + -- Normalize step within range + local effective_step = step + if math.abs(step) >= range then + effective_step = step % range + if effective_step == 0 then + effective_step = step > 0 and range or -range + end + end + + value = value + effective_step + -- Handle wrapping + if max then + while value > max do + value = min + (value - max - 1) + end + end + if min then + while value < min do + value = max - (min - value - 1) + end + end + else + -- Clamp values + value = value + step + if max and value > max then + return max + elseif min and value < min then + return min + end + end + return value +end + + +---Helper to cycle array index with proper step wrapping +---@param array table Array to cycle through +---@param current_value any Current value to find index for +---@param step number Step direction +---@param is_loop boolean If true, cycle values. If false, clamp at ends +---@return any Next value in cycle +local function step_array(array, current_value, step, is_loop) + local index = 1 + for i, v in ipairs(array) do + if v == current_value then + index = i + break + end + end + + if is_loop then + -- Normalize step within array length + local range = #array + local effective_step = step + if math.abs(step) >= range then + effective_step = step % range + if effective_step == 0 then + effective_step = step > 0 and range or -range + end + end + + index = index + effective_step + -- Handle wrapping + while index > range do + index = 1 + (index - range - 1) + end + while index < 1 do + index = range - (1 - index - 1) + end + else + -- Clamp values + index = index + step + if index > #array then + index = #array + elseif index < 1 then + index = 1 + end + end + + return array[index] +end + + +function M:on_button_left() + self:add_value(true) +end + +function M:on_button_right() + self:add_value(false) +end + + +function M:add_value(is_left) + local koef = is_left and -1 or 1 + local array_type = self.array_type + if array_type then + local value = self.value + local new_value = step_array(array_type.array, value, koef * array_type.steps, array_type.is_loop) + self:set_value(new_value) + return + end + + + local number_type = self.number_type + if number_type then + local value = tonumber(self.value) --[[@as number]] + local new_value = step_number(value, number_type.min, number_type.max, koef * number_type.steps, number_type.is_loop) + self:set_value(new_value) + return + end +end + + +function M:set_number_type(min, max, is_loop, steps) + self.number_type = { + min = min, + max = max, + steps = steps or 1, + is_loop = is_loop, + } + return self +end + + +function M:set_array_type(array, is_loop, steps) + self.array_type = { + array = array, + steps = steps or 1, + is_loop = is_loop, + } + + return self +end + + +---@param value string|number +function M:set_value(value, is_instant) + if self.value == value then + return + end + + self.value = value + self.text_value:set_text(tostring(value)) + self.on_change_value:trigger(value) + + if not is_instant then + gui.set_alpha(self.selected, 1) + gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16) + end +end + + +---@return string|number +function M:get_value() + return self.value +end + + +return M diff --git a/druid/widget/properties_panel/properties/property_slider.lua b/druid/widget/properties_panel/properties/property_slider.lua index cb89b09..16c74f4 100644 --- a/druid/widget/properties_panel/properties/property_slider.lua +++ b/druid/widget/properties_panel/properties/property_slider.lua @@ -1,4 +1,4 @@ ----@class property_slider: druid.widget +---@class widget.property_slider: druid.widget ---@field root node ---@field container druid.container ---@field druid druid_instance diff --git a/druid/widget/properties_panel/properties/property_text.lua b/druid/widget/properties_panel/properties/property_text.lua index 0f8b69d..a150e7d 100644 --- a/druid/widget/properties_panel/properties/property_text.lua +++ b/druid/widget/properties_panel/properties/property_text.lua @@ -1,4 +1,4 @@ ----@class property_text: druid.widget +---@class widget.property_text: druid.widget ---@field root node ---@field container druid.container ---@field text_name druid.text @@ -8,6 +8,7 @@ local M = {} function M:init() self.root = self:get_node("root") self.text_name = self.druid:new_text("text_name") + :set_text_adjust("scale_when_trim_left", 0.3) self.text_right = self.druid:new_text("text_right", "") self.container = self.druid:new_container(self.root) @@ -21,15 +22,15 @@ end ---@param text string ----@return property_text +---@return widget.property_text function M:set_text(text) self.text_name:set_text(text) return self end ----@param text string ----@return property_text +---@param text string|nil +---@return widget.property_text function M:set_right_text(text) self.text_right:set_text(text or "") return self diff --git a/druid/widget/properties_panel/properties_panel.gui b/druid/widget/properties_panel/properties_panel.gui index 2df907a..0352b4d 100644 --- a/druid/widget/properties_panel/properties_panel.gui +++ b/druid/widget/properties_panel/properties_panel.gui @@ -426,5 +426,69 @@ nodes { parent: "property_text/root" template_node_child: true } +nodes { + position { + y: -250.0 + } + type: TYPE_TEMPLATE + id: "property_left_right_selector" + parent: "propeties" + inherit_alpha: true + template: "/druid/widget/properties_panel/properties/property_left_right_selector.gui" +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/root" + parent: "property_left_right_selector" + template_node_child: true +} +nodes { + type: TYPE_TEXT + id: "property_left_right_selector/text_name" + parent: "property_left_right_selector/root" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/E_Anchor" + parent: "property_left_right_selector/root" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/button_left" + parent: "property_left_right_selector/E_Anchor" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/icon_left" + parent: "property_left_right_selector/button_left" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/button_right" + parent: "property_left_right_selector/E_Anchor" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/icon_right" + parent: "property_left_right_selector/button_right" + template_node_child: true +} +nodes { + type: TYPE_BOX + id: "property_left_right_selector/selected" + parent: "property_left_right_selector/E_Anchor" + template_node_child: true +} +nodes { + type: TYPE_TEXT + id: "property_left_right_selector/text_value" + parent: "property_left_right_selector/E_Anchor" + template_node_child: true +} material: "/builtins/materials/gui.material" adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties_panel.lua b/druid/widget/properties_panel/properties_panel.lua index 7cae30d..2993b6c 100644 --- a/druid/widget/properties_panel/properties_panel.lua +++ b/druid/widget/properties_panel/properties_panel.lua @@ -3,8 +3,9 @@ local property_slider = require("druid.widget.properties_panel.properties.proper local property_button = require("druid.widget.properties_panel.properties.property_button") local property_input = require("druid.widget.properties_panel.properties.property_input") local property_text = require("druid.widget.properties_panel.properties.property_text") +local property_left_right_selector = require("druid.widget.properties_panel.properties.property_left_right_selector") ----@class properties_panel: druid.widget +---@class widget.properties_panel: druid.widget ---@field root node ---@field scroll druid.scroll ---@field druid druid_instance @@ -24,6 +25,9 @@ function M:init() self.default_size = self.container:get_size() self.properties = {} + self.properties_constructors = {} + self.current_page = 1 + self.properties_per_page = 15 self.text_header = self.druid:new_text("text_header") self.scroll = self.druid:new_scroll("scroll_view", "scroll_content") @@ -51,6 +55,23 @@ function M:init() self.property_text_prefab = self:get_node("property_text/root") gui.set_enabled(self.property_text_prefab, false) + + self.property_left_right_selector_prefab = self:get_node("property_left_right_selector/root") + gui.set_enabled(self.property_left_right_selector_prefab, false) + + self.paginator = self:add_left_right_selector("Page", self.current_page, function(value) + self.current_page = value + self:refresh_page() + end):set_number_type(1, 1, true) + gui.set_enabled(self.paginator.root, false) + + -- Remove paginator from properties + for index = 1, #self.properties do + if self.properties[index] == self.paginator then + table.remove(self.properties, index) + break + end + end end @@ -66,7 +87,12 @@ function M:clear() self.druid:remove(self.properties[index]) end self.layout:clear_layout() + self.layout:add(self.paginator.root) + self.properties = {} + self.properties_constructors = {} + + self:refresh_page() end @@ -88,15 +114,11 @@ end ---@param text string ---@param initial_value boolean ---@param on_change_callback function ----@return property_checkbox +---@return widget.property_checkbox function M:add_checkbox(text, initial_value, on_change_callback) - text = tostring(text) + local instance = self:create_from_prefab(property_checkbox, "property_checkbox", self.property_checkbox_prefab) - local nodes = gui.clone_tree(self.property_checkbox_prefab) - local instance = self.druid:new_widget(property_checkbox, "property_checkbox", nodes) - self:add_property(instance) - - instance.text_name:set_to(text) + instance.text_name:set_text(text) instance:set_value(initial_value, true) instance.button.on_click:subscribe(function() on_change_callback(instance:get_value()) @@ -109,12 +131,9 @@ end ---@param text string ---@param initial_value number ---@param on_change_callback function ----@return property_slider +---@return widget.property_slider function M:add_slider(text, initial_value, on_change_callback) - text = tostring(text) - local nodes = gui.clone_tree(self.property_slider_prefab) - local instance = self.druid:new_widget(property_slider, "property_slider", nodes) - self:add_property(instance) + local instance = self:create_from_prefab(property_slider, "property_slider", self.property_slider_prefab) instance.text_name:set_text(text) instance:set_value(initial_value, true) @@ -129,10 +148,9 @@ end ---@param text string ---@param on_click_callback function|nil ---@param callback_context any|nil +---@return widget.property_button function M:add_button(text, on_click_callback, callback_context) - local nodes = gui.clone_tree(self.property_button_prefab) - local instance = self.druid:new_widget(property_button, "property_button", nodes) - self:add_property(instance) + local instance = self:create_from_prefab(property_button, "property_button", self.property_button_prefab) instance.text_name:set_text(text) if on_click_callback then @@ -143,11 +161,12 @@ function M:add_button(text, on_click_callback, callback_context) end +---@param text string +---@param initial_value string +---@param on_change_callback function +---@return widget.property_input function M:add_input(text, initial_value, on_change_callback) - text = tostring(text) - local nodes = gui.clone_tree(self.property_input_prefab) - local instance = self.druid:new_widget(property_input, "property_input", nodes) - self:add_property(instance) + local instance = self:create_from_prefab(property_input, "property_input", self.property_input_prefab) instance.text_name:set_text(text) instance.rich_input:set_text(initial_value) @@ -155,20 +174,44 @@ function M:add_input(text, initial_value, on_change_callback) instance.rich_input.input.on_input_unselect:subscribe(function(_, value) on_change_callback(value) end) + + return instance end ---@param text string ---@param right_text string|nil ----@return property_text +---@return widget.property_text function M:add_text(text, right_text) - text = tostring(text) - local nodes = gui.clone_tree(self.property_text_prefab) - local instance = self.druid:new_widget(property_text, "property_text", nodes) - self:add_property(instance) + local instance = self:create_from_prefab(property_text, "property_text", self.property_text_prefab) instance:set_text(text) instance:set_right_text(right_text) + + return instance +end + + +---@param text string +---@param value string|number|nil +---@param on_change_callback fun(value: string|number) +---@return widget.property_left_right_selector +function M:add_left_right_selector(text, value, on_change_callback) + local instance = self:create_from_prefab(property_left_right_selector, "property_left_right_selector", self.property_left_right_selector_prefab) + + instance:set_text(text) + instance:set_value(value or 0, true) + instance.on_change_value:subscribe(on_change_callback) + + return instance +end + + +---@private +function M:create_from_prefab(widget_class, widget_name, prefab) + local nodes = gui.clone_tree(prefab) + local instance = self.druid:new_widget(widget_class, widget_name, nodes) + self:add_property(instance) return instance end @@ -177,14 +220,36 @@ end function M:add_property(widget) gui.set_enabled(widget.root, true) self.layout:add(widget.root) + table.insert(self.properties, widget) local width = self.layout:get_size().x - self.layout.padding.x - self.layout.padding.z widget.container:set_size(width) + if #self.properties > self.properties_per_page then + self:refresh_page() + end + return widget end +function M:refresh_page() + local start_index = (self.current_page - 1) * self.properties_per_page + 1 + local end_index = start_index + self.properties_per_page - 1 + + for index = 1, #self.properties do + local is_visible = index >= start_index and index <= end_index + gui.set_enabled(self.properties[index].root, is_visible) + end + + gui.set_enabled(self.paginator.root, #self.properties > self.properties_per_page) + self.paginator:set_number_type(1, math.ceil(#self.properties / self.properties_per_page), true) + self.paginator.text_value:set_text(self.current_page .. " / " .. math.ceil(#self.properties / self.properties_per_page)) + + self.layout:set_dirty() +end + + function M:remove(widget) for index = 1, #self.properties do if self.properties[index] == widget then @@ -206,9 +271,14 @@ function M:toggle_hide() self.container:set_size(new_size.x, new_size.y, gui.PIVOT_N) gui.set_enabled(self.content, not self.is_hidden) - return self end +---@param properties_per_page number +function M:set_properties_per_page(properties_per_page) + self.properties_per_page = properties_per_page +end + + return M