diff --git a/druid/base/drag.lua b/druid/base/drag.lua index 87f5be5..bcc6f78 100644 --- a/druid/base/drag.lua +++ b/druid/base/drag.lua @@ -62,6 +62,10 @@ local const = require("druid.const") local helper = require("druid.helper") local component = require("druid.component") +---@class druid.drag.style +---@field DRAG_DEADZONE number Distance in pixels to start dragging. Default: 10 +---@field NO_USE_SCREEN_KOEF boolean If screen aspect ratio affects on drag values. Default: false + ---@class druid.drag: druid.base_component ---@field node node ---@field on_touch_start event @@ -69,7 +73,7 @@ local component = require("druid.component") ---@field on_drag_start event ---@field on_drag event ---@field on_drag_end event ----@field style table +---@field style druid.drag.style ---@field click_zone node|nil ---@field is_touch boolean ---@field is_drag boolean diff --git a/druid/bindings.lua b/druid/bindings.lua index 15f87a4..e63a11e 100644 --- a/druid/bindings.lua +++ b/druid/bindings.lua @@ -4,7 +4,7 @@ local M = {} local WRAPPED_WIDGETS = {} ---Set a widget to the current game object. The game object can acquire the widget by calling `bindings.get_widget` ----It wraps only top level functions, so no access to nested widgets +---It wraps with events only top level functions cross-context, so no access to nested widgets functions ---@param widget druid.widget function M.set_widget(widget) local object = msg.url() diff --git a/druid/component.lua b/druid/component.lua index e7deb30..6ea5918 100644 --- a/druid/component.lua +++ b/druid/component.lua @@ -81,7 +81,7 @@ function M:set_template(template) local parent = self:get_parent_component() if parent then local parent_template = parent:get_template() - if #parent_template > 0 then + if parent_template and #parent_template > 0 then if #template > 0 then template = "/" .. template end @@ -89,7 +89,12 @@ function M:set_template(template) end end - self._meta.template = template + if template ~= "" then + self._meta.template = template + else + self._meta.template = nil + end + return self end @@ -127,7 +132,7 @@ end ---Get Druid instance for inner component creation. ---@param template string|nil ----@param nodes table|nil +---@param nodes table|nil ---@return druid_instance function M:get_druid(template, nodes) local context = { _context = self } diff --git a/druid/custom/rich_text/rich_text.lua b/druid/custom/rich_text/rich_text.lua index e36b67a..a8ad888 100644 --- a/druid/custom/rich_text/rich_text.lua +++ b/druid/custom/rich_text/rich_text.lua @@ -76,6 +76,42 @@ local component = require("druid.component") local rich_text = require("druid.custom.rich_text.module.rt") +---@class druid.rich_text.word +---@field node node +---@field relative_scale number +---@field color vector4 +---@field position vector3 +---@field offset vector3 +---@field scale vector3 +---@field size vector3 +---@field metrics druid.rich_text.metrics +---@field pivot userdata +---@field text string +---@field shadow vector4 +---@field outline vector4 +---@field font string +---@field image druid.rich_text.word.image +---@field br boolean +---@field nobr boolean + +---@class druid.rich_text.word.image +---@field texture string +---@field anim string +---@field width number +---@field height number + +---@class druid.rich_text.lines_metrics +---@field text_width number +---@field text_height number +---@field lines table + +---@class druid.rich_text.metrics +---@field width number +---@field height number +---@field offset_x number|nil +---@field offset_y number|nil +---@field node_size vector3|nil + ---@class druid.rich_text: druid.base_component ---@field root node ---@field text_prefab node @@ -231,7 +267,7 @@ end --- Get all current words. ----@return table druid.rich_text.word[] +---@return druid.rich_text.word[] function M:get_words() return self._words end diff --git a/druid/extended/input.lua b/druid/extended/input.lua index 30de427..89492f6 100755 --- a/druid/extended/input.lua +++ b/druid/extended/input.lua @@ -373,7 +373,7 @@ function M:set_text(input_text) self.is_empty = #value == 0 and #marked_value == 0 local final_text = value .. marked_value - self.text:set_to(final_text) + self.text:set_text(final_text) -- measure it self.text_width = self.text:get_text_size(value) diff --git a/druid/extended/lang_text.lua b/druid/extended/lang_text.lua index a2ee5f3..8c8c276 100755 --- a/druid/extended/lang_text.lua +++ b/druid/extended/lang_text.lua @@ -72,13 +72,21 @@ end ---@return druid.lang_text Current instance function M:set_to(text) self.last_locale = false - self.text:set_to(text) + self.text:set_text(text) self.on_change:trigger() return self end +--- Setup raw text to lang_text component +---@param text string Text for text node +---@return druid.lang_text Current instance +function M:set_text(text) + return self:set_to(text) +end + + --- Translate the text by locale_id ---@param locale_id string Locale id ---@param a string|nil Optional param to string.format @@ -92,7 +100,7 @@ end function M:translate(locale_id, a, b, c, d, e, f, g) self.last_locale_args = { a, b, c, d, e, f, g } self.last_locale = locale_id or self.last_locale - self.text:set_to(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "") + self.text:set_text(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "") return self end @@ -109,7 +117,7 @@ end ---@return druid.lang_text Current instance function M:format(a, b, c, d, e, f, g) self.last_locale_args = { a, b, c, d, e, f, g } - self.text:set_to(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "") + self.text:set_text(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "") return self end diff --git a/druid/extended/layout.lua b/druid/extended/layout.lua index 3bee2bd..33d023d 100644 --- a/druid/extended/layout.lua +++ b/druid/extended/layout.lua @@ -45,7 +45,9 @@ function M:init(node_or_node_id, layout_type) self.size = gui.get_size(self.node) self.padding = gui.get_slice9(self.node) + -- Grab default margins from slice9 z/w values self.margin = { x = self.padding.z, y = self.padding.w } + -- Use symmetrical padding from x/z self.padding.z = self.padding.x self.padding.w = self.padding.y @@ -199,10 +201,11 @@ function M:get_size() end ----@return vector3 +---@return number, number function M:get_content_size() - local rows_data = self:calculate_rows_data() - return vmath.vector3(rows_data.total_width, rows_data.total_height, 0) + local width = self.size.x - self.padding.x - self.padding.z + local height = self.size.y - self.padding.y - self.padding.w + return width, height end diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index e39fad1..0c3fb69 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -476,14 +476,13 @@ end ---@generic T: druid.base_component ---@param widget T ---@param template string|nil The template name used by widget ----@param nodes table|node|nil The nodes table from gui.clone_tree or prefab node to use for clone +---@param nodes table|node|nil The nodes table from gui.clone_tree or prefab node to use for clone ---@vararg any ---@return T function M:new_widget(widget, template, nodes, ...) local instance = create_widget(self, widget) if type(nodes) == "userdata" then - -- It's a node, we will use it as a root node to make nodes nodes = gui.clone_tree(nodes) end diff --git a/druid/widget/properties_panel/properties/property_button.lua b/druid/widget/properties_panel/properties/property_button.lua index d7fa92f..eefa866 100644 --- a/druid/widget/properties_panel/properties/property_button.lua +++ b/druid/widget/properties_panel/properties/property_button.lua @@ -12,7 +12,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_then_trim_left", 0.3) + :set_text_adjust("scale_then_trim", 0.3) self.selected = self:get_node("selected") gui.set_alpha(self.selected, 0) @@ -34,6 +34,14 @@ function M:on_click() end +---@param text string +---@return widget.property_button +function M:set_text_property(text) + self.text_name:set_text(text) + return self +end + + ---@param text string ---@return widget.property_button function M:set_text_button(text) diff --git a/druid/widget/properties_panel/properties/property_checkbox.lua b/druid/widget/properties_panel/properties/property_checkbox.lua index bf4c2dc..a0fc7ea 100644 --- a/druid/widget/properties_panel/properties/property_checkbox.lua +++ b/druid/widget/properties_panel/properties/property_checkbox.lua @@ -1,3 +1,5 @@ +local event = require("event.event") + ---@class widget.property_checkbox: druid.widget ---@field root node ---@field druid druid_instance @@ -17,13 +19,15 @@ function M:init() gui.set_alpha(self.selected, 0) self.text_name = self.druid:new_text("text_name") - :set_text_adjust("scale_then_trim_left", 0.3) + :set_text_adjust("scale_then_trim", 0.3) self.button = self.druid:new_button("button", self.on_click) self.container = self.druid:new_container(self.root) self.container:add_container("text_name") self.container:add_container("E_Anchor") + + self.on_change_value = event.create() end @@ -35,6 +39,7 @@ function M:set_value(value, is_instant) self._value = value gui.set_enabled(self.icon, value) + self.on_change_value:trigger(value) if not is_instant then gui.set_alpha(self.selected, 1) @@ -54,4 +59,18 @@ function M:on_click() end +--- Set the text property of the checkbox +---@param text string +function M:set_text_property(text) + self.text_name:set_text(text) +end + + +--- Set the callback function for when the checkbox value changes +---@param callback function +function M:on_change(callback) + self.on_change_value:subscribe(callback) +end + + return M diff --git a/druid/widget/properties_panel/properties/property_input.lua b/druid/widget/properties_panel/properties/property_input.lua index 7f18847..7fa5712 100644 --- a/druid/widget/properties_panel/properties/property_input.lua +++ b/druid/widget/properties_panel/properties/property_input.lua @@ -3,14 +3,13 @@ ---@field container druid.container ---@field text_name druid.text ---@field button druid.button ----@field text_button druid.text ---@field druid druid_instance local M = {} function M:init() self.root = self:get_node("root") self.text_name = self.druid:new_text("text_name") - :set_text_adjust("scale_then_trim_left", 0.3) + :set_text_adjust("scale_then_trim", 0.3) self.selected = self:get_node("selected") gui.set_alpha(self.selected, 0) @@ -30,11 +29,23 @@ end ---@param text string ----@return property_input -function M:set_text_button(text) - self.text_button:set_text(text) +---@return widget.property_input +function M:set_text_property(text) + self.text_name:set_text(text) + return self +end + +---@param text string +---@return widget.property_input +function M:set_text_value(text) + self.rich_input:set_text(text) return self end +function M:on_change(callback, callback_context) + self.rich_input.input.on_input_unselect:subscribe(callback, callback_context) +end + + return M diff --git a/druid/widget/properties_panel/properties/property_left_right_selector.lua b/druid/widget/properties_panel/properties/property_left_right_selector.lua index 2065663..6cb79b5 100644 --- a/druid/widget/properties_panel/properties/property_left_right_selector.lua +++ b/druid/widget/properties_panel/properties/property_left_right_selector.lua @@ -7,6 +7,7 @@ local event = require("event.event") ---@field button druid.button ---@field selected node ---@field value string|number +---@field on_change_value event fun(value: string|number) local M = {} @@ -16,7 +17,7 @@ function M:init() gui.set_alpha(self.selected, 0) self.text_name = self.druid:new_text("text_name") - :set_text_adjust("scale_then_trim_left", 0.3) + :set_text_adjust("scale_then_trim", 0.3) self.text_value = self.druid:new_text("text_value") self.button_left = self.druid:new_button("button_left", self.on_button_left) diff --git a/druid/widget/properties_panel/properties/property_slider.gui b/druid/widget/properties_panel/properties/property_slider.gui index c0a9564..4a2f739 100644 --- a/druid/widget/properties_panel/properties/property_slider.gui +++ b/druid/widget/properties_panel/properties/property_slider.gui @@ -149,7 +149,7 @@ nodes { z: 0.49 } type: TYPE_BOX - texture: "druid/rect_round2_width1" + texture: "druid/rect_round2_width2" id: "button" pivot: PIVOT_E parent: "E_Anchor" diff --git a/druid/widget/properties_panel/properties/property_slider.lua b/druid/widget/properties_panel/properties/property_slider.lua index 805a183..d025989 100644 --- a/druid/widget/properties_panel/properties/property_slider.lua +++ b/druid/widget/properties_panel/properties/property_slider.lua @@ -21,7 +21,7 @@ function M:init() self.max = 1 self.text_name = self.druid:new_text("text_name") - :set_text_adjust("scale_then_trim_left", 0.3) + :set_text_adjust("scale_then_trim", 0.3) self.text_value = self.druid:new_text("text_value") self.slider = self.druid:new_slider("slider_pin", vmath.vector3(55, 0, 0), self.update_value) --[[@as druid.slider]] @@ -46,6 +46,20 @@ function M:set_text_function(callback) end +--- Sets the text property of the slider +---@param text string +function M:set_text_property(text) + self.text_name:set_text(text) +end + + +--- Sets the callback function for when the slider value changes +---@param callback fun(value:number) +function M:on_change(callback) + self.on_change_value:subscribe(callback) +end + + ---@param value number function M:set_value(value, is_instant) local diff = math.abs(self.max - self.min) diff --git a/druid/widget/properties_panel/properties/property_text.lua b/druid/widget/properties_panel/properties/property_text.lua index 20c0e81..cb73678 100644 --- a/druid/widget/properties_panel/properties/property_text.lua +++ b/druid/widget/properties_panel/properties/property_text.lua @@ -25,7 +25,7 @@ end ---@param text string ---@return widget.property_text -function M:set_text(text) +function M:set_text_property(text) self.text_name:set_text(text) return self end @@ -33,7 +33,7 @@ end ---@param text string|nil ---@return widget.property_text -function M:set_right_text(text) +function M:set_text_value(text) self.text_right:set_text(text or "") return self end diff --git a/druid/widget/properties_panel/properties_panel.lua b/druid/widget/properties_panel/properties_panel.lua index 1569870..e960ace 100644 --- a/druid/widget/properties_panel/properties_panel.lua +++ b/druid/widget/properties_panel/properties_panel.lua @@ -8,7 +8,15 @@ local property_left_right_selector = require("druid.widget.properties_panel.prop ---@class widget.properties_panel: druid.widget ---@field root node ---@field scroll druid.scroll ----@field druid druid_instance +---@field layout druid.layout +---@field container druid.container +---@field container_content druid.container +---@field container_scroll_view druid.container +---@field contaienr_scroll_content druid.container +---@field text_header druid.text +---@field paginator widget.property_left_right_selector +---@field properties druid.widget[] List of created properties +---@field properties_constructors fun()[] List of properties functions to create a new widget. Used to not spawn non-visible widgets but keep the reference local M = {} @@ -59,19 +67,18 @@ function M:init() 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) + -- We not using as a part of properties, since it handled in a way to be paginable + self.paginator = self.druid:new_widget(property_left_right_selector, "property_left_right_selector", self.property_left_right_selector_prefab) + self.paginator:set_text("Page") + self.paginator:set_number_type(1, 1, true) + self.paginator:set_value(self.current_page) + self.paginator.on_change_value:subscribe(function(value) + self:set_page(value) + end) + local width = self.layout:get_content_size() + self.paginator.container:set_size(width) - -- 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 + gui.set_enabled(self.paginator.root, false) end @@ -86,18 +93,23 @@ function M:on_drag_widget(dx, dy) end -function M:clear() +function M:clear_created_properties() for index = 1, #self.properties do gui.delete_node(self.properties[index].root) 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() + self.layout:clear_layout() + + -- Use paginator as "pinned" widget + self.layout:add(self.paginator.root) +end + + +function M:clear() + self:clear_created_properties() + self.properties_constructors = {} end @@ -113,117 +125,114 @@ function M:on_size_changed(new_size) for index = 1, #self.properties do self.properties[index].container:set_size(width) end + self.paginator.container:set_size(width) end ----@param text string ----@param initial_value boolean ----@param on_change_callback function ----@return widget.property_checkbox -function M:add_checkbox(text, initial_value, on_change_callback) - local instance = self:create_from_prefab(property_checkbox, "property_checkbox", self.property_checkbox_prefab) +function M:update(dt) + if self.is_dirty then + self.is_dirty = false - instance.text_name:set_text(text) - instance:set_value(initial_value, true) - instance.button.on_click:subscribe(function() - on_change_callback(instance:get_value()) - end) + self:clear_created_properties() - return instance -end + local properties_count = #self.properties_constructors + -- Render all current properties + local start_index = (self.current_page - 1) * self.properties_per_page + 1 + local end_index = start_index + self.properties_per_page - 1 + end_index = math.min(end_index, properties_count) ----@param text string ----@param initial_value number ----@param on_change_callback function ----@return widget.property_slider -function M:add_slider(text, initial_value, on_change_callback) - local instance = self:create_from_prefab(property_slider, "property_slider", self.property_slider_prefab) + local is_paginator_visible = properties_count > self.properties_per_page + gui.set_enabled(self.paginator.root, is_paginator_visible) + self.paginator:set_number_type(1, math.ceil(properties_count / self.properties_per_page), true) + self.paginator.text_value:set_text(self.current_page .. " / " .. math.ceil(properties_count / self.properties_per_page)) - instance.text_name:set_text(text) - instance:set_value(initial_value, true) - instance.on_change_value:subscribe(function(value) - on_change_callback(value) - end) - - return instance -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 instance = self:create_from_prefab(property_button, "property_button", self.property_button_prefab) - - instance.text_name:set_text(text) - if on_click_callback then - instance.button.on_click:subscribe(on_click_callback, callback_context) + for index = start_index, end_index do + self.properties_constructors[index]() + end end - - return instance 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) - local instance = self:create_from_prefab(property_input, "property_input", self.property_input_prefab) +---@param on_create fun(checkbox: widget.property_checkbox)|nil +---@return widget.properties_panel +function M:add_checkbox(on_create) + return self:add_inner_widget(property_checkbox, "property_checkbox", self.property_checkbox_prefab, on_create) +end - instance.text_name:set_text(text) - instance.rich_input:set_text(initial_value) - instance.rich_input:set_placeholder("") - instance.rich_input.input.on_input_unselect:subscribe(function(_, value) - on_change_callback(value) + +---@param on_create fun(slider: widget.property_slider)|nil +---@return widget.properties_panel +function M:add_slider(on_create) + return self:add_inner_widget(property_slider, "property_slider", self.property_slider_prefab, on_create) +end + + +---@param on_create fun(button: widget.property_button)|nil +---@return widget.properties_panel +function M:add_button(on_create) + return self:add_inner_widget(property_button, "property_button", self.property_button_prefab, on_create) +end + + +---@param on_create fun(input: widget.property_input)|nil +---@return widget.properties_panel +function M:add_input(on_create) + return self:add_inner_widget(property_input, "property_input", self.property_input_prefab, on_create) +end + + +---@param on_create fun(text: widget.property_text)|nil +function M:add_text(on_create) + return self:add_inner_widget(property_text, "property_text", self.property_text_prefab, on_create) +end + + +---@param on_create fun(selector: widget.property_left_right_selector)|nil +function M:add_left_right_selector(on_create) + return self:add_inner_widget(property_left_right_selector, "property_left_right_selector", self.property_left_right_selector_prefab, on_create) +end + + +---@generic T: druid.widget +---@param widget_class T +---@param template string|nil +---@param nodes table|node|nil +---@param on_create fun(widget: T)|nil +---@return widget.properties_panel +function M:add_inner_widget(widget_class, template, nodes, on_create) + table.insert(self.properties_constructors, function() + local widget = self.druid:new_widget(widget_class, template, nodes) + + self:add_property(widget) + if on_create then + on_create(widget) + end end) - return instance + self.is_dirty = true + + return self end ----@param text string ----@param right_text string|nil ----@return widget.property_text -function M:add_text(text, right_text) - local instance = self:create_from_prefab(property_text, "property_text", self.property_text_prefab) +---@param create_widget_callback fun(): druid.widget +---@return widget.properties_panel +function M:add_widget(create_widget_callback) + table.insert(self.properties_constructors, function() + local widget = create_widget_callback() + self:add_property(widget) + end) - instance:set_text(text) - instance:set_right_text(right_text) + self.is_dirty = true - 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 - - - ----@param widget druid.widget -function M:add_widget(widget) - self:add_property(widget) + return self end ---@private -function M:create_from_prefab(widget_class, widget_name, prefab) - local instance = self.druid:new_widget(widget_class, widget_name, prefab) - self:add_property(instance) - return instance +function M:create_from_prefab(widget_class, template, nodes) + return self:add_property(self.druid:new_widget(widget_class, template, nodes)) end @@ -233,34 +242,13 @@ function M:add_property(widget) 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 + local width = self.layout:get_content_size() 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 @@ -294,8 +282,7 @@ end function M:set_page(page) self.current_page = page - self.paginator:set_value(self.current_page, true) - self:refresh_page() + self.is_dirty = true end