33 Commits

Author SHA1 Message Date
Insality
19d84220f9 Merge branch 'master' into develop 2025-09-28 22:36:29 +03:00
Insality
134468da30 Add image component 2025-09-28 22:34:27 +03:00
Insality
b2d73bafa4 Release 1.1.6 2025-09-27 14:02:11 +03:00
Insality
872af8fb8e Update editor script linking messages 2025-09-27 14:02:11 +03:00
Insality
46b7d828f0 Use editor script to set gui script path 2025-09-27 14:02:11 +03:00
Insality
181a0aab43 Update docs and changelog 2025-09-27 13:52:47 +03:00
Insality
9e29c0ebde Update editor script linking messages 2025-09-27 13:34:15 +03:00
Insality
2a754c4159 Use editor script to set gui script path 2025-09-27 13:24:31 +03:00
Insality
9bf4d32d40 Update 2025-09-27 12:49:43 +03:00
Insality
9ee084c502 Update 2025-09-15 00:56:05 +03:00
Maksim Tuprikov
044eec50b2 Merge pull request #322 from vlaaad/patch-1 2025-08-22 14:43:05 +02:00
vlaaad
066a81f5f3 Create ext.properties 2025-08-22 14:36:54 +02:00
Insality
b6a7df5ff2 Update 2025-08-11 23:59:46 +03:00
Insality
a94121a1c6 Update properties panel 2025-08-10 00:56:08 +03:00
Insality
097963bdb3 Annotations update 2025-08-10 00:55:38 +03:00
Insality
574f764559 Add layout set_position_function 2025-08-08 18:22:03 +03:00
Insality
379c9acfc3 Add memory fps panel manual 2025-07-05 13:35:16 +03:00
Insality
4c8796e17d Merge branch 'master' into develop 2025-07-02 21:52:18 +03:00
Insality
ba7ee40510 Release 1.1.5 2025-07-02 21:51:43 +03:00
Insality
d019247ae4 Update for event v12 2025-07-02 21:50:48 +03:00
Insality
5dac7ed8e2 Merge branch 'properties_panel' into develop 2025-07-02 21:43:25 +03:00
Insality
b35b584fad Merge branch 'master' into develop 2025-06-19 20:28:56 +03:00
Insality
616c513fbd Use tagged releases of dependencies 2025-06-19 20:28:45 +03:00
Insality
3606c9f49f Fix color check 2025-06-19 20:24:57 +03:00
Insality
a8aea0adee Merge branch 'master' into develop 2025-05-28 23:28:31 +03:00
Insality
8fde964e0e Revert "Merge pull request #311 from rigo128/fix_get_screen_aspect_koef"
This reverts commit 9a0f341a67, reversing
changes made to 8ddb6e4e60.

Revert until fully investigated
2025-05-28 23:28:03 +03:00
Insality
7b5264aca0 Update changelog 2025-05-28 23:27:45 +03:00
Insality
8fc5f4d144 Merge branch 'properties_panel' into develop 2025-05-28 23:22:29 +03:00
Insality
61111536a8 Release 1.1.4 2025-05-20 00:51:34 +03:00
Maksim Tuprikov
b5d2f313cc Merge pull request #312 from polivanni/master
Reset width and leading of the reusable table TEXT_METRICS_OPTIONS by default
2025-05-20 00:50:09 +03:00
Ivan Polovyi
d0067e5496 Reset width and leading of the reusable table TEXT_METRICS_OPTIONS by default 2025-05-18 23:33:40 +03:00
Maksim Tuprikov
9a0f341a67 Merge pull request #311 from rigo128/fix_get_screen_aspect_koef
Fix helper.get_screen_aspect_koef() for gui layouts.
2025-05-15 23:33:07 +03:00
Yury Grigoryev
ce49c3bb41 Fix helper.get_screen_aspect_koef() for gui layouts. 2025-05-15 22:58:55 +03:00
28 changed files with 474 additions and 131 deletions

View File

