diff --git a/druid/base/blocker.lua b/druid/base/blocker.lua index f2190cb..87c4ee6 100644 --- a/druid/base/blocker.lua +++ b/druid/base/blocker.lua @@ -20,7 +20,7 @@ local M = component.create("blocker") ---@param node node|string The node to use as a blocker function M:init(node) self.node = self:get_node(node) - self._is_enabled = gui.is_enabled(self.node, true) + self._is_enabled = true end diff --git a/druid/color.lua b/druid/color.lua index b578ea7..0d22cfb 100644 --- a/druid/color.lua +++ b/druid/color.lua @@ -8,9 +8,13 @@ local M = {} ---Get color by string (hex or from palette) ----@param color_id string Color id from palette or hex color +---@param color_id string|vector4 Color id from palette or hex color ---@return vector4 function M.get_color(color_id) + if type(color_id) == "vector4" then + return color_id + end + if PALETTE_DATA[color_id] then return PALETTE_DATA[color_id] end @@ -175,21 +179,15 @@ function M.rgb2hex(red, green, blue) end -local load_palette_from_json = function(path) - local data = sys.load_resource(path) +local DEFAULT_PALETTE_PATH = sys.get_config_string("druid.palette_path") +if DEFAULT_PALETTE_PATH then + local loaded_palette = sys.load_resource(DEFAULT_PALETTE_PATH) + local data = loaded_palette and json.decode(loaded_palette) if not data then return end - return json.decode(data) -end - -local DEFAULT_PALETTE_PATH = sys.get_config_string("druid.palette_path") -if DEFAULT_PALETTE_PATH then - local loaded_palette = load_palette_from_json(DEFAULT_PALETTE_PATH) - if loaded_palette and loaded_palette["default"] then - M.add_palette(loaded_palette["default"]) - end + M.add_palette(data) end diff --git a/druid/component.lua b/druid/component.lua index 359af4a..9617636 100644 --- a/druid/component.lua +++ b/druid/component.lua @@ -20,20 +20,20 @@ local helper = require("druid.helper") ---@field _uid number ---@class druid.component ----@field druid druid.instance Druid instance to create inner components ----@field init fun(self:druid.component, ...)|nil Called when component is created ----@field update fun(self:druid.component, dt:number)|nil Called every frame ----@field on_remove fun(self:druid.component)|nil Called when component is removed ----@field on_input fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is triggered ----@field on_input_interrupt fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is consumed before ----@field on_message fun(self:druid.component, message_id:hash, message:table, sender:url)|nil Called when message is received ----@field on_late_init fun(self:druid.component)|nil Called before update once time after GUI init ----@field on_focus_lost fun(self:druid.component)|nil Called when app lost focus ----@field on_focus_gained fun(self:druid.component)|nil Called when app gained focus ----@field on_style_change fun(self:druid.component, style: table)|nil Called when style is changed ----@field on_layout_change fun(self:druid.component)|nil Called when GUI layout is changed ----@field on_window_resized fun(self:druid.component)|nil Called when window is resized ----@field on_language_change fun(self:druid.component)|nil Called when language is changed +---@field protected druid druid.instance Druid instance to create inner components +---@field protected init fun(self:druid.component, ...)|nil Called when component is created +---@field protected update fun(self:druid.component, dt:number)|nil Called every frame +---@field protected on_remove fun(self:druid.component)|nil Called when component is removed +---@field protected on_input fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is triggered +---@field protected on_input_interrupt fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is consumed before +---@field protected on_message fun(self:druid.component, message_id:hash, message:table, sender:url)|nil Called when message is received +---@field protected on_late_init fun(self:druid.component)|nil Called before update once time after GUI init +---@field protected on_focus_lost fun(self:druid.component)|nil Called when app lost focus +---@field protected on_focus_gained fun(self:druid.component)|nil Called when app gained focus +---@field protected on_style_change fun(self:druid.component, style: table)|nil Called when style is changed +---@field protected on_layout_change fun(self:druid.component)|nil Called when GUI layout is changed +---@field protected on_window_resized fun(self:druid.component)|nil Called when window is resized +---@field protected on_language_change fun(self:druid.component)|nil Called when language is changed ---@field private _component druid.component.component ---@field private _meta druid.component.meta local M = {} @@ -133,6 +133,7 @@ end ---Return current component context +---@protected ---@return any context Usually it's self of script but can be any other Druid component function M:get_context() return self._meta.context @@ -148,6 +149,7 @@ end ---Get Druid instance for inner component creation. +---@protected ---@param template string|nil ---@param nodes table|node|string|nil The nodes table from gui.clone_tree or prefab node to use for clone or node id to clone ---@return druid.instance @@ -176,6 +178,7 @@ end ---Get parent component name +---@protected ---@return string|nil parent_name The parent component name if exist or nil function M:get_parent_name() local parent = self:get_parent_component() @@ -228,6 +231,7 @@ end ---Get component UID, unique identifier created in component creation order. +---@protected ---@return number uid The component uid function M:get_uid() return self._component._uid @@ -310,6 +314,7 @@ end ---Get current component nodes +---@protected ---@return table|nil function M:get_nodes() local nodes = self._meta.nodes @@ -352,6 +357,7 @@ end ---Return all children components, recursive +---@protected ---@return table Array of childrens if the Druid component instance function M:get_childrens() local childrens = {} @@ -368,6 +374,7 @@ end ---Сreate a new component class, which will inherit from the base Druid component. +---@protected ---@param name string|nil The name of the component ---@param input_priority number|nil The input priority. The bigger number processed first. Default value: 10 ---@return druid.component diff --git a/druid/const.lua b/druid/const.lua index 0363871..8af8faa 100755 --- a/druid/const.lua +++ b/druid/const.lua @@ -73,10 +73,8 @@ M.REVERSE_PIVOTS = { M.LAYOUT_MODE = { STRETCH_X = "stretch_x", STRETCH_Y = "stretch_y", - ZOOM_MIN = "zoom_min", - ZOOM_MAX = "zoom_max", - FIT = gui.ADJUST_FIT, - STRETCH = gui.ADJUST_STRETCH, + FIT = "fit", + STRETCH = "stretch", } M.CURRENT_SYSTEM_NAME = sys.get_sys_info().system_name diff --git a/druid/custom/rich_text/module/rt.lua b/druid/custom/rich_text/module/rt.lua index e688f43..871c736 100755 --- a/druid/custom/rich_text/module/rt.lua +++ b/druid/custom/rich_text/module/rt.lua @@ -181,6 +181,7 @@ function M.create(text, settings, style) shadow = settings.shadow, outline = settings.outline, font = gui.get_font(settings.text_prefab), + split_to_characters = settings.split_to_characters, -- Image params ---@type druid.rich_text.word.image image = nil, diff --git a/druid/custom/rich_text/module/rt_parse.lua b/druid/custom/rich_text/module/rt_parse.lua index 3d019c7..94de709 100755 --- a/druid/custom/rich_text/module/rt_parse.lua +++ b/druid/custom/rich_text/module/rt_parse.lua @@ -29,6 +29,7 @@ local function add_word(text, settings, words) end words[#words + 1] = data + return data end @@ -44,7 +45,16 @@ local function split_line(line, settings, words) else local wi = #words for word in trimmed_text:gmatch("%S+") do - add_word(word .. " ", settings, words) + if settings.split_to_characters then + for i = 1, #word do + local symbol = utf8.sub(word, i, i) + local w = add_word(symbol, settings, words) + w.nobr = true + end + add_word(" ", settings, words) + else + add_word(word .. " ", settings, words) + end end local first = words[wi + 1] first.text = ws_start .. first.text diff --git a/druid/custom/rich_text/module/rt_tags.lua b/druid/custom/rich_text/module/rt_tags.lua index f061954..21c486a 100644 --- a/druid/custom/rich_text/module/rt_tags.lua +++ b/druid/custom/rich_text/module/rt_tags.lua @@ -2,7 +2,8 @@ -- Author: Britzl -- Modified by: Insality -local color = require("druid.custom.rich_text.module.rt_color") +--local color = require("druid.custom.rich_text.module.rt_color") +local color = require("druid.color") local M = {} local tags = {} @@ -43,20 +44,17 @@ end -- Format: {Text} -- Example: Rich Text M.register("color", function(params, settings, style) - params = style.COLORS[params] or params - settings.color = color.parse(params) + settings.color = color.get_color(params) end) M.register("shadow", function(params, settings, style) - params = style.COLORS[params] or params - settings.shadow = color.parse(params) + settings.shadow = color.get_color(params) end) M.register("outline", function(params, settings, style) - params = style.COLORS[params] or params - settings.outline = color.parse(params) + settings.outline = color.get_color(params) end) diff --git a/druid/custom/rich_text/rich_text.lua b/druid/custom/rich_text/rich_text.lua index 7b38045..5ae422f 100644 --- a/druid/custom/rich_text/rich_text.lua +++ b/druid/custom/rich_text/rich_text.lua @@ -13,6 +13,7 @@ local rich_text = require("druid.custom.rich_text.module.rt") ---@field image_pixel_grid_snap boolean ---@field combine_words boolean ---@field default_animation string +---@field split_by_character boolean ---@field text_prefab node ---@field adjust_scale number ---@field default_texture string @@ -50,7 +51,6 @@ local rich_text = require("druid.custom.rich_text.module.rt") ---@field height number ---@class druid.rich_text.style ----@field COLORS table ---@field ADJUST_STEPS number ---@field ADJUST_SCALE_DELTA number @@ -104,7 +104,6 @@ end ---@param style druid.rich_text.style function M:on_style_change(style) self.style = { - COLORS = style.COLORS or {}, ADJUST_STEPS = style.ADJUST_STEPS or 20, ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02, } @@ -194,6 +193,15 @@ function M:tagged(tag) end +---Set if the rich text should split to characters, not words +---@param value boolean +---@return druid.rich_text self +function M:set_split_to_characters(value) + self._settings.split_to_characters = value + return self +end + + ---Get all current created words, each word is a table that contains the information about the word ---@return druid.rich_text.word[] function M:get_words() @@ -239,6 +247,7 @@ function M:_create_settings() outline = gui.get_outline(self.root), text_leading = gui.get_leading(self.root), is_multiline = gui.get_line_break(self.root), + split_to_characters = false, -- Image settings image_pixel_grid_snap = false, -- disabled now diff --git a/druid/custom/tiling_node/tiling_node.lua b/druid/custom/tiling_node/tiling_node.lua index 2fef09b..bea3cd6 100644 --- a/druid/custom/tiling_node/tiling_node.lua +++ b/druid/custom/tiling_node/tiling_node.lua @@ -1,6 +1,6 @@ local component = require("druid.component") local helper = require("druid.helper") -local defer = require("event.defer") +local queues = require("event.queues") ---@class druid.tiling_node: druid.component ---@field animation table @@ -28,7 +28,7 @@ function M:init(node) print("The druid.script is not found, please add it nearby to the GUI collection", msg.url()) end) - defer.push("druid.get_atlas_path", { + queues.push("druid.get_atlas_path", { texture_name = gui.get_texture(self.node), sender = msg.url(), }, self.on_get_atlas_path, self) diff --git a/druid/druid.atlas b/druid/druid.atlas index 10868c4..94cba34 100644 --- a/druid/druid.atlas +++ b/druid/druid.atlas @@ -28,4 +28,7 @@ images { images { image: "/druid/images/icons/icon_arrow.png" } +images { + image: "/druid/images/icons/icon_refresh.png" +} extrude_borders: 2 diff --git a/druid/druid.lua b/druid/druid.lua index e9b161a..1a80846 100644 --- a/druid/druid.lua +++ b/druid/druid.lua @@ -114,6 +114,12 @@ local function wrap_widget(widget) end end + for key, value in pairs(widget) do + if event.is_event(value) then + wrapped_widget[key] = value + end + end + return wrapped_widget end @@ -127,22 +133,21 @@ end ---@generic T: druid.widget ---@param widget_class T The class of the widget to return ---@param gui_url url GUI url ----@return T? widget The new created widget, -function M.get_widget(widget_class, gui_url) +---@param params any|nil Additional parameters to pass to the widget's init function +---@return T widget The new created widget, +function M.get_widget(widget_class, gui_url, params) gui_url = gui_url or msg.url() local registered_druids = REGISTERED_GUI_WIDGETS[gui_url.socket] - if not registered_druids then - return nil - end + assert(registered_druids, "Druid widget not registered for this game object") for index = 1, #registered_druids do local druid = registered_druids[index] if druid.fragment == gui_url.fragment and druid.path == gui_url.path then - return druid.new_widget:trigger(widget_class) + return druid.new_widget:trigger(widget_class, nil, nil, params) end end - return nil + error("Druid widget not found for this game object: " .. gui_url) end @@ -156,8 +161,8 @@ function M.register_druid_as_widget(druid) table.insert(REGISTERED_GUI_WIDGETS[gui_url.socket], { path = gui_url.path, fragment = gui_url.fragment, - new_widget = event.create(function(widget_class) - return wrap_widget(druid:new_widget(widget_class)) + new_widget = event.create(function(widget_class, template, nodes, params) + return wrap_widget(druid:new_widget(widget_class, template, nodes, params)) end), }) end diff --git a/druid/druid.script b/druid/druid.script index 61738f7..09b3b77 100644 --- a/druid/druid.script +++ b/druid/druid.script @@ -3,7 +3,7 @@ -- This one is a required to make a unified "Shaders" pipeline in the GUI scripts -- This required to grab a texture data with `go.get` function -local defer = require("event.defer") +local queues = require("event.queues") ---Usage: defer.push("druid.get_atlas_path", { --- texture_name = gui.get_texture(self.node), @@ -35,10 +35,10 @@ end function init(self) - defer.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self) + queues.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self) end function final(self) - defer.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self) + queues.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self) end diff --git a/druid/extended/container.lua b/druid/extended/container.lua index 6769990..e79dbb0 100755 --- a/druid/extended/container.lua +++ b/druid/extended/container.lua @@ -31,6 +31,8 @@ local CORNER_PIVOTS = { ---@field DRAGGABLE_CORNER_SIZE vector3 Size of box node for debug draggable corners ---@field DRAGGABLE_CORNER_COLOR vector4 Color of debug draggable corners +---@alias druid.container.mode "stretch" | "fit" | "stretch_x" | "stretch_y" + ---Druid component to manage the size and positions with other containers relations to create a adaptable layouts. --- ---### Setup @@ -54,7 +56,7 @@ local CORNER_PIVOTS = { ---@field position vector3 The current position ---@field pivot_offset vector3 The pivot offset ---@field center_offset vector3 The center offset ----@field mode string The layout mode +---@field mode druid.container.mode The layout mode ---@field fit_size vector3 The fit size ---@field min_size_x number|nil The minimum size x ---@field min_size_y number|nil The minimum size y @@ -176,7 +178,7 @@ function M:set_size(width, height, anchor_pivot) if self.max_size_y then height = min(height, self.max_size_y) end - + if (width and width ~= self.size.x) or (height and height ~= self.size.y) then self.center_offset.x = -width * self.pivot_offset.x self.center_offset.y = -height * self.pivot_offset.y @@ -537,7 +539,7 @@ function M:_on_corner_drag(x, y, corner_offset) end if self.max_size_y and size.y + y > self.max_size_y then y = self.max_size_y - size.y - end + end if corner_offset.x < 0 then self.node_offset.x = self.node_offset.x - x diff --git a/druid/images/icons/icon_refresh.png b/druid/images/icons/icon_refresh.png new file mode 100644 index 0000000..970fc3c Binary files /dev/null and b/druid/images/icons/icon_refresh.png differ diff --git a/druid/styles/default/style.lua b/druid/styles/default/style.lua index 08c3195..363fbac 100644 --- a/druid/styles/default/style.lua +++ b/druid/styles/default/style.lua @@ -148,12 +148,4 @@ M["hotkey"] = { } -M["rich_text"] = { - COLORS = { - white = "#FFFFFF", - black = "#000000" - } -} - - return M diff --git a/druid/system/druid_annotations.lua b/druid/system/druid_annotations.lua index 2e30ddf..b538d87 100644 --- a/druid/system/druid_annotations.lua +++ b/druid/system/druid_annotations.lua @@ -1,5 +1,5 @@ ---@class druid.widget: druid.component ----@field druid druid.instance Ready to use druid instance +---@field protected druid druid.instance Ready to use druid instance ---@class druid.logger ---@field trace fun(message: string, context: any) diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index 08c8f33..20174a2 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -681,7 +681,7 @@ end local container = require("druid.extended.container") ---Create Container component ---@param node string|node The node_id or gui.get_node(node_id). ----@param mode string|nil Layout mode +---@param mode druid.container.mode|nil Layout mode. Default Fit or Stretch depends from node adjust mode from GUI scene ---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed ---@return druid.container container The new container component function M:new_container(node, mode, callback) diff --git a/druid/widget/properties_panel/properties/property_button.lua b/druid/widget/properties_panel/properties/property_button.lua index cce90fb..34c21ce 100644 --- a/druid/widget/properties_panel/properties/property_button.lua +++ b/druid/widget/properties_panel/properties/property_button.lua @@ -6,7 +6,6 @@ local color = require("druid.color") ---@field text_name druid.text ---@field button druid.button ---@field text_button druid.text ----@field druid druid.instance local M = {} @@ -51,6 +50,14 @@ function M:set_text_button(text) end +---@param enabled boolean +---@return druid.widget.property_button +function M:set_enabled(enabled) + self.button:set_enabled(enabled) + return self +end + + function M:set_color(color_value) color.set_color(self:get_node("button"), color_value) end diff --git a/druid/widget/properties_panel/properties/property_checkbox.lua b/druid/widget/properties_panel/properties/property_checkbox.lua index 71179c3..d267f2a 100644 --- a/druid/widget/properties_panel/properties/property_checkbox.lua +++ b/druid/widget/properties_panel/properties/property_checkbox.lua @@ -73,4 +73,11 @@ function M:on_change(callback) end +---Set the enabled state of the checkbox +---@param enabled boolean +function M:set_enabled(enabled) + self.button:set_enabled(enabled) +end + + return M diff --git a/druid/widget/properties_panel/properties_panel.gui b/druid/widget/properties_panel/properties_panel.gui index 43dbf4c..dfbad11 100644 --- a/druid/widget/properties_panel/properties_panel.gui +++ b/druid/widget/properties_panel/properties_panel.gui @@ -101,6 +101,45 @@ nodes { inherit_alpha: true size_mode: SIZE_MODE_AUTO } +nodes { + position { + x: 152.0 + y: -4.0 + } + color { + x: 0.306 + y: 0.31 + z: 0.314 + } + type: TYPE_BOX + texture: "druid/icon_refresh" + id: "icon_refresh" + pivot: PIVOT_NE + parent: "header" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO +} +nodes { + position { + x: 112.0 + y: -4.0 + } + scale { + x: -1.0 + } + color { + x: 0.306 + y: 0.31 + z: 0.314 + } + type: TYPE_BOX + texture: "druid/icon_arrow" + id: "icon_back" + pivot: PIVOT_NW + parent: "header" + inherit_alpha: true + size_mode: SIZE_MODE_AUTO +} nodes { position { y: -50.0 diff --git a/druid/widget/properties_panel/properties_panel.lua b/druid/widget/properties_panel/properties_panel.lua index 505c76d..2176f6d 100644 --- a/druid/widget/properties_panel/properties_panel.lua +++ b/druid/widget/properties_panel/properties_panel.lua @@ -1,3 +1,7 @@ +local event = require("event.event") + +local color = require("druid.color") +local helper = require("druid.helper") local property_checkbox = require("druid.widget.properties_panel.properties.property_checkbox") local property_slider = require("druid.widget.properties_panel.properties.property_slider") local property_button = require("druid.widget.properties_panel.properties.property_button") @@ -21,6 +25,8 @@ local property_vector3 = require("druid.widget.properties_panel.properties.prope ---@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 = {} +local COLOR_BUTTON = "#4E4F50" +local COLOR_REFRESH_ACTIVE = "#8BD092" function M:init() self.root = self:get_node("root") @@ -34,6 +40,9 @@ function M:init() self.default_size = self.container:get_size() + -- To have ability to go back to previous scene, collections of all properties to rebuild + self.scenes = {} + self.properties = {} self.properties_constructors = {} self.current_page = 1 @@ -52,6 +61,15 @@ function M:init() self:set_hidden(not self._is_hidden) end):set_style(nil) + self.button_back = self.druid:new_button("icon_back", function() + self:previous_scene() + end) + gui.set_enabled(self.button_back.node, false) + + self.button_refresh = self.druid:new_button("icon_refresh", function() + self:toggle_auto_refresh() + end) + -- 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", "root") self.paginator:set_text("Page") @@ -80,6 +98,23 @@ function M:on_remove() end +function M:toggle_auto_refresh() + self._is_auto_refresh = not self._is_auto_refresh + + if self._is_auto_refresh then + self.is_dirty = true + color.set_color(self.button_refresh.node, COLOR_REFRESH_ACTIVE) + self._timer_refresh = timer.delay(1, true, function() + self.is_dirty = true + end) + else + color.set_color(self.button_refresh.node, COLOR_BUTTON) + timer.cancel(self._timer_refresh) + self._timer_refresh = nil + end +end + + function M:on_drag_widget(dx, dy) local position = self.container:get_position() self.container:set_position(position.x + dx, position.y + dy) @@ -112,6 +147,37 @@ function M:clear_created_properties() end +function M:next_scene() + local scene = { + header = self.text_header:get_text(), + current_page = self.current_page, + } + + helper.add_array(scene, self.properties_constructors) + table.insert(self.scenes, scene) + + self:clear() + + self.is_dirty = true + + gui.set_enabled(self.button_back.node, #self.scenes > 0) +end + + +function M:previous_scene() + local scene = table.remove(self.scenes) + self:clear() + helper.add_array(self.properties_constructors, scene) + + self.text_header:set_text(scene.header) + self.current_page = scene.current_page + + self.is_dirty = true + + gui.set_enabled(self.button_back.node, #self.scenes > 0) +end + + function M:clear() self:clear_created_properties() self.properties_constructors = {} @@ -139,26 +205,28 @@ end function M:update(dt) - if self.is_dirty then - self.is_dirty = false + if not self.is_dirty then + return + end - self:clear_created_properties() + self.is_dirty = false - local properties_count = #self.properties_constructors + self:clear_created_properties() - -- 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) + local properties_count = #self.properties_constructors - 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)) + -- 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) - for index = start_index, end_index do - self.properties_constructors[index]() - end + 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)) + + for index = start_index, end_index do + self.properties_constructors[index]() end end @@ -285,6 +353,12 @@ function M:remove(widget) end +---Force to refresh properties next update +function M:set_dirty() + self.is_dirty = true +end + + function M:set_hidden(is_hidden) self._is_hidden = is_hidden local hidden_size = gui.get_size(self:get_node("header")) @@ -293,6 +367,11 @@ function M:set_hidden(is_hidden) self.container:set_size(new_size.x, new_size.y, gui.PIVOT_N) gui.set_enabled(self.content, not self._is_hidden) + gui.set_enabled(self.button_refresh.node, not self._is_hidden) + + if not self._is_hidden then + self.is_dirty = true + end end @@ -301,16 +380,183 @@ function M:is_hidden() end +function M:load_previous_page() + self.current_page = self.current_page - 1 + self.is_dirty = true +end + + ---@param properties_per_page number function M:set_properties_per_page(properties_per_page) self.properties_per_page = properties_per_page end +---Set a page of current scene +---@param page number function M:set_page(page) self.current_page = page self.is_dirty = true end +---Set a text at left top corner of the properties panel +---@param header string +function M:set_header(header) + self.text_header:set_text(header) +end + + +---@param data table +function M:render_lua_table(data) + local component_order = {} + for component_id in pairs(data) do + table.insert(component_order, component_id) + end + table.sort(component_order, function(a, b) + local a_type = type(data[a]) + local b_type = type(data[b]) + if a_type ~= b_type then + return a_type < b_type + end + if type(a) == "number" and type(b) == "number" then + return a < b + end + return tostring(a) < tostring(b) + end) + + for i = 1, #component_order do + local component_id = component_order[i] + self:add_property_component(component_id, data) + end + + local metatable = getmetatable(data) + if metatable and metatable.__index and type(metatable.__index) == "table" then + local metatable_order = {} + for key in pairs(metatable.__index) do + table.insert(metatable_order, key) + end + table.sort(metatable_order) + + for i = 1, #metatable_order do + local component_id = metatable_order[i] + local component = metatable.__index[component_id] + self:add_property_component("M:" .. component_id, data) + end + end +end + + +---@private +---@param component_id string +---@param data table +function M:add_property_component(component_id, data) + local component = data[component_id] + local component_type = type(component) + + if component_type == "table" then + local is_event = event.is_event(component) + if is_event then + self:add_button(function(button) + button:set_text_property(tostring(component_id)) + button:set_text_button("Call Event (" .. #component .. ")") + button.button.on_click:subscribe(function() + component:trigger() + end) + end) + else + self:add_button(function(button) + local is_empty = next(component) == nil + local is_array = component[1] ~= nil + local name = "Inspect" + if is_empty then + name = "Inspect (Empty)" + end + if is_array then + name = "Inspect (" .. #component .. ")" + end + + local button_name = component_id + -- If it's a number or array, try to get the id/name/prefab_id from the component + if type(component) == "table" and type(component_id) == "number" then + local extracted_id = component.name or component.prefab_id or component.node_id or component.id + if extracted_id then + button_name = component_id .. ". " .. extracted_id + end + end + + button:set_text_property(button_name) + button:set_text_button(name) + button.button.on_click:subscribe(function() + self:next_scene() + self:set_header(button_name) + self:render_lua_table(component) + end) + end) + end + end + + if component_type == "string" then + self:add_input(function(input) + input:set_text_property(tostring(component_id)) + input:set_text_value(tostring(data[component_id])) + input:on_change(function(_, value) + data[component_id] = value + end) + end) + end + + if component_type == "number" then + self:add_input(function(input) + input:set_text_property(tostring(component_id)) + input:set_text_value(tostring(helper.round(data[component_id], 3))) + input:on_change(function(_, value) + data[component_id] = tonumber(value) + end) + end) + end + + if component_type == "boolean" then + self:add_checkbox(function(checkbox) + checkbox:set_text_property(tostring(component_id)) + checkbox:set_value(data[component_id]) + checkbox:on_change(function(value) + data[component_id] = value + end) + end) + end + + if component_type == "userdata" then + if types.is_vector3(component) then + ---@cast component vector3 + self:add_vector3(function(vector3) + vector3:set_text_property(tostring(component_id)) + vector3:set_value(data[component_id].x, data[component_id].y, data[component_id].z) + vector3.on_change:subscribe(function(value) + data[component_id].x = value.x + data[component_id].y = value.y + data[component_id].z = value.z + end) + end) + else + self:add_text(function(text) + text:set_text_property(tostring(component_id)) + text:set_text_value(tostring(data[component_id])) + end) + end + end + + if component_type == "function" then + self:add_button(function(button) + button:set_text_property(tostring(component_id)) + button:set_text_button("Call") + button.button.on_click:subscribe(function() + component(data) + end) + end) + end +end + + + return M diff --git a/example/assets/default.display_profiles b/example/assets/default.display_profiles new file mode 100644 index 0000000..94671a0 --- /dev/null +++ b/example/assets/default.display_profiles @@ -0,0 +1,14 @@ +profiles { + name: "Landscape" + qualifiers { + width: 1920 + height: 1080 + } +} +profiles { + name: "Portrait" + qualifiers { + width: 1080 + height: 1920 + } +} diff --git a/test/tests/test_rich_text.lua b/test/tests/test_rich_text.lua index 0e4ad0d..d37d819 100644 --- a/test/tests/test_rich_text.lua +++ b/test/tests/test_rich_text.lua @@ -81,7 +81,7 @@ return function() local rich_text = druid:new_rich_text(text_node) -- Test color tag with named color - local words = rich_text:set_text("Colored Text") + local words = rich_text:set_text("Colored Text") assert(#words > 0) -- Word should have a tags field with color tag @@ -104,7 +104,7 @@ return function() local rich_text = druid:new_rich_text(text_node) -- Test shadow tag with named color - local words = rich_text:set_text("Shadowed Text") + local words = rich_text:set_text("Shadowed Text") assert(#words > 0) assert(words[1].shadow ~= nil) @@ -129,7 +129,7 @@ return function() local rich_text = druid:new_rich_text(text_node) -- Test outline tag with named color - local words = rich_text:set_text("Outlined Text") + local words = rich_text:set_text("Outlined Text") assert(#words > 0) assert(words[1].outline ~= nil) @@ -228,7 +228,7 @@ return function() local rich_text = druid:new_rich_text(text_node) -- Test combined tags - local words = rich_text:set_text("Big Red Text") + local words = rich_text:set_text("Big Red Text") assert(#words > 0) assert(words[1].tags.color) @@ -236,7 +236,7 @@ return function() assert(words[1].relative_scale == 2) -- Test nested tags - words = rich_text:set_text("Red Big Red Red") + words = rich_text:set_text("Red Big Red Red") assert(#words >= 3) -- All words should have color tag