From 8786f6e5b9176b3097eaedef40eba7742fe62918 Mon Sep 17 00:00:00 2001 From: Insality Date: Sat, 18 Oct 2025 18:50:26 +0300 Subject: [PATCH 01/22] Asset store WIP --- druid/editor_scripts/core/asset_store.lua | 239 +++++++++++++++ druid/editor_scripts/core/installer.lua | 110 +++++++ druid/editor_scripts/core/ui_components.lua | 316 ++++++++++++++++++++ druid/editor_scripts/druid.editor_script | 13 + 4 files changed, 678 insertions(+) create mode 100644 druid/editor_scripts/core/asset_store.lua create mode 100644 druid/editor_scripts/core/installer.lua create mode 100644 druid/editor_scripts/core/ui_components.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua new file mode 100644 index 0000000..5ab6587 --- /dev/null +++ b/druid/editor_scripts/core/asset_store.lua @@ -0,0 +1,239 @@ +--- Main asset store module for Druid widgets +--- Handles fetching widget data, displaying the store interface, and managing installations + +local installer = require("druid.editor_scripts.core.installer") +local ui_components = require("druid.editor_scripts.core.ui_components") + +local M = {} + +local STORE_URL = "https://insality.github.io/core/druid_widget_store.json" + + +---Fetch widget data from the remote store +---@return table|nil, string|nil - Store data or nil, error message or nil +local function fetch_store_data() + print("Fetching widget data from:", STORE_URL) + + local response = http.request(STORE_URL, { + as = "json" + }) + + if response.status ~= 200 then + return nil, "Failed to fetch store data. HTTP status: " .. response.status + end + + if not response.body or not response.body.items then + return nil, "Invalid store data format" + end + + print("Successfully fetched", #response.body.items, "widgets") + return response.body, nil +end + + +---Filter items based on author and tag filters +---@param items table - List of widget items +---@param author_filter string - Author filter value +---@param tag_filter string - Tag filter value +---@return table - Filtered list of items +local function filter_items(items, author_filter, tag_filter) + local filtered = {} + + for _, item in ipairs(items) do + local author_match = author_filter == "All Authors" or item.author == author_filter + local tag_match = tag_filter == "All Categories" or (item.tags and table.concat(item.tags, ","):find(tag_filter)) + + if author_match and tag_match then + table.insert(filtered, item) + end + end + + return filtered +end + + +---Handle widget installation +---@param item table - Widget item to install +---@param install_folder string - Installation folder +---@param on_success function - Success callback +---@param on_error function - Error callback +local function handle_install(item, install_folder, on_success, on_error) + print("Installing widget:", item.id) + + local success, message = installer.install_widget(item, install_folder) + + if success then + print("Installation successful:", message) + on_success(message) + else + print("Installation failed:", message) + on_error(message) + end +end + + +---Handle opening API documentation +---@param item table - Widget item +local function handle_open_api(item) + if item.api then + print("Opening API documentation:", item.api) + editor.browse(item.api) + else + print("No API documentation available for:", item.id) + end +end + + +---Show installation status dialog +---@param success boolean - Whether installation was successful +---@param message string - Status message +local function show_install_status(success, message) + local dialog_component = editor.ui.component(function() + return editor.ui.dialog({ + title = success and "Installation Successful" or "Installation Failed", + content = editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + padding = editor.ui.PADDING.MEDIUM, + children = { + editor.ui.label({ + text = message, + color = success and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.LEFT + }) + } + }), + buttons = { + editor.ui.dialog_button({ + text = "OK", + default = true + }) + } + }) + end) + + editor.ui.show_dialog(dialog_component({})) +end + + +---Open the asset store dialog +function M.open_asset_store() + print("Opening Druid Asset Store") + + -- Fetch data synchronously before creating the dialog + local store_data, fetch_error = fetch_store_data() + local initial_items = {} + local initial_loading = false + local initial_error = nil + + if store_data then + initial_items = store_data.items + print("Successfully loaded", #initial_items, "widgets") + else + initial_error = fetch_error + print("Failed to load widgets:", fetch_error) + end + + local dialog_component = editor.ui.component(function(props) + -- State management + local items, set_items = editor.ui.use_state(initial_items) + local loading, set_loading = editor.ui.use_state(initial_loading) + local error_message, set_error_message = editor.ui.use_state(initial_error) + local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder()) + local author_filter, set_author_filter = editor.ui.use_state("All Authors") + local tag_filter, set_tag_filter = editor.ui.use_state("All Categories") + local install_status, set_install_status = editor.ui.use_state("") + + -- Filter items + local filtered_items = editor.ui.use_memo(filter_items, items, author_filter, tag_filter) + + -- Installation status check function + local function is_widget_installed(item) + return installer.is_widget_installed(item, install_folder) + end + + -- Installation handlers + local function on_install(item) + handle_install(item, install_folder, + function(message) + set_install_status("Success: " .. message) + show_install_status(true, message) + end, + function(message) + set_install_status("Error: " .. message) + show_install_status(false, message) + end + ) + end + + local function on_open_api(item) + handle_open_api(item) + end + + -- Build UI content + local content_children = {} + + -- Settings section + table.insert(content_children, editor.ui.label({ + text = "Installation Folder: " .. install_folder, + color = editor.ui.COLOR.TEXT + })) + + -- Filter section (only show if we have items) + if #items > 0 then + table.insert(content_children, editor.ui.label({ + text = "Filters: Author: " .. author_filter .. ", Category: " .. tag_filter, + color = editor.ui.COLOR.TEXT + })) + end + + -- Main content area + if loading then + table.insert(content_children, ui_components.create_loading_indicator("Loading widget store...")) + elseif error_message then + table.insert(content_children, ui_components.create_error_message(error_message)) + elseif #filtered_items == 0 then + table.insert(content_children, editor.ui.label({ + text = "No widgets found matching the current filters.", + color = editor.ui.COLOR.HINT, + alignment = editor.ui.ALIGNMENT.CENTER + })) + else + table.insert(content_children, ui_components.create_widget_list( + filtered_items, is_widget_installed, on_install, on_open_api + )) + end + + -- Install status message + if install_status ~= "" then + table.insert(content_children, editor.ui.label({ + text = install_status, + color = install_status:find("Success") and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.CENTER + })) + end + + return editor.ui.dialog({ + title = "Druid Asset Store", + content = editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + padding = editor.ui.PADDING.MEDIUM, + children = content_children + }), + buttons = { + editor.ui.dialog_button({ + text = "Close", + cancel = true + }) + } + }) + end) + + local result = editor.ui.show_dialog(dialog_component({})) + + -- Save the install folder preference (this will be handled by the state management in the dialog) + + return result +end + + +return M diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua new file mode 100644 index 0000000..beae5ee --- /dev/null +++ b/druid/editor_scripts/core/installer.lua @@ -0,0 +1,110 @@ +--- Module for handling widget installation from zip files +--- Downloads zip files and extracts them to the specified folder + +local M = {} + +local DEFAULT_INSTALL_FOLDER = "/widget" + + +---Download a file from URL +---@param url string - The URL to download from +---@return string|nil, string|nil - Downloaded content or nil, error message or nil +local function download_file(url) + print("Downloading from:", url) + + -- Try different approaches for downloading binary data + local success, response = pcall(function() + -- First try without specifying 'as' parameter + return http.request(url) + end) + + -- If that fails, try with 'as = "string"' + if not success or not response or not response.body then + print("First attempt failed, trying with as='string'") + success, response = pcall(function() + return http.request(url, { + as = "string" + }) + end) + end + + if not success then + print("HTTP request failed:", response) + return nil, "HTTP request failed: " .. tostring(response) + end + + if not response then + print("No response received") + return nil, "No response received from server" + end + + print("Response status:", response.status) + print("Response body type:", type(response.body)) + print("Response body length:", response.body and #response.body or "nil") + if response.headers then + print("Response headers:", response.headers["content-type"] or "unknown") + print("Content length header:", response.headers["content-length"] or "unknown") + end + + if response.status ~= 200 then + return nil, "Failed to download file. HTTP status: " .. tostring(response.status) + end + + if not response.body then + return nil, "No content received from server" + end + + print("Downloaded", #response.body, "bytes") + return response.body, nil +end + + +---Install a widget from a zip URL +---@param item table - Widget item data containing zip_url and id +---@param install_folder string - Target folder to install to +---@return boolean, string - Success status and message +function M.install_widget(item, install_folder) + if not item.zip_url or not item.id then + return false, "Invalid widget data: missing zip_url or id" + end + + print("Installing widget:", item.id) + print("Download URL:", item.zip_url) + print("Target folder:", install_folder) + + -- Download the zip file + local zip_data, download_error = download_file(item.zip_url) + if not zip_data then + return false, "Failed to download widget: " .. download_error + end + + -- Create a simple success message for now + local success = true + local message = "Widget '" .. item.id .. "' downloaded successfully!" + message = message .. "\nDownload URL: " .. item.zip_url + message = message .. "\nSize: " .. tostring(#zip_data) .. " bytes" + message = message .. "\nTarget folder: " .. install_folder + + print("Successfully downloaded widget:", item.id) + return success, message +end + + +---Check if a widget is already installed +---@param item table - Widget item data containing id +---@param install_folder string - Install folder to check in +---@return boolean - True if widget is already installed +function M.is_widget_installed(item, install_folder) + -- For now, assume widgets are not installed to avoid path issues + return false +end + + +---Get default installation folder +---@return string - Default installation folder path +function M.get_default_install_folder() + return DEFAULT_INSTALL_FOLDER +end + + +return M diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua new file mode 100644 index 0000000..22b23c5 --- /dev/null +++ b/druid/editor_scripts/core/ui_components.lua @@ -0,0 +1,316 @@ +--- Module for reusable UI components in the asset store +--- Contains component builders for filters, widget items, and lists + +local M = {} + + +---Create a settings section with installation folder input +---@param install_path string - Current installation path +---@param on_change function - Callback when path changes +---@return userdata - UI component +function M.create_settings_section(install_path, on_change) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Installation Folder:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = install_path, + color = editor.ui.COLOR.TEXT, + grow = true + }) + } + }) +end + + +---Extract unique authors from items list +---@param items table - List of widget items +---@return table - Sorted list of unique authors +local function extract_authors(items) + local authors = {} + local author_set = {} + + for _, item in ipairs(items) do + if item.author and not author_set[item.author] then + author_set[item.author] = true + table.insert(authors, item.author) + end + end + + table.sort(authors) + return authors +end + + +---Extract unique tags from items list +---@param items table - List of widget items +---@return table - Sorted list of unique tags +local function extract_tags(items) + local tags = {} + local tag_set = {} + + for _, item in ipairs(items) do + if item.tags then + for _, tag in ipairs(item.tags) do + if not tag_set[tag] then + tag_set[tag] = true + table.insert(tags, tag) + end + end + end + end + + table.sort(tags) + return tags +end + + +---Create filter section with author and tag dropdowns +---@param items table - List of all widget items +---@param author_filter string - Current author filter +---@param tag_filter string - Current tag filter +---@param on_author_change function - Callback for author filter change +---@param on_tag_change function - Callback for tag filter change +---@return userdata - UI component +function M.create_filter_section(items, author_filter, tag_filter, on_author_change, on_tag_change) + local authors = extract_authors(items) + local tags = extract_tags(items) + + -- Build author options + local author_options = {"All Authors"} + for _, author in ipairs(authors) do + table.insert(author_options, author) + end + + -- Build tag options + local tag_options = {"All Categories"} + for _, tag in ipairs(tags) do + table.insert(tag_options, tag) + end + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Author:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = author_filter, + color = editor.ui.COLOR.TEXT + }) + } + }), + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Category:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = tag_filter, + color = editor.ui.COLOR.TEXT + }) + } + }) + } + }) +end + + +---Format file size for display +---@param size_bytes number - Size in bytes +---@return string - Formatted size string +local function format_size(size_bytes) + if size_bytes < 1024 then + return size_bytes .. " B" + elseif size_bytes < 1024 * 1024 then + return math.floor(size_bytes / 1024) .. " KB" + else + return math.floor(size_bytes / (1024 * 1024)) .. " MB" + end +end + + +---Create a widget item card +---@param item table - Widget item data +---@param is_installed boolean - Whether widget is already installed +---@param on_install function - Callback for install button +---@param on_open_api function - Callback for API docs button +---@return userdata - UI component +function M.create_widget_item(item, is_installed, on_install, on_open_api) + local size_text = item.size and format_size(item.size) or "Unknown size" + local version_text = item.version and "v" .. item.version or "Unknown version" + + -- Create tags display + local tags_text = "" + if item.tags and #item.tags > 0 then + tags_text = "Tags: " .. table.concat(item.tags, ", ") + end + + -- Create dependencies display + local deps_text = "" + if item.depends and #item.depends > 0 then + deps_text = "Depends on: " .. table.concat(item.depends, ", ") + end + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + padding = editor.ui.PADDING.MEDIUM, + children = { + -- Widget icon placeholder + editor.ui.label({ + text = "📦", + color = editor.ui.COLOR.HINT + }), + + -- Widget details + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + grow = true, + children = { + -- Title and author + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = item.title or item.id, + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = "by " .. (item.author or "Unknown"), + color = editor.ui.COLOR.HINT + }) + } + }), + + -- Version and size + editor.ui.label({ + text = version_text .. " • " .. size_text, + color = editor.ui.COLOR.HINT + }), + + -- Description + editor.ui.label({ + text = item.description or "No description available", + color = editor.ui.COLOR.TEXT + }), + + -- Tags + tags_text ~= "" and editor.ui.label({ + text = tags_text, + color = editor.ui.COLOR.HINT + }) or nil, + + -- Dependencies + deps_text ~= "" and editor.ui.label({ + text = deps_text, + color = editor.ui.COLOR.WARNING + }) or nil, + + -- Installation status + is_installed and editor.ui.label({ + text = "✓ Already installed", + color = editor.ui.COLOR.HINT + }) or nil + } + }), + + -- Action buttons + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.button({ + text = is_installed and "Reinstall" or "Install", + on_pressed = on_install, + enabled = true + }), + editor.ui.button({ + text = "API Docs", + on_pressed = on_open_api, + enabled = item.api ~= nil + }) + } + }) + } + }) +end + + +---Create a scrollable list of widget items +---@param items table - List of widget items to display +---@param is_installed_func function - Function to check if widget is installed +---@param on_install function - Callback for install button +---@param on_open_api function - Callback for API docs button +---@return userdata - UI component +function M.create_widget_list(items, is_installed_func, on_install, on_open_api) + local widget_items = {} + + for _, item in ipairs(items) do + local is_installed = is_installed_func and is_installed_func(item) or false + + table.insert(widget_items, M.create_widget_item(item, is_installed, + function() on_install(item) end, + function() on_open_api(item) end + )) + + -- Add separator between items (except for the last one) + if _ < #items then + table.insert(widget_items, editor.ui.label({ + text = "---", + color = editor.ui.COLOR.HINT + })) + end + end + + return editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = widget_items + }) +end + + +---Create a loading indicator +---@param message string - Loading message +---@return userdata - UI component +function M.create_loading_indicator(message) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + alignment = editor.ui.ALIGNMENT.CENTER, + children = { + editor.ui.label({ + text = message or "Loading...", + color = editor.ui.COLOR.TEXT, + alignment = editor.ui.ALIGNMENT.CENTER + }) + } + }) +end + + +---Create an error message display +---@param message string - Error message +---@return userdata - UI component +function M.create_error_message(message) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + alignment = editor.ui.ALIGNMENT.CENTER, + children = { + editor.ui.label({ + text = "Error: " .. message, + color = editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.CENTER + }) + } + }) +end + + +return M diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index ba9c01b..8f45587 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -2,6 +2,7 @@ local assign_layers = require("druid.editor_scripts.assign_layers") local create_druid_widget = require("druid.editor_scripts.create_druid_widget") local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script") local druid_settings = require("druid.editor_scripts.druid_settings") +local asset_store = require("druid.editor_scripts.core.asset_store") local M = {} @@ -18,6 +19,10 @@ function M.get_prefs_schema() ["druid.gui_script_template_path"] = editor.prefs.schema.string({ default = DEFAULT_GUI_SCRIPT_TEMPLATE_PATH, scope = editor.prefs.SCOPE.PROJECT + }), + ["druid.asset_install_folder"] = editor.prefs.schema.string({ + default = "/widget", + scope = editor.prefs.SCOPE.PROJECT }) } end @@ -65,6 +70,14 @@ function M.get_commands() end }, + { + label = "[Druid] Asset Store", + locations = { "Edit" }, + run = function() + return asset_store.open_asset_store() + end + }, + { label = "[Druid] Settings", locations = { "Edit" }, From 1f0075d124ce26bdab22125ba09c4bb12c89b35c Mon Sep 17 00:00:00 2001 From: Insality Date: Sat, 25 Oct 2025 16:18:05 +0300 Subject: [PATCH 02/22] Update --- druid/editor_scripts/core/asset_store.lua | 1 + druid/editor_scripts/core/base64.lua | 35 ++++++ druid/editor_scripts/core/installer.lua | 129 ++++++++++------------ 3 files changed, 96 insertions(+), 69 deletions(-) create mode 100644 druid/editor_scripts/core/base64.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 5ab6587..79e4294 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -134,6 +134,7 @@ function M.open_asset_store() end local dialog_component = editor.ui.component(function(props) + editor.prefs.set("druid.asset_install_folder", "./widget") -- State management local items, set_items = editor.ui.use_state(initial_items) local loading, set_loading = editor.ui.use_state(initial_loading) diff --git a/druid/editor_scripts/core/base64.lua b/druid/editor_scripts/core/base64.lua new file mode 100644 index 0000000..7353aa8 --- /dev/null +++ b/druid/editor_scripts/core/base64.lua @@ -0,0 +1,35 @@ +-- base64 encode/decode (http://lua-users.org/wiki/BaseSixtyFour) + +local M = {} + +local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +function M.encode(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +function M.decode(data) + data = string.gsub(data, '[^'..b..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(b:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +return M diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index beae5ee..5ba50fe 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -1,92 +1,83 @@ +local base64 = require("druid.editor_scripts.core.base64") --- Module for handling widget installation from zip files --- Downloads zip files and extracts them to the specified folder local M = {} -local DEFAULT_INSTALL_FOLDER = "/widget" +local DEFAULT_INSTALL_FOLDER = "./widget" + +---@class druid.core.item_info +---@field id string +---@field version string +---@field title string +---@field author string +---@field description string +---@field api string +---@field author_url string +---@field image string +---@field manifest_url string +---@field zip_url string +---@field json_zip_url string +---@field sha256 string +---@field size number +---@field depends string[] +---@field tags string[] ---Download a file from URL ---@param url string - The URL to download from ----@return string|nil, string|nil - Downloaded content or nil, error message or nil -local function download_file(url) - print("Downloading from:", url) +---@return string|nil, string|nil - Downloaded content or nil, filename or nil +local function download_file_zip_json(url) + local response = http.request(url, { as = "json" }) - -- Try different approaches for downloading binary data - local success, response = pcall(function() - -- First try without specifying 'as' parameter - return http.request(url) - end) + if response.status ~= 200 then + print("Failed to download file. HTTP status: " .. response.status) + return nil + end - -- If that fails, try with 'as = "string"' - if not success or not response or not response.body then - print("First attempt failed, trying with as='string'") - success, response = pcall(function() - return http.request(url, { - as = "string" - }) - end) - end + local data = response.body - if not success then - print("HTTP request failed:", response) - return nil, "HTTP request failed: " .. tostring(response) - end - - if not response then - print("No response received") - return nil, "No response received from server" - end - - print("Response status:", response.status) - print("Response body type:", type(response.body)) - print("Response body length:", response.body and #response.body or "nil") - if response.headers then - print("Response headers:", response.headers["content-type"] or "unknown") - print("Content length header:", response.headers["content-length"] or "unknown") - end - - if response.status ~= 200 then - return nil, "Failed to download file. HTTP status: " .. tostring(response.status) - end - - if not response.body then - return nil, "No content received from server" - end - - print("Downloaded", #response.body, "bytes") - return response.body, nil + return base64.decode(data.data), data.filename end ---Install a widget from a zip URL ----@param item table - Widget item data containing zip_url and id +---@param item druid.core.item_info - Widget item data containing zip_url and id ---@param install_folder string - Target folder to install to ---@return boolean, string - Success status and message function M.install_widget(item, install_folder) - if not item.zip_url or not item.id then - return false, "Invalid widget data: missing zip_url or id" - end + if not item.json_zip_url or not item.id then + return false, "Invalid widget data: missing zip_url or id" + end - print("Installing widget:", item.id) - print("Download URL:", item.zip_url) - print("Target folder:", install_folder) + -- Download the zip file + local zip_data, filename = download_file_zip_json(item.json_zip_url) + if not zip_data or not filename then + return false, "Failed to download widget: " .. filename + end - -- Download the zip file - local zip_data, download_error = download_file(item.zip_url) - if not zip_data then - return false, "Failed to download widget: " .. download_error - end - -- Create a simple success message for now - local success = true - local message = "Widget '" .. item.id .. "' downloaded successfully!" - message = message .. "\nDownload URL: " .. item.zip_url - message = message .. "\nSize: " .. tostring(#zip_data) .. " bytes" - message = message .. "\nTarget folder: " .. install_folder + local zip_file_path = install_folder .. "/" .. filename + local zip_file = io.open(zip_file_path, "wb") + if not zip_file then + return false, "Failed to open zip file: " .. zip_file_path + end - print("Successfully downloaded widget:", item.id) - return success, message + zip_file:write(zip_data) + zip_file:close() + print("Zip written to file: " .. zip_file_path) + + -- Unzip the zip file + local folder_name = item.id .. "-" .. item.version + zip.unpack(zip_file_path, install_folder .. "/" .. folder_name) + print("Widget unpacked successfully") + + -- Remove the zip file + os.remove(zip_file_path) + print("Zip file removed successfully") + + + return true, "Widget installed successfully" end @@ -95,15 +86,15 @@ end ---@param install_folder string - Install folder to check in ---@return boolean - True if widget is already installed function M.is_widget_installed(item, install_folder) - -- For now, assume widgets are not installed to avoid path issues - return false + -- For now, assume widgets are not installed to avoid path issues + return false end ---Get default installation folder ---@return string - Default installation folder path function M.get_default_install_folder() - return DEFAULT_INSTALL_FOLDER + return DEFAULT_INSTALL_FOLDER end From 9fba1899da62298e6cd3c3f75720387147e4e314 Mon Sep 17 00:00:00 2001 From: Insality Date: Sat, 25 Oct 2025 22:47:09 +0300 Subject: [PATCH 03/22] Update --- druid/editor_scripts/core/asset_store.lua | 2 +- druid/editor_scripts/core/installer.lua | 18 +- druid/editor_scripts/core/path_replacer.lua | 22 + druid/editor_scripts/core/ui_components.lua | 424 ++++++++++---------- 4 files changed, 245 insertions(+), 221 deletions(-) create mode 100644 druid/editor_scripts/core/path_replacer.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 79e4294..c36e1d9 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -134,7 +134,7 @@ function M.open_asset_store() end local dialog_component = editor.ui.component(function(props) - editor.prefs.set("druid.asset_install_folder", "./widget") + editor.prefs.set("druid.asset_install_folder", "/widget") -- State management local items, set_items = editor.ui.use_state(initial_items) local loading, set_loading = editor.ui.use_state(initial_loading) diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index 5ba50fe..dcf1d91 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -1,10 +1,11 @@ local base64 = require("druid.editor_scripts.core.base64") +local path_replacer = require("druid.editor_scripts.core.path_replacer") --- Module for handling widget installation from zip files --- Downloads zip files and extracts them to the specified folder local M = {} -local DEFAULT_INSTALL_FOLDER = "./widget" +local DEFAULT_INSTALL_FOLDER = "/widget" ---@class druid.core.item_info ---@field id string @@ -57,7 +58,7 @@ function M.install_widget(item, install_folder) end - local zip_file_path = install_folder .. "/" .. filename + local zip_file_path = "." .. install_folder .. "/" .. filename local zip_file = io.open(zip_file_path, "wb") if not zip_file then return false, "Failed to open zip file: " .. zip_file_path @@ -68,14 +69,23 @@ function M.install_widget(item, install_folder) print("Zip written to file: " .. zip_file_path) -- Unzip the zip file - local folder_name = item.id .. "-" .. item.version - zip.unpack(zip_file_path, install_folder .. "/" .. folder_name) + local folder_path = "." .. install_folder .. "/" .. item.id + + zip.unpack(zip_file_path, folder_path) print("Widget unpacked successfully") -- Remove the zip file os.remove(zip_file_path) print("Zip file removed successfully") + -- Process paths within the extracted widget + local files_in_folder = path_replacer.get_all_files(folder_path) + pprint(files_in_folder) + + --if not path_replacer.process_widget_paths(install_folder .. "/" .. folder_name, new_base_path) then + -- return false, "Failed to process widget paths" + --end + return true, "Widget installed successfully" end diff --git a/druid/editor_scripts/core/path_replacer.lua b/druid/editor_scripts/core/path_replacer.lua new file mode 100644 index 0000000..f38505a --- /dev/null +++ b/druid/editor_scripts/core/path_replacer.lua @@ -0,0 +1,22 @@ +--- Module for replacing paths in extracted widget files +--- Handles updating require statements and file paths to match the installation location + +local M = {} + + +---Recursively get all files in a directory +---@param dir_path string - Directory path to scan +---@param extension string|nil - Optional file extension filter (e.g., ".lua") +---@return string[] - List of file paths +function M.get_all_files(dir_path, extension) + local attributes = editor.resource_attributes(dir_path) + local files = io.popen("ls -R " .. dir_path) + for line in files:lines() do + print(line) + end + pprint(files) + return files +end + + +return M diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index 22b23c5..f6aa6ae 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -9,20 +9,20 @@ local M = {} ---@param on_change function - Callback when path changes ---@return userdata - UI component function M.create_settings_section(install_path, on_change) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Installation Folder:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = install_path, - color = editor.ui.COLOR.TEXT, - grow = true - }) - } - }) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Installation Folder:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = install_path, + color = editor.ui.COLOR.TEXT, + grow = true + }) + } + }) end @@ -30,18 +30,18 @@ end ---@param items table - List of widget items ---@return table - Sorted list of unique authors local function extract_authors(items) - local authors = {} - local author_set = {} + local authors = {} + local author_set = {} - for _, item in ipairs(items) do - if item.author and not author_set[item.author] then - author_set[item.author] = true - table.insert(authors, item.author) - end - end + for _, item in ipairs(items) do + if item.author and not author_set[item.author] then + author_set[item.author] = true + table.insert(authors, item.author) + end + end - table.sort(authors) - return authors + table.sort(authors) + return authors end @@ -49,22 +49,22 @@ end ---@param items table - List of widget items ---@return table - Sorted list of unique tags local function extract_tags(items) - local tags = {} - local tag_set = {} + local tags = {} + local tag_set = {} - for _, item in ipairs(items) do - if item.tags then - for _, tag in ipairs(item.tags) do - if not tag_set[tag] then - tag_set[tag] = true - table.insert(tags, tag) - end - end - end - end + for _, item in ipairs(items) do + if item.tags then + for _, tag in ipairs(item.tags) do + if not tag_set[tag] then + tag_set[tag] = true + table.insert(tags, tag) + end + end + end + end - table.sort(tags) - return tags + table.sort(tags) + return tags end @@ -76,52 +76,52 @@ end ---@param on_tag_change function - Callback for tag filter change ---@return userdata - UI component function M.create_filter_section(items, author_filter, tag_filter, on_author_change, on_tag_change) - local authors = extract_authors(items) - local tags = extract_tags(items) + local authors = extract_authors(items) + local tags = extract_tags(items) - -- Build author options - local author_options = {"All Authors"} - for _, author in ipairs(authors) do - table.insert(author_options, author) - end + -- Build author options + local author_options = {"All Authors"} + for _, author in ipairs(authors) do + table.insert(author_options, author) + end - -- Build tag options - local tag_options = {"All Categories"} - for _, tag in ipairs(tags) do - table.insert(tag_options, tag) - end + -- Build tag options + local tag_options = {"All Categories"} + for _, tag in ipairs(tags) do + table.insert(tag_options, tag) + end - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Author:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = author_filter, - color = editor.ui.COLOR.TEXT - }) - } - }), - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Category:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = tag_filter, - color = editor.ui.COLOR.TEXT - }) - } - }) - } - }) + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Author:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = author_filter, + color = editor.ui.COLOR.TEXT + }) + } + }), + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Category:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = tag_filter, + color = editor.ui.COLOR.TEXT + }) + } + }) + } + }) end @@ -129,13 +129,13 @@ end ---@param size_bytes number - Size in bytes ---@return string - Formatted size string local function format_size(size_bytes) - if size_bytes < 1024 then - return size_bytes .. " B" - elseif size_bytes < 1024 * 1024 then - return math.floor(size_bytes / 1024) .. " KB" - else - return math.floor(size_bytes / (1024 * 1024)) .. " MB" - end + if size_bytes < 1024 then + return size_bytes .. " B" + elseif size_bytes < 1024 * 1024 then + return math.floor(size_bytes / 1024) .. " KB" + else + return math.floor(size_bytes / (1024 * 1024)) .. " MB" + end end @@ -146,101 +146,99 @@ end ---@param on_open_api function - Callback for API docs button ---@return userdata - UI component function M.create_widget_item(item, is_installed, on_install, on_open_api) - local size_text = item.size and format_size(item.size) or "Unknown size" - local version_text = item.version and "v" .. item.version or "Unknown version" + local size_text = item.size and format_size(item.size) or "Unknown size" + local version_text = item.version and "v" .. item.version or "Unknown version" - -- Create tags display - local tags_text = "" - if item.tags and #item.tags > 0 then - tags_text = "Tags: " .. table.concat(item.tags, ", ") - end + -- Create tags display + local tags_text = "" + if item.tags and #item.tags > 0 then + tags_text = "Tags: " .. table.concat(item.tags, ", ") + end - -- Create dependencies display - local deps_text = "" - if item.depends and #item.depends > 0 then - deps_text = "Depends on: " .. table.concat(item.depends, ", ") - end + -- Create dependencies display + local deps_text = "" + if item.depends and #item.depends > 0 then + deps_text = "Depends on: " .. table.concat(item.depends, ", ") + end - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.MEDIUM, - children = { - -- Widget icon placeholder - editor.ui.label({ - text = "📦", - color = editor.ui.COLOR.HINT - }), + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + padding = editor.ui.PADDING.MEDIUM, + children = { + -- Widget icon placeholder + editor.ui.label({ + text = "📦", + color = editor.ui.COLOR.HINT + }), - -- Widget details - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - grow = true, - children = { - -- Title and author - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = item.title or item.id, - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = "by " .. (item.author or "Unknown"), - color = editor.ui.COLOR.HINT - }) - } - }), + -- Widget details + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + grow = true, + children = { + -- Title and author + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = item.title or item.id, + color = editor.ui.COLOR.TEXT + }), + editor.ui.label({ + text = "by " .. (item.author or "Unknown"), + color = editor.ui.COLOR.HINT + }), + editor.ui.label({ + text = version_text .. " • " .. size_text, + color = editor.ui.COLOR.HINT + }), + } + }), - -- Version and size - editor.ui.label({ - text = version_text .. " • " .. size_text, - color = editor.ui.COLOR.HINT - }), + -- Description + editor.ui.label({ + text = item.description or "No description available", + color = editor.ui.COLOR.TEXT + }), - -- Description - editor.ui.label({ - text = item.description or "No description available", - color = editor.ui.COLOR.TEXT - }), + -- Tags + tags_text ~= "" and editor.ui.label({ + text = tags_text, + color = editor.ui.COLOR.HINT + }) or nil, - -- Tags - tags_text ~= "" and editor.ui.label({ - text = tags_text, - color = editor.ui.COLOR.HINT - }) or nil, + -- Dependencies + deps_text ~= "" and editor.ui.label({ + text = deps_text, + color = editor.ui.COLOR.WARNING + }) or nil, - -- Dependencies - deps_text ~= "" and editor.ui.label({ - text = deps_text, - color = editor.ui.COLOR.WARNING - }) or nil, + -- Installation status + is_installed and editor.ui.label({ + text = "✓ Already installed", + color = editor.ui.COLOR.HINT + }) or nil + } + }), - -- Installation status - is_installed and editor.ui.label({ - text = "✓ Already installed", - color = editor.ui.COLOR.HINT - }) or nil - } - }), - - -- Action buttons - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.button({ - text = is_installed and "Reinstall" or "Install", - on_pressed = on_install, - enabled = true - }), - editor.ui.button({ - text = "API Docs", - on_pressed = on_open_api, - enabled = item.api ~= nil - }) - } - }) - } - }) + -- Action buttons + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.button({ + text = is_installed and "Reinstall" or "Install", + on_pressed = on_install, + enabled = true + }), + editor.ui.button({ + text = "API", + on_pressed = on_open_api, + enabled = item.api ~= nil + }) + } + }) + } + }) end @@ -251,29 +249,23 @@ end ---@param on_open_api function - Callback for API docs button ---@return userdata - UI component function M.create_widget_list(items, is_installed_func, on_install, on_open_api) - local widget_items = {} + local widget_items = {} - for _, item in ipairs(items) do - local is_installed = is_installed_func and is_installed_func(item) or false + for index = 1, 9 do + for _, item in ipairs(items) do + local is_installed = is_installed_func and is_installed_func(item) or false - table.insert(widget_items, M.create_widget_item(item, is_installed, - function() on_install(item) end, - function() on_open_api(item) end - )) + table.insert(widget_items, M.create_widget_item(item, is_installed, + function() on_install(item) end, + function() on_open_api(item) end + )) + end + end - -- Add separator between items (except for the last one) - if _ < #items then - table.insert(widget_items, editor.ui.label({ - text = "---", - color = editor.ui.COLOR.HINT - })) - end - end - - return editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = widget_items - }) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + children = widget_items + }) end @@ -281,17 +273,17 @@ end ---@param message string - Loading message ---@return userdata - UI component function M.create_loading_indicator(message) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - alignment = editor.ui.ALIGNMENT.CENTER, - children = { - editor.ui.label({ - text = message or "Loading...", - color = editor.ui.COLOR.TEXT, - alignment = editor.ui.ALIGNMENT.CENTER - }) - } - }) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + alignment = editor.ui.ALIGNMENT.CENTER, + children = { + editor.ui.label({ + text = message or "Loading...", + color = editor.ui.COLOR.TEXT, + alignment = editor.ui.ALIGNMENT.CENTER + }) + } + }) end @@ -299,17 +291,17 @@ end ---@param message string - Error message ---@return userdata - UI component function M.create_error_message(message) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - alignment = editor.ui.ALIGNMENT.CENTER, - children = { - editor.ui.label({ - text = "Error: " .. message, - color = editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.CENTER - }) - } - }) + return editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + alignment = editor.ui.ALIGNMENT.CENTER, + children = { + editor.ui.label({ + text = "Error: " .. message, + color = editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.CENTER + }) + } + }) end From 1f1438b4af81b84da7ea47d82e3c5f263e8ed7f2 Mon Sep 17 00:00:00 2001 From: Insality Date: Sun, 26 Oct 2025 19:00:39 +0200 Subject: [PATCH 04/22] Update --- druid/editor_scripts/core/asset_store.lua | 320 ++++++++++---------- druid/editor_scripts/core/ui_components.lua | 13 +- 2 files changed, 170 insertions(+), 163 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index c36e1d9..95dacdc 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -12,22 +12,22 @@ local STORE_URL = "https://insality.github.io/core/druid_widget_store.json" ---Fetch widget data from the remote store ---@return table|nil, string|nil - Store data or nil, error message or nil local function fetch_store_data() - print("Fetching widget data from:", STORE_URL) + print("Fetching widget data from:", STORE_URL) - local response = http.request(STORE_URL, { - as = "json" - }) + local response = http.request(STORE_URL, { + as = "json" + }) - if response.status ~= 200 then - return nil, "Failed to fetch store data. HTTP status: " .. response.status - end + if response.status ~= 200 then + return nil, "Failed to fetch store data. HTTP status: " .. response.status + end - if not response.body or not response.body.items then - return nil, "Invalid store data format" - end + if not response.body or not response.body.items then + return nil, "Invalid store data format" + end - print("Successfully fetched", #response.body.items, "widgets") - return response.body, nil + print("Successfully fetched", #response.body.items, "widgets") + return response.body, nil end @@ -37,18 +37,18 @@ end ---@param tag_filter string - Tag filter value ---@return table - Filtered list of items local function filter_items(items, author_filter, tag_filter) - local filtered = {} + local filtered = {} - for _, item in ipairs(items) do - local author_match = author_filter == "All Authors" or item.author == author_filter - local tag_match = tag_filter == "All Categories" or (item.tags and table.concat(item.tags, ","):find(tag_filter)) + for _, item in ipairs(items) do + local author_match = author_filter == "All Authors" or item.author == author_filter + local tag_match = tag_filter == "All Categories" or (item.tags and table.concat(item.tags, ","):find(tag_filter)) - if author_match and tag_match then - table.insert(filtered, item) - end - end + if author_match and tag_match then + table.insert(filtered, item) + end + end - return filtered + return filtered end @@ -58,29 +58,29 @@ end ---@param on_success function - Success callback ---@param on_error function - Error callback local function handle_install(item, install_folder, on_success, on_error) - print("Installing widget:", item.id) + print("Installing widget:", item.id) - local success, message = installer.install_widget(item, install_folder) + local success, message = installer.install_widget(item, install_folder) - if success then - print("Installation successful:", message) - on_success(message) - else - print("Installation failed:", message) - on_error(message) - end + if success then + print("Installation successful:", message) + on_success(message) + else + print("Installation failed:", message) + on_error(message) + end end ---Handle opening API documentation ---@param item table - Widget item local function handle_open_api(item) - if item.api then - print("Opening API documentation:", item.api) - editor.browse(item.api) - else - print("No API documentation available for:", item.id) - end + if item.api then + print("Opening API documentation:", item.api) + editor.browse(item.api) + else + print("No API documentation available for:", item.id) + end end @@ -88,152 +88,154 @@ end ---@param success boolean - Whether installation was successful ---@param message string - Status message local function show_install_status(success, message) - local dialog_component = editor.ui.component(function() - return editor.ui.dialog({ - title = success and "Installation Successful" or "Installation Failed", - content = editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.MEDIUM, - children = { - editor.ui.label({ - text = message, - color = success and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.LEFT - }) - } - }), - buttons = { - editor.ui.dialog_button({ - text = "OK", - default = true - }) - } - }) - end) + local dialog_component = editor.ui.component(function() + return editor.ui.dialog({ + title = success and "Installation Successful" or "Installation Failed", + content = editor.ui.vertical({ + spacing = editor.ui.SPACING.MEDIUM, + padding = editor.ui.PADDING.MEDIUM, + children = { + editor.ui.label({ + text = message, + color = success and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.LEFT + }) + } + }), + buttons = { + editor.ui.dialog_button({ + text = "OK", + default = true + }) + } + }) + end) - editor.ui.show_dialog(dialog_component({})) + editor.ui.show_dialog(dialog_component({})) end ---Open the asset store dialog function M.open_asset_store() - print("Opening Druid Asset Store") + print("Opening Druid Asset Store") - -- Fetch data synchronously before creating the dialog - local store_data, fetch_error = fetch_store_data() - local initial_items = {} - local initial_loading = false - local initial_error = nil + -- Fetch data synchronously before creating the dialog + local store_data, fetch_error = fetch_store_data() + local initial_items = {} + local initial_loading = false + local initial_error = nil - if store_data then - initial_items = store_data.items - print("Successfully loaded", #initial_items, "widgets") - else - initial_error = fetch_error - print("Failed to load widgets:", fetch_error) - end + if store_data then + initial_items = store_data.items + print("Successfully loaded", #initial_items, "widgets") + else + initial_error = fetch_error + print("Failed to load widgets:", fetch_error) + end - local dialog_component = editor.ui.component(function(props) + local dialog_component = editor.ui.component(function(props) editor.prefs.set("druid.asset_install_folder", "/widget") - -- State management - local items, set_items = editor.ui.use_state(initial_items) - local loading, set_loading = editor.ui.use_state(initial_loading) - local error_message, set_error_message = editor.ui.use_state(initial_error) - local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder()) - local author_filter, set_author_filter = editor.ui.use_state("All Authors") - local tag_filter, set_tag_filter = editor.ui.use_state("All Categories") - local install_status, set_install_status = editor.ui.use_state("") + -- State management + local items, set_items = editor.ui.use_state(initial_items) + local loading, set_loading = editor.ui.use_state(initial_loading) + local error_message, set_error_message = editor.ui.use_state(initial_error) + local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder()) + local author_filter, set_author_filter = editor.ui.use_state("All Authors") + local tag_filter, set_tag_filter = editor.ui.use_state("All Categories") + local install_status, set_install_status = editor.ui.use_state("") - -- Filter items - local filtered_items = editor.ui.use_memo(filter_items, items, author_filter, tag_filter) + -- Filter items + local filtered_items = editor.ui.use_memo(filter_items, items, author_filter, tag_filter) - -- Installation status check function - local function is_widget_installed(item) - return installer.is_widget_installed(item, install_folder) - end + -- Installation status check function + local function is_widget_installed(item) + return installer.is_widget_installed(item, install_folder) + end - -- Installation handlers - local function on_install(item) - handle_install(item, install_folder, - function(message) - set_install_status("Success: " .. message) - show_install_status(true, message) - end, - function(message) - set_install_status("Error: " .. message) - show_install_status(false, message) - end - ) - end + -- Installation handlers + local function on_install(item) + handle_install(item, install_folder, + function(message) + set_install_status("Success: " .. message) + show_install_status(true, message) + end, + function(message) + set_install_status("Error: " .. message) + show_install_status(false, message) + end + ) + end - local function on_open_api(item) - handle_open_api(item) - end + local function on_open_api(item) + handle_open_api(item) + end - -- Build UI content - local content_children = {} + -- Build UI content + local content_children = {} - -- Settings section - table.insert(content_children, editor.ui.label({ - text = "Installation Folder: " .. install_folder, - color = editor.ui.COLOR.TEXT - })) + -- Settings section + table.insert(content_children, editor.ui.label({ + spacing = editor.ui.SPACING.MEDIUM, + text = "Installation Folder: " .. install_folder, + color = editor.ui.COLOR.TEXT + })) - -- Filter section (only show if we have items) - if #items > 0 then - table.insert(content_children, editor.ui.label({ - text = "Filters: Author: " .. author_filter .. ", Category: " .. tag_filter, - color = editor.ui.COLOR.TEXT - })) - end + -- Filter section (only show if we have items) + if #items > 0 then + table.insert(content_children, editor.ui.label({ + spacing = editor.ui.SPACING.MEDIUM, + text = "Filters: Author: " .. author_filter .. ", Category: " .. tag_filter, + color = editor.ui.COLOR.TEXT + })) + end - -- Main content area - if loading then - table.insert(content_children, ui_components.create_loading_indicator("Loading widget store...")) - elseif error_message then - table.insert(content_children, ui_components.create_error_message(error_message)) - elseif #filtered_items == 0 then - table.insert(content_children, editor.ui.label({ - text = "No widgets found matching the current filters.", - color = editor.ui.COLOR.HINT, - alignment = editor.ui.ALIGNMENT.CENTER - })) - else - table.insert(content_children, ui_components.create_widget_list( - filtered_items, is_widget_installed, on_install, on_open_api - )) - end + -- Main content area + if loading then + table.insert(content_children, ui_components.create_loading_indicator("Loading widget store...")) + elseif error_message then + table.insert(content_children, ui_components.create_error_message(error_message)) + elseif #filtered_items == 0 then + table.insert(content_children, editor.ui.label({ + text = "No widgets found matching the current filters.", + color = editor.ui.COLOR.HINT, + alignment = editor.ui.ALIGNMENT.CENTER + })) + else + table.insert(content_children, ui_components.create_widget_list( + filtered_items, is_widget_installed, on_install, on_open_api + )) + end - -- Install status message - if install_status ~= "" then - table.insert(content_children, editor.ui.label({ - text = install_status, - color = install_status:find("Success") and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.CENTER - })) - end + -- Install status message + if install_status ~= "" then + table.insert(content_children, editor.ui.label({ + text = install_status, + color = install_status:find("Success") and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, + alignment = editor.ui.ALIGNMENT.CENTER + })) + end - return editor.ui.dialog({ - title = "Druid Asset Store", - content = editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.MEDIUM, - children = content_children - }), - buttons = { - editor.ui.dialog_button({ - text = "Close", - cancel = true - }) - } - }) - end) + return editor.ui.dialog({ + title = "Druid Asset Store", + content = editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + padding = editor.ui.PADDING.NONE, + children = content_children + }), + buttons = { + editor.ui.dialog_button({ + text = "Close", + cancel = true + }) + } + }) + end) - local result = editor.ui.show_dialog(dialog_component({})) + local result = editor.ui.show_dialog(dialog_component({})) - -- Save the install folder preference (this will be handled by the state management in the dialog) + -- Save the install folder preference (this will be handled by the state management in the dialog) - return result + return result end diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index f6aa6ae..ca125ba 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -184,12 +184,16 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) text = item.title or item.id, color = editor.ui.COLOR.TEXT }), + editor.ui.label({ + text = version_text .. " • ", + color = editor.ui.COLOR.HINT + }), editor.ui.label({ text = "by " .. (item.author or "Unknown"), color = editor.ui.COLOR.HINT }), editor.ui.label({ - text = version_text .. " • " .. size_text, + text = " • " .. size_text, color = editor.ui.COLOR.HINT }), } @@ -262,9 +266,10 @@ function M.create_widget_list(items, is_installed_func, on_install, on_open_api) end end - return editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = widget_items + return editor.ui.scroll({ + content = editor.ui.vertical({ + children = widget_items + }) }) end From f3f19337a34bcafa3f9e1feb0d9bad720a5d112e Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 27 Oct 2025 01:15:57 +0200 Subject: [PATCH 05/22] Update --- druid/editor_scripts/core/asset_store.lua | 132 ++++++------------ .../core/asset_store_internal.lua | 21 +++ druid/editor_scripts/core/installer.lua | 13 +- druid/editor_scripts/core/path_replacer.lua | 22 --- druid/editor_scripts/core/ui_components.lua | 52 ++----- druid/editor_scripts/druid.editor_script | 2 +- game.project | 2 +- 7 files changed, 83 insertions(+), 161 deletions(-) create mode 100644 druid/editor_scripts/core/asset_store_internal.lua delete mode 100644 druid/editor_scripts/core/path_replacer.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 95dacdc..3328619 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -3,54 +3,10 @@ local installer = require("druid.editor_scripts.core.installer") local ui_components = require("druid.editor_scripts.core.ui_components") +local internal = require("druid.editor_scripts.core.asset_store_internal") local M = {} -local STORE_URL = "https://insality.github.io/core/druid_widget_store.json" - - ----Fetch widget data from the remote store ----@return table|nil, string|nil - Store data or nil, error message or nil -local function fetch_store_data() - print("Fetching widget data from:", STORE_URL) - - local response = http.request(STORE_URL, { - as = "json" - }) - - if response.status ~= 200 then - return nil, "Failed to fetch store data. HTTP status: " .. response.status - end - - if not response.body or not response.body.items then - return nil, "Invalid store data format" - end - - print("Successfully fetched", #response.body.items, "widgets") - return response.body, nil -end - - ----Filter items based on author and tag filters ----@param items table - List of widget items ----@param author_filter string - Author filter value ----@param tag_filter string - Tag filter value ----@return table - Filtered list of items -local function filter_items(items, author_filter, tag_filter) - local filtered = {} - - for _, item in ipairs(items) do - local author_match = author_filter == "All Authors" or item.author == author_filter - local tag_match = tag_filter == "All Categories" or (item.tags and table.concat(item.tags, ","):find(tag_filter)) - - if author_match and tag_match then - table.insert(filtered, item) - end - end - - return filtered -end - ---Handle widget installation ---@param item table - Widget item to install @@ -116,37 +72,25 @@ end ---Open the asset store dialog -function M.open_asset_store() - print("Opening Druid Asset Store") +function M.open_asset_store(store_url) + print("Opening Druid Asset Store from:", store_url) -- Fetch data synchronously before creating the dialog - local store_data, fetch_error = fetch_store_data() - local initial_items = {} - local initial_loading = false - local initial_error = nil - - if store_data then - initial_items = store_data.items - print("Successfully loaded", #initial_items, "widgets") - else - initial_error = fetch_error + local store_data, fetch_error = internal.download_json(store_url) + if not store_data then print("Failed to load widgets:", fetch_error) + return end + print("Successfully loaded", #store_data.items, "widgets") + local initial_items = store_data.items local dialog_component = editor.ui.component(function(props) - editor.prefs.set("druid.asset_install_folder", "/widget") -- State management local items, set_items = editor.ui.use_state(initial_items) - local loading, set_loading = editor.ui.use_state(initial_loading) - local error_message, set_error_message = editor.ui.use_state(initial_error) local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder()) - local author_filter, set_author_filter = editor.ui.use_state("All Authors") - local tag_filter, set_tag_filter = editor.ui.use_state("All Categories") + local search_query, set_search_query = editor.ui.use_state("") local install_status, set_install_status = editor.ui.use_state("") - -- Filter items - local filtered_items = editor.ui.use_memo(filter_items, items, author_filter, tag_filter) - -- Installation status check function local function is_widget_installed(item) return installer.is_widget_installed(item, install_folder) @@ -174,27 +118,43 @@ function M.open_asset_store() local content_children = {} -- Settings section - table.insert(content_children, editor.ui.label({ + table.insert(content_children, editor.ui.horizontal({ spacing = editor.ui.SPACING.MEDIUM, - text = "Installation Folder: " .. install_folder, - color = editor.ui.COLOR.TEXT + children = { + editor.ui.label({ + spacing = editor.ui.SPACING.MEDIUM, + text = "Installation Folder:", + color = editor.ui.COLOR.TEXT + }), + + editor.ui.string_field({ + value = install_folder, + on_value_changed = set_install_folder, + title = "Installation Folder:", + tooltip = "The folder to install the assets to", + }), + } })) - -- Filter section (only show if we have items) - if #items > 0 then - table.insert(content_children, editor.ui.label({ - spacing = editor.ui.SPACING.MEDIUM, - text = "Filters: Author: " .. author_filter .. ", Category: " .. tag_filter, - color = editor.ui.COLOR.TEXT - })) - end + -- Search section + table.insert(content_children, editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.label({ + text = "Search:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.string_field({ + value = search_query, + on_value_changed = set_search_query, + title = "Search:", + tooltip = "Search for widgets by title, author, or description", + }) + }, + })) -- Main content area - if loading then - table.insert(content_children, ui_components.create_loading_indicator("Loading widget store...")) - elseif error_message then - table.insert(content_children, ui_components.create_error_message(error_message)) - elseif #filtered_items == 0 then + if #items == 0 then table.insert(content_children, editor.ui.label({ text = "No widgets found matching the current filters.", color = editor.ui.COLOR.HINT, @@ -202,7 +162,7 @@ function M.open_asset_store() })) else table.insert(content_children, ui_components.create_widget_list( - filtered_items, is_widget_installed, on_install, on_open_api + items, is_widget_installed, on_install, on_open_api )) end @@ -219,7 +179,7 @@ function M.open_asset_store() title = "Druid Asset Store", content = editor.ui.vertical({ spacing = editor.ui.SPACING.SMALL, - padding = editor.ui.PADDING.NONE, + padding = editor.ui.PADDING.SMALL, children = content_children }), buttons = { @@ -231,11 +191,7 @@ function M.open_asset_store() }) end) - local result = editor.ui.show_dialog(dialog_component({})) - - -- Save the install folder preference (this will be handled by the state management in the dialog) - - return result + return editor.ui.show_dialog(dialog_component({})) end diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua new file mode 100644 index 0000000..2733f53 --- /dev/null +++ b/druid/editor_scripts/core/asset_store_internal.lua @@ -0,0 +1,21 @@ +local M = {} + +---Download a JSON file from a URL +---@param json_url string - The URL to download the JSON from +---@return table|nil, string|nil - The JSON data or nil, error message or nil +function M.download_json(json_url) + local response = http.request(json_url, { as = "json" }) + + if response.status ~= 200 then + return nil, "Failed to fetch store data. HTTP status: " .. response.status + end + + if not response.body or not response.body.items then + return nil, "Invalid store data format" + end + + return response.body, nil +end + + +return M diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index dcf1d91..5fc485d 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -1,8 +1,8 @@ -local base64 = require("druid.editor_scripts.core.base64") -local path_replacer = require("druid.editor_scripts.core.path_replacer") --- Module for handling widget installation from zip files --- Downloads zip files and extracts them to the specified folder +local base64 = require("druid.editor_scripts.core.base64") + local M = {} local DEFAULT_INSTALL_FOLDER = "/widget" @@ -79,14 +79,13 @@ function M.install_widget(item, install_folder) print("Zip file removed successfully") -- Process paths within the extracted widget - local files_in_folder = path_replacer.get_all_files(folder_path) - pprint(files_in_folder) + --local files_in_folder = path_replacer.get_all_files(folder_path) + --pprint(files_in_folder) --if not path_replacer.process_widget_paths(install_folder .. "/" .. folder_name, new_base_path) then -- return false, "Failed to process widget paths" --end - return true, "Widget installed successfully" end @@ -96,8 +95,8 @@ end ---@param install_folder string - Install folder to check in ---@return boolean - True if widget is already installed function M.is_widget_installed(item, install_folder) - -- For now, assume widgets are not installed to avoid path issues - return false + local p = editor.resource_attributes(install_folder .. "/" .. item.id) + return p.exists end diff --git a/druid/editor_scripts/core/path_replacer.lua b/druid/editor_scripts/core/path_replacer.lua deleted file mode 100644 index f38505a..0000000 --- a/druid/editor_scripts/core/path_replacer.lua +++ /dev/null @@ -1,22 +0,0 @@ ---- Module for replacing paths in extracted widget files ---- Handles updating require statements and file paths to match the installation location - -local M = {} - - ----Recursively get all files in a directory ----@param dir_path string - Directory path to scan ----@param extension string|nil - Optional file extension filter (e.g., ".lua") ----@return string[] - List of file paths -function M.get_all_files(dir_path, extension) - local attributes = editor.resource_attributes(dir_path) - local files = io.popen("ls -R " .. dir_path) - for line in files:lines() do - print(line) - end - pprint(files) - return files -end - - -return M diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index ca125ba..045b5a8 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -162,9 +162,12 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) end return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.MEDIUM, + spacing = editor.ui.SPACING.NONE, + padding = editor.ui.PADDING.SMALL, children = { + editor.ui.separator({ + orientation = editor.ui.ORIENTATION.HORIZONTAL, + }), -- Widget icon placeholder editor.ui.label({ text = "📦", @@ -227,12 +230,13 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) -- Action buttons editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, + spacing = editor.ui.SPACING.MEDIUM, children = { editor.ui.button({ - text = is_installed and "Reinstall" or "Install", + + text = "Install", on_pressed = on_install, - enabled = true + enabled = is_installed == false }), editor.ui.button({ text = "API", @@ -240,7 +244,7 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) enabled = item.api ~= nil }) } - }) + }), } }) end @@ -274,40 +278,4 @@ function M.create_widget_list(items, is_installed_func, on_install, on_open_api) end ----Create a loading indicator ----@param message string - Loading message ----@return userdata - UI component -function M.create_loading_indicator(message) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - alignment = editor.ui.ALIGNMENT.CENTER, - children = { - editor.ui.label({ - text = message or "Loading...", - color = editor.ui.COLOR.TEXT, - alignment = editor.ui.ALIGNMENT.CENTER - }) - } - }) -end - - ----Create an error message display ----@param message string - Error message ----@return userdata - UI component -function M.create_error_message(message) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - alignment = editor.ui.ALIGNMENT.CENTER, - children = { - editor.ui.label({ - text = "Error: " .. message, - color = editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.CENTER - }) - } - }) -end - - return M diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index 8f45587..de7a0a1 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -74,7 +74,7 @@ function M.get_commands() label = "[Druid] Asset Store", locations = { "Edit" }, run = function() - return asset_store.open_asset_store() + return asset_store.open_asset_store("https://insality.github.io/core/druid_widget_store.json") end }, diff --git a/game.project b/game.project index e1be0a5..8ee53b8 100644 --- a/game.project +++ b/game.project @@ -58,7 +58,7 @@ cssfile = /builtins/manifests/web/dark_theme.css show_console_banner = 0 [native_extension] -app_manifest = +app_manifest = [graphics] texture_profiles = /builtins/graphics/default.texture_profiles From 1601d6d56e304b291ae8f3120728accf3d477fc4 Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 27 Oct 2025 01:49:16 +0200 Subject: [PATCH 06/22] Update --- druid/editor_scripts/core/asset_store.lua | 43 +++++-------- .../core/asset_store_internal.lua | 63 +++++++++++++++++++ druid/editor_scripts/core/installer.lua | 8 +-- druid/editor_scripts/core/ui_components.lua | 37 ++++++----- 4 files changed, 99 insertions(+), 52 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 3328619..4438daa 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -28,18 +28,6 @@ local function handle_install(item, install_folder, on_success, on_error) end ----Handle opening API documentation ----@param item table - Widget item -local function handle_open_api(item) - if item.api then - print("Opening API documentation:", item.api) - editor.browse(item.api) - else - print("No API documentation available for:", item.id) - end -end - - ---Show installation status dialog ---@param success boolean - Whether installation was successful ---@param message string - Status message @@ -71,6 +59,7 @@ local function show_install_status(success, message) end + ---Open the asset store dialog function M.open_asset_store(store_url) print("Opening Druid Asset Store from:", store_url) @@ -86,16 +75,12 @@ function M.open_asset_store(store_url) local initial_items = store_data.items local dialog_component = editor.ui.component(function(props) -- State management - local items, set_items = editor.ui.use_state(initial_items) - local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_default_install_folder()) + local all_items = editor.ui.use_state(initial_items) + local filtered_items, set_filtered_items = editor.ui.use_state(initial_items) + local install_folder, set_install_folder = editor.ui.use_state(installer.get_install_folder()) local search_query, set_search_query = editor.ui.use_state("") local install_status, set_install_status = editor.ui.use_state("") - -- Installation status check function - local function is_widget_installed(item) - return installer.is_widget_installed(item, install_folder) - end - -- Installation handlers local function on_install(item) handle_install(item, install_folder, @@ -110,10 +95,6 @@ function M.open_asset_store(store_url) ) end - local function on_open_api(item) - handle_open_api(item) - end - -- Build UI content local content_children = {} @@ -146,7 +127,10 @@ function M.open_asset_store(store_url) }), editor.ui.string_field({ value = search_query, - on_value_changed = set_search_query, + on_value_changed = function(new_query) + set_search_query(new_query) + set_filtered_items(internal.filter_items(all_items, new_query)) + end, title = "Search:", tooltip = "Search for widgets by title, author, or description", }) @@ -154,16 +138,17 @@ function M.open_asset_store(store_url) })) -- Main content area - if #items == 0 then + if #filtered_items == 0 then + local message = search_query ~= "" and + "No widgets found matching '" .. search_query .. "'." or + "No widgets found matching the current filters." table.insert(content_children, editor.ui.label({ - text = "No widgets found matching the current filters.", + text = message, color = editor.ui.COLOR.HINT, alignment = editor.ui.ALIGNMENT.CENTER })) else - table.insert(content_children, ui_components.create_widget_list( - items, is_widget_installed, on_install, on_open_api - )) + table.insert(content_children, ui_components.create_widget_list(filtered_items, on_install)) end -- Install status message diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua index 2733f53..41b8569 100644 --- a/druid/editor_scripts/core/asset_store_internal.lua +++ b/druid/editor_scripts/core/asset_store_internal.lua @@ -18,4 +18,67 @@ function M.download_json(json_url) end +---Filter items based on search query +---Filter items based on search query +---@param items table - List of widget items +---@param query string - Search query +---@return table - Filtered items +function M.filter_items(items, query) + if query == "" then + return items + end + + local filtered = {} + local lower_query = string.lower(query) + + for _, item in ipairs(items) do + -- Search in title, author, description + local matches = false + if item.title and string.find(string.lower(item.title), lower_query, 1, true) then + matches = true + elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then + matches = true + elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then + matches = true + end + + -- Search in tags + if not matches and item.tags then + for _, tag in ipairs(item.tags) do + if string.find(string.lower(tag), lower_query, 1, true) then + matches = true + break + end + end + end + + -- Search in dependencies + if not matches and item.depends then + for _, dep in ipairs(item.depends) do + if string.find(string.lower(dep), lower_query, 1, true) then + matches = true + break + end + end + end + + if matches then + table.insert(filtered, item) + end + end + + return filtered +end + + +---Open a URL in the default browser +---@param url string - The URL to open +function M.open_url(url) + if not url then + print("No URL available for:", url) + end + editor.browse(url) +end + + return M diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index 5fc485d..5197dbf 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -100,10 +100,10 @@ function M.is_widget_installed(item, install_folder) end ----Get default installation folder ----@return string - Default installation folder path -function M.get_default_install_folder() - return DEFAULT_INSTALL_FOLDER +---Get installation folder +---@return string - Installation folder path +function M.get_install_folder() + return editor.prefs.get("druid.asset_install_folder") or DEFAULT_INSTALL_FOLDER end diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index 045b5a8..0be7f4d 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -1,6 +1,9 @@ --- Module for reusable UI components in the asset store --- Contains component builders for filters, widget items, and lists +local internal = require("druid.editor_scripts.core.asset_store_internal") +local installer = require("druid.editor_scripts.core.installer") + local M = {} @@ -143,9 +146,8 @@ end ---@param item table - Widget item data ---@param is_installed boolean - Whether widget is already installed ---@param on_install function - Callback for install button ----@param on_open_api function - Callback for API docs button ---@return userdata - UI component -function M.create_widget_item(item, is_installed, on_install, on_open_api) +function M.create_widget_item(item, is_installed, on_install) local size_text = item.size and format_size(item.size) or "Unknown size" local version_text = item.version and "v" .. item.version or "Unknown version" @@ -158,7 +160,7 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) -- Create dependencies display local deps_text = "" if item.depends and #item.depends > 0 then - deps_text = "Depends on: " .. table.concat(item.depends, ", ") + deps_text = "Depends: " .. table.concat(item.depends, ", ") end return editor.ui.horizontal({ @@ -185,18 +187,18 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) children = { editor.ui.label({ text = item.title or item.id, - color = editor.ui.COLOR.TEXT + color = editor.ui.COLOR.OVERRIDE }), editor.ui.label({ - text = version_text .. " • ", + text = version_text, + color = editor.ui.COLOR.WARNING + }), + editor.ui.label({ + text = "• by " .. (item.author or "Unknown"), color = editor.ui.COLOR.HINT }), editor.ui.label({ - text = "by " .. (item.author or "Unknown"), - color = editor.ui.COLOR.HINT - }), - editor.ui.label({ - text = " • " .. size_text, + text = "• " .. size_text, color = editor.ui.COLOR.HINT }), } @@ -217,7 +219,7 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) -- Dependencies deps_text ~= "" and editor.ui.label({ text = deps_text, - color = editor.ui.COLOR.WARNING + color = editor.ui.COLOR.HINT }) or nil, -- Installation status @@ -240,7 +242,7 @@ function M.create_widget_item(item, is_installed, on_install, on_open_api) }), editor.ui.button({ text = "API", - on_pressed = on_open_api, + on_pressed = function() internal.open_url(item.api) end, enabled = item.api ~= nil }) } @@ -252,20 +254,17 @@ end ---Create a scrollable list of widget items ---@param items table - List of widget items to display ----@param is_installed_func function - Function to check if widget is installed ---@param on_install function - Callback for install button ----@param on_open_api function - Callback for API docs button ---@return userdata - UI component -function M.create_widget_list(items, is_installed_func, on_install, on_open_api) +function M.create_widget_list(items, on_install) local widget_items = {} + local install_folder = installer.get_install_folder() for index = 1, 9 do for _, item in ipairs(items) do - local is_installed = is_installed_func and is_installed_func(item) or false - + local is_installed = installer.is_widget_installed(item, install_folder) table.insert(widget_items, M.create_widget_item(item, is_installed, - function() on_install(item) end, - function() on_open_api(item) end + function() on_install(item) end )) end end From 1b2cdabd8b2e9d1e95fa6e24290123022c1b71a7 Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 27 Oct 2025 22:25:13 +0200 Subject: [PATCH 07/22] Update --- druid/editor_scripts/core/asset_store.lua | 34 ------ druid/editor_scripts/core/ui_components.lua | 122 ++++++++++---------- 2 files changed, 62 insertions(+), 94 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 4438daa..a46cd9e 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -28,38 +28,6 @@ local function handle_install(item, install_folder, on_success, on_error) end ----Show installation status dialog ----@param success boolean - Whether installation was successful ----@param message string - Status message -local function show_install_status(success, message) - local dialog_component = editor.ui.component(function() - return editor.ui.dialog({ - title = success and "Installation Successful" or "Installation Failed", - content = editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.MEDIUM, - children = { - editor.ui.label({ - text = message, - color = success and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.LEFT - }) - } - }), - buttons = { - editor.ui.dialog_button({ - text = "OK", - default = true - }) - } - }) - end) - - editor.ui.show_dialog(dialog_component({})) -end - - - ---Open the asset store dialog function M.open_asset_store(store_url) print("Opening Druid Asset Store from:", store_url) @@ -86,11 +54,9 @@ function M.open_asset_store(store_url) handle_install(item, install_folder, function(message) set_install_status("Success: " .. message) - show_install_status(true, message) end, function(message) set_install_status("Error: " .. message) - show_install_status(false, message) end ) end diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index 0be7f4d..df8d260 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -163,13 +163,65 @@ function M.create_widget_item(item, is_installed, on_install) deps_text = "Depends: " .. table.concat(item.depends, ", ") end + local widget_details_children = { + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = item.title or item.id, + color = editor.ui.COLOR.OVERRIDE + }), + editor.ui.label({ + text = version_text, + color = editor.ui.COLOR.WARNING + }), + editor.ui.label({ + text = "• by " .. (item.author or "Unknown"), + color = editor.ui.COLOR.HINT + }), + editor.ui.label({ + text = "• " .. size_text, + color = editor.ui.COLOR.HINT + }), + } + }), + + -- Description + editor.ui.paragraph({ + text = item.description or "No description available", + color = editor.ui.COLOR.TEXT + }), + } + + if tags_text ~= "" then + table.insert(widget_details_children, editor.ui.label({ + text = tags_text, + color = editor.ui.COLOR.HINT + })) + end + + if deps_text ~= "" then + table.insert(widget_details_children, editor.ui.label({ + text = deps_text, + color = editor.ui.COLOR.HINT + })) + end + + if is_installed then + table.insert(widget_details_children, editor.ui.label({ + text = "✓ Already installed", + color = editor.ui.COLOR.WARNING + })) + end + + table.insert(widget_details_children, editor.ui.separator({ + orientation = editor.ui.ORIENTATION.HORIZONTAL, + })) + return editor.ui.horizontal({ spacing = editor.ui.SPACING.NONE, padding = editor.ui.PADDING.SMALL, children = { - editor.ui.separator({ - orientation = editor.ui.ORIENTATION.HORIZONTAL, - }), -- Widget icon placeholder editor.ui.label({ text = "📦", @@ -180,54 +232,7 @@ function M.create_widget_item(item, is_installed, on_install) editor.ui.vertical({ spacing = editor.ui.SPACING.SMALL, grow = true, - children = { - -- Title and author - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = item.title or item.id, - color = editor.ui.COLOR.OVERRIDE - }), - editor.ui.label({ - text = version_text, - color = editor.ui.COLOR.WARNING - }), - editor.ui.label({ - text = "• by " .. (item.author or "Unknown"), - color = editor.ui.COLOR.HINT - }), - editor.ui.label({ - text = "• " .. size_text, - color = editor.ui.COLOR.HINT - }), - } - }), - - -- Description - editor.ui.label({ - text = item.description or "No description available", - color = editor.ui.COLOR.TEXT - }), - - -- Tags - tags_text ~= "" and editor.ui.label({ - text = tags_text, - color = editor.ui.COLOR.HINT - }) or nil, - - -- Dependencies - deps_text ~= "" and editor.ui.label({ - text = deps_text, - color = editor.ui.COLOR.HINT - }) or nil, - - -- Installation status - is_installed and editor.ui.label({ - text = "✓ Already installed", - color = editor.ui.COLOR.HINT - }) or nil - } + children = widget_details_children }), -- Action buttons @@ -235,10 +240,9 @@ function M.create_widget_item(item, is_installed, on_install) spacing = editor.ui.SPACING.MEDIUM, children = { editor.ui.button({ - text = "Install", on_pressed = on_install, - enabled = is_installed == false + enabled = not is_installed }), editor.ui.button({ text = "API", @@ -260,13 +264,11 @@ function M.create_widget_list(items, on_install) local widget_items = {} local install_folder = installer.get_install_folder() - for index = 1, 9 do - for _, item in ipairs(items) do - local is_installed = installer.is_widget_installed(item, install_folder) - table.insert(widget_items, M.create_widget_item(item, is_installed, - function() on_install(item) end - )) - end + for _, item in ipairs(items) do + local is_installed = installer.is_widget_installed(item, install_folder) + table.insert(widget_items, M.create_widget_item(item, is_installed, + function() on_install(item) end + )) end return editor.ui.scroll({ From 15c9bd5ff9d4c849a99d26e4f7fba85b9a966d25 Mon Sep 17 00:00:00 2001 From: Insality Date: Tue, 28 Oct 2025 19:39:07 +0200 Subject: [PATCH 08/22] Update --- druid/editor_scripts/core/ui_components.lua | 46 +++++++++++++++------ 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index df8d260..7d46880 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -218,6 +218,38 @@ function M.create_widget_item(item, is_installed, on_install) orientation = editor.ui.ORIENTATION.HORIZONTAL, })) + local widget_buttons = { + editor.ui.button({ + text = "Install", + on_pressed = on_install, + enabled = not is_installed + }), + } + + if item.api ~= nil then + table.insert(widget_buttons, editor.ui.button({ + text = "API", + on_pressed = function() internal.open_url(item.api) end, + enabled = item.api ~= nil + })) + end + + if item.example_url ~= nil then + table.insert(widget_buttons, editor.ui.button({ + text = "Example", + on_pressed = function() internal.open_url(item.example_url) end, + enabled = item.example_url ~= nil + })) + end + + if item.author_url ~= nil then + table.insert(widget_buttons, editor.ui.button({ + text = "Author", + on_pressed = function() internal.open_url(item.author_url) end, + enabled = item.author_url ~= nil + })) + end + return editor.ui.horizontal({ spacing = editor.ui.SPACING.NONE, padding = editor.ui.PADDING.SMALL, @@ -238,18 +270,8 @@ function M.create_widget_item(item, is_installed, on_install) -- Action buttons editor.ui.vertical({ spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.button({ - text = "Install", - on_pressed = on_install, - enabled = not is_installed - }), - editor.ui.button({ - text = "API", - on_pressed = function() internal.open_url(item.api) end, - enabled = item.api ~= nil - }) - } + grow = true, + children = widget_buttons }), } }) From b7f34ca01d384e6f09d3501732a093d1d16f967d Mon Sep 17 00:00:00 2001 From: Insality Date: Tue, 28 Oct 2025 21:11:55 +0200 Subject: [PATCH 09/22] Update --- druid/editor_scripts/core/asset_store.lua | 20 ++++++++-- druid/editor_scripts/core/installer.lua | 4 +- druid/editor_scripts/core/ui_components.lua | 41 ++++++++++----------- druid/editor_scripts/druid.editor_script | 3 +- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index a46cd9e..621ccb4 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -45,7 +45,7 @@ function M.open_asset_store(store_url) -- State management local all_items = editor.ui.use_state(initial_items) local filtered_items, set_filtered_items = editor.ui.use_state(initial_items) - local install_folder, set_install_folder = editor.ui.use_state(installer.get_install_folder()) + local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder()) local search_query, set_search_query = editor.ui.use_state("") local install_status, set_install_status = editor.ui.use_state("") @@ -76,7 +76,10 @@ function M.open_asset_store(store_url) editor.ui.string_field({ value = install_folder, - on_value_changed = set_install_folder, + on_value_changed = function(new_folder) + set_install_folder(new_folder) + editor.prefs.set("druid.asset_install_folder", new_folder) + end, title = "Installation Folder:", tooltip = "The folder to install the assets to", }), @@ -99,6 +102,7 @@ function M.open_asset_store(store_url) end, title = "Search:", tooltip = "Search for widgets by title, author, or description", + grow = true }) }, })) @@ -134,6 +138,10 @@ function M.open_asset_store(store_url) children = content_children }), buttons = { + editor.ui.dialog_button({ + text = "Info", + result = "info_assets_store", + }), editor.ui.dialog_button({ text = "Close", cancel = true @@ -142,7 +150,13 @@ function M.open_asset_store(store_url) }) end) - return editor.ui.show_dialog(dialog_component({})) + local result = editor.ui.show_dialog(dialog_component({})) + + if result and result == "info_assets_store" then + editor.browse("https://github.com/Insality/core/blob/main/druid_widget_store.md") + end + + return {} end diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index 5197dbf..90f5960 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -5,8 +5,6 @@ local base64 = require("druid.editor_scripts.core.base64") local M = {} -local DEFAULT_INSTALL_FOLDER = "/widget" - ---@class druid.core.item_info ---@field id string ---@field version string @@ -103,7 +101,7 @@ end ---Get installation folder ---@return string - Installation folder path function M.get_install_folder() - return editor.prefs.get("druid.asset_install_folder") or DEFAULT_INSTALL_FOLDER + return editor.prefs.get("druid.asset_install_folder") end diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index 7d46880..01006fc 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -175,10 +175,6 @@ function M.create_widget_item(item, is_installed, on_install) text = version_text, color = editor.ui.COLOR.WARNING }), - editor.ui.label({ - text = "• by " .. (item.author or "Unknown"), - color = editor.ui.COLOR.HINT - }), editor.ui.label({ text = "• " .. size_text, color = editor.ui.COLOR.HINT @@ -214,11 +210,8 @@ function M.create_widget_item(item, is_installed, on_install) })) end - table.insert(widget_details_children, editor.ui.separator({ - orientation = editor.ui.ORIENTATION.HORIZONTAL, - })) - - local widget_buttons = { + -- Create button row at the bottom + local button_children = { editor.ui.button({ text = "Install", on_pressed = on_install, @@ -227,7 +220,7 @@ function M.create_widget_item(item, is_installed, on_install) } if item.api ~= nil then - table.insert(widget_buttons, editor.ui.button({ + table.insert(button_children, editor.ui.button({ text = "API", on_pressed = function() internal.open_url(item.api) end, enabled = item.api ~= nil @@ -235,28 +228,41 @@ function M.create_widget_item(item, is_installed, on_install) end if item.example_url ~= nil then - table.insert(widget_buttons, editor.ui.button({ + table.insert(button_children, editor.ui.button({ text = "Example", on_pressed = function() internal.open_url(item.example_url) end, enabled = item.example_url ~= nil })) end + -- Add spacer to push Author button to the right + table.insert(button_children, editor.ui.horizontal({ grow = true })) + if item.author_url ~= nil then - table.insert(widget_buttons, editor.ui.button({ + table.insert(button_children, editor.ui.label({ text = "Author", + color = editor.ui.COLOR.HINT + })) + table.insert(button_children, editor.ui.button({ + text = item.author or "Author", on_pressed = function() internal.open_url(item.author_url) end, enabled = item.author_url ~= nil })) end + -- Add button row to widget details + table.insert(widget_details_children, editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = button_children + })) + return editor.ui.horizontal({ spacing = editor.ui.SPACING.NONE, padding = editor.ui.PADDING.SMALL, children = { -- Widget icon placeholder editor.ui.label({ - text = "📦", + text = "•••", color = editor.ui.COLOR.HINT }), @@ -266,13 +272,6 @@ function M.create_widget_item(item, is_installed, on_install) grow = true, children = widget_details_children }), - - -- Action buttons - editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - grow = true, - children = widget_buttons - }), } }) end @@ -284,7 +283,7 @@ end ---@return userdata - UI component function M.create_widget_list(items, on_install) local widget_items = {} - local install_folder = installer.get_install_folder() + local install_folder = editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder() for _, item in ipairs(items) do local is_installed = installer.is_widget_installed(item, install_folder) diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index de7a0a1..754732f 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -8,6 +8,7 @@ local M = {} local DEFAULT_WIDGET_TEMPLATE_PATH = "/druid/templates/widget_full.lua.template" local DEFAULT_GUI_SCRIPT_TEMPLATE_PATH = "/druid/templates/druid.gui_script.template" +local DEFAULT_ASSET_INSTALL_FOLDER = "/widget" ---Define preferences schema function M.get_prefs_schema() @@ -21,7 +22,7 @@ function M.get_prefs_schema() scope = editor.prefs.SCOPE.PROJECT }), ["druid.asset_install_folder"] = editor.prefs.schema.string({ - default = "/widget", + default = DEFAULT_ASSET_INSTALL_FOLDER, scope = editor.prefs.SCOPE.PROJECT }) } From b9e2102a8c1b4668707024301ea06c32bda25cc7 Mon Sep 17 00:00:00 2001 From: Insality Date: Sun, 9 Nov 2025 18:14:07 +0200 Subject: [PATCH 10/22] Up --- druid/editor_scripts/core/base64.lua | 4 ++-- druid/editor_scripts/core/installer.lua | 4 +++- druid/editor_scripts/core/ui_components.lua | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/druid/editor_scripts/core/base64.lua b/druid/editor_scripts/core/base64.lua index 7353aa8..ebc174f 100644 --- a/druid/editor_scripts/core/base64.lua +++ b/druid/editor_scripts/core/base64.lua @@ -6,8 +6,8 @@ local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' function M.encode(data) return ((data:gsub('.', function(x) - local r,b='',x:byte() - for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + local r,byte_val='',x:byte() + for i=8,1,-1 do r=r..(byte_val%2^i-byte_val%2^(i-1)>0 and '1' or '0') end return r; end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) if (#x < 6) then return '' end diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index 90f5960..f1a1f19 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -59,7 +59,9 @@ function M.install_widget(item, install_folder) local zip_file_path = "." .. install_folder .. "/" .. filename local zip_file = io.open(zip_file_path, "wb") if not zip_file then - return false, "Failed to open zip file: " .. zip_file_path + print("Directory does not exist: " .. install_folder) + print("Please create the directory manually and try again.") + return false, "Directory does not exist: " .. install_folder end zip_file:write(zip_data) diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua index 01006fc..82ec423 100644 --- a/druid/editor_scripts/core/ui_components.lua +++ b/druid/editor_scripts/core/ui_components.lua @@ -205,7 +205,7 @@ function M.create_widget_item(item, is_installed, on_install) if is_installed then table.insert(widget_details_children, editor.ui.label({ - text = "✓ Already installed", + text = "✓ Installed", color = editor.ui.COLOR.WARNING })) end From 54bf90472788552925a4aa244f475e4f988656a3 Mon Sep 17 00:00:00 2001 From: Insality Date: Sun, 9 Nov 2025 19:16:04 +0200 Subject: [PATCH 11/22] Up --- druid/component.lua | 3 +- druid/editor_scripts/core/path_replacer.lua | 144 ++++++++++++++++++++ druid/system/druid_instance.lua | 2 +- 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 druid/editor_scripts/core/path_replacer.lua diff --git a/druid/component.lua b/druid/component.lua index 4ed835c..79ef59a 100644 --- a/druid/component.lua +++ b/druid/component.lua @@ -410,7 +410,7 @@ local WIDGET_METATABLE = { __index = M } function M.create_widget(self, widget_class, context) local instance = setmetatable({}, { __index = setmetatable(widget_class, WIDGET_METATABLE) - }) + }) --[[@as druid.widget]] instance._component = { _uid = M.create_uid(), @@ -440,7 +440,6 @@ function M.create_widget(self, widget_class, context) instance._meta.parent:__add_child(instance) end - ---@cast instance druid.widget return instance end diff --git a/druid/editor_scripts/core/path_replacer.lua b/druid/editor_scripts/core/path_replacer.lua new file mode 100644 index 0000000..53e5947 --- /dev/null +++ b/druid/editor_scripts/core/path_replacer.lua @@ -0,0 +1,144 @@ +--- Module for replacing widget paths in installed files +--- Handles path replacement from original widget structure to user's installation path + +local system = require("druid.editor_scripts.defold_parser.system.parser_internal") + +local M = {} + + +---Detect the original path structure from item data +---Builds widget/author/widget_id path +---@param author string|nil - Author name +---@param widget_id string - Widget ID +---@return string|nil - Original path prefix (e.g., "widget/Insality/fps_panel") or nil +local function detect_original_path_from_item(author, widget_id) + if not author or not widget_id then + return nil + end + + local original_path = "widget/" .. author .. "/" .. widget_id + print("Detected original path from item:", original_path) + return original_path +end + + +---Replace paths in file content +---@param content string - File content +---@param original_path string - Original path to replace (e.g., "widget/Insality/fps_panel") +---@param target_path string - Target path (e.g., "widget/fps_panel") +---@return string - Modified content +local function replace_paths_in_content(content, original_path, target_path) + if not content or not original_path or not target_path then + return content + end + + -- Escape special characters for literal string replacement + local function escape_pattern(str) + return str:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") + end + + -- Replace paths with forward slashes: widget/Insality/fps_panel -> widget/fps_panel + local escaped_original = escape_pattern(original_path) + content = content:gsub(escaped_original, target_path) + + -- Replace require statements with dots: widget.Insality.fps_panel -> widget.fps_panel + local original_dots = original_path:gsub("/", ".") + local target_dots = target_path:gsub("/", ".") + local escaped_original_dots = escape_pattern(original_dots) + content = content:gsub(escaped_original_dots, target_dots) + + return content +end + + +---Process widget paths in all files +---@param folder_path string - Path to the unpacked widget folder +---@param install_folder string - Installation folder (e.g., "widget") +---@param widget_id string - Widget ID (e.g., "fps_panel") +---@param author string|nil - Author name (e.g., "Insality") +---@param file_list table - Optional list of file paths from zip content +---@return boolean, string|nil - Success status and error message if any +function M.process_widget_paths(folder_path, install_folder, widget_id, author, file_list) + print("Processing widget paths in:", folder_path) + + -- Detect original path structure from item data + local original_path = detect_original_path_from_item(author, widget_id) + + if not original_path then + print("Warning: Could not detect original path structure (missing author or widget_id), skipping path replacement") + return true, nil + end + + -- Construct target path + -- Remove leading / from install_folder if present + local clean_install_folder = install_folder + if clean_install_folder:sub(1, 1) == "/" then + clean_install_folder = clean_install_folder:sub(2) + end + + local target_path = clean_install_folder + if widget_id then + if target_path ~= "" then + target_path = target_path .. "/" .. widget_id + else + target_path = widget_id + end + end + + print("Replacing paths from:", original_path, "to:", target_path) + + -- Get absolute project path + local absolute_project_path = editor.external_file_attributes(".").path + if not absolute_project_path:match("[\\/]$") then + absolute_project_path = absolute_project_path .. "/" + end + + -- Clean folder_path + local clean_folder_path = folder_path + if clean_folder_path:sub(1, 1) == "." then + clean_folder_path = clean_folder_path:sub(2) + end + if clean_folder_path:sub(1, 1) == "/" then + clean_folder_path = clean_folder_path:sub(2) + end + + -- Process each file from the list + local processed_count = 0 + for _, file_path_in_zip in ipairs(file_list) do + -- Build full path to the file after unpacking + local file_path = clean_folder_path .. "/" .. file_path_in_zip + + -- Get absolute path + local clean_file_path = file_path + if clean_file_path:sub(1, 1) == "/" then + clean_file_path = clean_file_path:sub(2) + end + local absolute_file_path = absolute_project_path .. clean_file_path + + -- Read file content + local content, err = system.read_file(absolute_file_path) + if not content then + print("Warning: Could not read file:", file_path, err) + else + -- Replace paths + local modified_content = replace_paths_in_content(content, original_path, target_path) + if modified_content ~= content then + -- Write modified content back + local success, write_err = system.write_file(absolute_file_path, modified_content) + if success then + processed_count = processed_count + 1 + print("Processed:", file_path) + else + print("Warning: Could not write file:", file_path, write_err) + end + end + end + end + + print("Path replacement complete. Processed", processed_count, "files") + return true, nil +end + + +return M + diff --git a/druid/system/druid_instance.lua b/druid/system/druid_instance.lua index 52c8b1b..f38b4d5 100755 --- a/druid/system/druid_instance.lua +++ b/druid/system/druid_instance.lua @@ -357,7 +357,7 @@ function M:on_input(action_id, action) for i = #components, 1, -1 do local component = components[i] - local input_enabled = component:get_input_enabled() + local input_enabled = component._meta.input_enabled if input_enabled and self:_can_use_input_component(component) then if not is_input_consumed then From 788fc66113162a3e0262090424f371d005a0000a Mon Sep 17 00:00:00 2001 From: Insality Date: Sun, 9 Nov 2025 22:28:50 +0200 Subject: [PATCH 12/22] Up --- druid/editor_scripts/core/installer.lua | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index f1a1f19..9e6ea05 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -2,6 +2,7 @@ --- Downloads zip files and extracts them to the specified folder local base64 = require("druid.editor_scripts.core.base64") +local path_replacer = require("druid.editor_scripts.core.path_replacer") local M = {} @@ -25,7 +26,7 @@ local M = {} ---Download a file from URL ---@param url string - The URL to download from ----@return string|nil, string|nil - Downloaded content or nil, filename or nil +---@return string|nil, string|nil, table|nil - Downloaded content or nil, filename or nil, content list or nil local function download_file_zip_json(url) local response = http.request(url, { as = "json" }) @@ -35,8 +36,9 @@ local function download_file_zip_json(url) end local data = response.body + local content_list = data.content -- Array of file paths from zip - return base64.decode(data.data), data.filename + return base64.decode(data.data), data.filename, content_list end @@ -50,11 +52,17 @@ function M.install_widget(item, install_folder) end -- Download the zip file - local zip_data, filename = download_file_zip_json(item.json_zip_url) + local zip_data, filename, content_list = download_file_zip_json(item.json_zip_url) if not zip_data or not filename then return false, "Failed to download widget: " .. filename end + if content_list then + print("Got file list from JSON:", #content_list, "files") + else + print("Warning: No content list in JSON data") + end + local zip_file_path = "." .. install_folder .. "/" .. filename local zip_file = io.open(zip_file_path, "wb") @@ -79,12 +87,15 @@ function M.install_widget(item, install_folder) print("Zip file removed successfully") -- Process paths within the extracted widget - --local files_in_folder = path_replacer.get_all_files(folder_path) - --pprint(files_in_folder) - - --if not path_replacer.process_widget_paths(install_folder .. "/" .. folder_name, new_base_path) then - -- return false, "Failed to process widget paths" - --end + if content_list and #content_list > 0 then + local success, err = path_replacer.process_widget_paths(folder_path, install_folder, item.id, item.author, content_list) + if not success then + print("Warning: Path replacement failed:", err) + -- Don't fail installation if path replacement fails, just warn + end + else + print("Warning: No file list available, skipping path replacement") + end return true, "Widget installed successfully" end From e130e3978949711840881ae7cde1a41d3a3d629b Mon Sep 17 00:00:00 2001 From: Insality Date: Sun, 9 Nov 2025 22:47:47 +0200 Subject: [PATCH 13/22] Up --- druid/editor_scripts/core/asset_store.lua | 126 +++++++++++++++++- .../core/asset_store_internal.lua | 104 +++++++++++++++ 2 files changed, 224 insertions(+), 6 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index 621ccb4..be492c0 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -8,6 +8,37 @@ local internal = require("druid.editor_scripts.core.asset_store_internal") local M = {} +---Build type options array +---@return table +local function build_type_options() + return {"All", "Installed", "Not Installed"} +end + + +---Build author options array +---@param authors table +---@return table +local function build_author_options(authors) + local options = {"All Authors"} + for _, author in ipairs(authors) do + table.insert(options, author) + end + return options +end + + +---Build tag options array +---@param tags table +---@return table +local function build_tag_options(tags) + local options = {"All Tags"} + for _, tag in ipairs(tags) do + table.insert(options, tag) + end + return options +end + + ---Handle widget installation ---@param item table - Widget item to install ---@param install_folder string - Installation folder @@ -44,11 +75,44 @@ function M.open_asset_store(store_url) local dialog_component = editor.ui.component(function(props) -- State management local all_items = editor.ui.use_state(initial_items) - local filtered_items, set_filtered_items = editor.ui.use_state(initial_items) local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder()) local search_query, set_search_query = editor.ui.use_state("") + local filter_type, set_filter_type = editor.ui.use_state("All") + local filter_author, set_filter_author = editor.ui.use_state("All Authors") + local filter_tag, set_filter_tag = editor.ui.use_state("All Tags") local install_status, set_install_status = editor.ui.use_state("") + -- Extract unique authors and tags for dropdown options + local authors = editor.ui.use_memo(internal.extract_authors, all_items) + local tags = editor.ui.use_memo(internal.extract_tags, all_items) + + -- Build dropdown options (memoized to avoid recreation on each render) + local type_options = editor.ui.use_memo(build_type_options) + local author_options = editor.ui.use_memo(build_author_options, authors) + local tag_options = editor.ui.use_memo(build_tag_options, tags) + + -- Debug output + if #type_options > 0 then + print("Type options count:", #type_options, "first:", type_options[1]) + end + if #author_options > 0 then + print("Author options count:", #author_options, "first:", author_options[1]) + end + if #tag_options > 0 then + print("Tag options count:", #tag_options, "first:", tag_options[1]) + end + + -- Filter items based on all filters + local filtered_items = editor.ui.use_memo( + internal.filter_items_by_filters, + all_items, + search_query, + filter_type, + filter_author, + filter_tag, + install_folder + ) + -- Installation handlers local function on_install(item) handle_install(item, install_folder, @@ -86,6 +150,58 @@ function M.open_asset_store(store_url) } })) + -- Filter dropdowns section + table.insert(content_children, editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + -- Type filter dropdown + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Type:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = filter_type, + options = type_options, + on_value_changed = set_filter_type + }) + } + }), + -- Author filter dropdown + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Author:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = filter_author, + options = author_options, + on_value_changed = set_filter_author + }) + } + }), + -- Tag filter dropdown + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = "Tag:", + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = filter_tag, + options = tag_options, + on_value_changed = set_filter_tag + }) + } + }) + } + })) + -- Search section table.insert(content_children, editor.ui.horizontal({ spacing = editor.ui.SPACING.MEDIUM, @@ -96,10 +212,7 @@ function M.open_asset_store(store_url) }), editor.ui.string_field({ value = search_query, - on_value_changed = function(new_query) - set_search_query(new_query) - set_filtered_items(internal.filter_items(all_items, new_query)) - end, + on_value_changed = set_search_query, title = "Search:", tooltip = "Search for widgets by title, author, or description", grow = true @@ -133,8 +246,9 @@ function M.open_asset_store(store_url) return editor.ui.dialog({ title = "Druid Asset Store", content = editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, + spacing = editor.ui.SPACING.MEDIUM, padding = editor.ui.PADDING.SMALL, + grow = true, children = content_children }), buttons = { diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua index 41b8569..cbefb85 100644 --- a/druid/editor_scripts/core/asset_store_internal.lua +++ b/druid/editor_scripts/core/asset_store_internal.lua @@ -1,3 +1,5 @@ +local installer = require("druid.editor_scripts.core.installer") + local M = {} ---Download a JSON file from a URL @@ -71,6 +73,108 @@ function M.filter_items(items, query) end +---Extract unique authors from items list +---@param items table - List of widget items +---@return table - Sorted list of unique authors +function M.extract_authors(items) + local authors = {} + local author_set = {} + + for _, item in ipairs(items) do + if item.author and not author_set[item.author] then + author_set[item.author] = true + table.insert(authors, item.author) + end + end + + table.sort(authors) + return authors +end + + +---Extract unique tags from items list +---@param items table - List of widget items +---@return table - Sorted list of unique tags +function M.extract_tags(items) + local tags = {} + local tag_set = {} + + for _, item in ipairs(items) do + if item.tags then + for _, tag in ipairs(item.tags) do + if not tag_set[tag] then + tag_set[tag] = true + table.insert(tags, tag) + end + end + end + end + + table.sort(tags) + return tags +end + + +---Filter items based on all filters (search, type, author, tag) +---@param items table - List of widget items +---@param search_query string - Search query +---@param filter_type string - Type filter: "All", "Installed", "Not Installed" +---@param filter_author string - Author filter: "All Authors" or specific author +---@param filter_tag string - Tag filter: "All Tags" or specific tag +---@param install_folder string - Installation folder to check installed status +---@return table - Filtered items +function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) + local filtered = items + + -- Filter by search query + if search_query and search_query ~= "" then + filtered = M.filter_items(filtered, search_query) + end + + -- Filter by type (Installed/Not Installed) + if filter_type and filter_type ~= "All" then + local type_filtered = {} + for _, item in ipairs(filtered) do + local is_installed = installer.is_widget_installed(item, install_folder) + if (filter_type == "Installed" and is_installed) or + (filter_type == "Not Installed" and not is_installed) then + table.insert(type_filtered, item) + end + end + filtered = type_filtered + end + + -- Filter by author + if filter_author and filter_author ~= "All Authors" then + local author_filtered = {} + for _, item in ipairs(filtered) do + if item.author == filter_author then + table.insert(author_filtered, item) + end + end + filtered = author_filtered + end + + -- Filter by tag + if filter_tag and filter_tag ~= "All Tags" then + local tag_filtered = {} + for _, item in ipairs(filtered) do + if item.tags then + for _, tag in ipairs(item.tags) do + if tag == filter_tag then + table.insert(tag_filtered, item) + break + end + end + end + end + filtered = tag_filtered + end + + return filtered +end + + ---Open a URL in the default browser ---@param url string - The URL to open function M.open_url(url) From 9b8ff949bf22a9e3ec7264ce899d5dfcfbcef94c Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 10 Nov 2025 22:32:11 +0200 Subject: [PATCH 14/22] up --- druid/editor_scripts/core/asset_store.lua | 7 +- druid/editor_scripts/core/installer.lua | 109 +++++++++++++++++++- druid/editor_scripts/core/path_replacer.lua | 81 ++++++--------- 3 files changed, 140 insertions(+), 57 deletions(-) diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index be492c0..cb4c5e0 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -42,12 +42,13 @@ end ---Handle widget installation ---@param item table - Widget item to install ---@param install_folder string - Installation folder +---@param all_items table - List of all widgets for dependency resolution ---@param on_success function - Success callback ---@param on_error function - Error callback -local function handle_install(item, install_folder, on_success, on_error) +local function handle_install(item, install_folder, all_items, on_success, on_error) print("Installing widget:", item.id) - local success, message = installer.install_widget(item, install_folder) + local success, message = installer.install_widget(item, install_folder, all_items) if success then print("Installation successful:", message) @@ -115,7 +116,7 @@ function M.open_asset_store(store_url) -- Installation handlers local function on_install(item) - handle_install(item, install_folder, + handle_install(item, install_folder, all_items, function(message) set_install_status("Success: " .. message) end, diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/installer.lua index 9e6ea05..e339331 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/installer.lua @@ -42,19 +42,115 @@ local function download_file_zip_json(url) end +---Find widget by dependency string (format: "author:widget_id@version" or "author@widget_id" or "widget_id") +---@param dep_string string - Dependency string +---@param all_items table - List of all available widgets +---@return table|nil - Found widget item or nil +local function find_widget_by_dependency(dep_string, all_items) + if not dep_string or not all_items then + return nil + end + + local author, widget_id + + -- Try format: "author:widget_id@version" (e.g., "Insality:mini_graph@1") + author, widget_id = dep_string:match("^([^:]+):([^@]+)@") + if not author then + -- Try format: "author@widget_id" (e.g., "insality@mini_graph") + author, widget_id = dep_string:match("^([^@]+)@(.+)$") + if not author then + -- No author specified, search by widget_id only + widget_id = dep_string + end + end + + for _, item in ipairs(all_items) do + if item.id == widget_id then + -- If author was specified, check it matches (case-insensitive) + if not author or string.lower(item.author or "") == string.lower(author) then + return item + end + end + end + + return nil +end + + +---Install widget dependencies recursively +---@param item druid.core.item_info - Widget item +---@param all_items table - List of all available widgets +---@param install_folder string - Installation folder +---@param installing_set table - Set of widget IDs currently being installed (to prevent cycles) +---@return boolean, string|nil - Success status and message +local function install_dependencies(item, all_items, install_folder, installing_set) + if not item.depends or #item.depends == 0 then + return true, nil + end + + installing_set = installing_set or {} + + for _, dep_string in ipairs(item.depends) do + local dep_item = find_widget_by_dependency(dep_string, all_items) + if not dep_item then + print("Warning: Dependency not found:", dep_string) + -- Continue with other dependencies + else + -- Check if already installed + if M.is_widget_installed(dep_item, install_folder) then + print("Dependency already installed:", dep_item.id) + else + -- Check for circular dependencies + if installing_set[dep_item.id] then + print("Warning: Circular dependency detected:", dep_item.id) + -- Continue with other dependencies + else + print("Installing dependency:", dep_item.id) + local success, err = M.install_widget(dep_item, install_folder, all_items, installing_set) + if not success then + return false, "Failed to install dependency " .. dep_item.id .. ": " .. (err or "unknown error") + end + end + end + end + end + + return true, nil +end + + ---Install a widget from a zip URL ---@param item druid.core.item_info - Widget item data containing zip_url and id ---@param install_folder string - Target folder to install to +---@param all_items table|nil - Optional list of all widgets for dependency resolution +---@param installing_set table|nil - Optional set of widget IDs currently being installed (to prevent cycles) ---@return boolean, string - Success status and message -function M.install_widget(item, install_folder) +function M.install_widget(item, install_folder, all_items, installing_set) if not item.json_zip_url or not item.id then - return false, "Invalid widget data: missing zip_url or id" + return false, "Invalid widget data: missing json_zip_url or id" + end + + -- Install dependencies first if all_items is provided + if all_items then + installing_set = installing_set or {} + if installing_set[item.id] then + return false, "Circular dependency detected: " .. item.id + end + installing_set[item.id] = true + local dep_success, dep_err = install_dependencies(item, all_items, install_folder, installing_set) + if not dep_success then + installing_set[item.id] = nil + return false, dep_err or "Failed to install dependencies" + end end -- Download the zip file local zip_data, filename, content_list = download_file_zip_json(item.json_zip_url) if not zip_data or not filename then - return false, "Failed to download widget: " .. filename + if installing_set then + installing_set[item.id] = nil + end + return false, "Failed to download widget: " .. (filename or "unknown error") end if content_list then @@ -67,6 +163,9 @@ function M.install_widget(item, install_folder) local zip_file_path = "." .. install_folder .. "/" .. filename local zip_file = io.open(zip_file_path, "wb") if not zip_file then + if installing_set then + installing_set[item.id] = nil + end print("Directory does not exist: " .. install_folder) print("Please create the directory manually and try again.") return false, "Directory does not exist: " .. install_folder @@ -97,6 +196,10 @@ function M.install_widget(item, install_folder) print("Warning: No file list available, skipping path replacement") end + if installing_set then + installing_set[item.id] = nil + end + return true, "Widget installed successfully" end diff --git a/druid/editor_scripts/core/path_replacer.lua b/druid/editor_scripts/core/path_replacer.lua index 53e5947..1d0add6 100644 --- a/druid/editor_scripts/core/path_replacer.lua +++ b/druid/editor_scripts/core/path_replacer.lua @@ -6,29 +6,15 @@ local system = require("druid.editor_scripts.defold_parser.system.parser_interna local M = {} ----Detect the original path structure from item data ----Builds widget/author/widget_id path ----@param author string|nil - Author name ----@param widget_id string - Widget ID ----@return string|nil - Original path prefix (e.g., "widget/Insality/fps_panel") or nil -local function detect_original_path_from_item(author, widget_id) - if not author or not widget_id then - return nil - end - - local original_path = "widget/" .. author .. "/" .. widget_id - print("Detected original path from item:", original_path) - return original_path -end ---Replace paths in file content ---@param content string - File content ----@param original_path string - Original path to replace (e.g., "widget/Insality/fps_panel") ----@param target_path string - Target path (e.g., "widget/fps_panel") +---@param author string - Author name (e.g., "Insality") +---@param install_folder string - Installation folder (e.g., "widget") ---@return string - Modified content -local function replace_paths_in_content(content, original_path, target_path) - if not content or not original_path or not target_path then +local function replace_paths_in_content(content, author, install_folder) + if not content or not author then return content end @@ -37,15 +23,28 @@ local function replace_paths_in_content(content, original_path, target_path) return str:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") end - -- Replace paths with forward slashes: widget/Insality/fps_panel -> widget/fps_panel - local escaped_original = escape_pattern(original_path) - content = content:gsub(escaped_original, target_path) + -- Remove leading / from install_folder if present + local clean_install_folder = install_folder + if clean_install_folder:sub(1, 1) == "/" then + clean_install_folder = clean_install_folder:sub(2) + end - -- Replace require statements with dots: widget.Insality.fps_panel -> widget.fps_panel - local original_dots = original_path:gsub("/", ".") - local target_dots = target_path:gsub("/", ".") - local escaped_original_dots = escape_pattern(original_dots) - content = content:gsub(escaped_original_dots, target_dots) + -- Replace all paths with author: widget/Insality/* -> widget/* + local author_path_pattern = escape_pattern(clean_install_folder .. "/" .. author .. "/") + local target_path_prefix = clean_install_folder .. "/" + content = content:gsub(author_path_pattern, target_path_prefix) + + -- Replace all require statements with dots: widget.Insality.* -> widget.* + local author_dots_pattern = escape_pattern(clean_install_folder .. "." .. author .. ".") + local target_dots_prefix = clean_install_folder .. "." + content = content:gsub(author_dots_pattern, target_dots_prefix) + + -- Also replace paths that start with author directly: Insality/widget -> widget + -- But only if they're in require statements or paths + local author_start_pattern = escape_pattern(author .. "/") + content = content:gsub(author_start_pattern, "") + local author_start_dots_pattern = escape_pattern(author .. ".") + content = content:gsub(author_start_dots_pattern, "") return content end @@ -61,31 +60,12 @@ end function M.process_widget_paths(folder_path, install_folder, widget_id, author, file_list) print("Processing widget paths in:", folder_path) - -- Detect original path structure from item data - local original_path = detect_original_path_from_item(author, widget_id) - - if not original_path then - print("Warning: Could not detect original path structure (missing author or widget_id), skipping path replacement") + if not author then + print("Warning: Missing author, skipping path replacement") return true, nil end - -- Construct target path - -- Remove leading / from install_folder if present - local clean_install_folder = install_folder - if clean_install_folder:sub(1, 1) == "/" then - clean_install_folder = clean_install_folder:sub(2) - end - - local target_path = clean_install_folder - if widget_id then - if target_path ~= "" then - target_path = target_path .. "/" .. widget_id - else - target_path = widget_id - end - end - - print("Replacing paths from:", original_path, "to:", target_path) + print("Replacing all paths with author:", author, "in install folder:", install_folder) -- Get absolute project path local absolute_project_path = editor.external_file_attributes(".").path @@ -120,8 +100,8 @@ function M.process_widget_paths(folder_path, install_folder, widget_id, author, if not content then print("Warning: Could not read file:", file_path, err) else - -- Replace paths - local modified_content = replace_paths_in_content(content, original_path, target_path) + -- Replace all paths with author + local modified_content = replace_paths_in_content(content, author, install_folder) if modified_content ~= content then -- Write modified content back local success, write_err = system.write_file(absolute_file_path, modified_content) @@ -141,4 +121,3 @@ end return M - From ed24fc35d507112fa8ada12847386bdd2f1a315f Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 10 Nov 2025 22:34:41 +0200 Subject: [PATCH 15/22] Remove widgets from druid repo --- druid/custom/tiling_node/gui_tiling_node.fp | 83 -- .../tiling_node/gui_tiling_node.material | 37 - druid/custom/tiling_node/gui_tiling_node.vp | 40 - druid/custom/tiling_node/tiling_node.lua | 184 --- druid/widget/fps_panel/fps_panel.gui | 222 ---- druid/widget/fps_panel/fps_panel.lua | 112 -- druid/widget/memory_panel/memory_panel.gui | 244 ---- druid/widget/memory_panel/memory_panel.lua | 100 -- druid/widget/mini_graph/mini_graph.gui | 178 --- druid/widget/mini_graph/mini_graph.lua | 176 --- .../navigation_handler/navigation_handler.lua | 450 ------- .../properties/property_button.gui | 166 --- .../properties/property_button.lua | 66 - .../properties/property_checkbox.gui | 145 --- .../properties/property_checkbox.lua | 84 -- .../properties/property_input.gui | 152 --- .../properties/property_input.lua | 57 - .../property_left_right_selector.gui | 226 ---- .../property_left_right_selector.lua | 211 ---- .../properties/property_slider.gui | 236 ---- .../properties/property_slider.lua | 126 -- .../properties/property_text.gui | 105 -- .../properties/property_text.lua | 42 - .../properties/property_vector3.gui | 507 -------- .../properties/property_vector3.lua | 75 -- .../properties_panel/properties_panel.gui | 843 ------------- .../properties_panel/properties_panel.lua | 580 --------- example/druid.gui | 1086 ----------------- .../examples/using_widgets/using_widgets.gui | 84 -- example/examples/widgets/examples_list.lua | 175 --- .../widgets/fps_panel/example_fps_panel.gui | 104 -- .../widgets/fps_panel/example_fps_panel.lua | 12 - .../memory_panel/example_memory_panel.gui | 110 -- .../memory_panel/example_memory_panel.lua | 12 - .../example_properties_panel.gui | 626 ---------- .../example_properties_panel.lua | 67 - .../tiling_node/example_tiling_node.gui | 34 - .../tiling_node/example_tiling_node.lua | 37 - .../widgets/tiling_node/pattern_0004.png | Bin 1211 -> 0 bytes .../widgets/tiling_node/tiling_texture.atlas | 3 - example/other/go_bindings/go_widget.gui | 90 -- 41 files changed, 7887 deletions(-) delete mode 100644 druid/custom/tiling_node/gui_tiling_node.fp delete mode 100644 druid/custom/tiling_node/gui_tiling_node.material delete mode 100644 druid/custom/tiling_node/gui_tiling_node.vp delete mode 100644 druid/custom/tiling_node/tiling_node.lua delete mode 100644 druid/widget/fps_panel/fps_panel.gui delete mode 100644 druid/widget/fps_panel/fps_panel.lua delete mode 100644 druid/widget/memory_panel/memory_panel.gui delete mode 100644 druid/widget/memory_panel/memory_panel.lua delete mode 100644 druid/widget/mini_graph/mini_graph.gui delete mode 100644 druid/widget/mini_graph/mini_graph.lua delete mode 100644 druid/widget/navigation_handler/navigation_handler.lua delete mode 100644 druid/widget/properties_panel/properties/property_button.gui delete mode 100644 druid/widget/properties_panel/properties/property_button.lua delete mode 100644 druid/widget/properties_panel/properties/property_checkbox.gui delete mode 100644 druid/widget/properties_panel/properties/property_checkbox.lua delete mode 100644 druid/widget/properties_panel/properties/property_input.gui delete mode 100644 druid/widget/properties_panel/properties/property_input.lua delete mode 100644 druid/widget/properties_panel/properties/property_left_right_selector.gui delete mode 100644 druid/widget/properties_panel/properties/property_left_right_selector.lua delete mode 100644 druid/widget/properties_panel/properties/property_slider.gui delete mode 100644 druid/widget/properties_panel/properties/property_slider.lua delete mode 100644 druid/widget/properties_panel/properties/property_text.gui delete mode 100644 druid/widget/properties_panel/properties/property_text.lua delete mode 100644 druid/widget/properties_panel/properties/property_vector3.gui delete mode 100644 druid/widget/properties_panel/properties/property_vector3.lua delete mode 100644 druid/widget/properties_panel/properties_panel.gui delete mode 100644 druid/widget/properties_panel/properties_panel.lua delete mode 100644 example/examples/widgets/fps_panel/example_fps_panel.gui delete mode 100644 example/examples/widgets/fps_panel/example_fps_panel.lua delete mode 100644 example/examples/widgets/memory_panel/example_memory_panel.gui delete mode 100644 example/examples/widgets/memory_panel/example_memory_panel.lua delete mode 100644 example/examples/widgets/properties_panel/example_properties_panel.gui delete mode 100644 example/examples/widgets/properties_panel/example_properties_panel.lua delete mode 100644 example/examples/widgets/tiling_node/example_tiling_node.gui delete mode 100644 example/examples/widgets/tiling_node/example_tiling_node.lua delete mode 100644 example/examples/widgets/tiling_node/pattern_0004.png delete mode 100644 example/examples/widgets/tiling_node/tiling_texture.atlas diff --git a/druid/custom/tiling_node/gui_tiling_node.fp b/druid/custom/tiling_node/gui_tiling_node.fp deleted file mode 100644 index b18e002..0000000 --- a/druid/custom/tiling_node/gui_tiling_node.fp +++ /dev/null @@ -1,83 +0,0 @@ -#version 140 - -uniform sampler2D texture_sampler; - -in vec2 var_texcoord0; -in vec4 var_color; -in vec4 var_uv; -in vec4 var_repeat; // [repeat_x, repeat_y, anchor_x, anchor_y] -in vec4 var_params; // [margin_x, margin_y, offset_x, offset_y] -in vec4 var_uv_rotated; - -out vec4 color_out; - -void main() { - vec2 pivot = var_repeat.zw; - // Margin is a value between 0 and 1 that means offset/padding from the one image to another - vec2 margin = var_params.xy; - vec2 offset = var_params.zw; - vec2 repeat = var_repeat.xy; - - // Atlas UV to local UV [0, 1] - float u = (var_texcoord0.x - var_uv.x) / (var_uv.z - var_uv.x); - float v = (var_texcoord0.y - var_uv.y) / (var_uv.w - var_uv.y); - - // Adjust local UV by the pivot point. So 0:0 will be at the pivot point of node - u = u - (0.5 + pivot.x); - v = v - (0.5 - pivot.y); - - // If rotated, swap UV - if (var_uv_rotated.y < 0.5) { - float temp = u; - u = v; - v = temp; - } - - // Adjust repeat by the margin - repeat.x = repeat.x / (1.0 + margin.x); - repeat.y = repeat.y / (1.0 + margin.y); - - // Repeat is a value between 0 and 1 that represents the number of times the texture is repeated in the atlas. - float tile_u = fract(u * repeat.x); - float tile_v = fract(v * repeat.y); - - float tile_width = 1.0 / repeat.x; - float tile_height = 1.0 / repeat.y; - - // Adjust tile UV by the pivot point. - // Not center is left top corner, need to adjust it to pivot point - tile_u = fract(tile_u + pivot.x + 0.5); - tile_v = fract(tile_v - pivot.y + 0.5); - - // Apply offset - tile_u = fract(tile_u + offset.x); - tile_v = fract(tile_v + offset.y); - - // Extend margins - margin = margin * 0.5; - tile_u = mix(0.0 - margin.x, 1.0 + margin.x, tile_u); - tile_v = mix(0.0 - margin.y, 1.0 + margin.y, tile_v); - float alpha = 0.0; - // If the tile is outside the margins, make it transparent, without IF - alpha = step(0.0, tile_u) * step(tile_u, 1.0) * step(0.0, tile_v) * step(tile_v, 1.0); - - tile_u = clamp(tile_u, 0.0, 1.0); // Keep borders in the range 0-1 - tile_v = clamp(tile_v, 0.0, 1.0); // Keep borders in the range 0-1 - - if (var_uv_rotated.y < 0.5) { - float temp = tile_u; - tile_u = tile_v; - tile_v = temp; - } - - // Remap local UV to the atlas UV - vec2 uv = vec2( - mix(var_uv.x, var_uv.z, tile_u), // Get texture coordinate from the atlas - mix(var_uv.y, var_uv.w, tile_v) // Get texture coordinate from the atlas - //mix(var_uv.x, var_uv.z, tile_u * var_uv_rotated.x + tile_v * var_uv_rotated.z), - //mix(var_uv.y, var_uv.w, 1.0 - (tile_u * var_uv_rotated.y + tile_v * var_uv_rotated.x)) - ); - - lowp vec4 tex = texture(texture_sampler, uv); - color_out = tex * var_color; -} diff --git a/druid/custom/tiling_node/gui_tiling_node.material b/druid/custom/tiling_node/gui_tiling_node.material deleted file mode 100644 index 4c0a29e..0000000 --- a/druid/custom/tiling_node/gui_tiling_node.material +++ /dev/null @@ -1,37 +0,0 @@ -name: "repeat" -tags: "gui" -vertex_program: "/druid/custom/tiling_node/gui_tiling_node.vp" -fragment_program: "/druid/custom/tiling_node/gui_tiling_node.fp" -vertex_constants { - name: "view_proj" - type: CONSTANT_TYPE_VIEWPROJ -} -vertex_constants { - name: "uv_coord" - type: CONSTANT_TYPE_USER - value { - z: 1.0 - w: 1.0 - } -} -vertex_constants { - name: "uv_repeat" - type: CONSTANT_TYPE_USER - value { - x: 1.0 - y: 1.0 - } -} -vertex_constants { - name: "params" - type: CONSTANT_TYPE_USER - value { - } -} -vertex_constants { - name: "uv_rotated" - type: CONSTANT_TYPE_USER - value { - x: 1.0 - } -} diff --git a/druid/custom/tiling_node/gui_tiling_node.vp b/druid/custom/tiling_node/gui_tiling_node.vp deleted file mode 100644 index 93146f7..0000000 --- a/druid/custom/tiling_node/gui_tiling_node.vp +++ /dev/null @@ -1,40 +0,0 @@ -#version 140 - -in mediump vec3 position; -in mediump vec2 texcoord0; -in lowp vec4 color; - -uniform vertex_inputs -{ - highp mat4 view_proj; - highp vec4 uv_coord; - highp vec4 uv_repeat; // [repeat_x, repeat_y, pivot_x, pivot_y] - vec4 uv_rotated; - vec4 params; // [margin_x, margin_y, offset_x, offset_y] -}; - -out mediump vec2 var_texcoord0; -out lowp vec4 var_color; -out highp vec4 var_uv; -out highp vec4 var_repeat; -out vec4 var_params; -out vec4 var_uv_rotated; - -void main() -{ - var_texcoord0 = texcoord0; - var_color = vec4(color.rgb * color.a, color.a); - var_uv = uv_coord; - var_repeat = uv_repeat; - var_params = params; - var_uv_rotated = uv_rotated; - - mat4 transform = mat4( - 1.0, 0, 0, 0.0, - 0, 1.0, 0, 0.0, - 0, 0, 1, 0, - 0.0, position.z, 0, 1.0 - ); - - gl_Position = view_proj * vec4(position.xyz, 1.0) * transform; -} diff --git a/druid/custom/tiling_node/tiling_node.lua b/druid/custom/tiling_node/tiling_node.lua deleted file mode 100644 index bea3cd6..0000000 --- a/druid/custom/tiling_node/tiling_node.lua +++ /dev/null @@ -1,184 +0,0 @@ -local component = require("druid.component") -local helper = require("druid.helper") -local queues = require("event.queues") - ----@class druid.tiling_node: druid.component ----@field animation table ----@field node node ----@field params vector4 ----@field time number -local M = component.create("tiling_node") - -M.PROP_SIZE_X = hash("size.x") -M.PROP_SIZE_Y = hash("size.y") -M.PROP_SCALE_X = hash("scale.x") -M.PROP_SCALE_Y = hash("scale.y") - - ----@param node node|string -function M:init(node) - self.node = self:get_node(node) - self.animation = nil - self.time = 0 - self.margin = 0 - - self.params = gui.get(self.node, "params") --[[@as vector4]] - - self.timer_no_init = timer.delay(0.1, false, function() - print("The druid.script is not found, please add it nearby to the GUI collection", msg.url()) - end) - - queues.push("druid.get_atlas_path", { - texture_name = gui.get_texture(self.node), - sender = msg.url(), - }, self.on_get_atlas_path, self) -end - - ----@param atlas_path string -function M:on_get_atlas_path(atlas_path) - timer.cancel(self.timer_no_init) - self.is_inited = self:init_tiling_animation(atlas_path) - local repeat_x, repeat_y = self:get_repeat_count_from_node() - self:start_animation(repeat_x, repeat_y) -end - - ----@param node node ----@param property string -function M:on_node_property_changed(node, property) - if not self.is_inited or node ~= self.node then - return - end - - if property == "size" or property == "scale" then - local repeat_x, repeat_y = self:get_repeat_count_from_node() - self:set_repeat(repeat_x, repeat_y) - end -end - - -function M:get_repeat_count_from_node() - if not self.is_inited then - return 1, 1 - end - local size_x = gui.get(self.node, M.PROP_SIZE_X) - local size_y = gui.get(self.node, M.PROP_SIZE_Y) - local scale_x = gui.get(self.node, M.PROP_SCALE_X) - local scale_y = gui.get(self.node, M.PROP_SCALE_Y) - - local repeat_x = (size_x / self.animation.width) / scale_x - local repeat_y = (size_y / self.animation.height) / scale_y - - return repeat_x, repeat_y -end - - ----@param atlas_path string ----@return boolean -function M:init_tiling_animation(atlas_path) - if not atlas_path then - print("No atlas path found for node", gui.get_id(self.node), gui.get_texture(self.node)) - print("Probably you should add druid.script at window collection to access resources") - return false - end - - self.animation = helper.get_animation_data_from_node(self.node, atlas_path) - return true -end - - --- Start our repeat shader work ----@param repeat_x number X factor ----@param repeat_y number Y factor -function M:start_animation(repeat_x, repeat_y) - if not self.is_inited then - return - end - - self:set_repeat(repeat_x, repeat_y) - - local node = self.node - local animation = self.animation - local frame = animation.frames[1] - gui.set(node, "uv_coord", frame.uv_coord) - - if #animation.frames > 1 and animation.fps > 0 then - animation.handle = - timer.delay(1/animation.fps, true, function(_, handle, time_elapsed) - local next_rame = animation.frames[animation.current_frame] - gui.set(node, "uv_coord", next_rame.uv_coord) - - animation.current_frame = animation.current_frame + 1 - if animation.current_frame > #animation.frames then - animation.current_frame = 1 - end - end) - end -end - - -function M:final() - local animation = self.animation - if animation.handle then - timer.cancel(animation.handle) - animation.handle = nil - end -end - - ----Update repeat factor values ----@param repeat_x number? X factor ----@param repeat_y number? Y factor -function M:set_repeat(repeat_x, repeat_y) - local animation = self.animation - animation.v.x = repeat_x or animation.v.x - animation.v.y = repeat_y or animation.v.y - - local anchor = helper.get_pivot_offset(gui.get_pivot(self.node)) - animation.v.z = anchor.x - animation.v.w = anchor.y - - gui.set(self.node, "uv_repeat", animation.v) -end - - ----@param offset_perc_x number? X offset ----@param offset_perc_y number? Y offset -function M:set_offset(offset_perc_x, offset_perc_y) - self.params.z = offset_perc_x or self.params.z - self.params.w = offset_perc_y or self.params.w - gui.set(self.node, "params", self.params) - return self -end - - ----@param margin_x number? X margin ----@param margin_y number? Y margin -function M:set_margin(margin_x, margin_y) - self.params.x = margin_x or self.params.x - self.params.y = margin_y or self.params.y - gui.set(self.node, "params", self.params) - return self -end - - ----@param scale number -function M:set_scale(scale) - local current_scale_x = gui.get(self.node, M.PROP_SCALE_X) - local current_scale_y = gui.get(self.node, M.PROP_SCALE_Y) - local current_size_x = gui.get(self.node, M.PROP_SIZE_X) - local current_size_y = gui.get(self.node, M.PROP_SIZE_Y) - - local delta_scale_x = scale / current_scale_x - local delta_scale_y = scale / current_scale_y - gui.set(self.node, M.PROP_SCALE_X, scale) - gui.set(self.node, M.PROP_SCALE_Y, scale) - gui.set(self.node, M.PROP_SIZE_X, current_size_x / delta_scale_x) - gui.set(self.node, M.PROP_SIZE_Y, current_size_y / delta_scale_y) - - return self -end - - -return M diff --git a/druid/widget/fps_panel/fps_panel.gui b/druid/widget/fps_panel/fps_panel.gui deleted file mode 100644 index 5b85967..0000000 --- a/druid/widget/fps_panel/fps_panel.gui +++ /dev/null @@ -1,222 +0,0 @@ -fonts { - name: "druid_text_regular" - font: "/druid/fonts/druid_text_regular.font" -} -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_text_bold.font" -} -textures { - name: "druid" - texture: "/druid/druid.atlas" -} -nodes { - size { - x: 200.0 - y: 140.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "mini_graph" - parent: "root" - inherit_alpha: true - template: "/druid/widget/mini_graph/mini_graph.gui" -} -nodes { - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - id: "mini_graph/root" - parent: "mini_graph" - overridden_fields: 5 - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/header" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - text: "FPS" - id: "mini_graph/text_header" - parent: "mini_graph/header" - overridden_fields: 8 - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/icon_drag" - parent: "mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/content" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - color { - x: 0.525 - y: 0.525 - z: 0.525 - } - type: TYPE_BOX - id: "mini_graph/prefab_line" - parent: "mini_graph/content" - overridden_fields: 5 - template_node_child: true -} -nodes { - color { - x: 0.957 - y: 0.608 - z: 0.608 - } - type: TYPE_BOX - id: "mini_graph/color_low" - parent: "mini_graph/content" - overridden_fields: 5 - template_node_child: true -} -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "content" - parent: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: -96.0 - y: 12.0 - } - scale { - x: 0.3 - y: 0.3 - } - size { - x: 260.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "12 FPS" - font: "druid_text_regular" - id: "text_min_fps" - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "content" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - y: 12.0 - } - scale { - x: 0.3 - y: 0.3 - } - size { - x: 260.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "60 FPS" - font: "druid_text_bold" - id: "text_fps" - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "content" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: -33.4 - y: 30.0 - } - size { - x: 3.0 - y: 8.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "line_second_1" - pivot: PIVOT_N - parent: "content" - inherit_alpha: true -} -nodes { - position { - x: 33.2 - y: 30.0 - } - size { - x: 3.0 - y: 8.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "line_second_2" - pivot: PIVOT_N - parent: "content" - inherit_alpha: true -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/fps_panel/fps_panel.lua b/druid/widget/fps_panel/fps_panel.lua deleted file mode 100644 index 3066133..0000000 --- a/druid/widget/fps_panel/fps_panel.lua +++ /dev/null @@ -1,112 +0,0 @@ --- Title: FPS Panel --- Description: Shows current FPS and graph of the last 3 seconds of performance --- Author: Insality --- Widget: fps_panel --- Depends: insality@mini_graph --- Tags: debug, system - -local helper = require("druid.helper") -local mini_graph = require("druid.widget.mini_graph.mini_graph") - ----@class druid.widget.fps_panel: druid.widget ----@field root node -local M = {} - -local TARGET_FPS = sys.get_config_int("display.update_frequency", 60) -if TARGET_FPS == 0 then - TARGET_FPS = 60 -end - - -function M:init() - self.root = self:get_node("root") - - self.delta_time = 0.1 -- in seconds - self.collect_time = 3 -- in seconds - self.collect_time_counter = 0 - self.graph_samples = self.collect_time / self.delta_time - - -- Store frame time in seconds last collect_time seconds - self.fps_samples = {} - - self.mini_graph = self.druid:new_widget(mini_graph, "mini_graph") - self.mini_graph:set_samples(self.graph_samples) -- show last 30 seconds - self.mini_graph:set_max_value(TARGET_FPS) - - do -- Set parent manually - local parent_node = self.mini_graph.content - local position = helper.get_full_position(parent_node, self.mini_graph.root) - local content = self:get_node("content") - gui.set_parent(content, self.mini_graph.content) - gui.set_position(content, -position) - end - - self.text_min_fps = self.druid:new_text("text_min_fps") - self.text_fps = self.druid:new_text("text_fps") - - self.timer_id = timer.delay(self.delta_time, true, function() - self:push_fps_value() - end) - - --self.container = self.druid:new_container(self.root) - --self.container:add_container(self.mini_graph.container) - --local container_content = self.container:add_container("content") - --container_content:add_container("text_min_fps") - --container_content:add_container("text_fps") -end - - -function M:on_remove() - timer.cancel(self.timer_id) -end - - -function M:update(dt) - if not self.previous_time then - self.previous_time = socket.gettime() - return - end - - local current_time = socket.gettime() - local delta_time = current_time - self.previous_time - self.previous_time = current_time - - self.collect_time_counter = self.collect_time_counter + delta_time - table.insert(self.fps_samples, 1, delta_time) - - while self.collect_time_counter > self.collect_time do - -- Remove last - local removed_value = table.remove(self.fps_samples) - self.collect_time_counter = self.collect_time_counter - removed_value - end -end - - -function M:push_fps_value() - if #self.fps_samples == 0 then - return - end - - local max_frame_time = 0 - local average_frame_time = 0 - local average_samples_count = self.delta_time - local average_collected = 0 - for index = 1, #self.fps_samples do - if average_frame_time < average_samples_count then - average_frame_time = average_frame_time + self.fps_samples[index] - average_collected = average_collected + 1 - end - max_frame_time = math.max(max_frame_time, self.fps_samples[index]) - end - - average_frame_time = average_frame_time / average_collected - - self.mini_graph:push_line_value(1 / average_frame_time) - - self.text_fps:set_text(tostring(math.ceil(1 / average_frame_time) .. " FPS")) - local lowest_value = math.ceil(self.mini_graph:get_lowest_value()) - self.text_min_fps:set_text(lowest_value .. " lowest") -end - - -return M diff --git a/druid/widget/memory_panel/memory_panel.gui b/druid/widget/memory_panel/memory_panel.gui deleted file mode 100644 index 1892972..0000000 --- a/druid/widget/memory_panel/memory_panel.gui +++ /dev/null @@ -1,244 +0,0 @@ -fonts { - name: "druid_text_regular" - font: "/druid/fonts/druid_text_regular.font" -} -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_text_bold.font" -} -textures { - name: "druid" - texture: "/druid/druid.atlas" -} -nodes { - size { - x: 200.0 - y: 140.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "mini_graph" - parent: "root" - inherit_alpha: true - template: "/druid/widget/mini_graph/mini_graph.gui" -} -nodes { - type: TYPE_BOX - id: "mini_graph/root" - parent: "mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/header" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - text: "Memory" - id: "mini_graph/text_header" - parent: "mini_graph/header" - overridden_fields: 8 - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/icon_drag" - parent: "mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/content" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/prefab_line" - parent: "mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/color_low" - parent: "mini_graph/content" - template_node_child: true -} -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "content" - parent: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: -96.0 - y: 12.0 - } - scale { - x: 0.3 - y: 0.3 - } - size { - x: 200.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "120.23 KB" - font: "druid_text_regular" - id: "text_max_value" - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - adjust_mode: ADJUST_MODE_STRETCH - parent: "content" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 96.0 - y: 12.0 - } - scale { - x: 0.3 - y: 0.3 - } - size { - x: 200.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "120 KB/s" - font: "druid_text_regular" - id: "text_per_second" - pivot: PIVOT_E - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - adjust_mode: ADJUST_MODE_STRETCH - parent: "content" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: -33.4 - y: 30.0 - } - size { - x: 3.0 - y: 8.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "line_second_1" - pivot: PIVOT_N - parent: "content" - inherit_alpha: true -} -nodes { - position { - x: 33.2 - y: 30.0 - } - size { - x: 3.0 - y: 8.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "line_second_2" - pivot: PIVOT_N - parent: "content" - inherit_alpha: true -} -nodes { - position { - y: 12.0 - } - scale { - x: 0.3 - y: 0.3 - } - size { - x: 200.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "120 KB" - font: "druid_text_bold" - id: "text_memory" - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "content" - 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/memory_panel/memory_panel.lua b/druid/widget/memory_panel/memory_panel.lua deleted file mode 100644 index fafdd01..0000000 --- a/druid/widget/memory_panel/memory_panel.lua +++ /dev/null @@ -1,100 +0,0 @@ -local helper = require("druid.helper") -local mini_graph = require("druid.widget.mini_graph.mini_graph") - ----@class druid.widget.memory_panel: druid.widget ----@field root node -local M = {} - - -function M:init() - self.root = self:get_node("root") - self.delta_time = 0.1 - self.samples_count = 30 - self.memory_limit = 100 - - self.mini_graph = self.druid:new_widget(mini_graph, "mini_graph") - self.mini_graph:set_samples(self.samples_count) - - -- This one is not works with scaled root - --gui.set_parent(self:get_node("content"), self.mini_graph.content, true) - - do -- Set parent manually - local parent_node = self.mini_graph.content - local position = helper.get_full_position(parent_node, self.mini_graph.root) - local content = self:get_node("content") - gui.set_parent(content, self.mini_graph.content) - gui.set_position(content, -position) - end - - self.max_value = self.druid:new_text("text_max_value") - self.text_per_second = self.druid:new_text("text_per_second") - self.text_memory = self.druid:new_text("text_memory") - - self.memory = collectgarbage("count") - self.memory_samples = {} - - self:update_text_memory() - - self.timer_id = timer.delay(self.delta_time, true, function() - self:push_next_value() - end) - - --self.container = self.druid:new_container(self.root) - --self.container:add_container(self.mini_graph.container) - --local container_content = self.container:add_container("content") - --container_content:add_container("text_max_value") - --container_content:add_container("text_per_second") -end - - -function M:on_remove() - timer.cancel(self.timer_id) -end - - -function M:set_low_memory_limit(limit) - self.memory_limit = limit -end - - -function M:push_next_value() - local memory = collectgarbage("count") - local diff = math.max(0, memory - self.memory) - self.memory = memory - self:update_text_memory() - - table.insert(self.memory_samples, diff) - if #self.memory_samples > self.samples_count then - table.remove(self.memory_samples, 1) - end - - self.mini_graph:push_line_value(diff) - - local max_value = math.max(unpack(self.memory_samples)) - max_value = math.max(max_value, self.memory_limit) -- low limit to display - self.mini_graph:set_max_value(max_value) - - local max_memory = math.ceil(self.mini_graph:get_highest_value()) - self.max_value:set_text(max_memory .. " KB") - - local last_second = 0 - local last_second_samples = math.ceil(1 / self.delta_time) - for index = #self.memory_samples - last_second_samples + 1, #self.memory_samples do - last_second = last_second + (self.memory_samples[index] or 0) - end - self.text_per_second:set_text(math.ceil(last_second) .. " KB/s") -end - - -function M:update_text_memory() - local memory = math.ceil(collectgarbage("count")) -- in KB - if memory > 1024 then - memory = memory / 1024 - self.text_memory:set_text(string.format("%.2f", memory) .. " MB") - else - self.text_memory:set_text(memory .. " KB") - end -end - - -return M diff --git a/druid/widget/mini_graph/mini_graph.gui b/druid/widget/mini_graph/mini_graph.gui deleted file mode 100644 index ed5fa6d..0000000 --- a/druid/widget/mini_graph/mini_graph.gui +++ /dev/null @@ -1,178 +0,0 @@ -fonts { - name: "druid_text_regular" - font: "/druid/fonts/druid_text_regular.font" -} -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_text_bold.font" -} -textures { - name: "druid" - texture: "/druid/druid.atlas" -} -nodes { - size { - x: 200.0 - y: 140.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/ui_circle_16" - id: "root" - adjust_mode: ADJUST_MODE_STRETCH - inherit_alpha: true - slice9 { - x: 8.0 - y: 8.0 - z: 8.0 - w: 8.0 - } -} -nodes { - position { - y: 70.0 - } - size { - x: 200.0 - y: 40.0 - } - type: TYPE_BOX - id: "header" - pivot: PIVOT_N - parent: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: -92.0 - y: -8.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 260.0 - y: 50.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Mini Graph" - font: "druid_text_bold" - id: "text_header" - pivot: PIVOT_NW - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "header" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 96.0 - y: -4.0 - } - color { - x: 0.306 - y: 0.31 - z: 0.314 - } - type: TYPE_BOX - texture: "druid/icon_drag" - id: "icon_drag" - pivot: PIVOT_NE - parent: "header" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO -} -nodes { - position { - y: -70.0 - } - size { - x: 200.0 - y: 100.0 - } - color { - x: 0.129 - y: 0.141 - z: 0.157 - } - type: TYPE_BOX - texture: "druid/ui_circle_16" - id: "content" - pivot: PIVOT_S - adjust_mode: ADJUST_MODE_STRETCH - parent: "root" - inherit_alpha: true - slice9 { - x: 8.0 - y: 8.0 - z: 8.0 - w: 8.0 - } - clipping_mode: CLIPPING_MODE_STENCIL - material: "gui_stencil" -} -nodes { - size { - x: 8.0 - y: 70.0 - } - color { - x: 0.957 - y: 0.608 - z: 0.608 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "prefab_line" - pivot: PIVOT_S - parent: "content" - inherit_alpha: true -} -nodes { - position { - x: -10.0 - y: 4.0 - } - size { - x: 8.0 - y: 8.0 - } - color { - x: 0.557 - y: 0.835 - z: 0.62 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "color_low" - parent: "content" - inherit_alpha: true -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT -materials { - name: "gui_stencil" - material: "/druid/materials/stencil/gui_stencil.material" -} diff --git a/druid/widget/mini_graph/mini_graph.lua b/druid/widget/mini_graph/mini_graph.lua deleted file mode 100644 index 10a5237..0000000 --- a/druid/widget/mini_graph/mini_graph.lua +++ /dev/null @@ -1,176 +0,0 @@ -local color = require("druid.color") -local helper = require("druid.helper") - ----Widget to display a several lines with different height in a row ----Init, set amount of samples and max value of value means that the line will be at max height ----Use `push_line_value` to add a new value to the line ----Or `set_line_value` to set a value to the line by index ----Setup colors inside template file (at minimum and maximum) ----@class druid.widget.mini_graph: druid.widget -local M = {} - -local SIZE_Y = hash("size.y") - - -function M:init() - self.root = self:get_node("root") - self.text_header = self.druid:new_text("text_header") - - self.icon_drag = self:get_node("icon_drag") - self.druid:new_drag("header", self.on_drag_widget) - self.druid:new_button("icon_drag", self.toggle_hide) - :set_style(nil) - - self.content = self:get_node("content") - self.layout = self.druid:new_layout(self.content, "horizontal") - :set_margin(0, 0) - :set_padding(0, 0, 0, 0) - - self.prefab_line = self:get_node("prefab_line") - gui.set_enabled(self.prefab_line, false) - - local node_color_low = self:get_node("color_low") - self.color_zero = gui.get_color(node_color_low) - self.color_one = gui.get_color(self.prefab_line) - gui.set_enabled(node_color_low, false) - - self.is_hidden = false - self.max_value = 1 -- in this value line will be at max height - self.lines = {} - self.values = {} - - self.container = self.druid:new_container(self.root) - local container_header = self.container:add_container("header", "stretch_x") - container_header:add_container("text_header") - container_header:add_container("icon_drag") - - self.container:add_container("content", "stretch_x") - self.default_size = self.container:get_size() -end - - -function M:on_remove() - self:clear() -end - - -function M:clear() - self.layout:clear_layout() - for index = 1, #self.lines do - gui.delete_node(self.lines[index]) - end - - self.lines = {} -end - - -function M:set_samples(samples) - self.samples = samples - self:clear() - - local line_width = self.layout:get_size().x / self.samples - for index = 1, self.samples do - local line = gui.clone(self.prefab_line) - gui.set_enabled(line, true) - gui.set(line, "size.x", line_width) - self.layout:add(line) - table.insert(self.lines, line) - end -end - - -function M:get_samples() - return self.samples -end - - ----Set normalized to control the color of the line ---- for index = 1, mini_graph:get_samples() do ---- mini_graph:set_line_value(index, math.random()) ---- end ----@param index number ----@param value number The normalized value from 0 to 1 -function M:set_line_value(index, value) - local line = self.lines[index] - if not line then - return - end - - self.values[index] = value - - local normalized = helper.clamp(value/self.max_value, 0, 1) - local target_color = color.lerp(normalized, self.color_zero, self.color_one) - gui.set_color(line, target_color) - self:set_line_height(index) -end - - ----@return number -function M:get_line_value(index) - return self.values[index] or 0 -end - - -function M:push_line_value(value) - for index = 1, self.samples - 1 do - self:set_line_value(index, self:get_line_value(index + 1)) - end - - self:set_line_value(self.samples, value) -end - - -function M:set_max_value(max_value) - if self.max_value == max_value then - return - end - - self.max_value = max_value - for index = 1, self.samples do - self:set_line_height(index) - end -end - - -function M:set_line_height(index) - local value = self.values[index] or 0 - local normalized = helper.clamp(value / self.max_value, 0, 1) - local size_y = normalized * 70 - gui.set(self.lines[index], SIZE_Y, size_y) -end - - -function M:get_lowest_value() - return math.min(unpack(self.values)) -end - - -function M:get_highest_value() - return math.max(unpack(self.values)) -end - - -function M:on_drag_widget(dx, dy) - if not gui.is_enabled(self.icon_drag) then - return - end - - local position = self.container:get_position() - self.container:set_position(position.x + dx, position.y + dy) -end - - -function M:toggle_hide() - self.is_hidden = not self.is_hidden - local hidden_size = gui.get_size(self:get_node("header")) - - local new_size = self.is_hidden and hidden_size or self.default_size - 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 - - -return M diff --git a/druid/widget/navigation_handler/navigation_handler.lua b/druid/widget/navigation_handler/navigation_handler.lua deleted file mode 100644 index 76b64b3..0000000 --- a/druid/widget/navigation_handler/navigation_handler.lua +++ /dev/null @@ -1,450 +0,0 @@ --- Title: Navigation Handler --- Description: Adds support for navigating with keyboard and gamepad. --- Author: NaakkaDev --- Widget: navigation_handler --- Tags: input --- Version: 1 - - -local event = require("event.event") -local const = require("druid.const") - - ----Widget force handling GUI navigation via keyboard/gamepad. ---- ----### Setup ----Loads the widget module: ----`local navigation_handler = require("druid.widgets.navigation_handler.navigation_handler")` ---- ----Create the new widget instance: ----`self.nav = self.druid:new_widget(navigation_handler)` ---- ----Set the first component instance (likely a button) to be selected. This is **required**. ----`self.nav:select_component(self.my_button)` ---- ---- ----### Example using the `on_select` event ----``` ----local function on_select_btn(self, new, current) ---- gui.play_flipbook(new.node, "button_selected") ---- gui.play_flipbook(current.node, "button") ----end ----``` ----With `self.nav.on_select:subscribe(on_select_btn)` ---- ---- ----### Notes ----- `on_select` event callback params: (self, component_instance, component_instance). ----- - **self** - Druid self context. ----- - **new** - The component that will be selected next. ----- - **current** - The component that is about to be de-selected. ----- Key triggers in `input.binding` should match your setup. ----- Used `action_id`'s are:' `key_up`, `key_down`, `key_left` and `key_right`. ----@class druid.widget.navigation_handler: druid.widget ----@field on_select event fun(self, component_instance, component_instance) Triggers when a new component is selected. The first component is for the newly selected and the second is for the previous component. ----@field private _weight number The value used to control of the next button diagonal finding logic strictness. ----@field private _tolerance number Determines how lenient the next button finding logic is. Set larger value for further diagonal navigation. ----@field private _select_trigger hash Select trigger for the current component. Defaults to `druid.const.ACTION_SPACE`. ----@field private _selected_triggers table Table of action_ids that can trigger the selected component. Valid only for the current button when set. ----@field private _selected_component druid.component|druid.button|druid.slider Currently selected button instance. ----@field private _deselect_directions table The valid "escape" direction of the current selection. -local M = {} - --- Components that support navigation. -local COMPONENTS = { "button", "slider" } - - ----The constructor for the navigation_handler widget. -function M:init() - self._weight = 10 - self._tolerance = 250 - self._select_trigger = const.ACTION_SPACE - self._selected_triggers = {} - self._selected_component = nil - self._deselect_directions = {} - - -- Events - self.on_select = event.create() -end - - ----@private ----@param action_id hash Action id from on_input. ----@param action table Action from on_input. ----@return boolean is_consumed True if the input was consumed. -function M:on_input(action_id, action) - -- Do nothing if the selected component is not set. - if not self._selected_component then - return - end - - -- Trigger an action with the selected component, e.g. button click. - if self:_action_id_is_trigger(action_id) and self:_selected_is_button() then - ---@type druid.button - local btn = self._selected_component - local is_consume = false - - if action.pressed then - btn.is_repeated_started = false - btn.last_pressed_time = socket.gettime() - btn.on_pressed:trigger(self:get_context(), btn, self) - btn.can_action = true - return is_consume - end - - -- While hold button, repeat rate pick from input.repeat_interval - if action.repeated then - if not btn.on_repeated_click:is_empty() and btn.can_action then - btn:_on_button_repeated_click() - return is_consume - end - end - - if action.released then - return btn:_on_button_release() and is_consume - end - - return not btn.disabled and is_consume - end - - local is_left = action_id == const.ACTION_LEFT - local is_right = action_id == const.ACTION_RIGHT - local is_up = action_id == const.ACTION_UP - local is_down = action_id == const.ACTION_DOWN - - if action.pressed then - ---@type druid.component|nil - local component = nil - - if is_up then - component = self:_find_next_button("up") - elseif is_down then - component = self:_find_next_button("down") - elseif is_left then - component = self:_find_next_button("left") - elseif is_right then - component = self:_find_next_button("right") - end - - if component ~= nil and component ~= self._selected_component then - return self:_on_new_select(component) - end - end - - -- Handle chaning slider values when pressing left or right keys. - if (action.pressed or action.repeated) - and self:_selected_is_slider() - then - local is_directional = is_left or is_right or is_up or is_down - - -- The action_id was not one of the directions so go no further. - if not is_directional then - return false - end - - ---@type druid.slider - local slider = self._selected_component - local value = slider.value - local new_value = 0.01 - local is_horizontal = slider.dist.x > 0 - local negative_value = is_left or is_down - local positive_value = is_right or is_up - - -- Reteurn if a navigation should happen instead of a value change. - if is_horizontal and (is_up or is_down) then - return false - elseif not is_horizontal and (is_left or is_right) then - return false - end - - -- Speedup when holding the button. - if action.repeated and not action.pressed then - new_value = 0.05 - end - - if negative_value then - value = value - new_value - elseif positive_value then - value = value + new_value - end - - slider:set(value) - end - - return false -end - - ----Set the given `druid.component` as selected component. ----@param component druid.component Current druid component that starts as selected. ----@return druid.widget.navigation_handler self -function M:select_component(component) - self._selected_component = component - - -- Select the component if it's a button. - if component.hover then - component.hover:set_hover(true) - end - - return self -end - - ----Sets a new weight value which affects the next button diagonal finding logic. ----@param new_value number ----@return druid.widget.navigation_handler self -function M:set_weight(new_value) - self._weight = new_value - return self -end - - ----Sets a new tolerance value. Can be useful when scale or window size changes. ----@param new_value number How far to allow misalignment on the perpendicular axis when finding the next button. ----@return druid.widget.navigation_handler self The current navigation handler instance. -function M:set_tolerance(new_value) - self._tolerance = new_value - return self -end - - ----Set input action_id name to trigger selected component by keyboard/gamepad. ----@param key hash The action_id of the input key. Example: "key_space". ----@return druid.widget.navigation_handler self The current navigation handler instance. -function M:set_select_trigger(key) - self._select_trigger = key - return self -end - - ----Get current the trigger key for currently selected component. ----@return hash _select_trigger The action_id of the input key. -function M:get_select_trigger() - return self._select_trigger -end - - ----Set the trigger keys for the selected component. Stays valid until the selected component changes. ----@param keys table|string|hash Supports multiple action_ids if the given value is a table with the action_id hashes or strings. ----@return druid.widget.navigation_handler self The current navigation handler instance. -function M:set_temporary_select_triggers(keys) - if type(keys) == "table" then - for index, value in ipairs(keys) do - if type(value) == "string" then - keys[index] = hash(value) - end - end - self._selected_triggers = keys - elseif type(keys) == "string" then - self._selected_triggers = { hash(keys) } - else - self._selected_triggers = { keys } - end - - return self -end - - ----Get the currently selected component. ----@return druid.component|nil _selected_component Selected component, which often is a `druid.button`. -function M:get_selected_component() - return self._selected_component -end - - ----Set the de-select direction for the selected button. If this is set ----then the next button can only be in that direction. ----@param dir string|table Valid directions: "up", "down", "left", "right". Can take multiple values as a table of strings. ----@return druid.widget.navigation_handler self The current navigation handler instance. -function M:set_deselect_directions(dir) - if type(dir) == "table" then - self._deselect_directions = dir - elseif type(dir) == "string" then - self._deselect_directions = { dir } - end - - return self -end - - ----Returns true if the currently selected `druid.component` is a `druid.button`. ----@private ----@return boolean -function M:_selected_is_button() - return self._selected_component._component.name == "button" -end - - ----Returns true if the currently selected `druid.component` is a `druid.slider`. ----@private ----@return boolean -function M:_selected_is_slider() - return self._selected_component._component.name == "slider" -end - - ----Find the best next button based on the direction from the currently selected button. ----@private ----@param dir string Valid directions: "top", "bottom", "left", "right". ----@return druid.component|nil -function M:_find_next_button(dir) - -- Check if the deselect direction is set and - -- the direction is different from it. - if next(self._deselect_directions) ~= nil and not M._valid_direction(self._deselect_directions, dir) then - return nil - end - - local best_component, best_score = nil, math.huge - local screen_pos = gui.get_screen_position(self._selected_component.node) - - -- Use the slider parent node instead of the pin node. - if self._selected_component._component.name == "slider" then - screen_pos = gui.get_screen_position(gui.get_parent(self._selected_component.node)) - end - - ---@type druid.component - for _, input_component in ipairs(self._meta.druid.components_interest[const.ON_INPUT]) do - -- GUI node of the component being iterated. - local node = input_component.node - - -- If it is a slider component then use its parent node instead, - -- since the pin node moves around. - if input_component._component.name == "slider" then - node = gui.get_parent(node) - end - - -- Only check components that are supported. - if input_component ~= self._selected_component and M._valid_component(input_component) then - local pos = gui.get_screen_position(node) - local dx, dy = pos.x - screen_pos.x, pos.y - screen_pos.y - local valid = false - local score = math.huge - - if dir == "right" and dx > 0 and math.abs(dy) <= self._tolerance then - valid = true - score = dx * dx + dy * dy * self._weight - elseif dir == "left" and dx < 0 and math.abs(dy) <= self._tolerance then - valid = true - score = dx * dx + dy * dy * self._weight - elseif dir == "up" and dy > 0 and math.abs(dx) <= self._tolerance then - valid = true - score = dy * dy + dx * dx * self._weight - elseif dir == "down" and dy < 0 and math.abs(dx) <= self._tolerance then - valid = true - score = dy * dy + dx * dx * self._weight - end - - if valid and score < best_score then - best_score = score - best_component = input_component - end - end - end - - return best_component -end - - ----De-select the current selected component. ----@private -function M:_deselect_current() - if self._selected_component.hover then - self._selected_component.hover:set_hover(false) - end - self._selected_component = nil - self._selected_triggers = {} - - -- The deselect direction was used so remove it. - if self._deselect_directions then - self._deselect_directions = {} - end -end - - ----Check if the supplied action_id can trigger the selected component. ----@private ----@param action_id hash ----@return boolean -function M:_action_id_is_trigger(action_id) - for _, key in ipairs(self._selected_triggers) do - if action_id == key then - return true - end - end - - return action_id == self._select_trigger -end - - ----Handle new selection. ----@private ----@param new druid.component Instance of the selected component. ----@return boolean -function M:_on_new_select(new) - ---@type druid.component - local current = self._selected_component - - -- De-select the current component. - self:_deselect_current() - self._selected_component = new - - --- BUTTON - if new._component.name == "button" then - -- Set the active button hover state. - new.hover:set_hover(true) - end - - --- SLIDER - if new._component.name == "slider" then - -- Check if the slider is horizontal, if so then - -- the next component should be above or below of this one. - if new.dist.x > 0 then - self:set_deselect_directions({ "up", "down" }) - end - - -- Check if the slider is vertical, if so then - -- the next component should be left or right of this one. - if new.dist.y > 0 then - self:set_deselect_directions({ "left, right" }) - end - end - - --- EVENT - self.on_select:trigger(self:get_context(), new, current) - - return false -end - - ----Helper method for checking if the given direction is valid. ----@private ----@param dirs table ----@param dir string ----@return boolean -function M._valid_direction(dirs, dir) - for _index, value in ipairs(dirs) do - if value == dir then - return true - end - end - return false -end - - ----Helper method for checking iterating through components. ----Returns true if the given component is in the table of valid components. ----@private ----@param input_component druid.component ----@return boolean -function M._valid_component(input_component) - local component_name = input_component._component.name - for _index, component in ipairs(COMPONENTS) do - if component_name == component then - return true - end - end - return false -end - - -return M diff --git a/druid/widget/properties_panel/properties/property_button.gui b/druid/widget/properties_panel/properties/property_button.gui deleted file mode 100644 index e2c9973..0000000 --- a/druid/widget/properties_panel/properties/property_button.gui +++ /dev/null @@ -1,166 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 350.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Button" - font: "druid_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 - } - adjust_mode: ADJUST_MODE_STRETCH - parent: "root" - layer: "druid_text_bold" - 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: -100.0 - } - size { - x: 200.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_BOX - texture: "druid/rect_round2_width2" - id: "button" - parent: "E_Anchor" - layer: "druid" - inherit_alpha: true - slice9 { - x: 5.0 - y: 5.0 - z: 5.0 - w: 5.0 - } -} -nodes { - position { - y: -20.0 - } - size { - x: 200.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: "button" - layer: "druid" - inherit_alpha: true -} -nodes { - scale { - x: 0.5 - y: 0.5 - } - size { - x: 380.0 - y: 50.0 - } - color { - x: 0.722 - y: 0.741 - z: 0.761 - } - type: TYPE_TEXT - text: "Button" - font: "druid_text_bold" - id: "text_button" - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "button" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_button.lua b/druid/widget/properties_panel/properties/property_button.lua deleted file mode 100644 index 34c21ce..0000000 --- a/druid/widget/properties_panel/properties/property_button.lua +++ /dev/null @@ -1,66 +0,0 @@ -local color = require("druid.color") - ----@class druid.widget.property_button: druid.widget ----@field root node ----@field container druid.container ----@field text_name druid.text ----@field button druid.button ----@field text_button druid.text -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", 0.3) - - self.selected = self:get_node("selected") - gui.set_alpha(self.selected, 0) - - self.button = self.druid:new_button("button", self.on_click) - self.text_button = self.druid:new_text("text_button") - - self.container = self.druid:new_container(self.root) - self.container:add_container("text_name", nil, function(_, size) - self.text_button:set_size(size) - end) - self.container:add_container("E_Anchor") -end - - -function M:on_click() - gui.set_alpha(self.selected, 1) - gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16) -end - - ----@param text string ----@return druid.widget.property_button -function M:set_text_property(text) - self.text_name:set_text(text) - return self -end - - ----@param text string ----@return druid.widget.property_button -function M:set_text_button(text) - self.text_button:set_text(text) - return self -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 - - -return M diff --git a/druid/widget/properties_panel/properties/property_checkbox.gui b/druid/widget/properties_panel/properties/property_checkbox.gui deleted file mode 100644 index 9cf5768..0000000 --- a/druid/widget/properties_panel/properties/property_checkbox.gui +++ /dev/null @@ -1,145 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 700.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Checkbox" - font: "druid_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" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 200.0 - } - size { - x: 40.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: -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" - parent: "E_Anchor" - layer: "druid" - 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/ui_circle_16" - id: "icon" - parent: "button" - layer: "druid" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO -} -nodes { - position { - y: -20.0 - } - size { - x: 40.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: "button" - layer: "druid" - inherit_alpha: true -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_checkbox.lua b/druid/widget/properties_panel/properties/property_checkbox.lua deleted file mode 100644 index e091e8f..0000000 --- a/druid/widget/properties_panel/properties/property_checkbox.lua +++ /dev/null @@ -1,84 +0,0 @@ -local event = require("event.event") - ----@class druid.widget.property_checkbox: druid.widget ----@field root node ----@field druid druid.instance ----@field text_name druid.text ----@field button druid.button ----@field selected node -local M = {} - - -function M:init() - self.root = self:get_node("root") - - self.icon = self:get_node("icon") - gui.set_enabled(self.icon, false) - - self.selected = self:get_node("selected") - gui.set_alpha(self.selected, 0) - - self.text_name = self.druid:new_text("text_name") - :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 - - ----@param value boolean -function M:set_value(value, is_instant) - if self._value == value then - return - end - - 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) - gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16) - end -end - - ----@return boolean -function M:get_value() - return self._value -end - - -function M:on_click() - self:set_value(not self:get_value()) -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 - - ----Set the enabled state of the checkbox ----@param enabled boolean -function M:set_enabled(enabled) - self.button:set_enabled(enabled) - gui.set_alpha(self.button.node, enabled and 1 or 0.75) -end - - -return M diff --git a/druid/widget/properties_panel/properties/property_input.gui b/druid/widget/properties_panel/properties/property_input.gui deleted file mode 100644 index bb599c9..0000000 --- a/druid/widget/properties_panel/properties/property_input.gui +++ /dev/null @@ -1,152 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 350.0 - y: 50.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Input" - font: "druid_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" - layer: "druid_text_bold" - 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: -100.0 - } - type: TYPE_TEMPLATE - id: "rich_input" - parent: "E_Anchor" - inherit_alpha: true - template: "/druid/custom/rich_input/rich_input.gui" -} -nodes { - type: TYPE_BOX - id: "rich_input/root" - parent: "rich_input" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "rich_input/button" - parent: "rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input/placeholder_text" - parent: "rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input/input_text" - parent: "rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "rich_input/cursor_node" - parent: "rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input/cursor_text" - parent: "rich_input/cursor_node" - template_node_child: true -} -nodes { - position { - x: -100.0 - y: -20.0 - } - size { - x: 200.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" - layer: "druid" - inherit_alpha: true -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_input.lua b/druid/widget/properties_panel/properties/property_input.lua deleted file mode 100644 index 76d1e96..0000000 --- a/druid/widget/properties_panel/properties/property_input.lua +++ /dev/null @@ -1,57 +0,0 @@ -local event = require("event.event") - ----@class druid.widget.property_input: druid.widget ----@field root node ----@field container druid.container ----@field text_name druid.text ----@field button druid.button ----@field druid druid.instance ----@field on_change_value event fun(text: string) -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", 0.3) - - self.selected = self:get_node("selected") - gui.set_alpha(self.selected, 0) - - self.rich_input = self.druid:new_rich_input("rich_input") - - 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() - - self.rich_input.input.on_input_unselect:subscribe(function(_, text) - self.on_change_value:trigger(text) - end) -end - - ----@param text string ----@return druid.widget.property_input -function M:set_text_property(text) - self.text_name:set_text(text) - return self -end - - ----@param text string|number ----@return druid.widget.property_input -function M:set_text_value(text) - self.rich_input:set_text(tostring(text)) - return self -end - - ----@param callback fun(self: druid.widget.property_input, text: string) ----@param callback_context any -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.gui b/druid/widget/properties_panel/properties/property_left_right_selector.gui deleted file mode 100644 index 19c6a3d..0000000 --- a/druid/widget/properties_panel/properties/property_left_right_selector.gui +++ /dev/null @@ -1,226 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - 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: "druid_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" - layer: "druid_text_bold" - 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" - layer: "druid" - 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" - layer: "druid" - 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" - layer: "druid" - 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" - layer: "druid" - 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" - layer: "druid" - 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: "druid_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" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -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 deleted file mode 100644 index 2f3dd5d..0000000 --- a/druid/widget/properties_panel/properties/property_left_right_selector.lua +++ /dev/null @@ -1,211 +0,0 @@ -local event = require("event.event") - ----@class druid.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 ----@field on_change_value event fun(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") - :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) - self.button_left.on_repeated_click:subscribe(self.on_button_left, self) - - self.button_right = self.druid:new_button("button_right", self.on_button_right) - self.button_right.on_repeated_click:subscribe(self.on_button_right, self) - - 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_step(-1) -end - -function M:on_button_right() - self:add_step(1) -end - - ----@param koef number -1 0 1, on 0 will not move -function M:add_step(koef) - 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.gui b/druid/widget/properties_panel/properties/property_slider.gui deleted file mode 100644 index 22b5b3b..0000000 --- a/druid/widget/properties_panel/properties/property_slider.gui +++ /dev/null @@ -1,236 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 380.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Slider" - font: "druid_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" - layer: "druid_text_bold" - 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 - adjust_mode: ADJUST_MODE_STRETCH - parent: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: -133.0 - } - size { - x: 134.0 - y: 40.0 - } - color { - x: 0.129 - y: 0.141 - z: 0.157 - } - type: TYPE_BOX - texture: "druid/empty" - id: "slider" - parent: "E_Anchor" - layer: "druid" - inherit_alpha: true -} -nodes { - size { - x: 134.0 - y: 8.0 - } - color { - x: 0.129 - y: 0.141 - z: 0.157 - } - type: TYPE_BOX - texture: "druid/ui_circle_8" - id: "slider_back" - parent: "slider" - layer: "druid" - inherit_alpha: true - slice9 { - x: 4.0 - y: 4.0 - z: 4.0 - w: 4.0 - } -} -nodes { - position { - x: -55.0 - } - size { - x: 24.0 - y: 24.0 - } - color { - x: 0.722 - y: 0.741 - z: 0.761 - } - type: TYPE_BOX - texture: "druid/ui_circle_8" - id: "slider_pin" - parent: "slider" - layer: "druid" - inherit_alpha: true - slice9 { - x: 4.0 - y: 4.0 - z: 4.0 - w: 4.0 - } -} -nodes { - size { - x: 60.0 - y: 40.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_BOX - texture: "druid/rect_round2_width2" - id: "button" - pivot: PIVOT_E - parent: "E_Anchor" - layer: "druid" - inherit_alpha: true - slice9 { - x: 4.0 - y: 4.0 - z: 4.0 - w: 4.0 - } -} -nodes { - position { - y: -20.0 - } - size { - x: 60.0 - y: 4.0 - } - color { - x: 0.894 - y: 0.506 - z: 0.333 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "selected" - pivot: PIVOT_SE - adjust_mode: ADJUST_MODE_STRETCH - parent: "button" - layer: "druid" - inherit_alpha: true -} -nodes { - position { - x: -30.0 - } - scale { - x: 0.55 - y: 0.55 - } - size { - x: 100.0 - y: 40.0 - } - color { - x: 0.722 - y: 0.741 - z: 0.761 - } - type: TYPE_TEXT - text: "25 %" - font: "druid_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: "button" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_slider.lua b/druid/widget/properties_panel/properties/property_slider.lua deleted file mode 100644 index 38114b6..0000000 --- a/druid/widget/properties_panel/properties/property_slider.lua +++ /dev/null @@ -1,126 +0,0 @@ -local event = require("event.event") -local helper = require("druid.helper") - ----@class druid.widget.property_slider: druid.widget ----@field root node ----@field container druid.container ----@field druid druid.instance ----@field text_name druid.text ----@field text_value druid.text ----@field slider druid.slider ----@field on_change_value event fun(value: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._value = 0 - - self.min = 0 - self.max = 1 - self.step = 0.01 - - self.text_name = self.druid:new_text("text_name") - :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]] - self.slider:set_input_node("slider") - - self:set_text_function(function(value) - return math.floor(value * 100) .. "%" - end) - - 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 - - ----@param callback fun(value:number):string -function M:set_text_function(callback) - self._text_function = callback - self.text_value:set_text(self._text_function(self._value)) -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) - self.slider:set((value - self.min) / diff, true) - - local is_changed = self._value ~= value - if not is_changed then - return - end - - self._value = value - self.text_value:set_text(self._text_function(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 number -function M:get_value() - return self._value -end - - -function M:update_value(value) - local current_value = self._value - - local diff = math.abs(self.max - self.min) - -- [0..1] To range - value = value * diff + self.min - - -- Round to steps value (0.1, or 5. Should be divided on this value) - value = math.floor(value / self.step + 0.5) * self.step - - value = helper.clamp(value, self.min, self.max) - - self:set_value(value) -end - - -function M:set_number_type(min, max, step) - self.min = min or 0 - self.max = max or 1 - self.step = step - - self:set_text_function(function(value) - return tostring(value) - end) - - self:set_value(self._value, true) -end - - -function M:_on_slider_change_by_user(value) - self:set_value(value) -end - - -return M diff --git a/druid/widget/properties_panel/properties/property_text.gui b/druid/widget/properties_panel/properties/property_text.gui deleted file mode 100644 index f1a8937..0000000 --- a/druid/widget/properties_panel/properties/property_text.gui +++ /dev/null @@ -1,105 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 400.0 - y: 50.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Text" - font: "druid_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" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 350.0 - y: 50.0 - } - color { - x: 0.722 - y: 0.741 - z: 0.761 - } - type: TYPE_TEXT - text: "Text" - font: "druid_text_bold" - id: "text_right" - pivot: PIVOT_E - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "root" - layer: "druid_text_bold" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_text.lua b/druid/widget/properties_panel/properties/property_text.lua deleted file mode 100644 index ef2ed48..0000000 --- a/druid/widget/properties_panel/properties/property_text.lua +++ /dev/null @@ -1,42 +0,0 @@ ----@class druid.widget.property_text: druid.widget ----@field root node ----@field container druid.container ----@field text_name druid.text ----@field text_right druid.text -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) - - self.text_right = self.druid:new_text("text_right", "") - --:set_text_adjust("scale_then_trim_left", 0.3) -- TODO: not works? why? - - self.container = self.druid:new_container(self.root) - self.container:add_container("text_name", nil, function(_, size) - self.text_name:set_size(size) - end) - self.container:add_container("text_right", nil, function(_, size) - self.text_right:set_size(size) - end) -end - - ----@param text string ----@return druid.widget.property_text -function M:set_text_property(text) - self.text_name:set_text(text) - return self -end - - ----@param text string|nil ----@return druid.widget.property_text -function M:set_text_value(text) - self.text_right:set_text(text or "") - return self -end - - -return M diff --git a/druid/widget/properties_panel/properties/property_vector3.gui b/druid/widget/properties_panel/properties/property_vector3.gui deleted file mode 100644 index f5091e6..0000000 --- a/druid/widget/properties_panel/properties/property_vector3.gui +++ /dev/null @@ -1,507 +0,0 @@ -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_text_bold.font" -} -fonts { - name: "druid_text_regular" - font: "/druid/fonts/druid_text_regular.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 - layer: "druid" - inherit_alpha: true - visible: false -} -nodes { - position { - x: -200.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 350.0 - y: 50.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Vector3" - font: "druid_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" - layer: "druid_text_bold" - 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: -200.0 - } - size { - x: 66.0 - y: 40.0 - } - type: TYPE_BOX - id: "field_x" - pivot: PIVOT_W - parent: "E_Anchor" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: 7.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 30.0 - y: 40.0 - } - color { - x: 0.31 - y: 0.318 - z: 0.322 - } - type: TYPE_TEXT - text: "X" - font: "druid_text_regular" - id: "text_x" - parent: "field_x" - layer: "druid_text_regular" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 40.0 - } - type: TYPE_TEMPLATE - id: "rich_input_x" - parent: "field_x" - inherit_alpha: true - template: "/druid/custom/rich_input/rich_input.gui" -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_x/root" - parent: "rich_input_x" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_x/button" - parent: "rich_input_x/root" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - id: "rich_input_x/placeholder_text" - parent: "rich_input_x/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - text: "20" - id: "rich_input_x/input_text" - parent: "rich_input_x/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - position { - x: 18.0 - } - type: TYPE_BOX - id: "rich_input_x/cursor_node" - parent: "rich_input_x/root" - overridden_fields: 1 - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input_x/cursor_text" - parent: "rich_input_x/cursor_node" - template_node_child: true -} -nodes { - position { - x: 40.0 - y: -20.0 - } - size { - x: 50.0 - y: 4.0 - } - color { - x: 0.894 - y: 0.506 - z: 0.333 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "selected_x" - pivot: PIVOT_S - adjust_mode: ADJUST_MODE_STRETCH - parent: "field_x" - layer: "druid" - inherit_alpha: true -} -nodes { - position { - x: -132.0 - } - size { - x: 66.0 - y: 40.0 - } - type: TYPE_BOX - id: "field_y" - pivot: PIVOT_W - parent: "E_Anchor" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: 7.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 30.0 - y: 40.0 - } - color { - x: 0.31 - y: 0.318 - z: 0.322 - } - type: TYPE_TEXT - text: "Y" - font: "druid_text_regular" - id: "text_y" - parent: "field_y" - layer: "druid_text_regular" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 40.0 - } - type: TYPE_TEMPLATE - id: "rich_input_y" - parent: "field_y" - inherit_alpha: true - template: "/druid/custom/rich_input/rich_input.gui" -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_y/root" - parent: "rich_input_y" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_y/button" - parent: "rich_input_y/root" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - id: "rich_input_y/placeholder_text" - parent: "rich_input_y/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - text: "20" - id: "rich_input_y/input_text" - parent: "rich_input_y/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - position { - x: 18.0 - } - type: TYPE_BOX - id: "rich_input_y/cursor_node" - parent: "rich_input_y/root" - overridden_fields: 1 - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input_y/cursor_text" - parent: "rich_input_y/cursor_node" - template_node_child: true -} -nodes { - position { - x: 40.0 - y: -20.0 - } - size { - x: 50.0 - y: 4.0 - } - color { - x: 0.894 - y: 0.506 - z: 0.333 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "selected_y" - pivot: PIVOT_S - adjust_mode: ADJUST_MODE_STRETCH - parent: "field_y" - layer: "druid" - inherit_alpha: true -} -nodes { - position { - x: -66.0 - } - size { - x: 66.0 - y: 40.0 - } - type: TYPE_BOX - id: "field_z" - pivot: PIVOT_W - parent: "E_Anchor" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: 7.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 30.0 - y: 40.0 - } - color { - x: 0.31 - y: 0.318 - z: 0.322 - } - type: TYPE_TEXT - text: "Z" - font: "druid_text_regular" - id: "text_z" - parent: "field_z" - layer: "druid_text_regular" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: 40.0 - } - type: TYPE_TEMPLATE - id: "rich_input_z" - parent: "field_z" - inherit_alpha: true - template: "/druid/custom/rich_input/rich_input.gui" -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_z/root" - parent: "rich_input_z" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 50.0 - y: 40.0 - } - type: TYPE_BOX - id: "rich_input_z/button" - parent: "rich_input_z/root" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - id: "rich_input_z/placeholder_text" - parent: "rich_input_z/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - size { - x: 70.0 - y: 50.0 - } - type: TYPE_TEXT - text: "20" - id: "rich_input_z/input_text" - parent: "rich_input_z/root" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -nodes { - position { - x: 18.0 - } - type: TYPE_BOX - id: "rich_input_z/cursor_node" - parent: "rich_input_z/root" - overridden_fields: 1 - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "rich_input_z/cursor_text" - parent: "rich_input_z/cursor_node" - template_node_child: true -} -nodes { - position { - x: 40.0 - y: -20.0 - } - size { - x: 50.0 - y: 4.0 - } - color { - x: 0.894 - y: 0.506 - z: 0.333 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "selected_z" - pivot: PIVOT_S - adjust_mode: ADJUST_MODE_STRETCH - parent: "field_z" - layer: "druid" - inherit_alpha: true -} -layers { - name: "druid" -} -layers { - name: "druid_text_bold" -} -layers { - name: "druid_text_regular" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/druid/widget/properties_panel/properties/property_vector3.lua b/druid/widget/properties_panel/properties/property_vector3.lua deleted file mode 100644 index 820ccbd..0000000 --- a/druid/widget/properties_panel/properties/property_vector3.lua +++ /dev/null @@ -1,75 +0,0 @@ -local event = require("event.event") - - ----@class druid.widget.property_vector3: druid.widget ----@field root node ----@field container druid.container ----@field text_name druid.text ----@field button druid.button ----@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", 0.3) - - self.selected_x = self:get_node("selected_x") - gui.set_alpha(self.selected_x, 0) - - self.selected_y = self:get_node("selected_y") - gui.set_alpha(self.selected_y, 0) - - self.selected_z = self:get_node("selected_z") - gui.set_alpha(self.selected_z, 0) - - self.rich_input_x = self.druid:new_rich_input("rich_input_x") - self.rich_input_y = self.druid:new_rich_input("rich_input_y") - self.rich_input_z = self.druid:new_rich_input("rich_input_z") - - self.value = vmath.vector3(0) - - self.rich_input_x.input.on_input_unselect:subscribe(function() - self.value.x = tonumber(self.rich_input_x.input:get_text()) or 0 - self.on_change:trigger(self.value) - end) - - self.rich_input_y.input.on_input_unselect:subscribe(function() - self.value.y = tonumber(self.rich_input_y.input:get_text()) or 0 - self.on_change:trigger(self.value) - end) - - self.rich_input_z.input.on_input_unselect:subscribe(function() - self.value.z = tonumber(self.rich_input_z.input:get_text()) or 0 - self.on_change:trigger(self.value) - end) - - self.container = self.druid:new_container(self.root) - self.container:add_container("text_name") - self.container:add_container("E_Anchor") - - self.on_change = event.create() -end - - ----@param text string ----@return druid.widget.property_vector3 -function M:set_text_property(text) - self.text_name:set_text(text) - return self -end - - ----@param x number ----@param y number ----@param z number ----@return druid.widget.property_vector3 -function M:set_value(x, y, z) - self.rich_input_x:set_text(tostring(x)) - self.rich_input_y:set_text(tostring(y)) - self.rich_input_z:set_text(tostring(z)) - return self -end - - -return M diff --git a/druid/widget/properties_panel/properties_panel.gui b/druid/widget/properties_panel/properties_panel.gui deleted file mode 100644 index 9f801af..0000000 --- a/druid/widget/properties_panel/properties_panel.gui +++ /dev/null @@ -1,843 +0,0 @@ -fonts { - name: "druid_text_regular" - font: "/druid/fonts/druid_text_regular.font" -} -fonts { - name: "druid_text_bold" - font: "/druid/fonts/druid_text_bold.font" -} -textures { - name: "druid" - texture: "/druid/druid.atlas" -} -nodes { - size { - x: 400.0 - y: 400.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/ui_circle_16" - id: "root" - pivot: PIVOT_N - layer: "druid" - inherit_alpha: true - slice9 { - x: 8.0 - y: 8.0 - z: 8.0 - w: 8.0 - } -} -nodes { - position { - x: 200.0 - } - size { - x: 400.0 - y: 40.0 - } - color { - x: 0.173 - y: 0.184 - z: 0.204 - } - type: TYPE_BOX - texture: "druid/ui_circle_16" - id: "header" - pivot: PIVOT_NE - parent: "root" - layer: "druid" - inherit_alpha: true - slice9 { - x: 8.0 - y: 8.0 - z: 8.0 - w: 8.0 - } -} -nodes { - position { - x: -392.0 - y: -8.0 - } - scale { - x: 0.5 - y: 0.5 - } - size { - x: 500.0 - y: 50.0 - } - color { - x: 0.463 - y: 0.475 - z: 0.49 - } - type: TYPE_TEXT - text: "Properties" - font: "druid_text_regular" - id: "text_header" - pivot: PIVOT_NW - outline { - x: 1.0 - y: 1.0 - z: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - } - parent: "header" - layer: "druid_text_regular" - inherit_alpha: true - outline_alpha: 0.0 - shadow_alpha: 0.0 -} -nodes { - position { - x: -8.0 - y: -4.0 - } - color { - x: 0.306 - y: 0.31 - z: 0.314 - } - type: TYPE_BOX - texture: "druid/icon_drag" - id: "icon_drag" - pivot: PIVOT_NE - parent: "header" - layer: "druid" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO -} -nodes { - position { - x: -48.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" - layer: "druid" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO -} -nodes { - position { - x: -88.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" - layer: "druid" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO -} -nodes { - position { - y: -50.0 - } - size { - x: 400.0 - y: 350.0 - } - type: TYPE_BOX - id: "content" - pivot: PIVOT_N - parent: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - position { - x: -200.0 - } - size { - x: 400.0 - y: 350.0 - } - type: TYPE_BOX - texture: "druid/empty" - id: "scroll_view" - xanchor: XANCHOR_LEFT - pivot: PIVOT_NW - adjust_mode: ADJUST_MODE_STRETCH - parent: "content" - layer: "druid" - inherit_alpha: true -} -nodes { - size { - x: 400.0 - y: 350.0 - } - type: TYPE_BOX - texture: "druid/pixel" - id: "scroll_content" - pivot: PIVOT_NW - adjust_mode: ADJUST_MODE_STRETCH - parent: "scroll_view" - layer: "druid" - inherit_alpha: true - slice9 { - x: 8.0 - y: 8.0 - w: 6.0 - } - visible: false -} -nodes { - position { - y: -10.0 - } - type: TYPE_BOX - texture: "druid/empty" - id: "propeties" - parent: "content" - layer: "druid" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "property_slider" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_slider.gui" -} -nodes { - type: TYPE_BOX - id: "property_slider/root" - parent: "property_slider" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_slider/text_name" - parent: "property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/E_Anchor" - parent: "property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider" - parent: "property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider_back" - parent: "property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider_pin" - parent: "property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/button" - parent: "property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/selected" - parent: "property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_slider/text_value" - parent: "property_slider/button" - template_node_child: true -} -nodes { - position { - y: -50.0 - } - type: TYPE_TEMPLATE - id: "property_checkbox" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_checkbox.gui" -} -nodes { - type: TYPE_BOX - id: "property_checkbox/root" - parent: "property_checkbox" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_checkbox/text_name" - parent: "property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/E_Anchor" - parent: "property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/button" - parent: "property_checkbox/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/icon" - parent: "property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/selected" - parent: "property_checkbox/button" - template_node_child: true -} -nodes { - position { - y: -100.0 - } - type: TYPE_TEMPLATE - id: "property_button" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_button.gui" -} -nodes { - type: TYPE_BOX - id: "property_button/root" - parent: "property_button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_button/text_name" - parent: "property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_button/E_Anchor" - parent: "property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_button/button" - parent: "property_button/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_button/selected" - parent: "property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_button/text_button" - parent: "property_button/button" - template_node_child: true -} -nodes { - position { - y: -150.0 - } - type: TYPE_TEMPLATE - id: "property_input" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_input.gui" -} -nodes { - type: TYPE_BOX - id: "property_input/root" - parent: "property_input" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/text_name" - parent: "property_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/E_Anchor" - parent: "property_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_input/rich_input" - parent: "property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/root" - parent: "property_input/rich_input" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/button" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/placeholder_text" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/input_text" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/cursor_node" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/cursor_text" - parent: "property_input/rich_input/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/selected" - parent: "property_input/E_Anchor" - template_node_child: true -} -nodes { - position { - y: -200.0 - } - type: TYPE_TEMPLATE - id: "property_text" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_text.gui" -} -nodes { - type: TYPE_BOX - id: "property_text/root" - parent: "property_text" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_text/text_name" - parent: "property_text/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_text/text_right" - 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 -} -nodes { - position { - y: -300.0 - } - type: TYPE_TEMPLATE - id: "property_vector3" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_vector3.gui" -} -nodes { - type: TYPE_BOX - id: "property_vector3/root" - parent: "property_vector3" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/text_name" - parent: "property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/E_Anchor" - parent: "property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/field_x" - parent: "property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/text_x" - parent: "property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_vector3/rich_input_x" - parent: "property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_x/root" - parent: "property_vector3/rich_input_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_x/button" - parent: "property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_x/placeholder_text" - parent: "property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_x/input_text" - parent: "property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_x/cursor_node" - parent: "property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_x/cursor_text" - parent: "property_vector3/rich_input_x/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/selected_x" - parent: "property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/field_y" - parent: "property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/text_y" - parent: "property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_vector3/rich_input_y" - parent: "property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_y/root" - parent: "property_vector3/rich_input_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_y/button" - parent: "property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_y/placeholder_text" - parent: "property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_y/input_text" - parent: "property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_y/cursor_node" - parent: "property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_y/cursor_text" - parent: "property_vector3/rich_input_y/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/selected_y" - parent: "property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/field_z" - parent: "property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/text_z" - parent: "property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_vector3/rich_input_z" - parent: "property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_z/root" - parent: "property_vector3/rich_input_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_z/button" - parent: "property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_z/placeholder_text" - parent: "property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_z/input_text" - parent: "property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/rich_input_z/cursor_node" - parent: "property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_vector3/rich_input_z/cursor_text" - parent: "property_vector3/rich_input_z/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_vector3/selected_z" - parent: "property_vector3/field_z" - template_node_child: true -} -nodes { - position { - y: -100.0 - } - type: TYPE_TEMPLATE - id: "property_button_small" - parent: "propeties" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_button.gui" - enabled: false -} -nodes { - type: TYPE_BOX - id: "property_button_small/root" - parent: "property_button_small" - template_node_child: true -} -nodes { - size { - x: 700.0 - y: 40.0 - } - type: TYPE_TEXT - id: "property_button_small/text_name" - parent: "property_button_small/root" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 40.0 - y: 40.0 - } - type: TYPE_BOX - id: "property_button_small/E_Anchor" - parent: "property_button_small/root" - overridden_fields: 4 - template_node_child: true -} -nodes { - position { - x: -20.0 - } - size { - x: 40.0 - y: 40.0 - } - type: TYPE_BOX - id: "property_button_small/button" - parent: "property_button_small/E_Anchor" - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 40.0 - y: 4.0 - } - type: TYPE_BOX - id: "property_button_small/selected" - parent: "property_button_small/button" - overridden_fields: 4 - template_node_child: true -} -nodes { - size { - x: 40.0 - y: 50.0 - } - type: TYPE_TEXT - text: ">" - id: "property_button_small/text_button" - parent: "property_button_small/button" - overridden_fields: 4 - overridden_fields: 8 - template_node_child: true -} -layers { - name: "druid" -} -layers { - name: "druid_text_regular" -} -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 deleted file mode 100644 index 25ff9a1..0000000 --- a/druid/widget/properties_panel/properties_panel.lua +++ /dev/null @@ -1,580 +0,0 @@ -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") -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") -local property_vector3 = require("druid.widget.properties_panel.properties.property_vector3") - ----@class druid.widget.properties_panel: druid.widget ----@field root node ----@field scroll druid.scroll ----@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 button_hidden druid.button ----@field text_header druid.text ----@field paginator druid.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 = {} - -local COLOR_BUTTON = "#4E4F50" -local COLOR_REFRESH_ACTIVE = "#8BD092" - -function M:init() - self.root = self:get_node("root") - self.scale_root = gui.get_scale(self.root) - self.content = self:get_node("content") - - self.container = self.druid:new_container(self.root) - self.container:add_container("header") - self.container_content = self.container:add_container("content") - self.container_scroll_view = self.container_content:add_container("scroll_view") - self.contaienr_scroll_content = self.container_scroll_view:add_container("scroll_content") - - self.default_size = self.container:get_size() - self.header_size = gui.get_size(self:get_node("header")) - - -- 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 - self.properties_per_page = 15 - - self.text_header = self.druid:new_text("text_header") - self.scroll = self.druid:new_scroll("scroll_view", "scroll_content") - self.layout = self.druid:new_layout("scroll_content", "vertical") - :set_hug_content(false, true) - :set_padding(nil, 0) - - self.layout.on_size_changed:subscribe(self.on_size_changed, self) - - self.druid:new_drag("header", self.on_drag_widget) - self.button_hidden = self.druid:new_button("icon_drag", function() - 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") - 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) - - gui.set_enabled(self.paginator.root, false) - - gui.set_enabled(self:get_node("property_checkbox/root"), false) - gui.set_enabled(self:get_node("property_slider/root"), false) - gui.set_enabled(self:get_node("property_button/root"), false) - gui.set_enabled(self:get_node("property_button_small/root"), false) - gui.set_enabled(self:get_node("property_input/root"), false) - gui.set_enabled(self:get_node("property_text/root"), false) - gui.set_enabled(self:get_node("property_left_right_selector/root"), false) - gui.set_enabled(self:get_node("property_vector3/root"), false) -end - - -function M:on_remove() - self:clear() -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 * self.scale_root.x, position.y + dy * self.scale_root.y) -end - - -function M:clear_created_properties() - for index = 1, #self.properties do - local property = self.properties[index] - local root = property.root --[[@as node]] - - if root then - -- If prefab used clone nodes we can remove it - if property:get_nodes() then - gui.delete_node(root) - else - -- Probably we have component placed on scene directly - gui.set_enabled(root, false) - end - end - - self.druid:remove(self.properties[index]) - end - self.properties = {} - - self.layout:clear_layout() - - -- Use paginator as "pinned" widget - self.layout:add(self.paginator.root) -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 = {} - self.current_page = 1 -end - - -function M:on_size_changed(new_size) - self.container_content:set_size(new_size.x, new_size.y, gui.PIVOT_N) - - self.default_size = vmath.vector3(new_size.x, new_size.y + 50, 0) - if not self._is_hidden then - self.container:set_size(self.default_size.x, self.default_size.y, gui.PIVOT_N) - end - - local width = self.layout:get_size().x - self.layout.padding.x - self.layout.padding.z - for index = 1, #self.properties do - local property = self.properties[index] - local container = property.container --[[@as druid.container]] - if container then - container:set_size(width) - end - end - self.paginator.container:set_size(width) -end - - -function M:update(dt) - if not self.is_dirty then - return - end - - self.is_dirty = false - - self:clear_created_properties() - - 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) - - 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 - - ----@param on_create fun(checkbox: druid.widget.property_checkbox)|nil ----@return druid.widget.properties_panel -function M:add_checkbox(on_create) - return self:add_inner_widget(property_checkbox, "property_checkbox", "root", on_create) -end - - ----@param on_create fun(slider: druid.widget.property_slider)|nil ----@return druid.widget.properties_panel -function M:add_slider(on_create) - return self:add_inner_widget(property_slider, "property_slider", "root", on_create) -end - - ----@param on_create fun(button: druid.widget.property_button)|nil ----@return druid.widget.properties_panel -function M:add_button(on_create) - return self:add_inner_widget(property_button, "property_button", "root", on_create) -end - ----@param on_create fun(button: druid.widget.property_button)|nil ----@return druid.widget.properties_panel -function M:add_button_small(on_create) - return self:add_inner_widget(property_button, "property_button_small", "root", on_create) -end - - ----@param on_create fun(input: druid.widget.property_input)|nil ----@return druid.widget.properties_panel -function M:add_input(on_create) - return self:add_inner_widget(property_input, "property_input", "root", on_create) -end - - ----@param on_create fun(text: druid.widget.property_text)|nil -function M:add_text(on_create) - return self:add_inner_widget(property_text, "property_text", "root", on_create) -end - - ----@param on_create fun(selector: druid.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", "root", on_create) -end - - ----@param on_create fun(vector3: druid.widget.property_vector3)|nil -function M:add_vector3(on_create) - return self:add_inner_widget(property_vector3, "property_vector3", "root", on_create) -end - - ----@generic T: druid.widget ----@param widget_class T ----@param template string|nil ----@param nodes table|string|node|nil ----@param on_create fun(widget: T)|nil ----@return druid.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) - - self.is_dirty = true - - return self -end - - ----@param create_widget_callback fun(): druid.widget ----@return druid.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) - - self.is_dirty = true - - return self -end - - ----@private -function M:create_from_prefab(widget_class, template, nodes) - return self:add_property(self.druid:new_widget(widget_class, template, nodes)) -end - - ----@private -function M:add_property(widget) - gui.set_enabled(widget.root, true) - table.insert(self.properties, widget) - local width = self.layout:get_content_size() - widget.container:set_size(width) - - self.layout:add(widget.root) - - return widget -end - - -function M:remove(widget) - for index = 1, #self.properties do - if self.properties[index] == widget then - self.druid:remove(widget) - self.layout:remove(widget.root) - - -- If prefab used clone nodes we can remove it - if widget:get_nodes() then - gui.delete_node(widget.root) - else - -- Probably we have component placed on scene directly - gui.set_enabled(widget.root, false) - end - - table.remove(self.properties, index) - break - end - end -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 node_header = self:get_node("header") - - local new_size = self._is_hidden and self.header_size or self.default_size - self.container:set_size(new_size.x, new_size.y, gui.PIVOT_N) - - local hidden_width = self.header_size.y + 8 - gui.set(node_header, "size.x", self._is_hidden and hidden_width or self.header_size.x) - - gui.set_visible(node_header, self._is_hidden) - gui.set_visible(self.root, not self._is_hidden) - - gui.set_enabled(self.text_header.node, not self._is_hidden) - gui.set_enabled(self.content, not self._is_hidden) - gui.set_enabled(self.button_refresh.node, not self._is_hidden) - gui.set_visible(self.button_back.node, not self._is_hidden) - - if not self._is_hidden then - self.is_dirty = true - end -end - - -function M:is_hidden() - return self._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/druid.gui b/example/druid.gui index 54017d6..711500b 100644 --- a/example/druid.gui +++ b/example/druid.gui @@ -3815,1092 +3815,6 @@ nodes { parent: "hover_hint_example/root" template_node_child: true } -nodes { - position { - x: -200.0 - } - type: TYPE_TEMPLATE - id: "property_button" - parent: "widgets" - inherit_alpha: true - template: "/example/components/properties_panel/properties/property_button.gui" -} -nodes { - type: TYPE_BOX - id: "property_button/root" - parent: "property_button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_button/text_name" - parent: "property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_button/button" - parent: "property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_button/selected" - parent: "property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_button/text_button" - parent: "property_button/button" - template_node_child: true -} -nodes { - position { - x: -200.0 - } - type: TYPE_TEMPLATE - id: "property_checkbox" - parent: "widgets" - inherit_alpha: true - template: "/example/components/properties_panel/properties/property_checkbox.gui" -} -nodes { - type: TYPE_BOX - id: "property_checkbox/root" - parent: "property_checkbox" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_checkbox/text_name" - parent: "property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/button" - parent: "property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/icon" - parent: "property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_checkbox/selected" - parent: "property_checkbox/button" - template_node_child: true -} -nodes { - position { - x: -200.0 - } - type: TYPE_TEMPLATE - id: "property_slider" - parent: "widgets" - inherit_alpha: true - template: "/example/components/properties_panel/properties/property_slider.gui" -} -nodes { - type: TYPE_BOX - id: "property_slider/root" - parent: "property_slider" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_slider/text_name" - parent: "property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/E_Anchor" - parent: "property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/button" - parent: "property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/selected" - parent: "property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_slider/text_value" - parent: "property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider" - parent: "property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider_back" - parent: "property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_slider/slider_pin" - parent: "property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "fps_panel" - parent: "widgets" - inherit_alpha: true - template: "/druid/widget/fps_panel/fps_panel.gui" -} -nodes { - type: TYPE_BOX - id: "fps_panel/root" - parent: "fps_panel" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "fps_panel/mini_graph" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/root" - parent: "fps_panel/mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/header" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/mini_graph/text_header" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/icon_drag" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/content" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/prefab_line" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/color_low" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/content" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_min_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_1" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_2" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel" - parent: "widgets" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties_panel.gui" -} -nodes { - type: TYPE_BOX - id: "properties_panel/root" - parent: "properties_panel" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/header" - parent: "properties_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/text_header" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_drag" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_refresh" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_back" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/content" - parent: "properties_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/scroll_view" - parent: "properties_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/scroll_content" - parent: "properties_panel/scroll_view" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/propeties" - parent: "properties_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_slider" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/root" - parent: "properties_panel/property_slider" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_slider/text_name" - parent: "properties_panel/property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/E_Anchor" - parent: "properties_panel/property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider" - parent: "properties_panel/property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider_back" - parent: "properties_panel/property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider_pin" - parent: "properties_panel/property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/button" - parent: "properties_panel/property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/selected" - parent: "properties_panel/property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_slider/text_value" - parent: "properties_panel/property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_checkbox" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/root" - parent: "properties_panel/property_checkbox" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_checkbox/text_name" - parent: "properties_panel/property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/E_Anchor" - parent: "properties_panel/property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/button" - parent: "properties_panel/property_checkbox/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/icon" - parent: "properties_panel/property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/selected" - parent: "properties_panel/property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_button" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/root" - parent: "properties_panel/property_button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button/text_name" - parent: "properties_panel/property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/E_Anchor" - parent: "properties_panel/property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/button" - parent: "properties_panel/property_button/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/selected" - parent: "properties_panel/property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button/text_button" - parent: "properties_panel/property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_input" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/root" - parent: "properties_panel/property_input" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/text_name" - parent: "properties_panel/property_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/E_Anchor" - parent: "properties_panel/property_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_input/rich_input" - parent: "properties_panel/property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/root" - parent: "properties_panel/property_input/rich_input" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/button" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/placeholder_text" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/input_text" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/cursor_node" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/cursor_text" - parent: "properties_panel/property_input/rich_input/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/selected" - parent: "properties_panel/property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_text" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_text/root" - parent: "properties_panel/property_text" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_text/text_name" - parent: "properties_panel/property_text/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_text/text_right" - parent: "properties_panel/property_text/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_left_right_selector" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/root" - parent: "properties_panel/property_left_right_selector" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_left_right_selector/text_name" - parent: "properties_panel/property_left_right_selector/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/E_Anchor" - parent: "properties_panel/property_left_right_selector/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/button_left" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/icon_left" - parent: "properties_panel/property_left_right_selector/button_left" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/button_right" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/icon_right" - parent: "properties_panel/property_left_right_selector/button_right" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/selected" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_left_right_selector/text_value" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/root" - parent: "properties_panel/property_vector3" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_name" - parent: "properties_panel/property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/E_Anchor" - parent: "properties_panel/property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_x" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/root" - parent: "properties_panel/property_vector3/rich_input_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/button" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/input_text" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/cursor_node" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/cursor_text" - parent: "properties_panel/property_vector3/rich_input_x/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_y" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/root" - parent: "properties_panel/property_vector3/rich_input_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/button" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/input_text" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/cursor_node" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/cursor_text" - parent: "properties_panel/property_vector3/rich_input_y/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_z" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/root" - parent: "properties_panel/property_vector3/rich_input_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/button" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/input_text" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/cursor_node" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/cursor_text" - parent: "properties_panel/property_vector3/rich_input_z/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_button_small" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/root" - parent: "properties_panel/property_button_small" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button_small/text_name" - parent: "properties_panel/property_button_small/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/E_Anchor" - parent: "properties_panel/property_button_small/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/button" - parent: "properties_panel/property_button_small/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/selected" - parent: "properties_panel/property_button_small/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button_small/text_button" - parent: "properties_panel/property_button_small/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "memory_panel" - parent: "widgets" - inherit_alpha: true - template: "/druid/widget/memory_panel/memory_panel.gui" -} -nodes { - type: TYPE_BOX - id: "memory_panel/root" - parent: "memory_panel" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "memory_panel/mini_graph" - parent: "memory_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/root" - parent: "memory_panel/mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/header" - parent: "memory_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/mini_graph/text_header" - parent: "memory_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/icon_drag" - parent: "memory_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/content" - parent: "memory_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/prefab_line" - parent: "memory_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/color_low" - parent: "memory_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/content" - parent: "memory_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_max_value" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_per_second" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/line_second_1" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/line_second_2" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_memory" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "mini_graph" - parent: "widgets" - inherit_alpha: true - template: "/druid/widget/mini_graph/mini_graph.gui" -} -nodes { - type: TYPE_BOX - id: "mini_graph/root" - parent: "mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/header" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "mini_graph/text_header" - parent: "mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/icon_drag" - parent: "mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/content" - parent: "mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/prefab_line" - parent: "mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "mini_graph/color_low" - parent: "mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_input" - parent: "widgets" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties/property_input.gui" -} -nodes { - type: TYPE_BOX - id: "property_input/root" - parent: "property_input" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/text_name" - parent: "property_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/E_Anchor" - parent: "property_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "property_input/rich_input" - parent: "property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/root" - parent: "property_input/rich_input" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/button" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/placeholder_text" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/input_text" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/rich_input/cursor_node" - parent: "property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "property_input/rich_input/cursor_text" - parent: "property_input/rich_input/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "property_input/selected" - parent: "property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "example_tiling_node" - parent: "widgets" - inherit_alpha: true - template: "/example/examples/widgets/tiling_node/example_tiling_node.gui" -} -nodes { - type: TYPE_BOX - id: "example_tiling_node/root" - parent: "example_tiling_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "example_tiling_node/tiling_node" - parent: "example_tiling_node/root" - template_node_child: true -} nodes { size { x: 200.0 diff --git a/example/examples/using_widgets/using_widgets.gui b/example/examples/using_widgets/using_widgets.gui index bfd24e8..5fc85fa 100644 --- a/example/examples/using_widgets/using_widgets.gui +++ b/example/examples/using_widgets/using_widgets.gui @@ -25,89 +25,5 @@ nodes { inherit_alpha: true template: "/druid/widget/fps_panel/fps_panel.gui" } -nodes { - type: TYPE_BOX - id: "fps_panel/root" - parent: "fps_panel" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "fps_panel/mini_graph" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/root" - parent: "fps_panel/mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/header" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/mini_graph/text_header" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/icon_drag" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/content" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/prefab_line" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/color_low" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/content" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_min_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_1" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_2" - parent: "fps_panel/content" - template_node_child: true -} material: "/builtins/materials/gui.material" adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/example/examples/widgets/examples_list.lua b/example/examples/widgets/examples_list.lua index 0376eb2..0734f89 100644 --- a/example/examples/widgets/examples_list.lua +++ b/example/examples/widgets/examples_list.lua @@ -10,181 +10,6 @@ function M.get_examples() root = "hover_hint_example/root", code_url = "example/examples/widgets/hover_hint/hover_hint_example.lua", widget_class = require("example.examples.widgets.hover_hint.hover_hint_example"), - }, - { - name_id = "ui_example_widget_properties_panel", - information_text_id = "ui_example_widget_properties_panel_description", - template = "properties_panel", - root = "properties_panel/root", - code_url = "example/examples/widgets/examples_list.lua", - widget_class = require("druid.widget.properties_panel.properties_panel"), - on_create = function(instance, output_list) - ---@cast instance druid.widget.properties_panel - - instance:add_button(function(button) - button:set_text_button("Button") - button.button.on_click:subscribe(function() - print("Button clicked") - end) - end) - - instance:add_checkbox(function(checkbox) - --print("Checkbox clicked", value) - checkbox:set_text_property("Checkbox") - checkbox.on_change_value:subscribe(function(value) - print("Checkbox clicked", value) - end) - checkbox:set_value(false) - end) - - instance:add_input(function(input) - input:set_text_property("Input") - input:set_text_value("Initial") - input:on_change(function(text) - print("Input changed", text) - end) - end) - - instance:add_left_right_selector(function(selector) - selector:set_template("Arrows Number") - selector.on_change_value:subscribe(function(value) - print("Left Right Selector changed", value) - end) - selector:set_number_type(0, 42, true, 1) - selector:set_value(0) - end) - - instance:add_left_right_selector(function(selector) - selector:set_template("Arrows Array") - selector.on_change_value:subscribe(function(value) - print("Left Right Array value", value) - end) - selector:set_array_type({"Zero", "One", "Two", "Three", "Four", "Five"}, false, 1) - selector:set_value("Zero") - end) - - instance:add_slider(function(slider) - slider:set_text_property("Slider") - slider:set_value(0.5) - slider:on_change(function(value) - print("Slider changed", value) - end) - end) - - instance:add_text(function(text) - text:set_text_property("Text") - text:set_text_value("Hello, World!") - end) - end, - }, - { - name_id = "ui_example_widget_property_button", - information_text_id = "ui_example_widget_property_button_description", - template = "property_button", - root = "property_button/root", - code_url = "example/components/properties_panel/properties/property_button.lua", - widget_class = require("example.components.properties_panel.properties.property_button"), - on_create = function(instance, output_list) - ---@cast instance property_button - instance.button.on_click:subscribe(function() - output_list:add_log_text("Button clicked") - end) - end, - }, - { - name_id = "ui_example_widget_property_input", - information_text_id = "ui_example_widget_property_input_description", - template = "property_input", - root = "property_input/root", - code_url = "example/examples/widgets/examples_list.lua", - widget_class = require("druid.widget.properties_panel.properties.property_input"), - }, - { - name_id = "ui_example_widget_property_slider", - information_text_id = "ui_example_widget_property_slider_description", - template = "property_slider", - root = "property_slider/root", - code_url = "example/components/properties_panel/properties/property_slider.lua", - widget_class = require("example.components.properties_panel.properties.property_slider"), - on_create = function(instance, output_list) - ---@cast instance property_slider - instance.slider.on_change_value:subscribe(function(_, value) - output_list:add_log_text("Slider value: " .. value) - end) - end, - }, - { - name_id = "ui_example_widget_property_checkbox", - information_text_id = "ui_example_widget_property_checkbox_description", - template = "property_checkbox", - root = "property_checkbox/root", - code_url = "example/components/properties_panel/properties/property_checkbox.lua", - widget_class = require("example.components.properties_panel.properties.property_checkbox"), - on_create = function(instance, output_list) - ---@cast instance property_checkbox - instance.button.on_click:subscribe(function() - output_list:add_log_text("Checkbox clicked") - end) - end, - }, - { - name_id = "ui_example_widget_memory_panel", - information_text_id = "ui_example_widget_memory_panel_description", - template = "memory_panel", - root = "memory_panel/root", - code_url = "druid/widget/memory_panel/memory_panel.lua", - widget_class = require("druid.widget.memory_panel.memory_panel"), - on_create = function(instance, output_list) - ---@cast instance druid.widget.memory_panel - print("Memory panel created") - end, - }, - { - name_id = "ui_example_widget_fps_panel", - information_text_id = "ui_example_widget_fps_panel_description", - template = "fps_panel", - root = "fps_panel/root", - code_url = "druid/widget/fps_panel/fps_panel.lua", - widget_class = require("druid.widget.fps_panel.fps_panel"), - on_create = function(instance, output_list) - ---@cast instance druid.widget.fps_panel - print("FPS panel created") - end, - }, - { - name_id = "ui_example_widget_mini_graph", - information_text_id = "ui_example_widget_mini_graph_description", - template = "mini_graph", - root = "mini_graph/root", - code_url = "druid/widget/mini_graph/mini_graph.lua", - widget_class = require("druid.widget.mini_graph.mini_graph"), - on_create = function(instance, output_list) - ---@cast instance druid.widget.mini_graph - instance:set_samples(50) - end, - properties_control = function(instance, properties_panel) - ---@cast instance druid.widget.mini_graph - properties_panel:add_slider("value", 0.5, function(value) - -- Remap to -1, 2 - value = value * 3 - 1 - for index = 1, 50 do - -- Take value each 0.1 step, the higher value at argument value - local x = index * (1 / 50) - local distance = math.abs(x - value) - local line_v = 1 - distance^2 - - instance:set_line_value(index, line_v) - end - end) - end, - }, - { - name_id = "ui_example_widget_tiling_node", - information_text_id = "ui_example_widget_tiling_node_description", - template = "example_tiling_node", - root = "example_tiling_node/root", - code_url = "example/examples/widgets/tiling_node/example_tiling_node.lua", - widget_class = require("example.examples.widgets.tiling_node.example_tiling_node"), } } end diff --git a/example/examples/widgets/fps_panel/example_fps_panel.gui b/example/examples/widgets/fps_panel/example_fps_panel.gui deleted file mode 100644 index 4f01f77..0000000 --- a/example/examples/widgets/fps_panel/example_fps_panel.gui +++ /dev/null @@ -1,104 +0,0 @@ -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "fps_panel" - parent: "root" - inherit_alpha: true - template: "/druid/widget/fps_panel/fps_panel.gui" -} -nodes { - type: TYPE_BOX - id: "fps_panel/root" - parent: "fps_panel" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "fps_panel/mini_graph" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/root" - parent: "fps_panel/mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/header" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/mini_graph/text_header" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/icon_drag" - parent: "fps_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/content" - parent: "fps_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/prefab_line" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/mini_graph/color_low" - parent: "fps_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/content" - parent: "fps_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_min_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "fps_panel/text_fps" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_1" - parent: "fps_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "fps_panel/line_second_2" - parent: "fps_panel/content" - template_node_child: true -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/example/examples/widgets/fps_panel/example_fps_panel.lua b/example/examples/widgets/fps_panel/example_fps_panel.lua deleted file mode 100644 index a26b10c..0000000 --- a/example/examples/widgets/fps_panel/example_fps_panel.lua +++ /dev/null @@ -1,12 +0,0 @@ -local fps_panel = require("druid.widget.fps_panel.fps_panel") - ----@class examples.example_fps_panel: druid.widget -local M = {} - - -function M:init() - self.fps_panel = self.druid:new_widget(fps_panel, "fps_panel") -end - - -return M diff --git a/example/examples/widgets/memory_panel/example_memory_panel.gui b/example/examples/widgets/memory_panel/example_memory_panel.gui deleted file mode 100644 index 44ee04a..0000000 --- a/example/examples/widgets/memory_panel/example_memory_panel.gui +++ /dev/null @@ -1,110 +0,0 @@ -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "memory_panel" - parent: "root" - inherit_alpha: true - template: "/druid/widget/memory_panel/memory_panel.gui" -} -nodes { - type: TYPE_BOX - id: "memory_panel/root" - parent: "memory_panel" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "memory_panel/mini_graph" - parent: "memory_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/root" - parent: "memory_panel/mini_graph" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/header" - parent: "memory_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/mini_graph/text_header" - parent: "memory_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/icon_drag" - parent: "memory_panel/mini_graph/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/content" - parent: "memory_panel/mini_graph/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/prefab_line" - parent: "memory_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/mini_graph/color_low" - parent: "memory_panel/mini_graph/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/content" - parent: "memory_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_max_value" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_per_second" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/line_second_1" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "memory_panel/line_second_2" - parent: "memory_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "memory_panel/text_memory" - parent: "memory_panel/content" - template_node_child: true -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/example/examples/widgets/memory_panel/example_memory_panel.lua b/example/examples/widgets/memory_panel/example_memory_panel.lua deleted file mode 100644 index 29d6c45..0000000 --- a/example/examples/widgets/memory_panel/example_memory_panel.lua +++ /dev/null @@ -1,12 +0,0 @@ -local memory_panel = require("druid.widget.memory_panel.memory_panel") - ----@class examples.example_memory_panel: druid.widget -local M = {} - - -function M:init() - self.memory_panel = self.druid:new_widget(memory_panel, "memory_panel") -end - - -return M diff --git a/example/examples/widgets/properties_panel/example_properties_panel.gui b/example/examples/widgets/properties_panel/example_properties_panel.gui deleted file mode 100644 index 032830a..0000000 --- a/example/examples/widgets/properties_panel/example_properties_panel.gui +++ /dev/null @@ -1,626 +0,0 @@ -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel" - parent: "root" - inherit_alpha: true - template: "/druid/widget/properties_panel/properties_panel.gui" -} -nodes { - type: TYPE_BOX - id: "properties_panel/root" - parent: "properties_panel" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/header" - parent: "properties_panel/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/text_header" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_drag" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_refresh" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/icon_back" - parent: "properties_panel/header" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/content" - parent: "properties_panel/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/scroll_view" - parent: "properties_panel/content" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/scroll_content" - parent: "properties_panel/scroll_view" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/propeties" - parent: "properties_panel/content" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_slider" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/root" - parent: "properties_panel/property_slider" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_slider/text_name" - parent: "properties_panel/property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/E_Anchor" - parent: "properties_panel/property_slider/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider" - parent: "properties_panel/property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider_back" - parent: "properties_panel/property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/slider_pin" - parent: "properties_panel/property_slider/slider" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/button" - parent: "properties_panel/property_slider/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_slider/selected" - parent: "properties_panel/property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_slider/text_value" - parent: "properties_panel/property_slider/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_checkbox" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/root" - parent: "properties_panel/property_checkbox" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_checkbox/text_name" - parent: "properties_panel/property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/E_Anchor" - parent: "properties_panel/property_checkbox/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/button" - parent: "properties_panel/property_checkbox/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/icon" - parent: "properties_panel/property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_checkbox/selected" - parent: "properties_panel/property_checkbox/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_button" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/root" - parent: "properties_panel/property_button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button/text_name" - parent: "properties_panel/property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/E_Anchor" - parent: "properties_panel/property_button/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/button" - parent: "properties_panel/property_button/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button/selected" - parent: "properties_panel/property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button/text_button" - parent: "properties_panel/property_button/button" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_input" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/root" - parent: "properties_panel/property_input" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/text_name" - parent: "properties_panel/property_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/E_Anchor" - parent: "properties_panel/property_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_input/rich_input" - parent: "properties_panel/property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/root" - parent: "properties_panel/property_input/rich_input" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/button" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/placeholder_text" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/input_text" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/rich_input/cursor_node" - parent: "properties_panel/property_input/rich_input/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_input/rich_input/cursor_text" - parent: "properties_panel/property_input/rich_input/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_input/selected" - parent: "properties_panel/property_input/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_text" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_text/root" - parent: "properties_panel/property_text" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_text/text_name" - parent: "properties_panel/property_text/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_text/text_right" - parent: "properties_panel/property_text/root" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_left_right_selector" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/root" - parent: "properties_panel/property_left_right_selector" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_left_right_selector/text_name" - parent: "properties_panel/property_left_right_selector/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/E_Anchor" - parent: "properties_panel/property_left_right_selector/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/button_left" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/icon_left" - parent: "properties_panel/property_left_right_selector/button_left" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/button_right" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/icon_right" - parent: "properties_panel/property_left_right_selector/button_right" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_left_right_selector/selected" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_left_right_selector/text_value" - parent: "properties_panel/property_left_right_selector/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/root" - parent: "properties_panel/property_vector3" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_name" - parent: "properties_panel/property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/E_Anchor" - parent: "properties_panel/property_vector3/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_x" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/root" - parent: "properties_panel/property_vector3/rich_input_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/button" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/input_text" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_x/cursor_node" - parent: "properties_panel/property_vector3/rich_input_x/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_x/cursor_text" - parent: "properties_panel/property_vector3/rich_input_x/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_x" - parent: "properties_panel/property_vector3/field_x" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_y" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/root" - parent: "properties_panel/property_vector3/rich_input_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/button" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/input_text" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_y/cursor_node" - parent: "properties_panel/property_vector3/rich_input_y/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_y/cursor_text" - parent: "properties_panel/property_vector3/rich_input_y/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_y" - parent: "properties_panel/property_vector3/field_y" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/field_z" - parent: "properties_panel/property_vector3/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/text_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_vector3/rich_input_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/root" - parent: "properties_panel/property_vector3/rich_input_z" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/button" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/placeholder_text" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/input_text" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/rich_input_z/cursor_node" - parent: "properties_panel/property_vector3/rich_input_z/root" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_vector3/rich_input_z/cursor_text" - parent: "properties_panel/property_vector3/rich_input_z/cursor_node" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_vector3/selected_z" - parent: "properties_panel/property_vector3/field_z" - template_node_child: true -} -nodes { - type: TYPE_TEMPLATE - id: "properties_panel/property_button_small" - parent: "properties_panel/propeties" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/root" - parent: "properties_panel/property_button_small" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button_small/text_name" - parent: "properties_panel/property_button_small/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/E_Anchor" - parent: "properties_panel/property_button_small/root" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/button" - parent: "properties_panel/property_button_small/E_Anchor" - template_node_child: true -} -nodes { - type: TYPE_BOX - id: "properties_panel/property_button_small/selected" - parent: "properties_panel/property_button_small/button" - template_node_child: true -} -nodes { - type: TYPE_TEXT - id: "properties_panel/property_button_small/text_button" - parent: "properties_panel/property_button_small/button" - template_node_child: true -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT diff --git a/example/examples/widgets/properties_panel/example_properties_panel.lua b/example/examples/widgets/properties_panel/example_properties_panel.lua deleted file mode 100644 index 9106f9b..0000000 --- a/example/examples/widgets/properties_panel/example_properties_panel.lua +++ /dev/null @@ -1,67 +0,0 @@ -local properties_panel = require("druid.widget.properties_panel.properties_panel") - ----@class druid.widget.example_properties_panel: druid.widget -local M = {} - - -function M:init() - self.properties_panel = self.druid:new_widget(properties_panel, "properties_panel") - - self.properties_panel:add_button(function(button) - button:set_text_button("Button") - button.button.on_click:subscribe(function() - print("Button clicked") - end) - end) - - self.properties_panel:add_checkbox(function(checkbox) - --print("Checkbox clicked", value) - checkbox:set_text_property("Checkbox") - checkbox.on_change_value:subscribe(function(value) - print("Checkbox clicked", value) - end) - checkbox:set_value(false) - end) - - self.properties_panel:add_input(function(input) - input:set_text_property("Input") - input:set_text_value("Initial") - input:on_change(function(text) - print("Input changed", text) - end) - end) - - self.properties_panel:add_left_right_selector(function(selector) - selector:set_template("Arrows Number") - selector.on_change_value:subscribe(function(value) - print("Left Right Selector changed", value) - end) - selector:set_number_type(0, 42, true, 1) - selector:set_value(0) - end) - - self.properties_panel:add_left_right_selector(function(selector) - selector:set_template("Arrows Array") - selector.on_change_value:subscribe(function(value) - print("Left Right Array value", value) - end) - selector:set_array_type({"Zero", "One", "Two", "Three", "Four", "Five"}, false, 1) - selector:set_value("Zero") - end) - - self.properties_panel:add_slider(function(slider) - slider:set_text_property("Slider") - slider:set_value(0.5) - slider:on_change(function(value) - print("Slider changed", value) - end) - end) - - self.properties_panel:add_text(function(text) - text:set_text_property("Text") - text:set_text_value("Hello, World!") - end) -end - - -return M \ No newline at end of file diff --git a/example/examples/widgets/tiling_node/example_tiling_node.gui b/example/examples/widgets/tiling_node/example_tiling_node.gui deleted file mode 100644 index f98269f..0000000 --- a/example/examples/widgets/tiling_node/example_tiling_node.gui +++ /dev/null @@ -1,34 +0,0 @@ -textures { - name: "tiling_texture" - texture: "/example/examples/widgets/tiling_node/tiling_texture.atlas" -} -nodes { - size { - x: 200.0 - y: 100.0 - } - type: TYPE_BOX - id: "root" - inherit_alpha: true - size_mode: SIZE_MODE_AUTO - visible: false -} -nodes { - size { - x: 900.0 - y: 900.0 - } - type: TYPE_BOX - texture: "tiling_texture/pattern_0004" - id: "tiling_node" - parent: "root" - inherit_alpha: true - alpha: 0.42 - material: "gui_tiling_node" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT -materials { - name: "gui_tiling_node" - material: "/druid/custom/tiling_node/gui_tiling_node.material" -} diff --git a/example/examples/widgets/tiling_node/example_tiling_node.lua b/example/examples/widgets/tiling_node/example_tiling_node.lua deleted file mode 100644 index 53a8522..0000000 --- a/example/examples/widgets/tiling_node/example_tiling_node.lua +++ /dev/null @@ -1,37 +0,0 @@ -local tiling_node = require("druid.custom.tiling_node.tiling_node") - ----@class examples.example_tiling_node: druid.widget -local M = {} - - -function M:init() - self.tiling_node = self.druid:new(tiling_node, self:get_node("tiling_node")) -end - - ----@param properties_panel properties_panel -function M:properties_control(properties_panel) - properties_panel:add_slider("Repeat X", 0, function(value) - local repeat_x = math.floor(value * 10) - self.tiling_node:set_repeat(repeat_x, nil) - end) - properties_panel:add_slider("Repeat Y", 0, function(value) - local repeat_y = math.floor(value * 10) - self.tiling_node:set_repeat(nil, repeat_y) - end) - properties_panel:add_slider("Offset X", 0, function(value) - self.tiling_node:set_offset(value, nil) - end) - properties_panel:add_slider("Offset Y", 0, function(value) - self.tiling_node:set_offset(nil, value) - end) - properties_panel:add_slider("Margin X", 0, function(value) - self.tiling_node:set_margin(value, nil) - end) - properties_panel:add_slider("Margin Y", 0, function(value) - self.tiling_node:set_margin(nil, value) - end) -end - - -return M diff --git a/example/examples/widgets/tiling_node/pattern_0004.png b/example/examples/widgets/tiling_node/pattern_0004.png deleted file mode 100644 index c3adc2e4c3418bf3ddd84e4ba026ccae1c7a6761..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1211 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58CaNs)Vi3hp+Jfy-O<;Pfnj4m_n$;oAYVPe zC&cwXApleeRI*Th`eGo9rzFTPc)fi7eEy6MH>%ts|0`??EzYqpyS3j2rLhfuXe-`C*Y*Co_XS?xkZ@0x?UVGNd zFHG>OZceTIXBf7BTI#nyFBAXGdHC_Yb=|uE4f?0w{JgtKCsgjU;Jb8X85WVsGwwHR z{pQKDUa5HE{zHGkvB!qChfXv6*6RQLhNGQvTd3S_5N#GJr)+tsup#iN;Ur}lc8O5A zlY()qA(blb6`}!8CrUr;ZBXjJ!;ud(aEV)m`hsK1riZKlzYXY&2-7;ieMPalFAjz zYdBAQ3UH4Qp76BrTi5KL9IKi7&i}8uR554oqw57Nf78Czto}H`{^-f|Cq7)TKl)c~ zDoc2UM&brUd*T|=XCEmONUL%9)GS26(1_+i!S*1;Io~o!!yU{wSEm} zn$P{7!!VP5_IFQ4BYv~@lbKTFQtmBhU14)#k3V-nWkF@UfWoH-pY|y@JaK$l>)CLu z`PA=84881=zq>LD@vFV>W>S%Jxi_6v!=_`8K6gOHkxF|-hX;bs|F$qRy3hC?%v2$l zc+a2nL8VKjy=KER!KZ(hFwAnF@_jRtN1ei{-;s)ujnuifxy z|NcMEOJ>{hcliPw4(Ns#K!*NXeiv;a$+%nc9bqa) zdp6k{tSh|la8q95`i^{sH~cPKMSC{b3uvDB_~)O)5`~kWe=|>PQt^Kel-Ojm7bNlN z4M^hbW{`x5|2v?>C!3uhiO+X{5(^F#gCv;zf!1(o*=z+$toU#SByq$TBq7xQ?l%+A zHTVemclH=zsftmY#vN0|KISN z{f_;Ee~jPxchn#F%luBh07+=ecm5SM_5T}A?8cPCqMVvSM}9N!cL#b}_&0NM%6V>y afBK8lD#GNrQ)_`m34 Date: Mon, 10 Nov 2025 23:12:00 +0200 Subject: [PATCH 16/22] Up --- .gitignore | 2 ++ settings_deployer | 2 +- test/tests/test_rich_text.lua | 12 ------------ 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 6dced02..cef7422 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ manifest.private.der manifest.public.der /.editor_settings /.deployer_cache + +deployer_version_settings.ini diff --git a/settings_deployer b/settings_deployer index 36ed926..fc147e4 100644 --- a/settings_deployer +++ b/settings_deployer @@ -2,7 +2,7 @@ bob_folder=./ # You can point bob version for project in format "filename:sha" -bob_sha=":868769ba7a3458db12d149188bf3be80a339a85c" +bob_sha="1_11_2:cddb6eb43c32e4930257fcbbb30f19cf28deb081" # Select Defold channel. Values: stable, beta, alpha bob_channel="stable" diff --git a/test/tests/test_rich_text.lua b/test/tests/test_rich_text.lua index d37d819..29bd75a 100644 --- a/test/tests/test_rich_text.lua +++ b/test/tests/test_rich_text.lua @@ -106,12 +106,6 @@ return function() -- Test shadow tag with named color local words = rich_text:set_text("Shadowed Text") - assert(#words > 0) - assert(words[1].shadow ~= nil) - - -- Test shadow tag with RGBA values - words = rich_text:set_text("Shadowed Text") - assert(#words > 0) assert(words[1].shadow ~= nil) assert(words[1].shadow.x < 0.1) -- Black shadow should have low RGB values @@ -131,12 +125,6 @@ return function() -- Test outline tag with named color local words = rich_text:set_text("Outlined Text") - assert(#words > 0) - assert(words[1].outline ~= nil) - - -- Test outline tag with RGBA values - words = rich_text:set_text("Outlined Text") - assert(#words > 0) assert(words[1].outline ~= nil) assert(words[1].outline.x < 0.1) -- Black outline should have low RGB values From a28b15c35c15bf1b0ebe31b58954ecabfa8171ce Mon Sep 17 00:00:00 2001 From: Insality Date: Mon, 10 Nov 2025 23:37:40 +0200 Subject: [PATCH 17/22] Up --- .../core/asset_store_internal.lua | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua index cbefb85..5273d8a 100644 --- a/druid/editor_scripts/core/asset_store_internal.lua +++ b/druid/editor_scripts/core/asset_store_internal.lua @@ -20,6 +20,19 @@ function M.download_json(json_url) end +local function is_unlisted_visible(item, lower_query) + if not item.unlisted then + return true + end + + if not lower_query or lower_query == "" or not item.id then + return false + end + + return string.lower(item.id) == lower_query +end + + ---Filter items based on search query ---Filter items based on search query ---@param items table - List of widget items @@ -36,7 +49,9 @@ function M.filter_items(items, query) for _, item in ipairs(items) do -- Search in title, author, description local matches = false - if item.title and string.find(string.lower(item.title), lower_query, 1, true) then + if item.id and string.find(string.lower(item.id), lower_query, 1, true) then + matches = true + elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then matches = true elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then matches = true @@ -81,7 +96,7 @@ function M.extract_authors(items) local author_set = {} for _, item in ipairs(items) do - if item.author and not author_set[item.author] then + if not item.unlisted and item.author and not author_set[item.author] then author_set[item.author] = true table.insert(authors, item.author) end @@ -100,7 +115,7 @@ function M.extract_tags(items) local tag_set = {} for _, item in ipairs(items) do - if item.tags then + if not item.unlisted and item.tags then for _, tag in ipairs(item.tags) do if not tag_set[tag] then tag_set[tag] = true @@ -124,7 +139,19 @@ end ---@param install_folder string - Installation folder to check installed status ---@return table - Filtered items function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) - local filtered = items + local lower_query = nil + if search_query and search_query ~= "" then + lower_query = string.lower(search_query) + end + + local visible_items = {} + for _, item in ipairs(items) do + if is_unlisted_visible(item, lower_query) then + table.insert(visible_items, item) + end + end + + local filtered = visible_items -- Filter by search query if search_query and search_query ~= "" then From 138b2613ab6586afe8a0775b9f6e7c603634c3af Mon Sep 17 00:00:00 2001 From: Insality Date: Tue, 11 Nov 2025 01:36:18 +0200 Subject: [PATCH 18/22] Up --- druid/editor_scripts/core/asset_store.lua | 305 ++++++++---------- .../editor_scripts/core/asset_store/data.lua | 196 +++++++++++ .../core/asset_store/ui/dialog.lua | 19 ++ .../core/asset_store/ui/filters.lua | 119 +++++++ .../core/asset_store/ui/search.lua | 49 +++ .../core/asset_store/ui/settings.lua | 49 +++ .../core/asset_store/ui/widget_card.lua | 167 ++++++++++ .../core/asset_store/ui/widget_list.lua | 51 +++ .../core/asset_store_internal.lua | 192 +---------- druid/editor_scripts/core/ui_components.lua | 303 ----------------- druid/editor_scripts/druid.editor_script | 16 +- 11 files changed, 814 insertions(+), 652 deletions(-) create mode 100644 druid/editor_scripts/core/asset_store/data.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/dialog.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/filters.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/search.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/settings.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/widget_card.lua create mode 100644 druid/editor_scripts/core/asset_store/ui/widget_list.lua delete mode 100644 druid/editor_scripts/core/ui_components.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index cb4c5e0..f5f02d7 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -2,43 +2,88 @@ --- Handles fetching widget data, displaying the store interface, and managing installations local installer = require("druid.editor_scripts.core.installer") -local ui_components = require("druid.editor_scripts.core.ui_components") -local internal = require("druid.editor_scripts.core.asset_store_internal") +local internal = require("druid.editor_scripts.core.asset_store.data") +local dialog_ui = require("druid.editor_scripts.core.asset_store.ui.dialog") +local filters_ui = require("druid.editor_scripts.core.asset_store.ui.filters") +local search_ui = require("druid.editor_scripts.core.asset_store.ui.search") +local settings_ui = require("druid.editor_scripts.core.asset_store.ui.settings") +local widget_list_ui = require("druid.editor_scripts.core.asset_store.ui.widget_list") local M = {} - ----Build type options array ----@return table -local function build_type_options() - return {"All", "Installed", "Not Installed"} -end +local INFO_RESULT = "asset_store_open_info" +local DEFAULT_INSTALL_PREF_KEY = "druid.asset_install_folder" +local DEFAULT_INSTALL_FOLDER = "/widget" +local DEFAULT_TITLE = "Asset Store" +local DEFAULT_INFO_BUTTON = "Info" +local DEFAULT_CLOSE_BUTTON = "Close" +local DEFAULT_EMPTY_SEARCH_MESSAGE = "No widgets found matching '%s'." +local DEFAULT_EMPTY_FILTER_MESSAGE = "No widgets found matching the current filters." +local DEFAULT_SEARCH_LABELS = { + search_tooltip = "Search for widgets by title, author, or description" +} ----Build author options array ----@param authors table ----@return table -local function build_author_options(authors) - local options = {"All Authors"} - for _, author in ipairs(authors) do - table.insert(options, author) +local function normalize_config(input) + if type(input) == "string" then + input = { store_url = input } end - return options -end + assert(type(input) == "table", "asset_store.open expects a string URL or config table") + assert(input.store_url, "asset_store.open requires a store_url") ----Build tag options array ----@param tags table ----@return table -local function build_tag_options(tags) - local options = {"All Tags"} - for _, tag in ipairs(tags) do - table.insert(options, tag) + local config = { + store_url = input.store_url, + info_url = input.info_url, + title = input.title or DEFAULT_TITLE, + info_button_label = input.info_button_label or DEFAULT_INFO_BUTTON, + close_button_label = input.close_button_label or DEFAULT_CLOSE_BUTTON, + empty_search_message = input.empty_search_message or DEFAULT_EMPTY_SEARCH_MESSAGE, + empty_filter_message = input.empty_filter_message or DEFAULT_EMPTY_FILTER_MESSAGE, + install_prefs_key = input.install_prefs_key, + default_install_folder = input.default_install_folder or DEFAULT_INSTALL_FOLDER, + labels = input.labels or {}, + info_action = input.info_action, + } + + if config.install_prefs_key == nil then + config.install_prefs_key = DEFAULT_INSTALL_PREF_KEY + elseif config.install_prefs_key == false then + config.install_prefs_key = nil end - return options + + config.labels.search = config.labels.search or {} + for key, value in pairs(DEFAULT_SEARCH_LABELS) do + if config.labels.search[key] == nil then + config.labels.search[key] = value + end + end + + return config end + +local function get_initial_install_folder(config) + if not config.install_prefs_key then + return config.default_install_folder + end + + return editor.prefs.get(config.install_prefs_key) or config.default_install_folder +end + + + +local function persist_install_folder(config, folder) + if not config.install_prefs_key then + return + end + + editor.prefs.set(config.install_prefs_key, folder) +end + + + ---Handle widget installation ---@param item table - Widget item to install ---@param install_folder string - Installation folder @@ -60,50 +105,38 @@ local function handle_install(item, install_folder, all_items, on_success, on_er end ----Open the asset store dialog -function M.open_asset_store(store_url) - print("Opening Druid Asset Store from:", store_url) +function M.open(config_input) + local config = normalize_config(config_input) - -- Fetch data synchronously before creating the dialog - local store_data, fetch_error = internal.download_json(store_url) + print("Opening " .. config.title .. " from:", config.store_url) + + local store_data, fetch_error = internal.download_json(config.store_url) if not store_data then - print("Failed to load widgets:", fetch_error) + print("Failed to load store items:", fetch_error) return end - print("Successfully loaded", #store_data.items, "widgets") + print("Successfully loaded", #store_data.items, "items") local initial_items = store_data.items + local initial_install_folder = get_initial_install_folder(config) + local filter_overrides = config.labels.filters and { labels = config.labels.filters } or nil + local dialog_component = editor.ui.component(function(props) - -- State management local all_items = editor.ui.use_state(initial_items) - local install_folder, set_install_folder = editor.ui.use_state(editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder()) + local install_folder, set_install_folder = editor.ui.use_state(initial_install_folder) local search_query, set_search_query = editor.ui.use_state("") local filter_type, set_filter_type = editor.ui.use_state("All") local filter_author, set_filter_author = editor.ui.use_state("All Authors") local filter_tag, set_filter_tag = editor.ui.use_state("All Tags") local install_status, set_install_status = editor.ui.use_state("") - -- Extract unique authors and tags for dropdown options local authors = editor.ui.use_memo(internal.extract_authors, all_items) local tags = editor.ui.use_memo(internal.extract_tags, all_items) - -- Build dropdown options (memoized to avoid recreation on each render) - local type_options = editor.ui.use_memo(build_type_options) - local author_options = editor.ui.use_memo(build_author_options, authors) - local tag_options = editor.ui.use_memo(build_tag_options, tags) + local type_options = editor.ui.use_memo(filters_ui.build_type_options, filter_overrides) + local author_options = editor.ui.use_memo(filters_ui.build_author_options, authors, filter_overrides) + local tag_options = editor.ui.use_memo(filters_ui.build_tag_options, tags, filter_overrides) - -- Debug output - if #type_options > 0 then - print("Type options count:", #type_options, "first:", type_options[1]) - end - if #author_options > 0 then - print("Author options count:", #author_options, "first:", author_options[1]) - end - if #tag_options > 0 then - print("Tag options count:", #tag_options, "first:", tag_options[1]) - end - - -- Filter items based on all filters local filtered_items = editor.ui.use_memo( internal.filter_items_by_filters, all_items, @@ -114,7 +147,6 @@ function M.open_asset_store(store_url) install_folder ) - -- Installation handlers local function on_install(item) handle_install(item, install_folder, all_items, function(message) @@ -126,116 +158,57 @@ function M.open_asset_store(store_url) ) end - -- Build UI content local content_children = {} - -- Settings section - table.insert(content_children, editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.label({ - spacing = editor.ui.SPACING.MEDIUM, - text = "Installation Folder:", - color = editor.ui.COLOR.TEXT - }), - - editor.ui.string_field({ - value = install_folder, - on_value_changed = function(new_folder) - set_install_folder(new_folder) - editor.prefs.set("druid.asset_install_folder", new_folder) - end, - title = "Installation Folder:", - tooltip = "The folder to install the assets to", - }), - } + table.insert(content_children, settings_ui.create({ + install_folder = install_folder, + on_install_folder_changed = function(new_folder) + set_install_folder(new_folder) + persist_install_folder(config, new_folder) + end, + labels = config.labels.settings })) - -- Filter dropdowns section - table.insert(content_children, editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - -- Type filter dropdown - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Type:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = filter_type, - options = type_options, - on_value_changed = set_filter_type - }) - } - }), - -- Author filter dropdown - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Author:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = filter_author, - options = author_options, - on_value_changed = set_filter_author - }) - } - }), - -- Tag filter dropdown - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Tag:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = filter_tag, - options = tag_options, - on_value_changed = set_filter_tag - }) - } - }) - } + table.insert(content_children, filters_ui.create({ + filter_type = filter_type, + filter_author = filter_author, + filter_tag = filter_tag, + type_options = type_options, + author_options = author_options, + tag_options = tag_options, + on_type_change = set_filter_type, + on_author_change = set_filter_author, + on_tag_change = set_filter_tag, + labels = config.labels.filters, })) - -- Search section - table.insert(content_children, editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.label({ - text = "Search:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.string_field({ - value = search_query, - on_value_changed = set_search_query, - title = "Search:", - tooltip = "Search for widgets by title, author, or description", - grow = true - }) - }, + table.insert(content_children, search_ui.create({ + search_query = search_query, + on_search = set_search_query, + labels = config.labels.search, })) - -- Main content area if #filtered_items == 0 then - local message = search_query ~= "" and - "No widgets found matching '" .. search_query .. "'." or - "No widgets found matching the current filters." + local message = config.empty_filter_message + if search_query ~= "" then + message = string.format(config.empty_search_message, search_query) + end table.insert(content_children, editor.ui.label({ text = message, color = editor.ui.COLOR.HINT, alignment = editor.ui.ALIGNMENT.CENTER })) else - table.insert(content_children, ui_components.create_widget_list(filtered_items, on_install)) + table.insert(content_children, widget_list_ui.create(filtered_items, { + on_install = on_install, + open_url = internal.open_url, + is_installed = function(item) + return installer.is_widget_installed(item, install_folder) + end, + labels = config.labels.widget_card, + })) end - -- Install status message if install_status ~= "" then table.insert(content_children, editor.ui.label({ text = install_status, @@ -244,31 +217,33 @@ function M.open_asset_store(store_url) })) end - return editor.ui.dialog({ - title = "Druid Asset Store", - content = editor.ui.vertical({ - spacing = editor.ui.SPACING.MEDIUM, - padding = editor.ui.PADDING.SMALL, - grow = true, - children = content_children - }), - buttons = { - editor.ui.dialog_button({ - text = "Info", - result = "info_assets_store", - }), - editor.ui.dialog_button({ - text = "Close", - cancel = true - }) - } + local buttons = {} + if config.info_url or config.info_action then + table.insert(buttons, editor.ui.dialog_button({ + text = config.info_button_label, + result = INFO_RESULT, + })) + end + table.insert(buttons, editor.ui.dialog_button({ + text = config.close_button_label, + cancel = true + })) + + return dialog_ui.build({ + title = config.title, + children = content_children, + buttons = buttons }) end) local result = editor.ui.show_dialog(dialog_component({})) - if result and result == "info_assets_store" then - editor.browse("https://github.com/Insality/core/blob/main/druid_widget_store.md") + if result and result == INFO_RESULT then + if config.info_action then + config.info_action() + elseif config.info_url then + internal.open_url(config.info_url) + end end return {} diff --git a/druid/editor_scripts/core/asset_store/data.lua b/druid/editor_scripts/core/asset_store/data.lua new file mode 100644 index 0000000..18ab864 --- /dev/null +++ b/druid/editor_scripts/core/asset_store/data.lua @@ -0,0 +1,196 @@ +local installer = require("druid.editor_scripts.core.installer") + + +local M = {} + + +local function normalize_query(query) + if not query or query == "" then + return nil + end + + return string.lower(query) +end + + +local function is_unlisted_visible(item, lower_query) + if not item.unlisted then + return true + end + + if not lower_query or not item.id then + return false + end + + return string.lower(item.id) == lower_query +end + + +function M.download_json(json_url) + local response = http.request(json_url, { as = "json" }) + + if response.status ~= 200 then + return nil, "Failed to fetch store data. HTTP status: " .. response.status + end + + if not response.body or not response.body.items then + return nil, "Invalid store data format" + end + + return response.body, nil +end + + +function M.filter_items(items, query) + if query == "" or query == nil then + return items + end + + local filtered = {} + local lower_query = string.lower(query) + + for _, item in ipairs(items) do + local matches = false + if item.id and string.find(string.lower(item.id), lower_query, 1, true) then + matches = true + elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then + matches = true + elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then + matches = true + elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then + matches = true + end + + if not matches and item.tags then + for _, tag in ipairs(item.tags) do + if string.find(string.lower(tag), lower_query, 1, true) then + matches = true + break + end + end + end + + if not matches and item.depends then + for _, dep in ipairs(item.depends) do + if string.find(string.lower(dep), lower_query, 1, true) then + matches = true + break + end + end + end + + if matches then + table.insert(filtered, item) + end + end + + return filtered +end + + +function M.extract_authors(items) + local authors = {} + local author_set = {} + + for _, item in ipairs(items) do + if not item.unlisted and item.author and not author_set[item.author] then + author_set[item.author] = true + table.insert(authors, item.author) + end + end + + table.sort(authors) + + return authors +end + + +function M.extract_tags(items) + local tags = {} + local tag_set = {} + + for _, item in ipairs(items) do + if not item.unlisted and item.tags then + for _, tag in ipairs(item.tags) do + if not tag_set[tag] then + tag_set[tag] = true + table.insert(tags, tag) + end + end + end + end + + table.sort(tags) + + return tags +end + + +function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) + local lower_query = normalize_query(search_query) + local visible_items = {} + + for _, item in ipairs(items) do + if is_unlisted_visible(item, lower_query) then + table.insert(visible_items, item) + end + end + + local filtered = visible_items + + if lower_query then + filtered = M.filter_items(filtered, search_query) + end + + if filter_type and filter_type ~= "All" then + local type_filtered = {} + for _, item in ipairs(filtered) do + local is_installed = installer.is_widget_installed(item, install_folder) + if (filter_type == "Installed" and is_installed) or + (filter_type == "Not Installed" and not is_installed) then + table.insert(type_filtered, item) + end + end + filtered = type_filtered + end + + if filter_author and filter_author ~= "All Authors" then + local author_filtered = {} + for _, item in ipairs(filtered) do + if item.author == filter_author then + table.insert(author_filtered, item) + end + end + filtered = author_filtered + end + + if filter_tag and filter_tag ~= "All Tags" then + local tag_filtered = {} + for _, item in ipairs(filtered) do + if item.tags then + for _, tag in ipairs(item.tags) do + if tag == filter_tag then + table.insert(tag_filtered, item) + break + end + end + end + end + filtered = tag_filtered + end + + return filtered +end + + +function M.open_url(url) + if not url then + print("No URL available for:", url) + end + + editor.browse(url) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/dialog.lua b/druid/editor_scripts/core/asset_store/ui/dialog.lua new file mode 100644 index 0000000..7b6dfac --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/dialog.lua @@ -0,0 +1,19 @@ +local M = {} + + +function M.build(params) + return editor.ui.dialog({ + title = params.title or "Asset Store", + content = editor.ui.vertical({ + spacing = params.spacing or editor.ui.SPACING.MEDIUM, + padding = params.padding or editor.ui.PADDING.SMALL, + grow = true, + children = params.children or {} + }), + buttons = params.buttons or {} + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/filters.lua b/druid/editor_scripts/core/asset_store/ui/filters.lua new file mode 100644 index 0000000..efa750d --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/filters.lua @@ -0,0 +1,119 @@ +local DEFAULT_LABELS = { + type_label = "Type:", + author_label = "Author:", + tag_label = "Tag:", + all_types = "All", + installed = "Installed", + not_installed = "Not Installed", + all_authors = "All Authors", + all_tags = "All Tags", +} + + +local M = {} + + +local function build_labels(overrides) + if not overrides then + return DEFAULT_LABELS + end + + local labels = {} + for key, value in pairs(DEFAULT_LABELS) do + labels[key] = overrides[key] or value + end + + return labels +end + + +function M.build_type_options(overrides) + local labels = build_labels(overrides and overrides.labels) + + return { + labels.all_types, + labels.installed, + labels.not_installed, + } +end + + +function M.build_author_options(authors, overrides) + local labels = build_labels(overrides and overrides.labels) + local options = {labels.all_authors} + + for _, author in ipairs(authors or {}) do + table.insert(options, author) + end + + return options +end + + +function M.build_tag_options(tags, overrides) + local labels = build_labels(overrides and overrides.labels) + local options = {labels.all_tags} + + for _, tag in ipairs(tags or {}) do + table.insert(options, tag) + end + + return options +end + + +function M.create(params) + local labels = build_labels(params and params.labels) + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = labels.type_label, + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = params.filter_type, + options = params.type_options, + on_value_changed = params.on_type_change + }) + } + }), + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = labels.author_label, + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = params.filter_author, + options = params.author_options, + on_value_changed = params.on_author_change + }) + } + }), + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = labels.tag_label, + color = editor.ui.COLOR.TEXT + }), + editor.ui.select_box({ + value = params.filter_tag, + options = params.tag_options, + on_value_changed = params.on_tag_change + }) + } + }) + } + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/search.lua b/druid/editor_scripts/core/asset_store/ui/search.lua new file mode 100644 index 0000000..223b0d2 --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/search.lua @@ -0,0 +1,49 @@ +local DEFAULT_LABELS = { + search_label = "Search:", + search_title = "Search:", + search_tooltip = "Search for items", +} + + +local M = {} + + +local function build_labels(overrides) + if not overrides then + return DEFAULT_LABELS + end + + local labels = {} + for key, value in pairs(DEFAULT_LABELS) do + labels[key] = overrides[key] or value + end + + return labels +end + + + +function M.create(params) + local labels = build_labels(params and params.labels) + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.label({ + text = labels.search_label, + color = editor.ui.COLOR.TEXT + }), + editor.ui.string_field({ + value = params.search_query or "", + on_value_changed = params.on_search, + title = labels.search_title, + tooltip = labels.search_tooltip, + grow = true + }) + } + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/settings.lua b/druid/editor_scripts/core/asset_store/ui/settings.lua new file mode 100644 index 0000000..6731f52 --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/settings.lua @@ -0,0 +1,49 @@ +local DEFAULT_LABELS = { + install_label = "Installation Folder:", + install_title = "Installation Folder:", + install_tooltip = "The folder to install the assets to", +} + + +local M = {} + + +local function build_labels(overrides) + if not overrides then + return DEFAULT_LABELS + end + + local labels = {} + for key, value in pairs(DEFAULT_LABELS) do + labels[key] = overrides[key] or value + end + + return labels +end + + + +function M.create(params) + local labels = build_labels(params and params.labels) + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.MEDIUM, + children = { + editor.ui.label({ + spacing = editor.ui.SPACING.MEDIUM, + text = labels.install_label, + color = editor.ui.COLOR.TEXT + }), + editor.ui.string_field({ + value = params.install_folder, + on_value_changed = params.on_install_folder_changed, + title = labels.install_title, + tooltip = labels.install_tooltip, + }), + } + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/widget_card.lua b/druid/editor_scripts/core/asset_store/ui/widget_card.lua new file mode 100644 index 0000000..1884d80 --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/widget_card.lua @@ -0,0 +1,167 @@ +local DEFAULT_LABELS = { + install_button = "Install", + api_button = "API", + example_button = "Example", + author_caption = "Author", + installed_tag = "✓ Installed", + tags_prefix = "Tags: ", + depends_prefix = "Depends: ", + size_separator = "• ", + unknown_size = "Unknown size", + unknown_version = "Unknown version", +} + + +local M = {} + + +local function format_size(size_bytes) + if not size_bytes then + return DEFAULT_LABELS.unknown_size + end + + if size_bytes < 1024 then + return size_bytes .. " B" + elseif size_bytes < 1024 * 1024 then + return math.floor(size_bytes / 1024) .. " KB" + end + + return math.floor(size_bytes / (1024 * 1024)) .. " MB" +end + + + +local function build_labels(overrides) + if not overrides then + return DEFAULT_LABELS + end + + local labels = {} + for key, value in pairs(DEFAULT_LABELS) do + labels[key] = overrides[key] or value + end + + return labels +end + + + +function M.create(item, context) + local labels = build_labels(context and context.labels) + local open_url = context and context.open_url or function(_) end + local on_install = context and context.on_install or function(...) end + local is_installed = context and context.is_installed or false + + local size_text = format_size(item.size) + local version_text = item.version and ("v" .. item.version) or labels.unknown_version + local tags_text = item.tags and #item.tags > 0 and labels.tags_prefix .. table.concat(item.tags, ", ") or "" + local deps_text = item.depends and #item.depends > 0 and labels.depends_prefix .. table.concat(item.depends, ", ") or "" + + local widget_details_children = { + editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = { + editor.ui.label({ + text = item.title or item.id, + color = editor.ui.COLOR.OVERRIDE + }), + editor.ui.label({ + text = version_text, + color = editor.ui.COLOR.WARNING + }), + editor.ui.label({ + text = labels.size_separator .. size_text, + color = editor.ui.COLOR.HINT + }), + } + }), + editor.ui.paragraph({ + text = item.description or "No description available", + color = editor.ui.COLOR.TEXT + }) + } + + if tags_text ~= "" then + table.insert(widget_details_children, editor.ui.label({ + text = tags_text, + color = editor.ui.COLOR.HINT + })) + end + + if deps_text ~= "" then + table.insert(widget_details_children, editor.ui.label({ + text = deps_text, + color = editor.ui.COLOR.HINT + })) + end + + if is_installed then + table.insert(widget_details_children, editor.ui.label({ + text = labels.installed_tag, + color = editor.ui.COLOR.WARNING + })) + end + + local button_children = { + editor.ui.button({ + text = labels.install_button, + on_pressed = on_install, + enabled = not is_installed + }) + } + + if item.api then + table.insert(button_children, editor.ui.button({ + text = labels.api_button, + on_pressed = function() open_url(item.api) end, + enabled = item.api ~= nil + })) + end + + if item.example_url then + table.insert(button_children, editor.ui.button({ + text = labels.example_button, + on_pressed = function() open_url(item.example_url) end, + enabled = item.example_url ~= nil + })) + end + + table.insert(button_children, editor.ui.horizontal({ grow = true })) + + if item.author_url then + table.insert(button_children, editor.ui.label({ + text = labels.author_caption, + color = editor.ui.COLOR.HINT + })) + table.insert(button_children, editor.ui.button({ + text = item.author or labels.author_caption, + on_pressed = function() open_url(item.author_url) end, + enabled = item.author_url ~= nil + })) + end + + table.insert(widget_details_children, editor.ui.horizontal({ + spacing = editor.ui.SPACING.SMALL, + children = button_children + })) + + return editor.ui.horizontal({ + spacing = editor.ui.SPACING.NONE, + padding = editor.ui.PADDING.SMALL, + children = { + editor.ui.label({ + text = "•••", + color = editor.ui.COLOR.HINT + }), + editor.ui.vertical({ + spacing = editor.ui.SPACING.SMALL, + grow = true, + children = widget_details_children + }), + } + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store/ui/widget_list.lua b/druid/editor_scripts/core/asset_store/ui/widget_list.lua new file mode 100644 index 0000000..3322183 --- /dev/null +++ b/druid/editor_scripts/core/asset_store/ui/widget_list.lua @@ -0,0 +1,51 @@ +local widget_card = require("druid.editor_scripts.core.asset_store.ui.widget_card") + + +local M = {} + + +local function noop(...) +end + + + +local function build_context(overrides) + return { + on_install = overrides.on_install or noop, + open_url = overrides.open_url or noop, + labels = overrides.labels, + } +end + + + +function M.create(items, overrides) + local card_context = build_context(overrides or {}) + local is_installed = overrides and overrides.is_installed or function(_) + return false + end + + local widget_items = {} + for _, item in ipairs(items) do + local context = { + on_install = function() + card_context.on_install(item) + end, + open_url = card_context.open_url, + labels = card_context.labels, + is_installed = is_installed(item), + } + + table.insert(widget_items, widget_card.create(item, context)) + end + + return editor.ui.scroll({ + content = editor.ui.vertical({ + children = widget_items + }) + }) +end + + +return M + diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua index 5273d8a..0bd9d96 100644 --- a/druid/editor_scripts/core/asset_store_internal.lua +++ b/druid/editor_scripts/core/asset_store_internal.lua @@ -1,213 +1,39 @@ -local installer = require("druid.editor_scripts.core.installer") +local data = require("druid.editor_scripts.core.asset_store.data") + local M = {} ----Download a JSON file from a URL ----@param json_url string - The URL to download the JSON from ----@return table|nil, string|nil - The JSON data or nil, error message or nil + function M.download_json(json_url) - local response = http.request(json_url, { as = "json" }) - - if response.status ~= 200 then - return nil, "Failed to fetch store data. HTTP status: " .. response.status - end - - if not response.body or not response.body.items then - return nil, "Invalid store data format" - end - - return response.body, nil + return data.download_json(json_url) end -local function is_unlisted_visible(item, lower_query) - if not item.unlisted then - return true - end - - if not lower_query or lower_query == "" or not item.id then - return false - end - - return string.lower(item.id) == lower_query -end - - ----Filter items based on search query ----Filter items based on search query ----@param items table - List of widget items ----@param query string - Search query ----@return table - Filtered items function M.filter_items(items, query) - if query == "" then - return items - end - - local filtered = {} - local lower_query = string.lower(query) - - for _, item in ipairs(items) do - -- Search in title, author, description - local matches = false - if item.id and string.find(string.lower(item.id), lower_query, 1, true) then - matches = true - elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then - matches = true - elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then - matches = true - elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then - matches = true - end - - -- Search in tags - if not matches and item.tags then - for _, tag in ipairs(item.tags) do - if string.find(string.lower(tag), lower_query, 1, true) then - matches = true - break - end - end - end - - -- Search in dependencies - if not matches and item.depends then - for _, dep in ipairs(item.depends) do - if string.find(string.lower(dep), lower_query, 1, true) then - matches = true - break - end - end - end - - if matches then - table.insert(filtered, item) - end - end - - return filtered + return data.filter_items(items, query) end ----Extract unique authors from items list ----@param items table - List of widget items ----@return table - Sorted list of unique authors function M.extract_authors(items) - local authors = {} - local author_set = {} - - for _, item in ipairs(items) do - if not item.unlisted and item.author and not author_set[item.author] then - author_set[item.author] = true - table.insert(authors, item.author) - end - end - - table.sort(authors) - return authors + return data.extract_authors(items) end ----Extract unique tags from items list ----@param items table - List of widget items ----@return table - Sorted list of unique tags function M.extract_tags(items) - local tags = {} - local tag_set = {} - - for _, item in ipairs(items) do - if not item.unlisted and item.tags then - for _, tag in ipairs(item.tags) do - if not tag_set[tag] then - tag_set[tag] = true - table.insert(tags, tag) - end - end - end - end - - table.sort(tags) - return tags + return data.extract_tags(items) end ----Filter items based on all filters (search, type, author, tag) ----@param items table - List of widget items ----@param search_query string - Search query ----@param filter_type string - Type filter: "All", "Installed", "Not Installed" ----@param filter_author string - Author filter: "All Authors" or specific author ----@param filter_tag string - Tag filter: "All Tags" or specific tag ----@param install_folder string - Installation folder to check installed status ----@return table - Filtered items function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) - local lower_query = nil - if search_query and search_query ~= "" then - lower_query = string.lower(search_query) - end - - local visible_items = {} - for _, item in ipairs(items) do - if is_unlisted_visible(item, lower_query) then - table.insert(visible_items, item) - end - end - - local filtered = visible_items - - -- Filter by search query - if search_query and search_query ~= "" then - filtered = M.filter_items(filtered, search_query) - end - - -- Filter by type (Installed/Not Installed) - if filter_type and filter_type ~= "All" then - local type_filtered = {} - for _, item in ipairs(filtered) do - local is_installed = installer.is_widget_installed(item, install_folder) - if (filter_type == "Installed" and is_installed) or - (filter_type == "Not Installed" and not is_installed) then - table.insert(type_filtered, item) - end - end - filtered = type_filtered - end - - -- Filter by author - if filter_author and filter_author ~= "All Authors" then - local author_filtered = {} - for _, item in ipairs(filtered) do - if item.author == filter_author then - table.insert(author_filtered, item) - end - end - filtered = author_filtered - end - - -- Filter by tag - if filter_tag and filter_tag ~= "All Tags" then - local tag_filtered = {} - for _, item in ipairs(filtered) do - if item.tags then - for _, tag in ipairs(item.tags) do - if tag == filter_tag then - table.insert(tag_filtered, item) - break - end - end - end - end - filtered = tag_filtered - end - - return filtered + return data.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) end ----Open a URL in the default browser ----@param url string - The URL to open function M.open_url(url) if not url then print("No URL available for:", url) end + editor.browse(url) end diff --git a/druid/editor_scripts/core/ui_components.lua b/druid/editor_scripts/core/ui_components.lua deleted file mode 100644 index 82ec423..0000000 --- a/druid/editor_scripts/core/ui_components.lua +++ /dev/null @@ -1,303 +0,0 @@ ---- Module for reusable UI components in the asset store ---- Contains component builders for filters, widget items, and lists - -local internal = require("druid.editor_scripts.core.asset_store_internal") -local installer = require("druid.editor_scripts.core.installer") - -local M = {} - - ----Create a settings section with installation folder input ----@param install_path string - Current installation path ----@param on_change function - Callback when path changes ----@return userdata - UI component -function M.create_settings_section(install_path, on_change) - return editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Installation Folder:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = install_path, - color = editor.ui.COLOR.TEXT, - grow = true - }) - } - }) -end - - ----Extract unique authors from items list ----@param items table - List of widget items ----@return table - Sorted list of unique authors -local function extract_authors(items) - local authors = {} - local author_set = {} - - for _, item in ipairs(items) do - if item.author and not author_set[item.author] then - author_set[item.author] = true - table.insert(authors, item.author) - end - end - - table.sort(authors) - return authors -end - - ----Extract unique tags from items list ----@param items table - List of widget items ----@return table - Sorted list of unique tags -local function extract_tags(items) - local tags = {} - local tag_set = {} - - for _, item in ipairs(items) do - if item.tags then - for _, tag in ipairs(item.tags) do - if not tag_set[tag] then - tag_set[tag] = true - table.insert(tags, tag) - end - end - end - end - - table.sort(tags) - return tags -end - - ----Create filter section with author and tag dropdowns ----@param items table - List of all widget items ----@param author_filter string - Current author filter ----@param tag_filter string - Current tag filter ----@param on_author_change function - Callback for author filter change ----@param on_tag_change function - Callback for tag filter change ----@return userdata - UI component -function M.create_filter_section(items, author_filter, tag_filter, on_author_change, on_tag_change) - local authors = extract_authors(items) - local tags = extract_tags(items) - - -- Build author options - local author_options = {"All Authors"} - for _, author in ipairs(authors) do - table.insert(author_options, author) - end - - -- Build tag options - local tag_options = {"All Categories"} - for _, tag in ipairs(tags) do - table.insert(tag_options, tag) - end - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Author:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = author_filter, - color = editor.ui.COLOR.TEXT - }) - } - }), - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = "Category:", - color = editor.ui.COLOR.TEXT - }), - editor.ui.label({ - text = tag_filter, - color = editor.ui.COLOR.TEXT - }) - } - }) - } - }) -end - - ----Format file size for display ----@param size_bytes number - Size in bytes ----@return string - Formatted size string -local function format_size(size_bytes) - if size_bytes < 1024 then - return size_bytes .. " B" - elseif size_bytes < 1024 * 1024 then - return math.floor(size_bytes / 1024) .. " KB" - else - return math.floor(size_bytes / (1024 * 1024)) .. " MB" - end -end - - ----Create a widget item card ----@param item table - Widget item data ----@param is_installed boolean - Whether widget is already installed ----@param on_install function - Callback for install button ----@return userdata - UI component -function M.create_widget_item(item, is_installed, on_install) - local size_text = item.size and format_size(item.size) or "Unknown size" - local version_text = item.version and "v" .. item.version or "Unknown version" - - -- Create tags display - local tags_text = "" - if item.tags and #item.tags > 0 then - tags_text = "Tags: " .. table.concat(item.tags, ", ") - end - - -- Create dependencies display - local deps_text = "" - if item.depends and #item.depends > 0 then - deps_text = "Depends: " .. table.concat(item.depends, ", ") - end - - local widget_details_children = { - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = item.title or item.id, - color = editor.ui.COLOR.OVERRIDE - }), - editor.ui.label({ - text = version_text, - color = editor.ui.COLOR.WARNING - }), - editor.ui.label({ - text = "• " .. size_text, - color = editor.ui.COLOR.HINT - }), - } - }), - - -- Description - editor.ui.paragraph({ - text = item.description or "No description available", - color = editor.ui.COLOR.TEXT - }), - } - - if tags_text ~= "" then - table.insert(widget_details_children, editor.ui.label({ - text = tags_text, - color = editor.ui.COLOR.HINT - })) - end - - if deps_text ~= "" then - table.insert(widget_details_children, editor.ui.label({ - text = deps_text, - color = editor.ui.COLOR.HINT - })) - end - - if is_installed then - table.insert(widget_details_children, editor.ui.label({ - text = "✓ Installed", - color = editor.ui.COLOR.WARNING - })) - end - - -- Create button row at the bottom - local button_children = { - editor.ui.button({ - text = "Install", - on_pressed = on_install, - enabled = not is_installed - }), - } - - if item.api ~= nil then - table.insert(button_children, editor.ui.button({ - text = "API", - on_pressed = function() internal.open_url(item.api) end, - enabled = item.api ~= nil - })) - end - - if item.example_url ~= nil then - table.insert(button_children, editor.ui.button({ - text = "Example", - on_pressed = function() internal.open_url(item.example_url) end, - enabled = item.example_url ~= nil - })) - end - - -- Add spacer to push Author button to the right - table.insert(button_children, editor.ui.horizontal({ grow = true })) - - if item.author_url ~= nil then - table.insert(button_children, editor.ui.label({ - text = "Author", - color = editor.ui.COLOR.HINT - })) - table.insert(button_children, editor.ui.button({ - text = item.author or "Author", - on_pressed = function() internal.open_url(item.author_url) end, - enabled = item.author_url ~= nil - })) - end - - -- Add button row to widget details - table.insert(widget_details_children, editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = button_children - })) - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.NONE, - padding = editor.ui.PADDING.SMALL, - children = { - -- Widget icon placeholder - editor.ui.label({ - text = "•••", - color = editor.ui.COLOR.HINT - }), - - -- Widget details - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - grow = true, - children = widget_details_children - }), - } - }) -end - - ----Create a scrollable list of widget items ----@param items table - List of widget items to display ----@param on_install function - Callback for install button ----@return userdata - UI component -function M.create_widget_list(items, on_install) - local widget_items = {} - local install_folder = editor.prefs.get("druid.asset_install_folder") or installer.get_install_folder() - - for _, item in ipairs(items) do - local is_installed = installer.is_widget_installed(item, install_folder) - table.insert(widget_items, M.create_widget_item(item, is_installed, - function() on_install(item) end - )) - end - - return editor.ui.scroll({ - content = editor.ui.vertical({ - children = widget_items - }) - }) -end - - -return M diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index 754732f..2431208 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -3,6 +3,14 @@ local create_druid_widget = require("druid.editor_scripts.create_druid_widget") local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script") local druid_settings = require("druid.editor_scripts.druid_settings") local asset_store = require("druid.editor_scripts.core.asset_store") +-- Reuse tip: copy the snippet below into another editor script to open a custom store. +-- asset_store.open({ +-- store_url = "https://example.com/store.json", +-- info_url = "https://example.com/docs", +-- title = "My Library Store", +-- install_prefs_key = "my_lib.asset_install_folder", +-- default_install_folder = "/my_widgets", +-- }) local M = {} @@ -75,7 +83,13 @@ function M.get_commands() label = "[Druid] Asset Store", locations = { "Edit" }, run = function() - return asset_store.open_asset_store("https://insality.github.io/core/druid_widget_store.json") + return asset_store.open({ + store_url = "https://insality.github.io/core/druid_widget_store.json", + info_url = "https://github.com/Insality/core/blob/main/druid_widget_store.md", + title = "Druid Asset Store", + install_prefs_key = "druid.asset_install_folder", + default_install_folder = DEFAULT_ASSET_INSTALL_FOLDER, + }) end }, From 3ec93645c5a5ad427906127779c67e4ca5275199 Mon Sep 17 00:00:00 2001 From: Insality Date: Tue, 11 Nov 2025 19:30:39 +0200 Subject: [PATCH 19/22] Up --- druid/editor_scripts/core/asset_store.lua | 4 +- .../{data.lua => asset_store_internal.lua} | 2 +- .../core/{ => asset_store}/base64.lua | 0 .../core/{ => asset_store}/installer.lua | 4 +- .../core/{ => asset_store}/path_replacer.lua | 0 .../core/asset_store_internal.lua | 41 ------------------- 6 files changed, 5 insertions(+), 46 deletions(-) rename druid/editor_scripts/core/asset_store/{data.lua => asset_store_internal.lua} (98%) rename druid/editor_scripts/core/{ => asset_store}/base64.lua (100%) rename druid/editor_scripts/core/{ => asset_store}/installer.lua (97%) rename druid/editor_scripts/core/{ => asset_store}/path_replacer.lua (100%) delete mode 100644 druid/editor_scripts/core/asset_store_internal.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua index f5f02d7..300a5cc 100644 --- a/druid/editor_scripts/core/asset_store.lua +++ b/druid/editor_scripts/core/asset_store.lua @@ -1,8 +1,8 @@ --- Main asset store module for Druid widgets --- Handles fetching widget data, displaying the store interface, and managing installations -local installer = require("druid.editor_scripts.core.installer") -local internal = require("druid.editor_scripts.core.asset_store.data") +local installer = require("druid.editor_scripts.core.asset_store.installer") +local internal = require("druid.editor_scripts.core.asset_store.asset_store_internal") local dialog_ui = require("druid.editor_scripts.core.asset_store.ui.dialog") local filters_ui = require("druid.editor_scripts.core.asset_store.ui.filters") local search_ui = require("druid.editor_scripts.core.asset_store.ui.search") diff --git a/druid/editor_scripts/core/asset_store/data.lua b/druid/editor_scripts/core/asset_store/asset_store_internal.lua similarity index 98% rename from druid/editor_scripts/core/asset_store/data.lua rename to druid/editor_scripts/core/asset_store/asset_store_internal.lua index 18ab864..068eb6f 100644 --- a/druid/editor_scripts/core/asset_store/data.lua +++ b/druid/editor_scripts/core/asset_store/asset_store_internal.lua @@ -1,4 +1,4 @@ -local installer = require("druid.editor_scripts.core.installer") +local installer = require("druid.editor_scripts.core.asset_store.installer") local M = {} diff --git a/druid/editor_scripts/core/base64.lua b/druid/editor_scripts/core/asset_store/base64.lua similarity index 100% rename from druid/editor_scripts/core/base64.lua rename to druid/editor_scripts/core/asset_store/base64.lua diff --git a/druid/editor_scripts/core/installer.lua b/druid/editor_scripts/core/asset_store/installer.lua similarity index 97% rename from druid/editor_scripts/core/installer.lua rename to druid/editor_scripts/core/asset_store/installer.lua index e339331..910575c 100644 --- a/druid/editor_scripts/core/installer.lua +++ b/druid/editor_scripts/core/asset_store/installer.lua @@ -1,8 +1,8 @@ --- Module for handling widget installation from zip files --- Downloads zip files and extracts them to the specified folder -local base64 = require("druid.editor_scripts.core.base64") -local path_replacer = require("druid.editor_scripts.core.path_replacer") +local base64 = require("druid.editor_scripts.core.asset_store.base64") +local path_replacer = require("druid.editor_scripts.core.asset_store.path_replacer") local M = {} diff --git a/druid/editor_scripts/core/path_replacer.lua b/druid/editor_scripts/core/asset_store/path_replacer.lua similarity index 100% rename from druid/editor_scripts/core/path_replacer.lua rename to druid/editor_scripts/core/asset_store/path_replacer.lua diff --git a/druid/editor_scripts/core/asset_store_internal.lua b/druid/editor_scripts/core/asset_store_internal.lua deleted file mode 100644 index 0bd9d96..0000000 --- a/druid/editor_scripts/core/asset_store_internal.lua +++ /dev/null @@ -1,41 +0,0 @@ -local data = require("druid.editor_scripts.core.asset_store.data") - - -local M = {} - - -function M.download_json(json_url) - return data.download_json(json_url) -end - - -function M.filter_items(items, query) - return data.filter_items(items, query) -end - - -function M.extract_authors(items) - return data.extract_authors(items) -end - - -function M.extract_tags(items) - return data.extract_tags(items) -end - - -function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) - return data.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) -end - - -function M.open_url(url) - if not url then - print("No URL available for:", url) - end - - editor.browse(url) -end - - -return M From 5f2d4bd8dc5d329e1f26889611862085624d8fc0 Mon Sep 17 00:00:00 2001 From: Insality Date: Thu, 13 Nov 2025 00:13:16 +0200 Subject: [PATCH 20/22] Up --- druid/editor_scripts/core/asset_store/path_replacer.lua | 2 -- druid/editor_scripts/core/asset_store/ui/widget_card.lua | 2 -- druid/editor_scripts/core/asset_store/ui/widget_list.lua | 3 --- 3 files changed, 7 deletions(-) diff --git a/druid/editor_scripts/core/asset_store/path_replacer.lua b/druid/editor_scripts/core/asset_store/path_replacer.lua index 1d0add6..791020c 100644 --- a/druid/editor_scripts/core/asset_store/path_replacer.lua +++ b/druid/editor_scripts/core/asset_store/path_replacer.lua @@ -6,8 +6,6 @@ local system = require("druid.editor_scripts.defold_parser.system.parser_interna local M = {} - - ---Replace paths in file content ---@param content string - File content ---@param author string - Author name (e.g., "Insality") diff --git a/druid/editor_scripts/core/asset_store/ui/widget_card.lua b/druid/editor_scripts/core/asset_store/ui/widget_card.lua index 1884d80..b6b5ac6 100644 --- a/druid/editor_scripts/core/asset_store/ui/widget_card.lua +++ b/druid/editor_scripts/core/asset_store/ui/widget_card.lua @@ -45,7 +45,6 @@ local function build_labels(overrides) end - function M.create(item, context) local labels = build_labels(context and context.labels) local open_url = context and context.open_url or function(_) end @@ -164,4 +163,3 @@ end return M - diff --git a/druid/editor_scripts/core/asset_store/ui/widget_list.lua b/druid/editor_scripts/core/asset_store/ui/widget_list.lua index 3322183..f6a570b 100644 --- a/druid/editor_scripts/core/asset_store/ui/widget_list.lua +++ b/druid/editor_scripts/core/asset_store/ui/widget_list.lua @@ -8,7 +8,6 @@ local function noop(...) end - local function build_context(overrides) return { on_install = overrides.on_install or noop, @@ -18,7 +17,6 @@ local function build_context(overrides) end - function M.create(items, overrides) local card_context = build_context(overrides or {}) local is_installed = overrides and overrides.is_installed or function(_) @@ -48,4 +46,3 @@ end return M - From 817387b426a0c7249578ab7005aa5b088c32b188 Mon Sep 17 00:00:00 2001 From: Insality Date: Sat, 15 Nov 2025 20:42:10 +0200 Subject: [PATCH 21/22] Move core to sep repo --- druid/editor_scripts/core/asset_store.lua | 253 ------------------ .../core/asset_store/asset_store_internal.lua | 196 -------------- .../core/asset_store/base64.lua | 35 --- .../core/asset_store/installer.lua | 224 ---------------- .../core/asset_store/path_replacer.lua | 121 --------- .../core/asset_store/ui/dialog.lua | 19 -- .../core/asset_store/ui/filters.lua | 119 -------- .../core/asset_store/ui/search.lua | 49 ---- .../core/asset_store/ui/settings.lua | 49 ---- .../core/asset_store/ui/widget_card.lua | 165 ------------ .../core/asset_store/ui/widget_list.lua | 48 ---- druid/editor_scripts/druid.editor_script | 15 -- game.project | 1 + 13 files changed, 1 insertion(+), 1293 deletions(-) delete mode 100644 druid/editor_scripts/core/asset_store.lua delete mode 100644 druid/editor_scripts/core/asset_store/asset_store_internal.lua delete mode 100644 druid/editor_scripts/core/asset_store/base64.lua delete mode 100644 druid/editor_scripts/core/asset_store/installer.lua delete mode 100644 druid/editor_scripts/core/asset_store/path_replacer.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/dialog.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/filters.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/search.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/settings.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/widget_card.lua delete mode 100644 druid/editor_scripts/core/asset_store/ui/widget_list.lua diff --git a/druid/editor_scripts/core/asset_store.lua b/druid/editor_scripts/core/asset_store.lua deleted file mode 100644 index 300a5cc..0000000 --- a/druid/editor_scripts/core/asset_store.lua +++ /dev/null @@ -1,253 +0,0 @@ ---- Main asset store module for Druid widgets ---- Handles fetching widget data, displaying the store interface, and managing installations - -local installer = require("druid.editor_scripts.core.asset_store.installer") -local internal = require("druid.editor_scripts.core.asset_store.asset_store_internal") -local dialog_ui = require("druid.editor_scripts.core.asset_store.ui.dialog") -local filters_ui = require("druid.editor_scripts.core.asset_store.ui.filters") -local search_ui = require("druid.editor_scripts.core.asset_store.ui.search") -local settings_ui = require("druid.editor_scripts.core.asset_store.ui.settings") -local widget_list_ui = require("druid.editor_scripts.core.asset_store.ui.widget_list") - -local M = {} - -local INFO_RESULT = "asset_store_open_info" -local DEFAULT_INSTALL_PREF_KEY = "druid.asset_install_folder" -local DEFAULT_INSTALL_FOLDER = "/widget" -local DEFAULT_TITLE = "Asset Store" -local DEFAULT_INFO_BUTTON = "Info" -local DEFAULT_CLOSE_BUTTON = "Close" -local DEFAULT_EMPTY_SEARCH_MESSAGE = "No widgets found matching '%s'." -local DEFAULT_EMPTY_FILTER_MESSAGE = "No widgets found matching the current filters." -local DEFAULT_SEARCH_LABELS = { - search_tooltip = "Search for widgets by title, author, or description" -} - - -local function normalize_config(input) - if type(input) == "string" then - input = { store_url = input } - end - - assert(type(input) == "table", "asset_store.open expects a string URL or config table") - assert(input.store_url, "asset_store.open requires a store_url") - - local config = { - store_url = input.store_url, - info_url = input.info_url, - title = input.title or DEFAULT_TITLE, - info_button_label = input.info_button_label or DEFAULT_INFO_BUTTON, - close_button_label = input.close_button_label or DEFAULT_CLOSE_BUTTON, - empty_search_message = input.empty_search_message or DEFAULT_EMPTY_SEARCH_MESSAGE, - empty_filter_message = input.empty_filter_message or DEFAULT_EMPTY_FILTER_MESSAGE, - install_prefs_key = input.install_prefs_key, - default_install_folder = input.default_install_folder or DEFAULT_INSTALL_FOLDER, - labels = input.labels or {}, - info_action = input.info_action, - } - - if config.install_prefs_key == nil then - config.install_prefs_key = DEFAULT_INSTALL_PREF_KEY - elseif config.install_prefs_key == false then - config.install_prefs_key = nil - end - - config.labels.search = config.labels.search or {} - for key, value in pairs(DEFAULT_SEARCH_LABELS) do - if config.labels.search[key] == nil then - config.labels.search[key] = value - end - end - - return config -end - - - -local function get_initial_install_folder(config) - if not config.install_prefs_key then - return config.default_install_folder - end - - return editor.prefs.get(config.install_prefs_key) or config.default_install_folder -end - - - -local function persist_install_folder(config, folder) - if not config.install_prefs_key then - return - end - - editor.prefs.set(config.install_prefs_key, folder) -end - - - ----Handle widget installation ----@param item table - Widget item to install ----@param install_folder string - Installation folder ----@param all_items table - List of all widgets for dependency resolution ----@param on_success function - Success callback ----@param on_error function - Error callback -local function handle_install(item, install_folder, all_items, on_success, on_error) - print("Installing widget:", item.id) - - local success, message = installer.install_widget(item, install_folder, all_items) - - if success then - print("Installation successful:", message) - on_success(message) - else - print("Installation failed:", message) - on_error(message) - end -end - - -function M.open(config_input) - local config = normalize_config(config_input) - - print("Opening " .. config.title .. " from:", config.store_url) - - local store_data, fetch_error = internal.download_json(config.store_url) - if not store_data then - print("Failed to load store items:", fetch_error) - return - end - print("Successfully loaded", #store_data.items, "items") - - local initial_items = store_data.items - local initial_install_folder = get_initial_install_folder(config) - local filter_overrides = config.labels.filters and { labels = config.labels.filters } or nil - - local dialog_component = editor.ui.component(function(props) - local all_items = editor.ui.use_state(initial_items) - local install_folder, set_install_folder = editor.ui.use_state(initial_install_folder) - local search_query, set_search_query = editor.ui.use_state("") - local filter_type, set_filter_type = editor.ui.use_state("All") - local filter_author, set_filter_author = editor.ui.use_state("All Authors") - local filter_tag, set_filter_tag = editor.ui.use_state("All Tags") - local install_status, set_install_status = editor.ui.use_state("") - - local authors = editor.ui.use_memo(internal.extract_authors, all_items) - local tags = editor.ui.use_memo(internal.extract_tags, all_items) - - local type_options = editor.ui.use_memo(filters_ui.build_type_options, filter_overrides) - local author_options = editor.ui.use_memo(filters_ui.build_author_options, authors, filter_overrides) - local tag_options = editor.ui.use_memo(filters_ui.build_tag_options, tags, filter_overrides) - - local filtered_items = editor.ui.use_memo( - internal.filter_items_by_filters, - all_items, - search_query, - filter_type, - filter_author, - filter_tag, - install_folder - ) - - local function on_install(item) - handle_install(item, install_folder, all_items, - function(message) - set_install_status("Success: " .. message) - end, - function(message) - set_install_status("Error: " .. message) - end - ) - end - - local content_children = {} - - table.insert(content_children, settings_ui.create({ - install_folder = install_folder, - on_install_folder_changed = function(new_folder) - set_install_folder(new_folder) - persist_install_folder(config, new_folder) - end, - labels = config.labels.settings - })) - - table.insert(content_children, filters_ui.create({ - filter_type = filter_type, - filter_author = filter_author, - filter_tag = filter_tag, - type_options = type_options, - author_options = author_options, - tag_options = tag_options, - on_type_change = set_filter_type, - on_author_change = set_filter_author, - on_tag_change = set_filter_tag, - labels = config.labels.filters, - })) - - table.insert(content_children, search_ui.create({ - search_query = search_query, - on_search = set_search_query, - labels = config.labels.search, - })) - - if #filtered_items == 0 then - local message = config.empty_filter_message - if search_query ~= "" then - message = string.format(config.empty_search_message, search_query) - end - table.insert(content_children, editor.ui.label({ - text = message, - color = editor.ui.COLOR.HINT, - alignment = editor.ui.ALIGNMENT.CENTER - })) - else - table.insert(content_children, widget_list_ui.create(filtered_items, { - on_install = on_install, - open_url = internal.open_url, - is_installed = function(item) - return installer.is_widget_installed(item, install_folder) - end, - labels = config.labels.widget_card, - })) - end - - if install_status ~= "" then - table.insert(content_children, editor.ui.label({ - text = install_status, - color = install_status:find("Success") and editor.ui.COLOR.TEXT or editor.ui.COLOR.ERROR, - alignment = editor.ui.ALIGNMENT.CENTER - })) - end - - local buttons = {} - if config.info_url or config.info_action then - table.insert(buttons, editor.ui.dialog_button({ - text = config.info_button_label, - result = INFO_RESULT, - })) - end - table.insert(buttons, editor.ui.dialog_button({ - text = config.close_button_label, - cancel = true - })) - - return dialog_ui.build({ - title = config.title, - children = content_children, - buttons = buttons - }) - end) - - local result = editor.ui.show_dialog(dialog_component({})) - - if result and result == INFO_RESULT then - if config.info_action then - config.info_action() - elseif config.info_url then - internal.open_url(config.info_url) - end - end - - return {} -end - - -return M diff --git a/druid/editor_scripts/core/asset_store/asset_store_internal.lua b/druid/editor_scripts/core/asset_store/asset_store_internal.lua deleted file mode 100644 index 068eb6f..0000000 --- a/druid/editor_scripts/core/asset_store/asset_store_internal.lua +++ /dev/null @@ -1,196 +0,0 @@ -local installer = require("druid.editor_scripts.core.asset_store.installer") - - -local M = {} - - -local function normalize_query(query) - if not query or query == "" then - return nil - end - - return string.lower(query) -end - - -local function is_unlisted_visible(item, lower_query) - if not item.unlisted then - return true - end - - if not lower_query or not item.id then - return false - end - - return string.lower(item.id) == lower_query -end - - -function M.download_json(json_url) - local response = http.request(json_url, { as = "json" }) - - if response.status ~= 200 then - return nil, "Failed to fetch store data. HTTP status: " .. response.status - end - - if not response.body or not response.body.items then - return nil, "Invalid store data format" - end - - return response.body, nil -end - - -function M.filter_items(items, query) - if query == "" or query == nil then - return items - end - - local filtered = {} - local lower_query = string.lower(query) - - for _, item in ipairs(items) do - local matches = false - if item.id and string.find(string.lower(item.id), lower_query, 1, true) then - matches = true - elseif item.title and string.find(string.lower(item.title), lower_query, 1, true) then - matches = true - elseif item.author and string.find(string.lower(item.author), lower_query, 1, true) then - matches = true - elseif item.description and string.find(string.lower(item.description), lower_query, 1, true) then - matches = true - end - - if not matches and item.tags then - for _, tag in ipairs(item.tags) do - if string.find(string.lower(tag), lower_query, 1, true) then - matches = true - break - end - end - end - - if not matches and item.depends then - for _, dep in ipairs(item.depends) do - if string.find(string.lower(dep), lower_query, 1, true) then - matches = true - break - end - end - end - - if matches then - table.insert(filtered, item) - end - end - - return filtered -end - - -function M.extract_authors(items) - local authors = {} - local author_set = {} - - for _, item in ipairs(items) do - if not item.unlisted and item.author and not author_set[item.author] then - author_set[item.author] = true - table.insert(authors, item.author) - end - end - - table.sort(authors) - - return authors -end - - -function M.extract_tags(items) - local tags = {} - local tag_set = {} - - for _, item in ipairs(items) do - if not item.unlisted and item.tags then - for _, tag in ipairs(item.tags) do - if not tag_set[tag] then - tag_set[tag] = true - table.insert(tags, tag) - end - end - end - end - - table.sort(tags) - - return tags -end - - -function M.filter_items_by_filters(items, search_query, filter_type, filter_author, filter_tag, install_folder) - local lower_query = normalize_query(search_query) - local visible_items = {} - - for _, item in ipairs(items) do - if is_unlisted_visible(item, lower_query) then - table.insert(visible_items, item) - end - end - - local filtered = visible_items - - if lower_query then - filtered = M.filter_items(filtered, search_query) - end - - if filter_type and filter_type ~= "All" then - local type_filtered = {} - for _, item in ipairs(filtered) do - local is_installed = installer.is_widget_installed(item, install_folder) - if (filter_type == "Installed" and is_installed) or - (filter_type == "Not Installed" and not is_installed) then - table.insert(type_filtered, item) - end - end - filtered = type_filtered - end - - if filter_author and filter_author ~= "All Authors" then - local author_filtered = {} - for _, item in ipairs(filtered) do - if item.author == filter_author then - table.insert(author_filtered, item) - end - end - filtered = author_filtered - end - - if filter_tag and filter_tag ~= "All Tags" then - local tag_filtered = {} - for _, item in ipairs(filtered) do - if item.tags then - for _, tag in ipairs(item.tags) do - if tag == filter_tag then - table.insert(tag_filtered, item) - break - end - end - end - end - filtered = tag_filtered - end - - return filtered -end - - -function M.open_url(url) - if not url then - print("No URL available for:", url) - end - - editor.browse(url) -end - - -return M - diff --git a/druid/editor_scripts/core/asset_store/base64.lua b/druid/editor_scripts/core/asset_store/base64.lua deleted file mode 100644 index ebc174f..0000000 --- a/druid/editor_scripts/core/asset_store/base64.lua +++ /dev/null @@ -1,35 +0,0 @@ --- base64 encode/decode (http://lua-users.org/wiki/BaseSixtyFour) - -local M = {} - -local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' - -function M.encode(data) - return ((data:gsub('.', function(x) - local r,byte_val='',x:byte() - for i=8,1,-1 do r=r..(byte_val%2^i-byte_val%2^(i-1)>0 and '1' or '0') end - return r; - end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) - if (#x < 6) then return '' end - local c=0 - for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end - return b:sub(c+1,c+1) - end)..({ '', '==', '=' })[#data%3+1]) -end - -function M.decode(data) - data = string.gsub(data, '[^'..b..'=]', '') - return (data:gsub('.', function(x) - if (x == '=') then return '' end - local r,f='',(b:find(x)-1) - for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end - return r; - end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) - if (#x ~= 8) then return '' end - local c=0 - for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end - return string.char(c) - end)) -end - -return M diff --git a/druid/editor_scripts/core/asset_store/installer.lua b/druid/editor_scripts/core/asset_store/installer.lua deleted file mode 100644 index 910575c..0000000 --- a/druid/editor_scripts/core/asset_store/installer.lua +++ /dev/null @@ -1,224 +0,0 @@ ---- Module for handling widget installation from zip files ---- Downloads zip files and extracts them to the specified folder - -local base64 = require("druid.editor_scripts.core.asset_store.base64") -local path_replacer = require("druid.editor_scripts.core.asset_store.path_replacer") - -local M = {} - ----@class druid.core.item_info ----@field id string ----@field version string ----@field title string ----@field author string ----@field description string ----@field api string ----@field author_url string ----@field image string ----@field manifest_url string ----@field zip_url string ----@field json_zip_url string ----@field sha256 string ----@field size number ----@field depends string[] ----@field tags string[] - - ----Download a file from URL ----@param url string - The URL to download from ----@return string|nil, string|nil, table|nil - Downloaded content or nil, filename or nil, content list or nil -local function download_file_zip_json(url) - local response = http.request(url, { as = "json" }) - - if response.status ~= 200 then - print("Failed to download file. HTTP status: " .. response.status) - return nil - end - - local data = response.body - local content_list = data.content -- Array of file paths from zip - - return base64.decode(data.data), data.filename, content_list -end - - ----Find widget by dependency string (format: "author:widget_id@version" or "author@widget_id" or "widget_id") ----@param dep_string string - Dependency string ----@param all_items table - List of all available widgets ----@return table|nil - Found widget item or nil -local function find_widget_by_dependency(dep_string, all_items) - if not dep_string or not all_items then - return nil - end - - local author, widget_id - - -- Try format: "author:widget_id@version" (e.g., "Insality:mini_graph@1") - author, widget_id = dep_string:match("^([^:]+):([^@]+)@") - if not author then - -- Try format: "author@widget_id" (e.g., "insality@mini_graph") - author, widget_id = dep_string:match("^([^@]+)@(.+)$") - if not author then - -- No author specified, search by widget_id only - widget_id = dep_string - end - end - - for _, item in ipairs(all_items) do - if item.id == widget_id then - -- If author was specified, check it matches (case-insensitive) - if not author or string.lower(item.author or "") == string.lower(author) then - return item - end - end - end - - return nil -end - - ----Install widget dependencies recursively ----@param item druid.core.item_info - Widget item ----@param all_items table - List of all available widgets ----@param install_folder string - Installation folder ----@param installing_set table - Set of widget IDs currently being installed (to prevent cycles) ----@return boolean, string|nil - Success status and message -local function install_dependencies(item, all_items, install_folder, installing_set) - if not item.depends or #item.depends == 0 then - return true, nil - end - - installing_set = installing_set or {} - - for _, dep_string in ipairs(item.depends) do - local dep_item = find_widget_by_dependency(dep_string, all_items) - if not dep_item then - print("Warning: Dependency not found:", dep_string) - -- Continue with other dependencies - else - -- Check if already installed - if M.is_widget_installed(dep_item, install_folder) then - print("Dependency already installed:", dep_item.id) - else - -- Check for circular dependencies - if installing_set[dep_item.id] then - print("Warning: Circular dependency detected:", dep_item.id) - -- Continue with other dependencies - else - print("Installing dependency:", dep_item.id) - local success, err = M.install_widget(dep_item, install_folder, all_items, installing_set) - if not success then - return false, "Failed to install dependency " .. dep_item.id .. ": " .. (err or "unknown error") - end - end - end - end - end - - return true, nil -end - - ----Install a widget from a zip URL ----@param item druid.core.item_info - Widget item data containing zip_url and id ----@param install_folder string - Target folder to install to ----@param all_items table|nil - Optional list of all widgets for dependency resolution ----@param installing_set table|nil - Optional set of widget IDs currently being installed (to prevent cycles) ----@return boolean, string - Success status and message -function M.install_widget(item, install_folder, all_items, installing_set) - if not item.json_zip_url or not item.id then - return false, "Invalid widget data: missing json_zip_url or id" - end - - -- Install dependencies first if all_items is provided - if all_items then - installing_set = installing_set or {} - if installing_set[item.id] then - return false, "Circular dependency detected: " .. item.id - end - installing_set[item.id] = true - local dep_success, dep_err = install_dependencies(item, all_items, install_folder, installing_set) - if not dep_success then - installing_set[item.id] = nil - return false, dep_err or "Failed to install dependencies" - end - end - - -- Download the zip file - local zip_data, filename, content_list = download_file_zip_json(item.json_zip_url) - if not zip_data or not filename then - if installing_set then - installing_set[item.id] = nil - end - return false, "Failed to download widget: " .. (filename or "unknown error") - end - - if content_list then - print("Got file list from JSON:", #content_list, "files") - else - print("Warning: No content list in JSON data") - end - - - local zip_file_path = "." .. install_folder .. "/" .. filename - local zip_file = io.open(zip_file_path, "wb") - if not zip_file then - if installing_set then - installing_set[item.id] = nil - end - print("Directory does not exist: " .. install_folder) - print("Please create the directory manually and try again.") - return false, "Directory does not exist: " .. install_folder - end - - zip_file:write(zip_data) - zip_file:close() - print("Zip written to file: " .. zip_file_path) - - -- Unzip the zip file - local folder_path = "." .. install_folder .. "/" .. item.id - - zip.unpack(zip_file_path, folder_path) - print("Widget unpacked successfully") - - -- Remove the zip file - os.remove(zip_file_path) - print("Zip file removed successfully") - - -- Process paths within the extracted widget - if content_list and #content_list > 0 then - local success, err = path_replacer.process_widget_paths(folder_path, install_folder, item.id, item.author, content_list) - if not success then - print("Warning: Path replacement failed:", err) - -- Don't fail installation if path replacement fails, just warn - end - else - print("Warning: No file list available, skipping path replacement") - end - - if installing_set then - installing_set[item.id] = nil - end - - return true, "Widget installed successfully" -end - - ----Check if a widget is already installed ----@param item table - Widget item data containing id ----@param install_folder string - Install folder to check in ----@return boolean - True if widget is already installed -function M.is_widget_installed(item, install_folder) - local p = editor.resource_attributes(install_folder .. "/" .. item.id) - return p.exists -end - - ----Get installation folder ----@return string - Installation folder path -function M.get_install_folder() - return editor.prefs.get("druid.asset_install_folder") -end - - -return M diff --git a/druid/editor_scripts/core/asset_store/path_replacer.lua b/druid/editor_scripts/core/asset_store/path_replacer.lua deleted file mode 100644 index 791020c..0000000 --- a/druid/editor_scripts/core/asset_store/path_replacer.lua +++ /dev/null @@ -1,121 +0,0 @@ ---- Module for replacing widget paths in installed files ---- Handles path replacement from original widget structure to user's installation path - -local system = require("druid.editor_scripts.defold_parser.system.parser_internal") - -local M = {} - - ----Replace paths in file content ----@param content string - File content ----@param author string - Author name (e.g., "Insality") ----@param install_folder string - Installation folder (e.g., "widget") ----@return string - Modified content -local function replace_paths_in_content(content, author, install_folder) - if not content or not author then - return content - end - - -- Escape special characters for literal string replacement - local function escape_pattern(str) - return str:gsub("[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") - end - - -- Remove leading / from install_folder if present - local clean_install_folder = install_folder - if clean_install_folder:sub(1, 1) == "/" then - clean_install_folder = clean_install_folder:sub(2) - end - - -- Replace all paths with author: widget/Insality/* -> widget/* - local author_path_pattern = escape_pattern(clean_install_folder .. "/" .. author .. "/") - local target_path_prefix = clean_install_folder .. "/" - content = content:gsub(author_path_pattern, target_path_prefix) - - -- Replace all require statements with dots: widget.Insality.* -> widget.* - local author_dots_pattern = escape_pattern(clean_install_folder .. "." .. author .. ".") - local target_dots_prefix = clean_install_folder .. "." - content = content:gsub(author_dots_pattern, target_dots_prefix) - - -- Also replace paths that start with author directly: Insality/widget -> widget - -- But only if they're in require statements or paths - local author_start_pattern = escape_pattern(author .. "/") - content = content:gsub(author_start_pattern, "") - local author_start_dots_pattern = escape_pattern(author .. ".") - content = content:gsub(author_start_dots_pattern, "") - - return content -end - - ----Process widget paths in all files ----@param folder_path string - Path to the unpacked widget folder ----@param install_folder string - Installation folder (e.g., "widget") ----@param widget_id string - Widget ID (e.g., "fps_panel") ----@param author string|nil - Author name (e.g., "Insality") ----@param file_list table - Optional list of file paths from zip content ----@return boolean, string|nil - Success status and error message if any -function M.process_widget_paths(folder_path, install_folder, widget_id, author, file_list) - print("Processing widget paths in:", folder_path) - - if not author then - print("Warning: Missing author, skipping path replacement") - return true, nil - end - - print("Replacing all paths with author:", author, "in install folder:", install_folder) - - -- Get absolute project path - local absolute_project_path = editor.external_file_attributes(".").path - if not absolute_project_path:match("[\\/]$") then - absolute_project_path = absolute_project_path .. "/" - end - - -- Clean folder_path - local clean_folder_path = folder_path - if clean_folder_path:sub(1, 1) == "." then - clean_folder_path = clean_folder_path:sub(2) - end - if clean_folder_path:sub(1, 1) == "/" then - clean_folder_path = clean_folder_path:sub(2) - end - - -- Process each file from the list - local processed_count = 0 - for _, file_path_in_zip in ipairs(file_list) do - -- Build full path to the file after unpacking - local file_path = clean_folder_path .. "/" .. file_path_in_zip - - -- Get absolute path - local clean_file_path = file_path - if clean_file_path:sub(1, 1) == "/" then - clean_file_path = clean_file_path:sub(2) - end - local absolute_file_path = absolute_project_path .. clean_file_path - - -- Read file content - local content, err = system.read_file(absolute_file_path) - if not content then - print("Warning: Could not read file:", file_path, err) - else - -- Replace all paths with author - local modified_content = replace_paths_in_content(content, author, install_folder) - if modified_content ~= content then - -- Write modified content back - local success, write_err = system.write_file(absolute_file_path, modified_content) - if success then - processed_count = processed_count + 1 - print("Processed:", file_path) - else - print("Warning: Could not write file:", file_path, write_err) - end - end - end - end - - print("Path replacement complete. Processed", processed_count, "files") - return true, nil -end - - -return M diff --git a/druid/editor_scripts/core/asset_store/ui/dialog.lua b/druid/editor_scripts/core/asset_store/ui/dialog.lua deleted file mode 100644 index 7b6dfac..0000000 --- a/druid/editor_scripts/core/asset_store/ui/dialog.lua +++ /dev/null @@ -1,19 +0,0 @@ -local M = {} - - -function M.build(params) - return editor.ui.dialog({ - title = params.title or "Asset Store", - content = editor.ui.vertical({ - spacing = params.spacing or editor.ui.SPACING.MEDIUM, - padding = params.padding or editor.ui.PADDING.SMALL, - grow = true, - children = params.children or {} - }), - buttons = params.buttons or {} - }) -end - - -return M - diff --git a/druid/editor_scripts/core/asset_store/ui/filters.lua b/druid/editor_scripts/core/asset_store/ui/filters.lua deleted file mode 100644 index efa750d..0000000 --- a/druid/editor_scripts/core/asset_store/ui/filters.lua +++ /dev/null @@ -1,119 +0,0 @@ -local DEFAULT_LABELS = { - type_label = "Type:", - author_label = "Author:", - tag_label = "Tag:", - all_types = "All", - installed = "Installed", - not_installed = "Not Installed", - all_authors = "All Authors", - all_tags = "All Tags", -} - - -local M = {} - - -local function build_labels(overrides) - if not overrides then - return DEFAULT_LABELS - end - - local labels = {} - for key, value in pairs(DEFAULT_LABELS) do - labels[key] = overrides[key] or value - end - - return labels -end - - -function M.build_type_options(overrides) - local labels = build_labels(overrides and overrides.labels) - - return { - labels.all_types, - labels.installed, - labels.not_installed, - } -end - - -function M.build_author_options(authors, overrides) - local labels = build_labels(overrides and overrides.labels) - local options = {labels.all_authors} - - for _, author in ipairs(authors or {}) do - table.insert(options, author) - end - - return options -end - - -function M.build_tag_options(tags, overrides) - local labels = build_labels(overrides and overrides.labels) - local options = {labels.all_tags} - - for _, tag in ipairs(tags or {}) do - table.insert(options, tag) - end - - return options -end - - -function M.create(params) - local labels = build_labels(params and params.labels) - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = labels.type_label, - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = params.filter_type, - options = params.type_options, - on_value_changed = params.on_type_change - }) - } - }), - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = labels.author_label, - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = params.filter_author, - options = params.author_options, - on_value_changed = params.on_author_change - }) - } - }), - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = labels.tag_label, - color = editor.ui.COLOR.TEXT - }), - editor.ui.select_box({ - value = params.filter_tag, - options = params.tag_options, - on_value_changed = params.on_tag_change - }) - } - }) - } - }) -end - - -return M - diff --git a/druid/editor_scripts/core/asset_store/ui/search.lua b/druid/editor_scripts/core/asset_store/ui/search.lua deleted file mode 100644 index 223b0d2..0000000 --- a/druid/editor_scripts/core/asset_store/ui/search.lua +++ /dev/null @@ -1,49 +0,0 @@ -local DEFAULT_LABELS = { - search_label = "Search:", - search_title = "Search:", - search_tooltip = "Search for items", -} - - -local M = {} - - -local function build_labels(overrides) - if not overrides then - return DEFAULT_LABELS - end - - local labels = {} - for key, value in pairs(DEFAULT_LABELS) do - labels[key] = overrides[key] or value - end - - return labels -end - - - -function M.create(params) - local labels = build_labels(params and params.labels) - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.label({ - text = labels.search_label, - color = editor.ui.COLOR.TEXT - }), - editor.ui.string_field({ - value = params.search_query or "", - on_value_changed = params.on_search, - title = labels.search_title, - tooltip = labels.search_tooltip, - grow = true - }) - } - }) -end - - -return M - diff --git a/druid/editor_scripts/core/asset_store/ui/settings.lua b/druid/editor_scripts/core/asset_store/ui/settings.lua deleted file mode 100644 index 6731f52..0000000 --- a/druid/editor_scripts/core/asset_store/ui/settings.lua +++ /dev/null @@ -1,49 +0,0 @@ -local DEFAULT_LABELS = { - install_label = "Installation Folder:", - install_title = "Installation Folder:", - install_tooltip = "The folder to install the assets to", -} - - -local M = {} - - -local function build_labels(overrides) - if not overrides then - return DEFAULT_LABELS - end - - local labels = {} - for key, value in pairs(DEFAULT_LABELS) do - labels[key] = overrides[key] or value - end - - return labels -end - - - -function M.create(params) - local labels = build_labels(params and params.labels) - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.MEDIUM, - children = { - editor.ui.label({ - spacing = editor.ui.SPACING.MEDIUM, - text = labels.install_label, - color = editor.ui.COLOR.TEXT - }), - editor.ui.string_field({ - value = params.install_folder, - on_value_changed = params.on_install_folder_changed, - title = labels.install_title, - tooltip = labels.install_tooltip, - }), - } - }) -end - - -return M - diff --git a/druid/editor_scripts/core/asset_store/ui/widget_card.lua b/druid/editor_scripts/core/asset_store/ui/widget_card.lua deleted file mode 100644 index b6b5ac6..0000000 --- a/druid/editor_scripts/core/asset_store/ui/widget_card.lua +++ /dev/null @@ -1,165 +0,0 @@ -local DEFAULT_LABELS = { - install_button = "Install", - api_button = "API", - example_button = "Example", - author_caption = "Author", - installed_tag = "✓ Installed", - tags_prefix = "Tags: ", - depends_prefix = "Depends: ", - size_separator = "• ", - unknown_size = "Unknown size", - unknown_version = "Unknown version", -} - - -local M = {} - - -local function format_size(size_bytes) - if not size_bytes then - return DEFAULT_LABELS.unknown_size - end - - if size_bytes < 1024 then - return size_bytes .. " B" - elseif size_bytes < 1024 * 1024 then - return math.floor(size_bytes / 1024) .. " KB" - end - - return math.floor(size_bytes / (1024 * 1024)) .. " MB" -end - - - -local function build_labels(overrides) - if not overrides then - return DEFAULT_LABELS - end - - local labels = {} - for key, value in pairs(DEFAULT_LABELS) do - labels[key] = overrides[key] or value - end - - return labels -end - - -function M.create(item, context) - local labels = build_labels(context and context.labels) - local open_url = context and context.open_url or function(_) end - local on_install = context and context.on_install or function(...) end - local is_installed = context and context.is_installed or false - - local size_text = format_size(item.size) - local version_text = item.version and ("v" .. item.version) or labels.unknown_version - local tags_text = item.tags and #item.tags > 0 and labels.tags_prefix .. table.concat(item.tags, ", ") or "" - local deps_text = item.depends and #item.depends > 0 and labels.depends_prefix .. table.concat(item.depends, ", ") or "" - - local widget_details_children = { - editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = { - editor.ui.label({ - text = item.title or item.id, - color = editor.ui.COLOR.OVERRIDE - }), - editor.ui.label({ - text = version_text, - color = editor.ui.COLOR.WARNING - }), - editor.ui.label({ - text = labels.size_separator .. size_text, - color = editor.ui.COLOR.HINT - }), - } - }), - editor.ui.paragraph({ - text = item.description or "No description available", - color = editor.ui.COLOR.TEXT - }) - } - - if tags_text ~= "" then - table.insert(widget_details_children, editor.ui.label({ - text = tags_text, - color = editor.ui.COLOR.HINT - })) - end - - if deps_text ~= "" then - table.insert(widget_details_children, editor.ui.label({ - text = deps_text, - color = editor.ui.COLOR.HINT - })) - end - - if is_installed then - table.insert(widget_details_children, editor.ui.label({ - text = labels.installed_tag, - color = editor.ui.COLOR.WARNING - })) - end - - local button_children = { - editor.ui.button({ - text = labels.install_button, - on_pressed = on_install, - enabled = not is_installed - }) - } - - if item.api then - table.insert(button_children, editor.ui.button({ - text = labels.api_button, - on_pressed = function() open_url(item.api) end, - enabled = item.api ~= nil - })) - end - - if item.example_url then - table.insert(button_children, editor.ui.button({ - text = labels.example_button, - on_pressed = function() open_url(item.example_url) end, - enabled = item.example_url ~= nil - })) - end - - table.insert(button_children, editor.ui.horizontal({ grow = true })) - - if item.author_url then - table.insert(button_children, editor.ui.label({ - text = labels.author_caption, - color = editor.ui.COLOR.HINT - })) - table.insert(button_children, editor.ui.button({ - text = item.author or labels.author_caption, - on_pressed = function() open_url(item.author_url) end, - enabled = item.author_url ~= nil - })) - end - - table.insert(widget_details_children, editor.ui.horizontal({ - spacing = editor.ui.SPACING.SMALL, - children = button_children - })) - - return editor.ui.horizontal({ - spacing = editor.ui.SPACING.NONE, - padding = editor.ui.PADDING.SMALL, - children = { - editor.ui.label({ - text = "•••", - color = editor.ui.COLOR.HINT - }), - editor.ui.vertical({ - spacing = editor.ui.SPACING.SMALL, - grow = true, - children = widget_details_children - }), - } - }) -end - - -return M diff --git a/druid/editor_scripts/core/asset_store/ui/widget_list.lua b/druid/editor_scripts/core/asset_store/ui/widget_list.lua deleted file mode 100644 index f6a570b..0000000 --- a/druid/editor_scripts/core/asset_store/ui/widget_list.lua +++ /dev/null @@ -1,48 +0,0 @@ -local widget_card = require("druid.editor_scripts.core.asset_store.ui.widget_card") - - -local M = {} - - -local function noop(...) -end - - -local function build_context(overrides) - return { - on_install = overrides.on_install or noop, - open_url = overrides.open_url or noop, - labels = overrides.labels, - } -end - - -function M.create(items, overrides) - local card_context = build_context(overrides or {}) - local is_installed = overrides and overrides.is_installed or function(_) - return false - end - - local widget_items = {} - for _, item in ipairs(items) do - local context = { - on_install = function() - card_context.on_install(item) - end, - open_url = card_context.open_url, - labels = card_context.labels, - is_installed = is_installed(item), - } - - table.insert(widget_items, widget_card.create(item, context)) - end - - return editor.ui.scroll({ - content = editor.ui.vertical({ - children = widget_items - }) - }) -end - - -return M diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index 2431208..bff6596 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -2,7 +2,6 @@ local assign_layers = require("druid.editor_scripts.assign_layers") local create_druid_widget = require("druid.editor_scripts.create_druid_widget") local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script") local druid_settings = require("druid.editor_scripts.druid_settings") -local asset_store = require("druid.editor_scripts.core.asset_store") -- Reuse tip: copy the snippet below into another editor script to open a custom store. -- asset_store.open({ -- store_url = "https://example.com/store.json", @@ -79,20 +78,6 @@ function M.get_commands() end }, - { - label = "[Druid] Asset Store", - locations = { "Edit" }, - run = function() - return asset_store.open({ - store_url = "https://insality.github.io/core/druid_widget_store.json", - info_url = "https://github.com/Insality/core/blob/main/druid_widget_store.md", - title = "Druid Asset Store", - install_prefs_key = "druid.asset_install_folder", - default_install_folder = DEFAULT_ASSET_INSTALL_FOLDER, - }) - end - }, - { label = "[Druid] Settings", locations = { "Edit" }, diff --git a/game.project b/game.project index 2a3093d..6d0bd17 100644 --- a/game.project +++ b/game.project @@ -25,6 +25,7 @@ dependencies#3 = https://github.com/Insality/panthera/archive/refs/tags/runtime. dependencies#4 = https://github.com/Insality/defold-lang/archive/refs/tags/3.zip dependencies#5 = https://github.com/Insality/defold-event/archive/refs/tags/13.zip dependencies#6 = https://github.com/subsoap/defos/archive/refs/tags/v2.8.0.zip +dependencies#7 = https://github.com/Insality/core/archive/refs/heads/main.zip [library] include_dirs = druid From 5a36fa0d231ffa3987bb74c155006f2f713f9e17 Mon Sep 17 00:00:00 2001 From: Insality Date: Sat, 15 Nov 2025 20:43:26 +0200 Subject: [PATCH 22/22] Clear editor script from core --- druid/editor_scripts/druid.editor_script | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/druid/editor_scripts/druid.editor_script b/druid/editor_scripts/druid.editor_script index bff6596..43057f8 100644 --- a/druid/editor_scripts/druid.editor_script +++ b/druid/editor_scripts/druid.editor_script @@ -2,20 +2,11 @@ local assign_layers = require("druid.editor_scripts.assign_layers") local create_druid_widget = require("druid.editor_scripts.create_druid_widget") local create_druid_gui_script = require("druid.editor_scripts.create_druid_gui_script") local druid_settings = require("druid.editor_scripts.druid_settings") --- Reuse tip: copy the snippet below into another editor script to open a custom store. --- asset_store.open({ --- store_url = "https://example.com/store.json", --- info_url = "https://example.com/docs", --- title = "My Library Store", --- install_prefs_key = "my_lib.asset_install_folder", --- default_install_folder = "/my_widgets", --- }) local M = {} local DEFAULT_WIDGET_TEMPLATE_PATH = "/druid/templates/widget_full.lua.template" local DEFAULT_GUI_SCRIPT_TEMPLATE_PATH = "/druid/templates/druid.gui_script.template" -local DEFAULT_ASSET_INSTALL_FOLDER = "/widget" ---Define preferences schema function M.get_prefs_schema() @@ -28,10 +19,6 @@ function M.get_prefs_schema() default = DEFAULT_GUI_SCRIPT_TEMPLATE_PATH, scope = editor.prefs.SCOPE.PROJECT }), - ["druid.asset_install_folder"] = editor.prefs.schema.string({ - default = DEFAULT_ASSET_INSTALL_FOLDER, - scope = editor.prefs.SCOPE.PROJECT - }) } end