@@ -33,7 +33,8 @@
"Lua.runtime.version": "Lua 5.1", "Lua.runtime.version": "Lua 5.1",
"Lua.workspace.library": [ "Lua.workspace.library": [
"~/Library/Application Support/Cursor/User/globalStorage/astronachos.defold", "~/Library/Application Support/Cursor/User/globalStorage/astronachos.defold",
"~/Library/Application Support/Cursor/User/workspaceStorage/1446075a23c89451a63f0e82b2291def/astronachos.defold" "~/Library/Application Support/Cursor/User/workspaceStorage/1446075a23c89451a63f0e82b2291def/astronachos.defold",
"~/Library/Application Support/Cursor/User/workspaceStorage/7975bec62a9fa9724d190779fa01ec63/astronachos.defold"
], ],
"files.exclude": { "files.exclude": {
"**/*.gui": true "**/*.gui": true

View File

@@ -39,13 +39,13 @@ Open your `game.project` file and add the following lines to the dependencies fi
**[Druid](https://github.com/Insality/druid/)** **[Druid](https://github.com/Insality/druid/)**
``` ```
https://github.com/Insality/druid/archive/refs/tags/1.1.3.zip https://github.com/Insality/druid/archive/refs/tags/1.1.6.zip
``` ```
**[Defold Event](https://github.com/Insality/defold-event)** **[Defold Event](https://github.com/Insality/defold-event)**
``` ```
https://github.com/Insality/defold-event/archive/refs/tags/11.zip https://github.com/Insality/defold-event/archive/refs/tags/12.zip
``` ```
After that, select `Project ▸ Fetch Libraries` to update [library dependencies]((https://defold.com/manuals/libraries/#setting-up-library-dependencies)). This happens automatically whenever you open a project so you will only need to do this if the dependencies change without re-opening the project. After that, select `Project ▸ Fetch Libraries` to update [library dependencies]((https://defold.com/manuals/libraries/#setting-up-library-dependencies)). This happens automatically whenever you open a project so you will only need to do this if the dependencies change without re-opening the project.

View File

@@ -37,7 +37,7 @@ local utf8 = utf8 or utf8_lua --[[@as utf8]]
---@field on_update_text_scale event fun(self: druid.text, scale: vector3, metrics: table) The event triggered when the text scale is updated ---@field on_update_text_scale event fun(self: druid.text, scale: vector3, metrics: table) The event triggered when the text scale is updated
---@field on_set_pivot event fun(self: druid.text, pivot: userdata) The event triggered when the text pivot is set ---@field on_set_pivot event fun(self: druid.text, pivot: userdata) The event triggered when the text pivot is set
---@field style druid.text.style The style of the text ---@field style druid.text.style The style of the text
---@field start_pivot userdata The start pivot of the text ---@field start_pivot number The start pivot of the text
---@field start_scale vector3 The start scale of the text ---@field start_scale vector3 The start scale of the text
---@field scale vector3 The current scale of the text ---@field scale vector3 The current scale of the text
local M = component.create("text") local M = component.create("text")
@@ -224,7 +224,7 @@ end
---Set text pivot. Text will re-anchor inside text area ---Set text pivot. Text will re-anchor inside text area
---@param pivot userdata The gui.PIVOT_* constant ---@param pivot number The gui.PIVOT_* constant
---@return druid.text self Current text instance ---@return druid.text self Current text instance
function M:set_pivot(pivot) function M:set_pivot(pivot)
local prev_pivot = gui.get_pivot(self.node) local prev_pivot = gui.get_pivot(self.node)

View File

@@ -1,8 +1,11 @@
---@alias color vector4|vector3|string
local PALETTE_DATA = {} local PALETTE_DATA = {}
local COLOR_WHITE = vmath.vector4(1, 1, 1, 1) local COLOR_WHITE = vmath.vector4(1, 1, 1, 1)
local COLOR_X = hash("color.x") local COLOR_X = hash("color.x")
local COLOR_Y = hash("color.y") local COLOR_Y = hash("color.y")
local COLOR_Z = hash("color.z") local COLOR_Z = hash("color.z")
local ALPHA = hash("color.w")
local M = {} local M = {}
@@ -11,7 +14,7 @@ local M = {}
---@param color_id string|vector4 Color id from palette or hex color ---@param color_id string|vector4 Color id from palette or hex color
---@return vector4 ---@return vector4
function M.get_color(color_id) function M.get_color(color_id)
if type(color_id) == "vector4" then if type(color_id) ~= "string" then
return color_id return color_id
end end
@@ -81,7 +84,6 @@ function M.lerp(t, color1, color2)
end end
---Convert hex color to rgb values. ---Convert hex color to rgb values.
---@param hex string Hex color. #00BBAA or 00BBAA or #0BA or 0BA ---@param hex string Hex color. #00BBAA or 00BBAA or #0BA or 0BA
---@return number, number, number ---@return number, number, number

View File

@@ -374,7 +374,6 @@ end
---Сreate a new component class, which will inherit from the base Druid component. ---Сreate a new component class, which will inherit from the base Druid component.
---@protected
---@param name string|nil The name of the component ---@param name string|nil The name of the component
---@param input_priority number|nil The input priority. The bigger number processed first. Default value: 10 ---@param input_priority number|nil The input priority. The bigger number processed first. Default value: 10
---@return druid.component ---@return druid.component

View File

@@ -34,7 +34,7 @@ local rich_text = require("druid.custom.rich_text.module.rt")
---@field scale vector3 ---@field scale vector3
---@field size vector3 ---@field size vector3
---@field metrics druid.rich_text.metrics ---@field metrics druid.rich_text.metrics
---@field pivot userdata ---@field pivot constant
---@field text string ---@field text string
---@field shadow vector4 ---@field shadow vector4
---@field outline vector4 ---@field outline vector4
@@ -68,7 +68,7 @@ local rich_text = require("druid.custom.rich_text.module.rt")
---The component that handles a rich text display, allows to custom color, size, font, etc. of the parts of the text ---The component that handles a rich text display, allows to custom color, size, font, etc. of the parts of the text
---@class druid.rich_text: druid.component ---@class druid.rich_text: druid.component
---@field root node The root node of the rich text ---@field root node The root text node of the rich text
---@field text_prefab node The text prefab node ---@field text_prefab node The text prefab node
---@field private _last_value string The last value of the rich text ---@field private _last_value string The last value of the rich text
---@field private _settings table The settings of the rich text ---@field private _settings table The settings of the rich text
@@ -255,4 +255,18 @@ function M:_create_settings()
end end
---Set the width of the rich text, not affects the size of current spawned words
---@param width number
function M:set_width(width)
self._settings.width = width
end
---Set the height of the rich text, not affects the size of current spawned words
---@param height number
function M:set_height(height)
self._settings.height = height
end
return M return M

View File

@@ -132,10 +132,14 @@ end
--- msg.url(nil, object_url, "gui_widget") -- other game object --- msg.url(nil, object_url, "gui_widget") -- other game object
---@generic T: druid.widget ---@generic T: druid.widget
---@param widget_class T The class of the widget to return ---@param widget_class T The class of the widget to return
---@param gui_url url GUI url ---@param gui_url url|string GUI url or string of component name near current script
---@param params any|nil Additional parameters to pass to the widget's init function ---@param params any|nil Additional parameters to pass to the widget's init function
---@return T widget The new created widget, ---@return T widget The new created widget,
function M.get_widget(widget_class, gui_url, params) function M.get_widget(widget_class, gui_url, params)
if type(gui_url) == "string" then
gui_url = msg.url(nil, nil, gui_url)
end
gui_url = gui_url or msg.url() gui_url = gui_url or msg.url()
local registered_druids = REGISTERED_GUI_WIDGETS[gui_url.socket] local registered_druids = REGISTERED_GUI_WIDGETS[gui_url.socket]
assert(registered_druids, "Druid widget not registered for this game object") assert(registered_druids, "Druid widget not registered for this game object")
@@ -143,7 +147,7 @@ function M.get_widget(widget_class, gui_url, params)
for index = 1, #registered_druids do for index = 1, #registered_druids do
local druid = registered_druids[index] local druid = registered_druids[index]
if druid.fragment == gui_url.fragment and druid.path == gui_url.path then if druid.fragment == gui_url.fragment and druid.path == gui_url.path then
return druid.new_widget:trigger(widget_class, nil, nil, params) return druid.new_widget(widget_class, nil, nil, params)
end end
end end

View File

@@ -5,7 +5,7 @@
local queues = require("event.queues") local queues = require("event.queues")
---Usage: defer.push("druid.get_atlas_path", { ---Usage: queues.push("druid.get_atlas_path", {
--- texture_name = gui.get_texture(self.node), --- texture_name = gui.get_texture(self.node),
--- sender = msg.url(), --- sender = msg.url(),
---}, callback, [context]) ---}, callback, [context])
@@ -30,7 +30,7 @@ local function get_atlas_path(self, request)
return nil return nil
end end
return go.get(request.sender, "textures", { key = request.texture_name }) return go.get(request.sender, "textures", { key = request.texture_name }) --[[@as string]]
end end

View File

@@ -1,7 +1,7 @@
--- Module for assigning layers to GUI nodes based on textures and fonts --- Module for assigning layers to GUI nodes based on textures and fonts
local defold_parser = require("druid.editor_scripts.defold_parser.defold_parser") local defold_parser = require("druid.editor_scripts.defold_parser.defold_parser")
local system = require("druid.editor_scripts.defold_parser.system.system") local system = require("druid.editor_scripts.defold_parser.system.parser_internal")
local M = {} local M = {}

View File

@@ -12,7 +12,7 @@ end
function M.create_druid_gui_script(selection) function M.create_druid_gui_script(selection)
local gui_filepath = editor.get(selection, "path") local gui_filepath = editor.get(selection, "path")
local filename = gui_filepath:match("([^/]+)%.gui$") local filename = gui_filepath:match("([^/]+)%.gui$")
print("Create Druid GUI Script for", gui_filepath) print("Create GUI script for", gui_filepath)
local absolute_project_path = editor.external_file_attributes(".").path local absolute_project_path = editor.external_file_attributes(".").path
local widget_resource_path = gui_filepath:gsub("%.gui$", ".gui_script") local widget_resource_path = gui_filepath:gsub("%.gui$", ".gui_script")
@@ -25,8 +25,8 @@ function M.create_druid_gui_script(selection)
local f = io.open(new_widget_absolute_path, "r") local f = io.open(new_widget_absolute_path, "r")
if f then if f then
f:close() f:close()
print("Widget file already exists at " .. new_widget_absolute_path) print("GUI script file already exists at " .. new_widget_absolute_path)
print("Creation aborted to prevent overwriting") error("Creation aborted to prevent overwriting")
return return
end end
@@ -37,7 +37,7 @@ function M.create_druid_gui_script(selection)
local template_content = editor.get(template_path, "text") local template_content = editor.get(template_path, "text")
if not template_content then if not template_content then
print("Error: Could not load template from", template_path) print("Error: Could not load template from", template_path)
print("Check the template path in [Druid] Settings") error("Check the template path in [Druid] Settings")
return return
end end
@@ -54,90 +54,12 @@ function M.create_druid_gui_script(selection)
file:write(template_content) file:write(template_content)
file:close() file:close()
print("Widget created at " .. widget_resource_path) print("Widget created: " .. widget_resource_path)
editor.transact({
editor.tx.set(selection, "script", widget_resource_path)
})
editor.save()
M.link_gui_script(selection, widget_resource_path)
end
---Links a GUI script to a GUI file by updating the script property
---@param selection string The GUI resource to modify
---@param widget_resource_path string The path to the GUI script to link
function M.link_gui_script(selection, widget_resource_path)
local defold_parser = require("druid.editor_scripts.defold_parser.defold_parser")
local system = require("druid.editor_scripts.defold_parser.system.system")
local gui_filepath = editor.get(selection, "path")
print("Linking GUI script to", gui_filepath)
-- Get the absolute path to the file
local absolute_project_path = editor.external_file_attributes(".").path
if not absolute_project_path:match("[\\/]$") then
absolute_project_path = absolute_project_path .. "/"
end
local clean_gui_path = gui_filepath
if clean_gui_path:sub(1, 1) == "/" then
clean_gui_path = clean_gui_path:sub(2)
end
local gui_absolute_path = absolute_project_path .. clean_gui_path
-- Create a backup
local backup_path = gui_absolute_path .. ".backup"
print("Creating backup at:", backup_path)
-- Read and write backup
local content, err_read = system.read_file(gui_absolute_path)
if not content then
print("Error reading original file for backup:", err_read)
return
end
local success, err_write = system.write_file(backup_path, content)
if not success then
print("Error creating backup file:", err_write)
return
end
-- Parse the GUI file
print("Parsing GUI file...")
local gui_data = defold_parser.load_from_file(gui_absolute_path)
if not gui_data then
print("Error: Failed to parse GUI file")
return
end
-- Update the script property
print("Setting script property to:", widget_resource_path)
gui_data.script = widget_resource_path
-- Write the updated GUI file
print("Writing updated GUI file...")
local save_success = defold_parser.save_to_file(gui_absolute_path, gui_data)
if not save_success then
print("Error: Failed to save GUI file")
print("Attempting to restore from backup...")
-- Restore from backup on failure
local backup_content, backup_err_read = system.read_file(backup_path)
if not backup_content then
print("Error reading backup file:", backup_err_read)
return
end
local restore_success, restore_err_write = system.write_file(gui_absolute_path, backup_content)
if not restore_success then
print("Critical: Failed to restore from backup:", restore_err_write)
return
end
print("Restored successfully from backup")
return
end
-- Remove backup on success
os.remove(backup_path)
print("Successfully linked GUI script to:", gui_filepath)
end end

View File

@@ -26,7 +26,7 @@ function M.create_druid_widget(selection)
if f then if f then
f:close() f:close()
print("Widget file already exists at " .. new_widget_absolute_path) print("Widget file already exists at " .. new_widget_absolute_path)
print("Creation aborted to prevent overwriting") error("Creation aborted to prevent overwriting")
return return
end end
@@ -37,7 +37,7 @@ function M.create_druid_widget(selection)
local template_content = editor.get(template_path, "text") local template_content = editor.get(template_path, "text")
if not template_content then if not template_content then
print("Error: Could not load template from", template_path) print("Error: Could not load template from", template_path)
print("Check the template path in [Druid] Settings") error("Check the template path in [Druid] Settings")
return return
end end

View File

@@ -1,7 +1,7 @@
--- Defold Text Proto format encoder/decoder to lua table --- Defold Text Proto format encoder/decoder to lua table
local config = require("druid.editor_scripts.defold_parser.system.config") local config = require("druid.editor_scripts.defold_parser.system.config")
local system = require("druid.editor_scripts.defold_parser.system.system") local parser_internal = require("druid.editor_scripts.defold_parser.system.parser_internal")
local M = {} local M = {}
@@ -17,7 +17,7 @@ function M.decode_defold_object(text)
-- For each line in the text, we go through the following steps: -- For each line in the text, we go through the following steps:
for raw_line in text:gmatch("[^\r\n]+") do for raw_line in text:gmatch("[^\r\n]+") do
system.parse_line(raw_line, stack) parser_internal.parse_line(raw_line, stack)
end end
return root return root
@@ -39,8 +39,8 @@ function M.encode_defold_object(obj, spaces, data_level, extension)
end end
table.sort(keys, function(a, b) table.sort(keys, function(a, b)
local index_a = system.contains(key_order, a) or 0 local index_a = parser_internal.contains(key_order, a) or 0
local index_b = system.contains(key_order, b) or 0 local index_b = parser_internal.contains(key_order, b) or 0
return index_a < index_b return index_a < index_b
end) end)
@@ -63,7 +63,7 @@ function M.encode_defold_object(obj, spaces, data_level, extension)
result = result .. tabString .. key .. ': "' .. encodedChild .. '"\n' result = result .. tabString .. key .. ': "' .. encodedChild .. '"\n'
elseif item_type == "number" or item_type == "boolean" then elseif item_type == "number" or item_type == "boolean" then
local is_contains_dot = string.find(key, "%.") local is_contains_dot = string.find(key, "%.")
if item_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then if item_type == "number" and (parser_internal.contains(config.with_dot_params, key) and not is_contains_dot) then
result = result .. tabString .. key .. ': ' .. string.format("%.1f", array_item) .. '\n' result = result .. tabString .. key .. ': ' .. string.format("%.1f", array_item) .. '\n'
else else
result = result .. tabString .. key .. ': ' .. tostring(array_item) .. '\n' result = result .. tabString .. key .. ': ' .. tostring(array_item) .. '\n'
@@ -94,7 +94,7 @@ function M.encode_defold_object(obj, spaces, data_level, extension)
-- Handle scalar values (string, number, boolean) -- Handle scalar values (string, number, boolean)
if value_type == "number" or value_type == "boolean" then if value_type == "number" or value_type == "boolean" then
local is_contains_dot = string.find(key, "%.") local is_contains_dot = string.find(key, "%.")
if value_type == "number" and (system.contains(config.with_dot_params, key) and not is_contains_dot) then if value_type == "number" and (parser_internal.contains(config.with_dot_params, key) and not is_contains_dot) then
result = result .. tabString .. key .. ': ' .. string.format("%.1f", value) .. '\n' result = result .. tabString .. key .. ': ' .. string.format("%.1f", value) .. '\n'
else else
result = result .. tabString .. key .. ': ' .. tostring(value) .. '\n' result = result .. tabString .. key .. ': ' .. tostring(value) .. '\n'
@@ -125,7 +125,7 @@ end
---@param file_path string ---@param file_path string
---@return table|nil, string|nil ---@return table|nil, string|nil
function M.load_from_file(file_path) function M.load_from_file(file_path)
local content, reason = system.read_file(file_path) local content, reason = parser_internal.read_file(file_path)
if not content then if not content then
return nil, reason return nil, reason
end end
@@ -146,7 +146,7 @@ function M.save_to_file(file_path, lua_table)
local encoded_object = M.encode_defold_object(lua_table, nil, nil, defold_format_name) local encoded_object = M.encode_defold_object(lua_table, nil, nil, defold_format_name)
return system.write_file(file_path, encoded_object) return parser_internal.write_file(file_path, encoded_object)
end end

35
druid/ext.properties Normal file
View File

@@ -0,0 +1,35 @@
[druid]
help = Settings for Druid extension
group = Runtime
input_text.default = text
input_touch.default = touch
input_marked_text.default = marked_text
input_key_esc.default = key_esc
input_key_back.default = key_back
input_key_enter.default = key_enter
input_key_backspace.default = key_backspace
input_multitouch.default = touch_multi
input_scroll_up.default = mouse_wheel_up
input_scroll_down.default = mouse_wheel_down
input_key_left.default = key_left
input_key_right.default = key_right
input_key_lshift.default = key_lshift
input_key_lctrl.default = key_lctrl
input_key_lsuper.default = key_lsuper
no_auto_input.type = bool

View File

@@ -273,7 +273,7 @@ end
---@param node_or_container node|string|druid.container|table The node or container to add ---@param node_or_container node|string|druid.container|table The node or container to add
---@param mode string|nil stretch, fit, stretch_x, stretch_y. Default: Pick from node, "fit" or "stretch" ---@param mode druid.container.mode|nil stretch, fit, stretch_x, stretch_y. Default: Pick from node, "fit" or "stretch"
---@param on_resize_callback fun(self: userdata, size: vector3)|nil ---@param on_resize_callback fun(self: userdata, size: vector3)|nil
---@return druid.container Container New created layout instance ---@return druid.container Container New created layout instance
function M:add_container(node_or_container, mode, on_resize_callback) function M:add_container(node_or_container, mode, on_resize_callback)

186
druid/extended/image.lua Normal file
View File

@@ -0,0 +1,186 @@
local component = require("druid.component")
---@class druid.image: druid.component
---@field root node
---@field private size vector3
---@field private texture_id string
---@field private adjust_mode number
local M = component.create("druid.image")
local REFERENCE_COUNTER = {}
local TEXTURE_DATA = {}
local SOCKET_IDS = {}
local SOCKET_IDS_COUNT = 0
---@param node_or_node_id node|string
function M:init(node_or_node_id)
self.root = self:get_node(node_or_node_id)
self.size = gui.get_size(self.root)
self.adjust_mode = gui.get_adjust_mode(self.root)
end
function M:on_remove()
self:_release_texture(self.texture_id)
end
function M:load_from_resource_path(resource_path)
local texture_data = sys.load_resource(resource_path)
if not texture_data then
return
end
local texture_id = self:_process_texture_id(resource_path)
self:_create_texture(texture_id, texture_data)
self:_set_texture(texture_id)
end
function M:load_from_absolute_path(absolute_path)
local file = io.open(absolute_path, "rb")
if not file then
return nil
end
local texture_data = file:read("*all")
file:close()
local texture_id = self:_process_texture_id(absolute_path)
self:_create_texture(texture_id, texture_data)
self:_set_texture(texture_id)
end
function M:load_from_url(url)
local headers = nil
local cache_path = self:_convert_url_to_absolute_path(url)
local is_loaded_from_cache = self:load_from_absolute_path(cache_path)
if is_loaded_from_cache then
return
end
http.request(url, "GET", function(_, _, response)
if response.status == 200 then
self:load_from_absolute_path(cache_path)
end
end, headers, nil, { path = cache_path })
end
---@param texture_id string
---@param bytes string
---@return boolean
function M:_create_texture(texture_id, bytes)
if self.texture_id then
self:_release_texture(self.texture_id)
end
if REFERENCE_COUNTER[texture_id] then
REFERENCE_COUNTER[texture_id] = REFERENCE_COUNTER[texture_id] + 1
self.texture_id = texture_id
return true
end
local texture_data = image.load(bytes)
if not texture_data then
TEXTURE_DATA[texture_id] = nil
return false
end
local texture_created = gui.new_texture(texture_id, texture_data.width, texture_data.height, texture_data.type, texture_data.buffer, false)
TEXTURE_DATA[texture_id] = texture_data
if not texture_created then
return false
end
REFERENCE_COUNTER[texture_id] = 1
self.texture_id = texture_id
return true
end
function M:_release_texture(texture_id)
if not REFERENCE_COUNTER[texture_id] then
return false
end
REFERENCE_COUNTER[texture_id] = REFERENCE_COUNTER[texture_id] - 1
if REFERENCE_COUNTER[texture_id] <= 0 and TEXTURE_DATA[texture_id] then
REFERENCE_COUNTER[texture_id] = nil
TEXTURE_DATA[texture_id] = nil
gui.delete_texture(texture_id)
end
return true
end
---@param texture_id string
function M:_set_texture(texture_id)
gui.set_texture(self.root, texture_id)
if self.adjust_mode == gui.ADJUST_FIT then
local texture_data = TEXTURE_DATA[texture_id]
local texture_width = texture_data.width
local texture_height = texture_data.height
-- Calculate scale to fit texture inside self.size while maintaining aspect ratio
local scale_x = self.size.x / texture_width
local scale_y = self.size.y / texture_height
local scale = math.min(scale_x, scale_y)
-- Set the new size maintaining aspect ratio
local new_width = texture_width * scale
local new_height = texture_height * scale
gui.set_size(self.root, vmath.vector3(new_width, new_height, 0))
end
end
---@param textures_refs table<string, number>
---@param texture_id string
---@return boolean
function M:_delete_texture(textures_refs, texture_id)
if not textures_refs[texture_id] then
return false
end
textures_refs[texture_id] = nil
gui.delete_texture(texture_id)
return true
end
---@param texture_id string
---@return string
function M:_process_texture_id(texture_id)
local current_url = msg.url()
local socket = current_url.socket
local path = current_url.path
SOCKET_IDS[socket] = SOCKET_IDS[socket] or {}
if not SOCKET_IDS[socket][path] then
SOCKET_IDS[socket][path] = SOCKET_IDS_COUNT
SOCKET_IDS_COUNT = SOCKET_IDS_COUNT + 1
end
return SOCKET_IDS[socket][path] .. "#" .. texture_id
end
---@param url string
---@return string
function M:_convert_url_to_absolute_path(url)
-- Use sys.get save path to generate a filename from url, replace all special characters with _
local filename = url:gsub("[^a-zA-Z0-9_.-]", "_")
return sys.get_save_file(sys.get_config_string("project.title"), filename)
end
return M

View File

@@ -69,6 +69,7 @@ function M:init(node_or_node_id, layout_type)
self.is_resize_width = false self.is_resize_width = false
self.is_resize_height = false self.is_resize_height = false
self.is_justify = false self.is_justify = false
self._set_position_function = gui.set_position
self.on_size_changed = event.create() --[[@as event.on_size_changed]] self.on_size_changed = event.create() --[[@as event.on_size_changed]]
end end
@@ -79,7 +80,7 @@ function M:update()
return return
end end
self:refresh_layout() self:refresh_layout(false)
end end
@@ -235,8 +236,9 @@ function M:get_content_size()
end end
---@param is_instant boolean|nil If true, node position update instantly, otherwise with set_position_function callback
---@return druid.layout self Current layout instance ---@return druid.layout self Current layout instance
function M:refresh_layout() function M:refresh_layout(is_instant)
local layout_node = self.node local layout_node = self.node
local entities = self.entities local entities = self.entities
@@ -354,7 +356,7 @@ function M:refresh_layout()
end end
end end
self:set_node_position(node, position_x, position_y) self:set_node_position(node, position_x, position_y, is_instant)
end end
end end
@@ -499,13 +501,27 @@ local TEMP_VECTOR = vmath.vector3(0, 0, 0)
---@param x number ---@param x number
---@param y number ---@param y number
---@return node ---@return node
function M:set_node_position(node, x, y) function M:set_node_position(node, x, y, is_instant)
TEMP_VECTOR.x = x TEMP_VECTOR.x = x
TEMP_VECTOR.y = y TEMP_VECTOR.y = y
if is_instant then
gui.set_position(node, TEMP_VECTOR) gui.set_position(node, TEMP_VECTOR)
else
self._set_position_function(node, TEMP_VECTOR)
end
return node return node
end end
---Set custom position function for layout nodes. It will call on update poses on layout elements. Default: gui.set_position
---@param callback function
---@return druid.layout self Current layout instance
function M:set_position_function(callback)
self._set_position_function = callback or gui.set_position
return self
end
return M return M

View File

@@ -473,6 +473,9 @@ function M.get_text_metrics_from_node(text_node)
options.tracking = gui.get_tracking(text_node) options.tracking = gui.get_tracking(text_node)
options.line_break = gui.get_line_break(text_node) options.line_break = gui.get_line_break(text_node)
options.width = 0
options.leading = 0
-- Gather other options only if it used in node -- Gather other options only if it used in node
if options.line_break then if options.line_break then
options.width = gui.get_size(text_node).x options.width = gui.get_size(text_node).x

View File

@@ -1,3 +1,4 @@
---@diagnostic disable: invisible
-- Hello, Defolder! Wish you a good day! -- Hello, Defolder! Wish you a good day!
local events = require("event.events") local events = require("event.events")

View File

@@ -1,3 +1,10 @@
-- Title: FPS Panel
-- Description: Shows current FPS and graph of the last 3 seconds of performance
-- Author: Insality <https://github.com/Insality>
-- Widget: fps_panel
-- Depends: insality@mini_graph
-- Tags: debug, system
local helper = require("druid.helper") local helper = require("druid.helper")
local mini_graph = require("druid.widget.mini_graph.mini_graph") local mini_graph = require("druid.widget.mini_graph.mini_graph")

View File

@@ -33,21 +33,34 @@ nodes {
} }
} }
nodes { nodes {
position {
x: 200.0
}
size { size {
x: 400.0 x: 400.0
y: 40.0 y: 40.0
} }
color {
x: 0.173
y: 0.184
z: 0.204
}
type: TYPE_BOX type: TYPE_BOX
texture: "druid/ui_circle_16"
id: "header" id: "header"
pivot: PIVOT_N pivot: PIVOT_NE
parent: "root" parent: "root"
inherit_alpha: true inherit_alpha: true
size_mode: SIZE_MODE_AUTO slice9 {
visible: false x: 8.0
y: 8.0
z: 8.0
w: 8.0
}
} }
nodes { nodes {
position { position {
x: -192.0 x: -392.0
y: -8.0 y: -8.0
} }
scale { scale {
@@ -85,7 +98,7 @@ nodes {
} }
nodes { nodes {
position { position {
x: 192.0 x: -8.0
y: -4.0 y: -4.0
} }
color { color {
@@ -103,7 +116,7 @@ nodes {
} }
nodes { nodes {
position { position {
x: 152.0 x: -48.0
y: -4.0 y: -4.0
} }
color { color {
@@ -121,7 +134,7 @@ nodes {
} }
nodes { nodes {
position { position {
x: 112.0 x: -88.0
y: -4.0 y: -4.0
} }
scale { scale {

View File

@@ -39,6 +39,7 @@ function M:init()
self.contaienr_scroll_content = self.container_scroll_view:add_container("scroll_content") self.contaienr_scroll_content = self.container_scroll_view:add_container("scroll_content")
self.default_size = self.container:get_size() 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 -- To have ability to go back to previous scene, collections of all properties to rebuild
self.scenes = {} self.scenes = {}
@@ -362,11 +363,18 @@ end
function M:set_hidden(is_hidden) function M:set_hidden(is_hidden)
self._is_hidden = is_hidden self._is_hidden = is_hidden
local hidden_size = gui.get_size(self:get_node("header")) local node_header = self:get_node("header")
local new_size = self._is_hidden and hidden_size or self.default_size 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) 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.content, not self._is_hidden)
gui.set_enabled(self.button_refresh.node, not self._is_hidden) gui.set_enabled(self.button_refresh.node, not self._is_hidden)

View File

@@ -14,16 +14,16 @@ update_frequency = 60
[project] [project]
title = Druid title = Druid
version = 1.1.3 version = 1.1.5
publisher = Insality publisher = Insality
developer = Maksim Tuprikov developer = Maksim Tuprikov
custom_resources = /example/locales custom_resources = /example/locales
dependencies#0 = https://github.com/britzl/deftest/archive/refs/tags/2.8.0.zip dependencies#0 = https://github.com/britzl/deftest/archive/refs/tags/2.8.0.zip
dependencies#1 = https://github.com/Insality/defold-saver/archive/refs/heads/develop.zip dependencies#1 = https://github.com/Insality/defold-saver/archive/refs/tags/5.zip
dependencies#2 = https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip dependencies#2 = https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip
dependencies#3 = https://github.com/Insality/panthera/archive/refs/heads/develop.zip dependencies#3 = https://github.com/Insality/panthera/archive/refs/tags/runtime.4.zip
dependencies#4 = https://github.com/Insality/defold-lang/archive/refs/tags/3.zip dependencies#4 = https://github.com/Insality/defold-lang/archive/refs/tags/3.zip
dependencies#5 = https://github.com/Insality/defold-event/archive/refs/heads/develop.zip dependencies#5 = https://github.com/Insality/defold-event/archive/refs/tags/12.zip
dependencies#6 = https://github.com/subsoap/defos/archive/refs/tags/v2.8.0.zip dependencies#6 = https://github.com/subsoap/defos/archive/refs/tags/v2.8.0.zip
[library] [library]

View File

@@ -712,3 +712,37 @@ Please support me if you like this project! It will help me keep engaged to upda
#### Druid 1.1.3 #### Druid 1.1.3
- Fix for node_id of cloned nodes with `gui.clone_tree` - Fix for node_id of cloned nodes with `gui.clone_tree`
#### Druid 1.1.4
- [#312] Fix for text metrics issue if returned height is 0 sometimes
#### Druid 1.1.5
- Update for using `defold-event` library v12
#### Druid 1.1.6
- [#326] Fix for Editor Scripts corrupt file issue
### Druid 1.2.0
- [Image] Add image component
- Create with `druid:new_image(node_or_node_id)`
- Currently used to load image from resource path, absolute path or URL
- Can be fit inside (keeping aspect ratio) stretched to the node area, depends on the GUI adjust mode
- [Blocker] Fix for internal is_enabled state
- [Button] expose all click functions for the button
- [Scroll] Add `scroll_to_make_node_visible` function
- [Palette] Add Druid Color module
- Manage color palettes
- Color convertations
- Convenient usage
- [Container] Fix for container stretch mode (stretch and fit is not worked in init function)
- [Rich Text] Using color names from the palette
- [Rich Text] Add `rich_text:set_split_to_characters(true)` to split each letter node separately
- Weird implementation, but nice to have
- [Rich Text] Add `set_width` and `set_height` functions
- [GO Widgets] Now passes events and functions from the widget to the GO context
- [Layout] Add `set_position_function` function, similar to the Grid component
- [Properties Panel] Update with deep navigation support
- Add "Scenes" to manage a list of properties with back button support
- Add "Pages" to manage a a big lists of properties with paginations support
- Add `properties_panel:render_lua_table` to easily render your lua tables with a various types support (any simple types and vector, functions, events etc)
- Add "Refresh" button, which active a 1-sec refresh for current page

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -0,0 +1,98 @@
# Memory and FPS Panel Widgets
The `Druid 1.1` comes with two included widgets: `Memory Panel` and `FPS Panel`, which allow you to monitor memory and FPS in your game.
Widgets in Druid usually consist of two files: GUI, which is used to place as a template on your GUI scene and Lua script, which is used to be create with Druid.
<!-- Video -->
## Memory Panel
The `Memory Panel` is a widget which allows you to monitor memory of your game. It displays the last 3 seconds of memory allocations in graph, largest memory allocation step, total Lua memory and memory per second.
When you see an empty space in graphs - it means the garbage collector is working at this moment.
### How to add:
- Add `/druid/widget/memory_panel/memory_panel.gui` to your `*.gui` scene
![](/wiki/manuals/media/memory_fps_panel_add.png)
![](/wiki/manuals/media/memory_fps_panel_select.png)
- You can adjust a scale of the template if required
- Add Druid and widget setup to your `*.gui_script`
```lua
local druid = require("druid.druid")
local memory_panel = require("druid.widget.memory_panel.memory_panel")
function init(self)
self.druid = druid.new(self)
-- "memory_panel" is a name of the template in the GUI scene, often it matches the name of the template file
self.memory_panel = self.druid:new_widget(memory_panel, "memory_panel")
end
function final(self)
self.druid:final()
end
function update(self, dt)
self.druid:update(dt)
end
function on_message(self, message_id, message, sender)
self.druid:on_message(message_id, message, sender)
end
function on_input(self, action_id, action)
return self.druid:on_input(action_id, action)
end
```
And make sure of:
- The `*.gui_script` is attached to your `*.gui` scene
- The GUI component is added to your game scene
## FPS Panel
The `FPS Panel` is a widget which allows you to monitor FPS of your game. It displays the last 3 seconds of FPS graph, lowest and current FPS values
### How to add:
- Add `/druid/widget/fps_panel/fps_panel.gui` to your `*.gui` scene
- You can adjust a scale of the template if required
- Add Druid and widget setup to your `*.gui_script`
```lua
local druid = require("druid.druid")
local fps_panel = require("druid.widget.fps_panel.fps_panel")
function init(self)
self.druid = druid.new(self)
-- "fps_panel" is a name of the template in the GUI scene, often it matches the name of the template file
self.fps_panel = self.druid:new_widget(fps_panel, "fps_panel")
end
function final(self)
self.druid:final()
end
function update(self, dt)
self.druid:update(dt)
end
function on_message(self, message_id, message, sender)
self.druid:on_message(message_id, message, sender)
end
function on_input(self, action_id, action)
return self.druid:on_input(action_id, action)
end
```
And make sure of:
- The `*.gui_script` is attached to your `*.gui` scene
- The GUI component is added to your game scene
These widgets not only can be useful for development and profiling your game, but also as an example of how to create custom widgets with Druid and use them in your game.
Thanks for reading!