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/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/widget/properties_panel/properties/property_button.lua b/druid/widget/properties_panel/properties/property_button.lua index cce90fb..61b91e2 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 = {} diff --git a/druid/widget/properties_panel/properties_panel.gui b/druid/widget/properties_panel/properties_panel.gui index 43dbf4c..63ba475 100644 --- a/druid/widget/properties_panel/properties_panel.gui +++ b/druid/widget/properties_panel/properties_panel.gui @@ -101,6 +101,27 @@ nodes { inherit_alpha: true size_mode: SIZE_MODE_AUTO } +nodes { + position { + x: 152.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..8f19358 100644 --- a/druid/widget/properties_panel/properties_panel.lua +++ b/druid/widget/properties_panel/properties_panel.lua @@ -1,3 +1,4 @@ +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") @@ -34,6 +35,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 +56,11 @@ 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) + -- 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") @@ -112,6 +121,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 = {} @@ -301,16 +341,173 @@ 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] + local component = data[component_id] + self:add_property_component(component_id, component, 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, component, data) + end + end +end + + +---@private +---@param component_id string +---@param component table +---@param context table +function M:add_property_component(component_id, component, context) + local component_type = type(component) + + if component_type == "table" then + 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 + + self:add_button(function(button) + 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 + + if component_type == "string" then + self:add_input(function(input) + input:set_text_property(tostring(component_id)) + input:set_text_value(tostring(component)) + input:on_change(function(_, value) + context[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(component, 3))) + input:on_change(function(_, value) + context[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(component) + checkbox:on_change(function(value) + context[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(component.x, component.y, component.z) + vector3.on_change:subscribe(function(value) + component.x = value.x + component.y = value.y + component.z = value.z + end) + end) + else + self:add_text(function(text) + text:set_text_property(tostring(component_id)) + text:set_text_value(tostring(component)) + 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(context) + 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 + } +}