mirror of
https://github.com/Insality/druid
synced 2025-09-27 18:12:21 +02:00
Compare commits
42 Commits
1.1.1
...
copilot/fi
Author | SHA1 | Date | |
---|---|---|---|
|
cc818e4c6e | ||
|
92db5319a7 | ||
|
b6a7df5ff2 | ||
|
a94121a1c6 | ||
|
097963bdb3 | ||
|
574f764559 | ||
|
379c9acfc3 | ||
|
4c8796e17d | ||
|
ba7ee40510 | ||
|
d019247ae4 | ||
|
5dac7ed8e2 | ||
|
633bb8ed5c | ||
|
b35b584fad | ||
|
616c513fbd | ||
|
3606c9f49f | ||
|
a8aea0adee | ||
|
8fde964e0e | ||
|
7b5264aca0 | ||
|
8fc5f4d144 | ||
|
16a5d9936a | ||
|
350770ba9e | ||
|
97942965cd | ||
|
0cf5ba30db | ||
|
2e1f280944 | ||
|
61111536a8 | ||
|
b5d2f313cc | ||
|
d0067e5496 | ||
|
fe955b6e64 | ||
|
2133492efe | ||
|
22c49540df | ||
|
9a0f341a67 | ||
|
8ddb6e4e60 | ||
|
b982bc8277 | ||
|
d939d017cb | ||
|
488e78c9d7 | ||
|
52659a96ee | ||
|
ce49c3bb41 | ||
|
58f14d0a64 | ||
|
6572da3b14 | ||
|
d7c26358a0 | ||
|
f007a28ab7 | ||
|
d98c3c2ef1 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -17,3 +17,7 @@ manifest.private.der
|
|||||||
manifest.public.der
|
manifest.public.der
|
||||||
/.editor_settings
|
/.editor_settings
|
||||||
/.deployer_cache
|
/.deployer_cache
|
||||||
|
|
||||||
|
# Example and documentation files (not part of core library)
|
||||||
|
example_color_api_usage.lua
|
||||||
|
DRUID_COLOR_API_IMPROVEMENTS.md
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -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
|
||||||
|
@@ -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.1.zip
|
https://github.com/Insality/druid/archive/refs/tags/1.1.5.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.
|
||||||
|
@@ -20,7 +20,7 @@ local M = component.create("blocker")
|
|||||||
---@param node node|string The node to use as a blocker
|
---@param node node|string The node to use as a blocker
|
||||||
function M:init(node)
|
function M:init(node)
|
||||||
self.node = self:get_node(node)
|
self.node = self:get_node(node)
|
||||||
self._is_enabled = gui.is_enabled(self.node, true)
|
self._is_enabled = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@@ -67,8 +67,8 @@ function M:init(node_or_node_id, callback, custom_args, anim_node)
|
|||||||
self.start_scale = gui.get_scale(self.anim_node)
|
self.start_scale = gui.get_scale(self.anim_node)
|
||||||
self.start_pos = gui.get_position(self.anim_node)
|
self.start_pos = gui.get_position(self.anim_node)
|
||||||
self.params = custom_args
|
self.params = custom_args
|
||||||
self.hover = self.druid:new_hover(node_or_node_id, self._on_button_hover)
|
self.hover = self.druid:new_hover(node_or_node_id, self.button_hover)
|
||||||
self.hover.on_mouse_hover:subscribe(self._on_button_mouse_hover)
|
self.hover.on_mouse_hover:subscribe(self.button_mouse_hover)
|
||||||
self.click_zone = nil
|
self.click_zone = nil
|
||||||
self.is_repeated_started = false
|
self.is_repeated_started = false
|
||||||
self.last_pressed_time = 0
|
self.last_pressed_time = 0
|
||||||
@@ -184,7 +184,7 @@ function M:on_input(action_id, action)
|
|||||||
if self._is_html5_mode then
|
if self._is_html5_mode then
|
||||||
self._is_html5_listener_set = true
|
self._is_html5_listener_set = true
|
||||||
html5.set_interaction_listener(function()
|
html5.set_interaction_listener(function()
|
||||||
self:_on_button_click()
|
self:button_click()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
return is_consume
|
return is_consume
|
||||||
@@ -193,7 +193,7 @@ function M:on_input(action_id, action)
|
|||||||
-- While hold button, repeat rate pick from input.repeat_interval
|
-- While hold button, repeat rate pick from input.repeat_interval
|
||||||
if action.repeated then
|
if action.repeated then
|
||||||
if not self.on_repeated_click:is_empty() and self.can_action then
|
if not self.on_repeated_click:is_empty() and self.can_action then
|
||||||
self:_on_button_repeated_click()
|
self:button_repeated_click()
|
||||||
return is_consume
|
return is_consume
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -211,7 +211,7 @@ function M:on_input(action_id, action)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if press_time >= self.style.LONGTAP_TIME then
|
if press_time >= self.style.LONGTAP_TIME then
|
||||||
self:_on_button_hold(press_time)
|
self:button_hold(press_time)
|
||||||
return is_consume
|
return is_consume
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -326,18 +326,18 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---@param hover_state boolean True if the hover state is active
|
---@param hover_state boolean True if the hover state is active
|
||||||
function M:_on_button_hover(hover_state)
|
function M:button_hover(hover_state)
|
||||||
self.style.on_hover(self, self.anim_node, hover_state)
|
self.style.on_hover(self, self.anim_node, hover_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param hover_state boolean True if the hover state is active
|
---@param hover_state boolean True if the hover state is active
|
||||||
function M:_on_button_mouse_hover(hover_state)
|
function M:button_mouse_hover(hover_state)
|
||||||
self.style.on_mouse_hover(self, self.anim_node, hover_state)
|
self.style.on_mouse_hover(self, self.anim_node, hover_state)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function M:_on_button_click()
|
function M:button_click()
|
||||||
if self._is_html5_mode then
|
if self._is_html5_mode then
|
||||||
self._is_html5_listener_set = false
|
self._is_html5_listener_set = false
|
||||||
html5.set_interaction_listener(nil)
|
html5.set_interaction_listener(nil)
|
||||||
@@ -348,7 +348,7 @@ function M:_on_button_click()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function M:_on_button_repeated_click()
|
function M:button_repeated_click()
|
||||||
if not self.is_repeated_started then
|
if not self.is_repeated_started then
|
||||||
self.click_in_row = 0
|
self.click_in_row = 0
|
||||||
self.is_repeated_started = true
|
self.is_repeated_started = true
|
||||||
@@ -360,7 +360,7 @@ function M:_on_button_repeated_click()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function M:_on_button_long_click()
|
function M:button_long_click()
|
||||||
self.click_in_row = 1
|
self.click_in_row = 1
|
||||||
local time = socket.gettime() - self.last_pressed_time
|
local time = socket.gettime() - self.last_pressed_time
|
||||||
self.on_long_click:trigger(self:get_context(), self.params, self, time)
|
self.on_long_click:trigger(self:get_context(), self.params, self, time)
|
||||||
@@ -368,7 +368,7 @@ function M:_on_button_long_click()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function M:_on_button_double_click()
|
function M:button_double_click()
|
||||||
self.click_in_row = self.click_in_row + 1
|
self.click_in_row = self.click_in_row + 1
|
||||||
self.on_double_click:trigger(self:get_context(), self.params, self, self.click_in_row)
|
self.on_double_click:trigger(self:get_context(), self.params, self, self.click_in_row)
|
||||||
self.style.on_click(self, self.anim_node)
|
self.style.on_click(self, self.anim_node)
|
||||||
@@ -376,7 +376,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---@param press_time number Amount of time the button was held
|
---@param press_time number Amount of time the button was held
|
||||||
function M:_on_button_hold(press_time)
|
function M:button_hold(press_time)
|
||||||
self.on_hold_callback:trigger(self:get_context(), self.params, self, press_time)
|
self.on_hold_callback:trigger(self:get_context(), self.params, self, press_time)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -413,14 +413,14 @@ function M:_on_button_release()
|
|||||||
if is_long_click then
|
if is_long_click then
|
||||||
local is_hold_complete = (time - self.last_pressed_time) >= self.style.AUTOHOLD_TRIGGER
|
local is_hold_complete = (time - self.last_pressed_time) >= self.style.AUTOHOLD_TRIGGER
|
||||||
if is_hold_complete then
|
if is_hold_complete then
|
||||||
self:_on_button_long_click()
|
self:button_long_click()
|
||||||
else
|
else
|
||||||
self.on_click_outside:trigger(self:get_context(), self.params, self)
|
self.on_click_outside:trigger(self:get_context(), self.params, self)
|
||||||
end
|
end
|
||||||
elseif is_double_click then
|
elseif is_double_click then
|
||||||
self:_on_button_double_click()
|
self:button_double_click()
|
||||||
else
|
else
|
||||||
self:_on_button_click()
|
self:button_click()
|
||||||
end
|
end
|
||||||
|
|
||||||
self.last_released_time = time
|
self.last_released_time = time
|
||||||
|
@@ -205,6 +205,59 @@ function M:scroll_to(point, is_instant)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M:scroll_to_make_node_visible(node, is_instant)
|
||||||
|
-- Can be any node not only directly at scroll content
|
||||||
|
local screen_position = gui.get_screen_position(node)
|
||||||
|
local local_position = gui.screen_to_local(self.content_node, screen_position)
|
||||||
|
|
||||||
|
-- Get the node borders in content node space
|
||||||
|
local node_border = helper.get_border(node, local_position)
|
||||||
|
|
||||||
|
-- Calculate how much we need to scroll to make the node visible
|
||||||
|
local scroll_position = vmath.vector3(self.position)
|
||||||
|
local view_border = self.view_border
|
||||||
|
|
||||||
|
-- Convert content position to view position
|
||||||
|
local node_in_view_x = node_border.x + scroll_position.x
|
||||||
|
local node_in_view_y = node_border.y + scroll_position.y
|
||||||
|
local node_in_view_z = node_border.z + scroll_position.x
|
||||||
|
local node_in_view_w = node_border.w + scroll_position.y
|
||||||
|
|
||||||
|
local target_position = vmath.vector3(scroll_position)
|
||||||
|
|
||||||
|
-- Check if node is outside view horizontally
|
||||||
|
if self._is_horizontal_scroll then
|
||||||
|
-- If the node is too far to the right (left side not visible)
|
||||||
|
if node_in_view_x < view_border.x then
|
||||||
|
target_position.x = scroll_position.x + (view_border.x - node_in_view_x)
|
||||||
|
end
|
||||||
|
-- If the node is too far to the left (right side not visible)
|
||||||
|
if node_in_view_z > view_border.z then
|
||||||
|
target_position.x = scroll_position.x - (node_in_view_z - view_border.z)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if node is outside view vertically
|
||||||
|
if self._is_vertical_scroll then
|
||||||
|
-- If the node is too far up (bottom side not visible)
|
||||||
|
if node_in_view_w < view_border.w then
|
||||||
|
target_position.y = scroll_position.y + (view_border.w - node_in_view_w)
|
||||||
|
end
|
||||||
|
-- If the node is too far down (top side not visible)
|
||||||
|
if node_in_view_y > view_border.y then
|
||||||
|
target_position.y = scroll_position.y - (node_in_view_y - view_border.y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If we need to scroll, do it
|
||||||
|
if target_position.x ~= scroll_position.x or target_position.y ~= scroll_position.y then
|
||||||
|
-- Convert to scroll_to expected format (content position, not scroll position)
|
||||||
|
local scroll_to_position = vmath.vector3(-target_position.x, -target_position.y, 0)
|
||||||
|
self:scroll_to(scroll_to_position, is_instant)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
---Scroll to item in scroll by point index.
|
---Scroll to item in scroll by point index.
|
||||||
---@param index number Point index
|
---@param index number Point index
|
||||||
---@param skip_cb boolean|nil If true, skip the point callback
|
---@param skip_cb boolean|nil If true, skip the point callback
|
||||||
|
@@ -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)
|
||||||
|
243
druid/color.lua
243
druid/color.lua
@@ -6,11 +6,23 @@ local COLOR_Z = hash("color.z")
|
|||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- COLOR PARSING AND CREATION FUNCTIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
---Get color by string (hex or from palette)
|
|
||||||
---@param color_id string Color id from palette or hex color
|
---Get color by string (hex or from palette) or pass through vector4
|
||||||
|
---@param color_id string|vector4 Color id from palette, hex color, or existing vector4
|
||||||
---@return vector4
|
---@return vector4
|
||||||
function M.get_color(color_id)
|
function M.get_color(color_id)
|
||||||
|
if type(color_id) ~= "string" then
|
||||||
|
return color_id
|
||||||
|
end
|
||||||
|
|
||||||
|
if PALETTE_DATA[color_id] then
|
||||||
|
return PALETTE_DATA[color_id]
|
||||||
|
end
|
||||||
|
|
||||||
-- Check is it hex: starts with "#" or contains only 3 or 6 hex symbols
|
-- Check is it hex: starts with "#" or contains only 3 or 6 hex symbols
|
||||||
if type(color_id) == "string" then
|
if type(color_id) == "string" then
|
||||||
if string.sub(color_id, 1, 1) == "#" or string.match(color_id, "^[0-9a-fA-F]+$") then
|
if string.sub(color_id, 1, 1) == "#" or string.match(color_id, "^[0-9a-fA-F]+$") then
|
||||||
@@ -18,12 +30,86 @@ function M.get_color(color_id)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return PALETTE_DATA[color_id] or COLOR_WHITE
|
return COLOR_WHITE
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Parse color from various formats (hex, palette ID, or vector4)
|
||||||
|
---This is a clearer alias for get_color
|
||||||
|
---@param color_value string|vector4 Color value to parse
|
||||||
|
---@return vector4
|
||||||
|
function M.parse(color_value)
|
||||||
|
return M.get_color(color_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create a color from RGBA values
|
||||||
|
---@param r number Red component (0-1)
|
||||||
|
---@param g number Green component (0-1)
|
||||||
|
---@param b number Blue component (0-1)
|
||||||
|
---@param a number|nil Alpha component (0-1), defaults to 1
|
||||||
|
---@return vector4
|
||||||
|
function M.new(r, g, b, a)
|
||||||
|
return vmath.vector4(r, g, b, a or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create a color from hex string
|
||||||
|
---@param hex string Hex color. #00BBAA or 00BBAA or #0BA or 0BA
|
||||||
|
---@param alpha number|nil Alpha value. Default is 1
|
||||||
|
---@return vector4
|
||||||
|
function M.from_hex(hex, alpha)
|
||||||
|
return M.hex2vector4(hex, alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Create a color from HSB values
|
||||||
|
---@param h number Hue (0-1)
|
||||||
|
---@param s number Saturation (0-1)
|
||||||
|
---@param b number Brightness/Value (0-1)
|
||||||
|
---@param a number|nil Alpha value. Default is 1
|
||||||
|
---@return vector4
|
||||||
|
function M.from_hsb(h, s, b, a)
|
||||||
|
local r, g, b_val, alpha = M.hsb2rgb(h, s, b, a)
|
||||||
|
return vmath.vector4(r, g, b_val, alpha or 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- COLOR FORMAT CONVERSION FUNCTIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
---Convert color to hex string
|
||||||
|
---@param color vector4 Color to convert
|
||||||
|
---@return string Hex color string (without #)
|
||||||
|
function M.to_hex(color)
|
||||||
|
return M.rgb2hex(color.x, color.y, color.z)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Convert color to RGB values
|
||||||
|
---@param color vector4 Color to convert
|
||||||
|
---@return number, number, number, number r, g, b, a
|
||||||
|
function M.to_rgb(color)
|
||||||
|
return color.x, color.y, color.z, color.w
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Convert color to HSB values
|
||||||
|
---@param color vector4 Color to convert
|
||||||
|
---@return number, number, number, number h, s, b, a
|
||||||
|
function M.to_hsb(color)
|
||||||
|
return M.rgb2hsb(color.x, color.y, color.z, color.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- PALETTE MANAGEMENT FUNCTIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
|
||||||
---Add palette to palette data
|
---Add palette to palette data
|
||||||
---@param palette_data table<string, vector4>
|
---@param palette_data table<string, vector4|string>
|
||||||
function M.add_palette(palette_data)
|
function M.add_palette(palette_data)
|
||||||
for color_id, color in pairs(palette_data) do
|
for color_id, color in pairs(palette_data) do
|
||||||
if type(color) == "string" then
|
if type(color) == "string" then
|
||||||
@@ -35,11 +121,59 @@ function M.add_palette(palette_data)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Set a single color in the palette
|
||||||
|
---@param color_id string Color identifier
|
||||||
|
---@param color vector4|string Color value (vector4 or hex string)
|
||||||
|
function M.set_palette_color(color_id, color)
|
||||||
|
if type(color) == "string" then
|
||||||
|
PALETTE_DATA[color_id] = M.hex2vector4(color)
|
||||||
|
else
|
||||||
|
PALETTE_DATA[color_id] = color
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Remove a color from the palette
|
||||||
|
---@param color_id string Color identifier to remove
|
||||||
|
function M.remove_palette_color(color_id)
|
||||||
|
PALETTE_DATA[color_id] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Check if a color exists in the palette
|
||||||
|
---@param color_id string Color identifier to check
|
||||||
|
---@return boolean
|
||||||
|
function M.has_palette_color(color_id)
|
||||||
|
return PALETTE_DATA[color_id] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Get a color from the palette
|
||||||
|
---@param color_id string Color identifier
|
||||||
|
---@return vector4|nil Color or nil if not found
|
||||||
|
function M.get_palette_color(color_id)
|
||||||
|
return PALETTE_DATA[color_id]
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Get the entire palette
|
||||||
|
---@return table<string, vector4>
|
||||||
function M.get_palette()
|
function M.get_palette()
|
||||||
return PALETTE_DATA
|
return PALETTE_DATA
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Clear the entire palette
|
||||||
|
function M.clear_palette()
|
||||||
|
PALETTE_DATA = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- GUI NODE OPERATIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
|
||||||
---Set color of gui node without changing alpha
|
---Set color of gui node without changing alpha
|
||||||
---@param gui_node node
|
---@param gui_node node
|
||||||
---@param color vector4|vector3|string Color in vector4, vector3 or color id from palette
|
---@param color vector4|vector3|string Color in vector4, vector3 or color id from palette
|
||||||
@@ -54,6 +188,34 @@ function M.set_color(gui_node, color)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Apply color to gui node (clearer alias for set_color)
|
||||||
|
---@param gui_node node GUI node to apply color to
|
||||||
|
---@param color vector4|vector3|string Color in vector4, vector3 or color id from palette
|
||||||
|
function M.apply_to_node(gui_node, color)
|
||||||
|
M.set_color(gui_node, color)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Set node color including alpha
|
||||||
|
---@param gui_node node GUI node to apply color to
|
||||||
|
---@param color vector4|string Color with alpha channel
|
||||||
|
function M.set_node_color_with_alpha(gui_node, color)
|
||||||
|
if type(color) == "string" then
|
||||||
|
color = M.get_color(color)
|
||||||
|
end
|
||||||
|
|
||||||
|
gui.set(gui_node, COLOR_X, color.x)
|
||||||
|
gui.set(gui_node, COLOR_Y, color.y)
|
||||||
|
gui.set(gui_node, COLOR_Z, color.z)
|
||||||
|
gui.set(gui_node, "color.w", color.w)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- COLOR MANIPULATION FUNCTIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
|
||||||
---Lerp colors via color HSB values
|
---Lerp colors via color HSB values
|
||||||
---@param t number Lerp value. 0 - color1, 1 - color2
|
---@param t number Lerp value. 0 - color1, 1 - color2
|
||||||
---@param color1 vector4 Color 1
|
---@param color1 vector4 Color 1
|
||||||
@@ -73,6 +235,55 @@ function M.lerp(t, color1, color2)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Mix two colors using linear RGB interpolation (simpler than lerp)
|
||||||
|
---@param color1 vector4 First color
|
||||||
|
---@param color2 vector4 Second color
|
||||||
|
---@param ratio number Mixing ratio (0 = color1, 1 = color2)
|
||||||
|
---@return vector4 Mixed color
|
||||||
|
function M.mix(color1, color2, ratio)
|
||||||
|
local inv_ratio = 1 - ratio
|
||||||
|
return vmath.vector4(
|
||||||
|
color1.x * inv_ratio + color2.x * ratio,
|
||||||
|
color1.y * inv_ratio + color2.y * ratio,
|
||||||
|
color1.z * inv_ratio + color2.z * ratio,
|
||||||
|
color1.w * inv_ratio + color2.w * ratio
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Lighten a color by mixing with white
|
||||||
|
---@param color vector4 Color to lighten
|
||||||
|
---@param amount number Amount to lighten (0-1)
|
||||||
|
---@return vector4 Lightened color
|
||||||
|
function M.lighten(color, amount)
|
||||||
|
return M.mix(color, COLOR_WHITE, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Darken a color by mixing with black
|
||||||
|
---@param color vector4 Color to darken
|
||||||
|
---@param amount number Amount to darken (0-1)
|
||||||
|
---@return vector4 Darkened color
|
||||||
|
function M.darken(color, amount)
|
||||||
|
local black = vmath.vector4(0, 0, 0, color.w)
|
||||||
|
return M.mix(color, black, amount)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Adjust color alpha
|
||||||
|
---@param color vector4 Color to adjust
|
||||||
|
---@param alpha number New alpha value (0-1)
|
||||||
|
---@return vector4 Color with adjusted alpha
|
||||||
|
function M.with_alpha(color, alpha)
|
||||||
|
return vmath.vector4(color.x, color.y, color.z, alpha)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- LOW-LEVEL CONVERSION FUNCTIONS
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---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
|
||||||
@@ -160,9 +371,10 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Convert rgb color to hex color
|
---Convert rgb color to hex color
|
||||||
---@param red number Red value
|
---@param red number Red value (0-1)
|
||||||
---@param green number Green value
|
---@param green number Green value (0-1)
|
||||||
---@param blue number Blue value
|
---@param blue number Blue value (0-1)
|
||||||
|
---@return string Hex color string (without #)
|
||||||
function M.rgb2hex(red, green, blue)
|
function M.rgb2hex(red, green, blue)
|
||||||
local r = string.format("%x", math.floor(red * 255))
|
local r = string.format("%x", math.floor(red * 255))
|
||||||
local g = string.format("%x", math.floor(green * 255))
|
local g = string.format("%x", math.floor(green * 255))
|
||||||
@@ -171,4 +383,21 @@ function M.rgb2hex(red, green, blue)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- INITIALIZATION
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
local DEFAULT_PALETTE_PATH = sys.get_config_string("druid.palette_path")
|
||||||
|
if DEFAULT_PALETTE_PATH then
|
||||||
|
local loaded_palette = sys.load_resource(DEFAULT_PALETTE_PATH)
|
||||||
|
local data = loaded_palette and json.decode(loaded_palette)
|
||||||
|
if not data then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.add_palette(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@@ -20,20 +20,20 @@ local helper = require("druid.helper")
|
|||||||
---@field _uid number
|
---@field _uid number
|
||||||
|
|
||||||
---@class druid.component
|
---@class druid.component
|
||||||
---@field druid druid.instance Druid instance to create inner components
|
---@field protected druid druid.instance Druid instance to create inner components
|
||||||
---@field init fun(self:druid.component, ...)|nil Called when component is created
|
---@field protected init fun(self:druid.component, ...)|nil Called when component is created
|
||||||
---@field update fun(self:druid.component, dt:number)|nil Called every frame
|
---@field protected update fun(self:druid.component, dt:number)|nil Called every frame
|
||||||
---@field on_remove fun(self:druid.component)|nil Called when component is removed
|
---@field protected on_remove fun(self:druid.component)|nil Called when component is removed
|
||||||
---@field on_input fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is triggered
|
---@field protected on_input fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is triggered
|
||||||
---@field on_input_interrupt fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is consumed before
|
---@field protected on_input_interrupt fun(self:druid.component, action_id:hash, action:table)|nil Called when input event is consumed before
|
||||||
---@field on_message fun(self:druid.component, message_id:hash, message:table, sender:url)|nil Called when message is received
|
---@field protected on_message fun(self:druid.component, message_id:hash, message:table, sender:url)|nil Called when message is received
|
||||||
---@field on_late_init fun(self:druid.component)|nil Called before update once time after GUI init
|
---@field protected on_late_init fun(self:druid.component)|nil Called before update once time after GUI init
|
||||||
---@field on_focus_lost fun(self:druid.component)|nil Called when app lost focus
|
---@field protected on_focus_lost fun(self:druid.component)|nil Called when app lost focus
|
||||||
---@field on_focus_gained fun(self:druid.component)|nil Called when app gained focus
|
---@field protected on_focus_gained fun(self:druid.component)|nil Called when app gained focus
|
||||||
---@field on_style_change fun(self:druid.component, style: table)|nil Called when style is changed
|
---@field protected on_style_change fun(self:druid.component, style: table)|nil Called when style is changed
|
||||||
---@field on_layout_change fun(self:druid.component)|nil Called when GUI layout is changed
|
---@field protected on_layout_change fun(self:druid.component)|nil Called when GUI layout is changed
|
||||||
---@field on_window_resized fun(self:druid.component)|nil Called when window is resized
|
---@field protected on_window_resized fun(self:druid.component)|nil Called when window is resized
|
||||||
---@field on_language_change fun(self:druid.component)|nil Called when language is changed
|
---@field protected on_language_change fun(self:druid.component)|nil Called when language is changed
|
||||||
---@field private _component druid.component.component
|
---@field private _component druid.component.component
|
||||||
---@field private _meta druid.component.meta
|
---@field private _meta druid.component.meta
|
||||||
local M = {}
|
local M = {}
|
||||||
@@ -117,12 +117,23 @@ function M:set_nodes(nodes)
|
|||||||
nodes = gui.clone_tree(nodes) --[[@as table<hash, node>]]
|
nodes = gui.clone_tree(nodes) --[[@as table<hash, node>]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- When we use gui.clone_tree in inner template (template inside other template)
|
||||||
|
-- this nodes have no id. We have table: hash(correct_id) : hash("") or hash("_nodeX"
|
||||||
|
-- It's wrong and we use this hack to fix this
|
||||||
|
if nodes then
|
||||||
|
for id, node in pairs(nodes) do
|
||||||
|
gui.set_id(node, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
self._meta.nodes = nodes
|
self._meta.nodes = nodes
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---Return current component context
|
---Return current component context
|
||||||
|
---@protected
|
||||||
---@return any context Usually it's self of script but can be any other Druid component
|
---@return any context Usually it's self of script but can be any other Druid component
|
||||||
function M:get_context()
|
function M:get_context()
|
||||||
return self._meta.context
|
return self._meta.context
|
||||||
@@ -138,6 +149,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Get Druid instance for inner component creation.
|
---Get Druid instance for inner component creation.
|
||||||
|
---@protected
|
||||||
---@param template string|nil
|
---@param template string|nil
|
||||||
---@param nodes table<hash, node>|node|string|nil The nodes table from gui.clone_tree or prefab node to use for clone or node id to clone
|
---@param nodes table<hash, node>|node|string|nil The nodes table from gui.clone_tree or prefab node to use for clone or node id to clone
|
||||||
---@return druid.instance
|
---@return druid.instance
|
||||||
@@ -166,6 +178,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Get parent component name
|
---Get parent component name
|
||||||
|
---@protected
|
||||||
---@return string|nil parent_name The parent component name if exist or nil
|
---@return string|nil parent_name The parent component name if exist or nil
|
||||||
function M:get_parent_name()
|
function M:get_parent_name()
|
||||||
local parent = self:get_parent_component()
|
local parent = self:get_parent_component()
|
||||||
@@ -218,6 +231,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Get component UID, unique identifier created in component creation order.
|
---Get component UID, unique identifier created in component creation order.
|
||||||
|
---@protected
|
||||||
---@return number uid The component uid
|
---@return number uid The component uid
|
||||||
function M:get_uid()
|
function M:get_uid()
|
||||||
return self._component._uid
|
return self._component._uid
|
||||||
@@ -300,6 +314,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Get current component nodes
|
---Get current component nodes
|
||||||
|
---@protected
|
||||||
---@return table<hash, node>|nil
|
---@return table<hash, node>|nil
|
||||||
function M:get_nodes()
|
function M:get_nodes()
|
||||||
local nodes = self._meta.nodes
|
local nodes = self._meta.nodes
|
||||||
@@ -342,6 +357,7 @@ end
|
|||||||
|
|
||||||
|
|
||||||
---Return all children components, recursive
|
---Return all children components, recursive
|
||||||
|
---@protected
|
||||||
---@return table Array of childrens if the Druid component instance
|
---@return table Array of childrens if the Druid component instance
|
||||||
function M:get_childrens()
|
function M:get_childrens()
|
||||||
local childrens = {}
|
local childrens = {}
|
||||||
|
@@ -73,10 +73,8 @@ M.REVERSE_PIVOTS = {
|
|||||||
M.LAYOUT_MODE = {
|
M.LAYOUT_MODE = {
|
||||||
STRETCH_X = "stretch_x",
|
STRETCH_X = "stretch_x",
|
||||||
STRETCH_Y = "stretch_y",
|
STRETCH_Y = "stretch_y",
|
||||||
ZOOM_MIN = "zoom_min",
|
FIT = "fit",
|
||||||
ZOOM_MAX = "zoom_max",
|
STRETCH = "stretch",
|
||||||
FIT = gui.ADJUST_FIT,
|
|
||||||
STRETCH = gui.ADJUST_STRETCH,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.CURRENT_SYSTEM_NAME = sys.get_sys_info().system_name
|
M.CURRENT_SYSTEM_NAME = sys.get_sys_info().system_name
|
||||||
|
@@ -181,6 +181,7 @@ function M.create(text, settings, style)
|
|||||||
shadow = settings.shadow,
|
shadow = settings.shadow,
|
||||||
outline = settings.outline,
|
outline = settings.outline,
|
||||||
font = gui.get_font(settings.text_prefab),
|
font = gui.get_font(settings.text_prefab),
|
||||||
|
split_to_characters = settings.split_to_characters,
|
||||||
-- Image params
|
-- Image params
|
||||||
---@type druid.rich_text.word.image
|
---@type druid.rich_text.word.image
|
||||||
image = nil,
|
image = nil,
|
||||||
|
@@ -29,6 +29,7 @@ local function add_word(text, settings, words)
|
|||||||
end
|
end
|
||||||
|
|
||||||
words[#words + 1] = data
|
words[#words + 1] = data
|
||||||
|
return data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -44,7 +45,16 @@ local function split_line(line, settings, words)
|
|||||||
else
|
else
|
||||||
local wi = #words
|
local wi = #words
|
||||||
for word in trimmed_text:gmatch("%S+") do
|
for word in trimmed_text:gmatch("%S+") do
|
||||||
add_word(word .. " ", settings, words)
|
if settings.split_to_characters then
|
||||||
|
for i = 1, #word do
|
||||||
|
local symbol = utf8.sub(word, i, i)
|
||||||
|
local w = add_word(symbol, settings, words)
|
||||||
|
w.nobr = true
|
||||||
|
end
|
||||||
|
add_word(" ", settings, words)
|
||||||
|
else
|
||||||
|
add_word(word .. " ", settings, words)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
local first = words[wi + 1]
|
local first = words[wi + 1]
|
||||||
first.text = ws_start .. first.text
|
first.text = ws_start .. first.text
|
||||||
|
@@ -2,7 +2,8 @@
|
|||||||
-- Author: Britzl
|
-- Author: Britzl
|
||||||
-- Modified by: Insality
|
-- Modified by: Insality
|
||||||
|
|
||||||
local color = require("druid.custom.rich_text.module.rt_color")
|
--local color = require("druid.custom.rich_text.module.rt_color")
|
||||||
|
local color = require("druid.color")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
local tags = {}
|
local tags = {}
|
||||||
@@ -43,20 +44,17 @@ end
|
|||||||
-- Format: <color={COLOR_NAME}>{Text}</color>
|
-- Format: <color={COLOR_NAME}>{Text}</color>
|
||||||
-- Example: <color=FF0000>Rich Text</color>
|
-- Example: <color=FF0000>Rich Text</color>
|
||||||
M.register("color", function(params, settings, style)
|
M.register("color", function(params, settings, style)
|
||||||
params = style.COLORS[params] or params
|
settings.color = color.get_color(params)
|
||||||
settings.color = color.parse(params)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
M.register("shadow", function(params, settings, style)
|
M.register("shadow", function(params, settings, style)
|
||||||
params = style.COLORS[params] or params
|
settings.shadow = color.get_color(params)
|
||||||
settings.shadow = color.parse(params)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
M.register("outline", function(params, settings, style)
|
M.register("outline", function(params, settings, style)
|
||||||
params = style.COLORS[params] or params
|
settings.outline = color.get_color(params)
|
||||||
settings.outline = color.parse(params)
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
@@ -13,6 +13,7 @@ local rich_text = require("druid.custom.rich_text.module.rt")
|
|||||||
---@field image_pixel_grid_snap boolean
|
---@field image_pixel_grid_snap boolean
|
||||||
---@field combine_words boolean
|
---@field combine_words boolean
|
||||||
---@field default_animation string
|
---@field default_animation string
|
||||||
|
---@field split_by_character boolean
|
||||||
---@field text_prefab node
|
---@field text_prefab node
|
||||||
---@field adjust_scale number
|
---@field adjust_scale number
|
||||||
---@field default_texture string
|
---@field default_texture string
|
||||||
@@ -50,7 +51,6 @@ local rich_text = require("druid.custom.rich_text.module.rt")
|
|||||||
---@field height number
|
---@field height number
|
||||||
|
|
||||||
---@class druid.rich_text.style
|
---@class druid.rich_text.style
|
||||||
---@field COLORS table<string, vector4>
|
|
||||||
---@field ADJUST_STEPS number
|
---@field ADJUST_STEPS number
|
||||||
---@field ADJUST_SCALE_DELTA number
|
---@field ADJUST_SCALE_DELTA number
|
||||||
|
|
||||||
@@ -104,7 +104,6 @@ end
|
|||||||
---@param style druid.rich_text.style
|
---@param style druid.rich_text.style
|
||||||
function M:on_style_change(style)
|
function M:on_style_change(style)
|
||||||
self.style = {
|
self.style = {
|
||||||
COLORS = style.COLORS or {},
|
|
||||||
ADJUST_STEPS = style.ADJUST_STEPS or 20,
|
ADJUST_STEPS = style.ADJUST_STEPS or 20,
|
||||||
ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02,
|
ADJUST_SCALE_DELTA = style.ADJUST_SCALE_DELTA or 0.02,
|
||||||
}
|
}
|
||||||
@@ -194,6 +193,15 @@ function M:tagged(tag)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Set if the rich text should split to characters, not words
|
||||||
|
---@param value boolean
|
||||||
|
---@return druid.rich_text self
|
||||||
|
function M:set_split_to_characters(value)
|
||||||
|
self._settings.split_to_characters = value
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
---Get all current created words, each word is a table that contains the information about the word
|
---Get all current created words, each word is a table that contains the information about the word
|
||||||
---@return druid.rich_text.word[]
|
---@return druid.rich_text.word[]
|
||||||
function M:get_words()
|
function M:get_words()
|
||||||
@@ -239,6 +247,7 @@ function M:_create_settings()
|
|||||||
outline = gui.get_outline(self.root),
|
outline = gui.get_outline(self.root),
|
||||||
text_leading = gui.get_leading(self.root),
|
text_leading = gui.get_leading(self.root),
|
||||||
is_multiline = gui.get_line_break(self.root),
|
is_multiline = gui.get_line_break(self.root),
|
||||||
|
split_to_characters = false,
|
||||||
|
|
||||||
-- Image settings
|
-- Image settings
|
||||||
image_pixel_grid_snap = false, -- disabled now
|
image_pixel_grid_snap = false, -- disabled now
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
local component = require("druid.component")
|
local component = require("druid.component")
|
||||||
local helper = require("druid.helper")
|
local helper = require("druid.helper")
|
||||||
local defer = require("event.defer")
|
local queues = require("event.queues")
|
||||||
|
|
||||||
---@class druid.tiling_node: druid.component
|
---@class druid.tiling_node: druid.component
|
||||||
---@field animation table
|
---@field animation table
|
||||||
@@ -28,7 +28,7 @@ function M:init(node)
|
|||||||
print("The druid.script is not found, please add it nearby to the GUI collection", msg.url())
|
print("The druid.script is not found, please add it nearby to the GUI collection", msg.url())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
defer.push("druid.get_atlas_path", {
|
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(),
|
||||||
}, self.on_get_atlas_path, self)
|
}, self.on_get_atlas_path, self)
|
||||||
|
@@ -28,4 +28,7 @@ images {
|
|||||||
images {
|
images {
|
||||||
image: "/druid/images/icons/icon_arrow.png"
|
image: "/druid/images/icons/icon_arrow.png"
|
||||||
}
|
}
|
||||||
|
images {
|
||||||
|
image: "/druid/images/icons/icon_refresh.png"
|
||||||
|
}
|
||||||
extrude_borders: 2
|
extrude_borders: 2
|
||||||
|
@@ -114,6 +114,12 @@ local function wrap_widget(widget)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for key, value in pairs(widget) do
|
||||||
|
if event.is_event(value) then
|
||||||
|
wrapped_widget[key] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return wrapped_widget
|
return wrapped_widget
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -126,23 +132,26 @@ 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
|
||||||
---@return T? widget The new created widget,
|
---@param params any|nil Additional parameters to pass to the widget's init function
|
||||||
function M.get_widget(widget_class, gui_url)
|
---@return T widget The new created widget,
|
||||||
|
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]
|
||||||
if not registered_druids then
|
assert(registered_druids, "Druid widget not registered for this game object")
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
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)
|
return druid.new_widget:trigger(widget_class, nil, nil, params)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return nil
|
error("Druid widget not found for this game object: " .. gui_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -156,8 +165,8 @@ function M.register_druid_as_widget(druid)
|
|||||||
table.insert(REGISTERED_GUI_WIDGETS[gui_url.socket], {
|
table.insert(REGISTERED_GUI_WIDGETS[gui_url.socket], {
|
||||||
path = gui_url.path,
|
path = gui_url.path,
|
||||||
fragment = gui_url.fragment,
|
fragment = gui_url.fragment,
|
||||||
new_widget = event.create(function(widget_class)
|
new_widget = event.create(function(widget_class, template, nodes, params)
|
||||||
return wrap_widget(druid:new_widget(widget_class))
|
return wrap_widget(druid:new_widget(widget_class, template, nodes, params))
|
||||||
end),
|
end),
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
@@ -3,9 +3,9 @@
|
|||||||
-- This one is a required to make a unified "Shaders" pipeline in the GUI scripts
|
-- This one is a required to make a unified "Shaders" pipeline in the GUI scripts
|
||||||
-- This required to grab a texture data with `go.get` function
|
-- This required to grab a texture data with `go.get` function
|
||||||
|
|
||||||
local defer = require("event.defer")
|
local queues = require("event.queues")
|
||||||
|
|
||||||
---Usage: defer.push("druid.get_atlas_path", {
|
---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,15 +30,15 @@ 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
|
||||||
|
|
||||||
|
|
||||||
function init(self)
|
function init(self)
|
||||||
defer.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
|
queues.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function final(self)
|
function final(self)
|
||||||
defer.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
|
queues.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
|
||||||
end
|
end
|
||||||
|
@@ -31,6 +31,8 @@ local CORNER_PIVOTS = {
|
|||||||
---@field DRAGGABLE_CORNER_SIZE vector3 Size of box node for debug draggable corners
|
---@field DRAGGABLE_CORNER_SIZE vector3 Size of box node for debug draggable corners
|
||||||
---@field DRAGGABLE_CORNER_COLOR vector4 Color of debug draggable corners
|
---@field DRAGGABLE_CORNER_COLOR vector4 Color of debug draggable corners
|
||||||
|
|
||||||
|
---@alias druid.container.mode "stretch" | "fit" | "stretch_x" | "stretch_y"
|
||||||
|
|
||||||
---Druid component to manage the size and positions with other containers relations to create a adaptable layouts.
|
---Druid component to manage the size and positions with other containers relations to create a adaptable layouts.
|
||||||
---
|
---
|
||||||
---### Setup
|
---### Setup
|
||||||
@@ -54,7 +56,7 @@ local CORNER_PIVOTS = {
|
|||||||
---@field position vector3 The current position
|
---@field position vector3 The current position
|
||||||
---@field pivot_offset vector3 The pivot offset
|
---@field pivot_offset vector3 The pivot offset
|
||||||
---@field center_offset vector3 The center offset
|
---@field center_offset vector3 The center offset
|
||||||
---@field mode string The layout mode
|
---@field mode druid.container.mode The layout mode
|
||||||
---@field fit_size vector3 The fit size
|
---@field fit_size vector3 The fit size
|
||||||
---@field min_size_x number|nil The minimum size x
|
---@field min_size_x number|nil The minimum size x
|
||||||
---@field min_size_y number|nil The minimum size y
|
---@field min_size_y number|nil The minimum size y
|
||||||
@@ -176,7 +178,7 @@ function M:set_size(width, height, anchor_pivot)
|
|||||||
if self.max_size_y then
|
if self.max_size_y then
|
||||||
height = min(height, self.max_size_y)
|
height = min(height, self.max_size_y)
|
||||||
end
|
end
|
||||||
|
|
||||||
if (width and width ~= self.size.x) or (height and height ~= self.size.y) then
|
if (width and width ~= self.size.x) or (height and height ~= self.size.y) then
|
||||||
self.center_offset.x = -width * self.pivot_offset.x
|
self.center_offset.x = -width * self.pivot_offset.x
|
||||||
self.center_offset.y = -height * self.pivot_offset.y
|
self.center_offset.y = -height * self.pivot_offset.y
|
||||||
@@ -271,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)
|
||||||
@@ -537,7 +539,7 @@ function M:_on_corner_drag(x, y, corner_offset)
|
|||||||
end
|
end
|
||||||
if self.max_size_y and size.y + y > self.max_size_y then
|
if self.max_size_y and size.y + y > self.max_size_y then
|
||||||
y = self.max_size_y - size.y
|
y = self.max_size_y - size.y
|
||||||
end
|
end
|
||||||
|
|
||||||
if corner_offset.x < 0 then
|
if corner_offset.x < 0 then
|
||||||
self.node_offset.x = self.node_offset.x - x
|
self.node_offset.x = self.node_offset.x - x
|
||||||
|
@@ -267,10 +267,17 @@ function M:_refresh()
|
|||||||
local start_index = self.grid:get_index(start_pos)
|
local start_index = self.grid:get_index(start_pos)
|
||||||
start_index = math.max(1, start_index)
|
start_index = math.max(1, start_index)
|
||||||
|
|
||||||
local pivot = helper.get_pivot_offset(gui.get_pivot(self.scroll.view_node))
|
local offset_x = self.scroll.view_size.x
|
||||||
local offset_x = self.scroll.view_size.x * (0.5 - pivot.x)
|
local offset_y = self.scroll.view_size.y
|
||||||
local offset_y = self.scroll.view_size.y * (0.5 + pivot.y)
|
|
||||||
local end_pos = vmath.vector3(start_pos.x + offset_x, start_pos.y - offset_y, 0)
|
local end_pos = vmath.vector3(start_pos.x + offset_x, start_pos.y - offset_y, 0)
|
||||||
|
|
||||||
|
local max_offset_x = (self.grid.in_row - 1) * self.grid.node_size.x
|
||||||
|
end_pos.x = math.min(end_pos.x, start_pos.x + max_offset_x)
|
||||||
|
|
||||||
|
if #self._data <= self.grid.in_row then
|
||||||
|
end_pos.y = start_pos.y
|
||||||
|
end
|
||||||
|
|
||||||
local end_index = self.grid:get_index(end_pos)
|
local end_index = self.grid:get_index(end_pos)
|
||||||
end_index = math.min(#self._data, end_index)
|
end_index = math.min(#self._data, end_index)
|
||||||
|
|
||||||
|
@@ -55,12 +55,13 @@ function M:init(node_or_node_id, layout_type)
|
|||||||
self.entities = {}
|
self.entities = {}
|
||||||
self.size = gui.get_size(self.node)
|
self.size = gui.get_size(self.node)
|
||||||
|
|
||||||
|
-- Padding X is a Slice9 L Value
|
||||||
|
-- Padding Y is a Slice9 T Value
|
||||||
self.padding = gui.get_slice9(self.node)
|
self.padding = gui.get_slice9(self.node)
|
||||||
-- Margin X is a Slice9 R Value
|
-- Margin X is a Slice9 R Value
|
||||||
-- Margin Y is a Slice9 B Value
|
-- Margin Y is a Slice9 B Value
|
||||||
self.margin = { x = self.padding.z, y = self.padding.w }
|
self.margin = { x = self.padding.z, y = self.padding.w }
|
||||||
-- Padding X is a Slice9 L Value
|
|
||||||
-- Padding Y is a Slice9 T Value
|
|
||||||
self.padding.z = self.padding.x
|
self.padding.z = self.padding.x
|
||||||
self.padding.w = self.padding.y
|
self.padding.w = self.padding.y
|
||||||
|
|
||||||
@@ -68,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
|
||||||
@@ -78,7 +80,7 @@ function M:update()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
self:refresh_layout()
|
self:refresh_layout(false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -123,10 +125,10 @@ function M:set_margin(margin_x, margin_y)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param padding_x number|nil The padding x
|
---@param padding_x number|nil From Left
|
||||||
---@param padding_y number|nil The padding y
|
---@param padding_y number|nil From Top
|
||||||
---@param padding_z number|nil The padding z
|
---@param padding_z number|nil From Right
|
||||||
---@param padding_w number|nil The padding w
|
---@param padding_w number|nil From Bottom
|
||||||
---@return druid.layout self Current layout instance
|
---@return druid.layout self Current layout instance
|
||||||
function M:set_padding(padding_x, padding_y, padding_z, padding_w)
|
function M:set_padding(padding_x, padding_y, padding_z, padding_w)
|
||||||
self.padding.x = padding_x or self.padding.x
|
self.padding.x = padding_x or self.padding.x
|
||||||
@@ -234,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
|
||||||
@@ -353,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
|
||||||
|
|
||||||
@@ -498,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
|
||||||
gui.set_position(node, TEMP_VECTOR)
|
|
||||||
|
if is_instant then
|
||||||
|
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
|
||||||
|
@@ -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
|
||||||
|
BIN
druid/images/icons/icon_refresh.png
Normal file
BIN
druid/images/icons/icon_refresh.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 593 B |
@@ -148,12 +148,4 @@ M["hotkey"] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
M["rich_text"] = {
|
|
||||||
COLORS = {
|
|
||||||
white = "#FFFFFF",
|
|
||||||
black = "#000000"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
---@class druid.widget: druid.component
|
---@class druid.widget: druid.component
|
||||||
---@field druid druid.instance Ready to use druid instance
|
---@field protected druid druid.instance Ready to use druid instance
|
||||||
|
|
||||||
---@class druid.logger
|
---@class druid.logger
|
||||||
---@field trace fun(message: string, context: any)
|
---@field trace fun(message: string, context: any)
|
||||||
|
@@ -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")
|
||||||
@@ -681,7 +682,7 @@ end
|
|||||||
local container = require("druid.extended.container")
|
local container = require("druid.extended.container")
|
||||||
---Create Container component
|
---Create Container component
|
||||||
---@param node string|node The node_id or gui.get_node(node_id).
|
---@param node string|node The node_id or gui.get_node(node_id).
|
||||||
---@param mode string|nil Layout mode
|
---@param mode druid.container.mode|nil Layout mode. Default Fit or Stretch depends from node adjust mode from GUI scene
|
||||||
---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed
|
---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed
|
||||||
---@return druid.container container The new container component
|
---@return druid.container container The new container component
|
||||||
function M:new_container(node, mode, callback)
|
function M:new_container(node, mode, callback)
|
||||||
|
@@ -6,7 +6,6 @@ local color = require("druid.color")
|
|||||||
---@field text_name druid.text
|
---@field text_name druid.text
|
||||||
---@field button druid.button
|
---@field button druid.button
|
||||||
---@field text_button druid.text
|
---@field text_button druid.text
|
||||||
---@field druid druid.instance
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,6 +50,14 @@ function M:set_text_button(text)
|
|||||||
end
|
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)
|
function M:set_color(color_value)
|
||||||
color.set_color(self:get_node("button"), color_value)
|
color.set_color(self:get_node("button"), color_value)
|
||||||
end
|
end
|
||||||
|
@@ -73,4 +73,11 @@ function M:on_change(callback)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Set the enabled state of the checkbox
|
||||||
|
---@param enabled boolean
|
||||||
|
function M:set_enabled(enabled)
|
||||||
|
self.button:set_enabled(enabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@@ -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 {
|
||||||
@@ -101,6 +114,45 @@ nodes {
|
|||||||
inherit_alpha: true
|
inherit_alpha: true
|
||||||
size_mode: SIZE_MODE_AUTO
|
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"
|
||||||
|
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"
|
||||||
|
inherit_alpha: true
|
||||||
|
size_mode: SIZE_MODE_AUTO
|
||||||
|
}
|
||||||
nodes {
|
nodes {
|
||||||
position {
|
position {
|
||||||
y: -50.0
|
y: -50.0
|
||||||
|
@@ -1,3 +1,7 @@
|
|||||||
|
local event = require("event.event")
|
||||||
|
|
||||||
|
local color = require("druid.color")
|
||||||
|
local helper = require("druid.helper")
|
||||||
local property_checkbox = require("druid.widget.properties_panel.properties.property_checkbox")
|
local property_checkbox = require("druid.widget.properties_panel.properties.property_checkbox")
|
||||||
local property_slider = require("druid.widget.properties_panel.properties.property_slider")
|
local property_slider = require("druid.widget.properties_panel.properties.property_slider")
|
||||||
local property_button = require("druid.widget.properties_panel.properties.property_button")
|
local property_button = require("druid.widget.properties_panel.properties.property_button")
|
||||||
@@ -21,6 +25,8 @@ local property_vector3 = require("druid.widget.properties_panel.properties.prope
|
|||||||
---@field properties_constructors fun()[] List of properties functions to create a new widget. Used to not spawn non-visible widgets but keep the reference
|
---@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 M = {}
|
||||||
|
|
||||||
|
local COLOR_BUTTON = "#4E4F50"
|
||||||
|
local COLOR_REFRESH_ACTIVE = "#8BD092"
|
||||||
|
|
||||||
function M:init()
|
function M:init()
|
||||||
self.root = self:get_node("root")
|
self.root = self:get_node("root")
|
||||||
@@ -33,6 +39,10 @@ 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
|
||||||
|
self.scenes = {}
|
||||||
|
|
||||||
self.properties = {}
|
self.properties = {}
|
||||||
self.properties_constructors = {}
|
self.properties_constructors = {}
|
||||||
@@ -52,6 +62,15 @@ function M:init()
|
|||||||
self:set_hidden(not self._is_hidden)
|
self:set_hidden(not self._is_hidden)
|
||||||
end):set_style(nil)
|
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
|
-- 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 = self.druid:new_widget(property_left_right_selector, "property_left_right_selector", "root")
|
||||||
self.paginator:set_text("Page")
|
self.paginator:set_text("Page")
|
||||||
@@ -80,6 +99,23 @@ function M:on_remove()
|
|||||||
end
|
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)
|
function M:on_drag_widget(dx, dy)
|
||||||
local position = self.container:get_position()
|
local position = self.container:get_position()
|
||||||
self.container:set_position(position.x + dx, position.y + dy)
|
self.container:set_position(position.x + dx, position.y + dy)
|
||||||
@@ -112,9 +148,41 @@ function M:clear_created_properties()
|
|||||||
end
|
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()
|
function M:clear()
|
||||||
self:clear_created_properties()
|
self:clear_created_properties()
|
||||||
self.properties_constructors = {}
|
self.properties_constructors = {}
|
||||||
|
self.current_page = 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -139,26 +207,28 @@ end
|
|||||||
|
|
||||||
|
|
||||||
function M:update(dt)
|
function M:update(dt)
|
||||||
if self.is_dirty then
|
if not self.is_dirty then
|
||||||
self.is_dirty = false
|
return
|
||||||
|
end
|
||||||
|
|
||||||
self:clear_created_properties()
|
self.is_dirty = false
|
||||||
|
|
||||||
local properties_count = #self.properties_constructors
|
self:clear_created_properties()
|
||||||
|
|
||||||
-- Render all current properties
|
local properties_count = #self.properties_constructors
|
||||||
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
|
-- Render all current properties
|
||||||
gui.set_enabled(self.paginator.root, is_paginator_visible)
|
local start_index = (self.current_page - 1) * self.properties_per_page + 1
|
||||||
self.paginator:set_number_type(1, math.ceil(properties_count / self.properties_per_page), true)
|
local end_index = start_index + self.properties_per_page - 1
|
||||||
self.paginator.text_value:set_text(self.current_page .. " / " .. math.ceil(properties_count / self.properties_per_page))
|
end_index = math.min(end_index, properties_count)
|
||||||
|
|
||||||
for index = start_index, end_index do
|
local is_paginator_visible = properties_count > self.properties_per_page
|
||||||
self.properties_constructors[index]()
|
gui.set_enabled(self.paginator.root, is_paginator_visible)
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -285,14 +355,32 @@ function M:remove(widget)
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Force to refresh properties next update
|
||||||
|
function M:set_dirty()
|
||||||
|
self.is_dirty = true
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not self._is_hidden then
|
||||||
|
self.is_dirty = true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -301,16 +389,183 @@ function M:is_hidden()
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M:load_previous_page()
|
||||||
|
self.current_page = self.current_page - 1
|
||||||
|
self.is_dirty = true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
---@param properties_per_page number
|
---@param properties_per_page number
|
||||||
function M:set_properties_per_page(properties_per_page)
|
function M:set_properties_per_page(properties_per_page)
|
||||||
self.properties_per_page = properties_per_page
|
self.properties_per_page = properties_per_page
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---Set a page of current scene
|
||||||
|
---@param page number
|
||||||
function M:set_page(page)
|
function M:set_page(page)
|
||||||
self.current_page = page
|
self.current_page = page
|
||||||
self.is_dirty = true
|
self.is_dirty = true
|
||||||
end
|
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
|
return M
|
||||||
|
14
example/assets/default.display_profiles
Normal file
14
example/assets/default.display_profiles
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
profiles {
|
||||||
|
name: "Landscape"
|
||||||
|
qualifiers {
|
||||||
|
width: 1920
|
||||||
|
height: 1080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profiles {
|
||||||
|
name: "Portrait"
|
||||||
|
qualifiers {
|
||||||
|
width: 1080
|
||||||
|
height: 1920
|
||||||
|
}
|
||||||
|
}
|
@@ -2863,6 +2863,49 @@ nodes {
|
|||||||
parent: "data_list_cache_with_component/button_component/root"
|
parent: "data_list_cache_with_component/button_component/root"
|
||||||
template_node_child: true
|
template_node_child: true
|
||||||
}
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_TEMPLATE
|
||||||
|
id: "data_list_matrix_basic"
|
||||||
|
parent: "data_list"
|
||||||
|
inherit_alpha: true
|
||||||
|
template: "/example/examples/data_list/basic/data_list_matrix_basic.gui"
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "data_list_matrix_basic/root"
|
||||||
|
parent: "data_list_matrix_basic"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "data_list_matrix_basic/view"
|
||||||
|
parent: "data_list_matrix_basic/root"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "data_list_matrix_basic/content"
|
||||||
|
parent: "data_list_matrix_basic/view"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "data_list_matrix_basic/prefab"
|
||||||
|
parent: "data_list_matrix_basic/content"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "data_list_matrix_basic/panel"
|
||||||
|
parent: "data_list_matrix_basic/prefab"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_TEXT
|
||||||
|
id: "data_list_matrix_basic/text"
|
||||||
|
parent: "data_list_matrix_basic/prefab"
|
||||||
|
template_node_child: true
|
||||||
|
}
|
||||||
nodes {
|
nodes {
|
||||||
type: TYPE_BOX
|
type: TYPE_BOX
|
||||||
texture: "druid_example/empty"
|
texture: "druid_example/empty"
|
||||||
|
@@ -11,6 +11,12 @@ function M:init()
|
|||||||
self.drag = self.druid:new_drag("drag/root", self.on_drag)
|
self.drag = self.druid:new_drag("drag/root", self.on_drag)
|
||||||
self.drag.on_drag_end:subscribe(self.on_drag_end)
|
self.drag.on_drag_end:subscribe(self.on_drag_end)
|
||||||
|
|
||||||
|
self.druid:new_button("drag/root", function()
|
||||||
|
self.counter = self.counter - 1
|
||||||
|
gui.set_text(self.text_counter, self.counter)
|
||||||
|
self:on_drop_to_zone()
|
||||||
|
end)
|
||||||
|
|
||||||
-- Save start position for animation
|
-- Save start position for animation
|
||||||
self.start_position = gui.get_position(self.drag.node)
|
self.start_position = gui.get_position(self.drag.node)
|
||||||
end
|
end
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
---@class widget.container_anchors: druid.widget
|
---@class widget.container_resize: druid.widget
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
117
example/examples/data_list/basic/data_list_matrix_basic.gui
Normal file
117
example/examples/data_list/basic/data_list_matrix_basic.gui
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
fonts {
|
||||||
|
name: "text_bold"
|
||||||
|
font: "/example/assets/fonts/text_bold.font"
|
||||||
|
}
|
||||||
|
textures {
|
||||||
|
name: "druid_example"
|
||||||
|
texture: "/example/assets/druid_example.atlas"
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
type: TYPE_BOX
|
||||||
|
texture: "druid_example/empty"
|
||||||
|
id: "root"
|
||||||
|
inherit_alpha: true
|
||||||
|
size_mode: SIZE_MODE_AUTO
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
y: 350.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 400.0
|
||||||
|
y: 700.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 0.173
|
||||||
|
y: 0.184
|
||||||
|
z: 0.204
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
texture: "druid_example/pixel"
|
||||||
|
id: "view"
|
||||||
|
pivot: PIVOT_N
|
||||||
|
parent: "root"
|
||||||
|
inherit_alpha: true
|
||||||
|
clipping_mode: CLIPPING_MODE_STENCIL
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
size {
|
||||||
|
x: 400.0
|
||||||
|
y: 700.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
texture: "druid_example/empty"
|
||||||
|
id: "content"
|
||||||
|
pivot: PIVOT_N
|
||||||
|
parent: "view"
|
||||||
|
inherit_alpha: true
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
position {
|
||||||
|
y: -350.0
|
||||||
|
}
|
||||||
|
size {
|
||||||
|
x: 100.0
|
||||||
|
y: 100.0
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
id: "prefab"
|
||||||
|
parent: "content"
|
||||||
|
inherit_alpha: true
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
size {
|
||||||
|
x: 90.0
|
||||||
|
y: 90.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 0.631
|
||||||
|
y: 0.843
|
||||||
|
z: 0.961
|
||||||
|
}
|
||||||
|
type: TYPE_BOX
|
||||||
|
texture: "druid_example/ui_circle_32"
|
||||||
|
id: "panel"
|
||||||
|
parent: "prefab"
|
||||||
|
inherit_alpha: true
|
||||||
|
slice9 {
|
||||||
|
x: 16.0
|
||||||
|
y: 16.0
|
||||||
|
z: 16.0
|
||||||
|
w: 16.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
size {
|
||||||
|
x: 50.0
|
||||||
|
y: 50.0
|
||||||
|
}
|
||||||
|
color {
|
||||||
|
x: 0.31
|
||||||
|
y: 0.318
|
||||||
|
z: 0.322
|
||||||
|
}
|
||||||
|
type: TYPE_TEXT
|
||||||
|
text: "1"
|
||||||
|
font: "text_bold"
|
||||||
|
id: "text"
|
||||||
|
outline {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
}
|
||||||
|
shadow {
|
||||||
|
x: 1.0
|
||||||
|
y: 1.0
|
||||||
|
z: 1.0
|
||||||
|
}
|
||||||
|
parent: "prefab"
|
||||||
|
inherit_alpha: true
|
||||||
|
outline_alpha: 0.0
|
||||||
|
shadow_alpha: 0.0
|
||||||
|
}
|
||||||
|
material: "/builtins/materials/gui.material"
|
||||||
|
adjust_reference: ADJUST_REFERENCE_PARENT
|
95
example/examples/data_list/basic/data_list_matrix_basic.lua
Normal file
95
example/examples/data_list/basic/data_list_matrix_basic.lua
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
local event = require("event.event")
|
||||||
|
|
||||||
|
---@class examples.data_list_matrix_basic: druid.widget
|
||||||
|
---@field prefab node
|
||||||
|
---@field scroll druid.scroll
|
||||||
|
---@field grid druid.grid
|
||||||
|
---@field data_list druid.data_list
|
||||||
|
---@field on_item_click event
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
|
||||||
|
function M:init()
|
||||||
|
self.prefab = self:get_node("prefab")
|
||||||
|
gui.set_enabled(self.prefab, false)
|
||||||
|
|
||||||
|
self.scroll = self.druid:new_scroll("view", "content")
|
||||||
|
self.grid = self.druid:new_grid("content", self.prefab, 4)
|
||||||
|
self.data_list = self.druid:new_data_list(self.scroll, self.grid, self.create_item_callback) --[[@as druid.data_list]]
|
||||||
|
|
||||||
|
local data = {}
|
||||||
|
for index = 1, 1000 do
|
||||||
|
table.insert(data, {})
|
||||||
|
end
|
||||||
|
self.data_list:set_data(data)
|
||||||
|
|
||||||
|
self.on_item_click = event.create()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param item_data table
|
||||||
|
---@param index number
|
||||||
|
---@return node, druid.component
|
||||||
|
function M:create_item_callback(item_data, index)
|
||||||
|
local nodes = gui.clone_tree(self.prefab)
|
||||||
|
|
||||||
|
local root = nodes[self:get_template() .. "/prefab"]
|
||||||
|
local text = nodes[self:get_template() .. "/text"]
|
||||||
|
gui.set_enabled(root, true)
|
||||||
|
gui.set_text(text, tostring(index))
|
||||||
|
|
||||||
|
local button = self.druid:new_button(root, self.on_button_click, index)
|
||||||
|
return root, button
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M:on_button_click(index)
|
||||||
|
self.on_item_click:trigger(index)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param output_list output_list
|
||||||
|
function M:on_example_created(output_list)
|
||||||
|
self.on_item_click:subscribe(function(index)
|
||||||
|
output_list:add_log_text("Item clicked: " .. index)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param properties_panel properties_panel
|
||||||
|
function M:properties_control(properties_panel)
|
||||||
|
local view_node = self.scroll.view_node
|
||||||
|
local is_stencil = gui.get_clipping_mode(view_node) == gui.CLIPPING_MODE_STENCIL
|
||||||
|
|
||||||
|
properties_panel:add_checkbox("ui_clipping", is_stencil, function(value)
|
||||||
|
gui.set_clipping_mode(view_node, value and gui.CLIPPING_MODE_STENCIL or gui.CLIPPING_MODE_NONE)
|
||||||
|
end)
|
||||||
|
|
||||||
|
properties_panel:add_slider("ui_scroll", 0, function(value)
|
||||||
|
self.scroll:scroll_to_percent(vmath.vector3(0, 1 - value, 0), true)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@return string
|
||||||
|
function M:get_debug_info()
|
||||||
|
local data_list = self.data_list
|
||||||
|
|
||||||
|
local data = data_list:get_data()
|
||||||
|
local info = ""
|
||||||
|
info = info .. "Data length: " .. #data .. "\n"
|
||||||
|
info = info .. "First Visual Index: " .. data_list.top_index .. "\n"
|
||||||
|
info = info .. "Last Visual Index: " .. data_list.last_index .. "\n"
|
||||||
|
|
||||||
|
local s = self.scroll
|
||||||
|
info = info .. "\n"
|
||||||
|
info = info .. "View Size Y: " .. gui.get(s.view_node, "size.y") .. "\n"
|
||||||
|
info = info .. "Content Size Y: " .. gui.get(s.content_node, "size.y") .. "\n"
|
||||||
|
info = info .. "Content position Y: " .. math.ceil(s.position.y) .. "\n"
|
||||||
|
info = info .. "Content Range Y: " .. s.available_pos.y .. " - " .. s.available_pos.w .. "\n"
|
||||||
|
|
||||||
|
return info
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
@@ -21,6 +21,15 @@ function M.get_examples()
|
|||||||
widget_class = require("example.examples.data_list.basic.data_list_horizontal_basic"),
|
widget_class = require("example.examples.data_list.basic.data_list_horizontal_basic"),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name_id = "ui_example_data_list_matrix_basic",
|
||||||
|
information_text_id = "ui_example_data_list_matrix_basic_description",
|
||||||
|
template = "data_list_matrix_basic",
|
||||||
|
root = "data_list_matrix_basic/root",
|
||||||
|
code_url = "example/examples/data_list/basic/data_list_matrix_basic.lua",
|
||||||
|
widget_class = require("example.examples.data_list.basic.data_list_matrix_basic"),
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name_id = "ui_example_data_list_add_remove_clear",
|
name_id = "ui_example_data_list_add_remove_clear",
|
||||||
information_text_id = "ui_example_data_list_add_remove_clear_description",
|
information_text_id = "ui_example_data_list_add_remove_clear_description",
|
||||||
|
@@ -147,6 +147,9 @@
|
|||||||
"ui_example_data_list_horizontal_basic": "Data List Horizontal Basic",
|
"ui_example_data_list_horizontal_basic": "Data List Horizontal Basic",
|
||||||
"ui_example_data_list_horizontal_basic_description": "How to make a horizontal data list",
|
"ui_example_data_list_horizontal_basic_description": "How to make a horizontal data list",
|
||||||
|
|
||||||
|
"ui_example_data_list_matrix_basic": "Data List Matrix Basic",
|
||||||
|
"ui_example_data_list_matrix_basic_description": "How to make a matrix data list",
|
||||||
|
|
||||||
"ui_example_data_list_add_remove_clear": "Data List Add Remove Clear",
|
"ui_example_data_list_add_remove_clear": "Data List Add Remove Clear",
|
||||||
"ui_example_data_list_add_remove_clear_description": "How the add, remove and clear functions work in the data list",
|
"ui_example_data_list_add_remove_clear_description": "How the add, remove and clear functions work in the data list",
|
||||||
|
|
||||||
|
10
game.project
10
game.project
@@ -14,16 +14,16 @@ update_frequency = 60
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
title = Druid
|
title = Druid
|
||||||
version = 1.1.1
|
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]
|
||||||
@@ -58,7 +58,7 @@ cssfile = /builtins/manifests/web/dark_theme.css
|
|||||||
show_console_banner = 0
|
show_console_banner = 0
|
||||||
|
|
||||||
[native_extension]
|
[native_extension]
|
||||||
app_manifest =
|
app_manifest =
|
||||||
|
|
||||||
[graphics]
|
[graphics]
|
||||||
texture_profiles = /builtins/graphics/default.texture_profiles
|
texture_profiles = /builtins/graphics/default.texture_profiles
|
||||||
|
@@ -5,6 +5,7 @@ function init(self)
|
|||||||
deftest.add(require("test.tests.test_back_handler"))
|
deftest.add(require("test.tests.test_back_handler"))
|
||||||
deftest.add(require("test.tests.test_blocker"))
|
deftest.add(require("test.tests.test_blocker"))
|
||||||
deftest.add(require("test.tests.test_button"))
|
deftest.add(require("test.tests.test_button"))
|
||||||
|
deftest.add(require("test.tests.test_color"))
|
||||||
deftest.add(require("test.tests.test_container"))
|
deftest.add(require("test.tests.test_container"))
|
||||||
deftest.add(require("test.tests.test_drag"))
|
deftest.add(require("test.tests.test_drag"))
|
||||||
deftest.add(require("test.tests.test_grid"))
|
deftest.add(require("test.tests.test_grid"))
|
||||||
|
313
test/tests/test_color.lua
Normal file
313
test/tests/test_color.lua
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
return function()
|
||||||
|
describe("Color Module", function()
|
||||||
|
local color = nil
|
||||||
|
|
||||||
|
before(function()
|
||||||
|
color = require("druid.color")
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Color Creation and Parsing", function()
|
||||||
|
it("Should create colors from RGBA values", function()
|
||||||
|
local test_color = color.new(1, 0.5, 0, 0.8)
|
||||||
|
assert(test_color.x == 1)
|
||||||
|
assert(test_color.y == 0.5)
|
||||||
|
assert(test_color.z == 0)
|
||||||
|
assert(test_color.w == 0.8)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should create colors from hex strings", function()
|
||||||
|
local red = color.from_hex("#FF0000")
|
||||||
|
assert(red.x == 1)
|
||||||
|
assert(red.y == 0)
|
||||||
|
assert(red.z == 0)
|
||||||
|
assert(red.w == 1)
|
||||||
|
|
||||||
|
local blue_with_alpha = color.from_hex("0000FF", 0.5)
|
||||||
|
assert(blue_with_alpha.x == 0)
|
||||||
|
assert(blue_with_alpha.y == 0)
|
||||||
|
assert(blue_with_alpha.z == 1)
|
||||||
|
assert(blue_with_alpha.w == 0.5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should create colors from HSB values", function()
|
||||||
|
local red = color.from_hsb(0, 1, 1) -- HSB for red
|
||||||
|
assert(red.x == 1)
|
||||||
|
assert(red.y == 0)
|
||||||
|
assert(red.z == 0)
|
||||||
|
assert(red.w == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should parse various color formats", function()
|
||||||
|
-- Test parsing alias for get_color
|
||||||
|
local hex_color = color.parse("#FF0000")
|
||||||
|
assert(hex_color.x == 1)
|
||||||
|
|
||||||
|
local vector_color = color.parse(vmath.vector4(0, 1, 0, 1))
|
||||||
|
assert(vector_color.y == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should parse hex colors correctly with get_color", function()
|
||||||
|
-- Test with # prefix
|
||||||
|
local color1 = color.get_color("#FF0000")
|
||||||
|
assert(color1.x == 1)
|
||||||
|
assert(color1.y == 0)
|
||||||
|
assert(color1.z == 0)
|
||||||
|
assert(color1.w == 1)
|
||||||
|
|
||||||
|
-- Test without # prefix
|
||||||
|
local color2 = color.get_color("00FF00")
|
||||||
|
assert(color2.x == 0)
|
||||||
|
assert(color2.y == 1)
|
||||||
|
assert(color2.z == 0)
|
||||||
|
assert(color2.w == 1)
|
||||||
|
|
||||||
|
-- Test 3-digit hex
|
||||||
|
local color3 = color.get_color("F0F")
|
||||||
|
assert(color3.x == 1)
|
||||||
|
assert(color3.y == 0)
|
||||||
|
assert(color3.z == 1)
|
||||||
|
assert(color3.w == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should handle vector4 input in get_color", function()
|
||||||
|
local input_color = vmath.vector4(1, 0.5, 0, 1)
|
||||||
|
local result = color.get_color(input_color)
|
||||||
|
assert(result == input_color)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should return white for unknown color IDs", function()
|
||||||
|
local result = color.get_color("unknown_color")
|
||||||
|
assert(result.x == 1)
|
||||||
|
assert(result.y == 1)
|
||||||
|
assert(result.z == 1)
|
||||||
|
assert(result.w == 1)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Color Format Conversion", function()
|
||||||
|
it("Should convert colors to hex", function()
|
||||||
|
local red = vmath.vector4(1, 0, 0, 1)
|
||||||
|
local hex = color.to_hex(red)
|
||||||
|
assert(hex == "FF0000")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert colors to RGB values", function()
|
||||||
|
local test_color = vmath.vector4(1, 0.5, 0.25, 0.8)
|
||||||
|
local r, g, b, a = color.to_rgb(test_color)
|
||||||
|
assert(r == 1)
|
||||||
|
assert(g == 0.5)
|
||||||
|
assert(b == 0.25)
|
||||||
|
assert(a == 0.8)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert colors to HSB values", function()
|
||||||
|
local red = vmath.vector4(1, 0, 0, 1)
|
||||||
|
local h, s, b, a = color.to_hsb(red)
|
||||||
|
assert(h == 0) -- Red hue
|
||||||
|
assert(s == 1) -- Full saturation
|
||||||
|
assert(b == 1) -- Full brightness
|
||||||
|
assert(a == 1) -- Full alpha
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert hex to rgb values", function()
|
||||||
|
local r, g, b = color.hex2rgb("#FF8000")
|
||||||
|
assert(r == 1)
|
||||||
|
assert(g == 0.5019607843137255) -- 128/255
|
||||||
|
assert(b == 0)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert hex to vector4", function()
|
||||||
|
local vec = color.hex2vector4("#FF8000", 0.5)
|
||||||
|
assert(vec.x == 1)
|
||||||
|
assert(vec.y == 0.5019607843137255)
|
||||||
|
assert(vec.z == 0)
|
||||||
|
assert(vec.w == 0.5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert rgb to hex", function()
|
||||||
|
local hex = color.rgb2hex(1, 0.5019607843137255, 0)
|
||||||
|
assert(hex == "FF8000")
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Color Space Conversion", function()
|
||||||
|
it("Should convert RGB to HSB correctly", function()
|
||||||
|
local h, s, v, a = color.rgb2hsb(1, 0, 0, 1) -- Red
|
||||||
|
assert(h == 0)
|
||||||
|
assert(s == 1)
|
||||||
|
assert(v == 1)
|
||||||
|
assert(a == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should convert HSB to RGB correctly", function()
|
||||||
|
local r, g, b, a = color.hsb2rgb(0, 1, 1, 1) -- Red
|
||||||
|
assert(r == 1)
|
||||||
|
assert(g == 0)
|
||||||
|
assert(b == 0)
|
||||||
|
assert(a == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should handle round-trip HSB conversion", function()
|
||||||
|
local original_r, original_g, original_b = 0.5, 0.7, 0.3
|
||||||
|
local h, s, v = color.rgb2hsb(original_r, original_g, original_b)
|
||||||
|
local converted_r, converted_g, converted_b = color.hsb2rgb(h, s, v)
|
||||||
|
|
||||||
|
-- Allow for small floating point differences
|
||||||
|
assert(math.abs(converted_r - original_r) < 0.001)
|
||||||
|
assert(math.abs(converted_g - original_g) < 0.001)
|
||||||
|
assert(math.abs(converted_b - original_b) < 0.001)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Palette Management", function()
|
||||||
|
it("Should add and retrieve palette colors", function()
|
||||||
|
local test_palette = {
|
||||||
|
primary = vmath.vector4(1, 0, 0, 1),
|
||||||
|
secondary = "#00FF00",
|
||||||
|
tertiary = vmath.vector4(0, 0, 1, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
color.add_palette(test_palette)
|
||||||
|
|
||||||
|
local primary = color.get_color("primary")
|
||||||
|
assert(primary.x == 1)
|
||||||
|
assert(primary.y == 0)
|
||||||
|
assert(primary.z == 0)
|
||||||
|
assert(primary.w == 1)
|
||||||
|
|
||||||
|
local secondary = color.get_color("secondary")
|
||||||
|
assert(secondary.x == 0)
|
||||||
|
assert(secondary.y == 1)
|
||||||
|
assert(secondary.z == 0)
|
||||||
|
assert(secondary.w == 1)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should manage individual palette colors", function()
|
||||||
|
-- Set a palette color
|
||||||
|
color.set_palette_color("test_red", vmath.vector4(1, 0, 0, 1))
|
||||||
|
assert(color.has_palette_color("test_red") == true)
|
||||||
|
|
||||||
|
local retrieved = color.get_palette_color("test_red")
|
||||||
|
assert(retrieved.x == 1)
|
||||||
|
assert(retrieved.y == 0)
|
||||||
|
assert(retrieved.z == 0)
|
||||||
|
|
||||||
|
-- Remove the color
|
||||||
|
color.remove_palette_color("test_red")
|
||||||
|
assert(color.has_palette_color("test_red") == false)
|
||||||
|
assert(color.get_palette_color("test_red") == nil)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should return the palette", function()
|
||||||
|
local palette = color.get_palette()
|
||||||
|
assert(type(palette) == "table")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should clear the palette", function()
|
||||||
|
color.set_palette_color("temp_color", vmath.vector4(1, 1, 1, 1))
|
||||||
|
color.clear_palette()
|
||||||
|
assert(color.has_palette_color("temp_color") == false)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("Color Manipulation", function()
|
||||||
|
it("Should lerp colors correctly", function()
|
||||||
|
local color1 = vmath.vector4(1, 0, 0, 1) -- Red
|
||||||
|
local color2 = vmath.vector4(0, 1, 0, 1) -- Green
|
||||||
|
|
||||||
|
local mid_color = color.lerp(0.5, color1, color2)
|
||||||
|
-- Note: lerp uses HSB interpolation, so the result might not be a simple average
|
||||||
|
assert(type(mid_color.x) == "number")
|
||||||
|
assert(type(mid_color.y) == "number")
|
||||||
|
assert(type(mid_color.z) == "number")
|
||||||
|
assert(mid_color.w == 1)
|
||||||
|
|
||||||
|
-- Test endpoints
|
||||||
|
local start_color = color.lerp(0, color1, color2)
|
||||||
|
local end_color = color.lerp(1, color1, color2)
|
||||||
|
|
||||||
|
assert(math.abs(start_color.x - color1.x) < 0.001)
|
||||||
|
assert(math.abs(end_color.x - color2.x) < 0.001)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should mix colors using linear RGB interpolation", function()
|
||||||
|
local red = vmath.vector4(1, 0, 0, 1)
|
||||||
|
local blue = vmath.vector4(0, 0, 1, 1)
|
||||||
|
|
||||||
|
local mixed = color.mix(red, blue, 0.5)
|
||||||
|
assert(mixed.x == 0.5) -- Half red
|
||||||
|
assert(mixed.y == 0) -- No green
|
||||||
|
assert(mixed.z == 0.5) -- Half blue
|
||||||
|
assert(mixed.w == 1) -- Full alpha
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should lighten colors", function()
|
||||||
|
local dark_red = vmath.vector4(0.5, 0, 0, 1)
|
||||||
|
local lightened = color.lighten(dark_red, 0.5)
|
||||||
|
|
||||||
|
assert(lightened.x > dark_red.x) -- Should be lighter
|
||||||
|
assert(lightened.y > dark_red.y) -- Should have some green/white
|
||||||
|
assert(lightened.z > dark_red.z) -- Should have some blue/white
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should darken colors", function()
|
||||||
|
local bright_red = vmath.vector4(1, 0, 0, 1)
|
||||||
|
local darkened = color.darken(bright_red, 0.5)
|
||||||
|
|
||||||
|
assert(darkened.x < bright_red.x) -- Should be darker
|
||||||
|
assert(darkened.y == 0) -- Should still have no green
|
||||||
|
assert(darkened.z == 0) -- Should still have no blue
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should adjust alpha", function()
|
||||||
|
local opaque_red = vmath.vector4(1, 0, 0, 1)
|
||||||
|
local semi_transparent = color.with_alpha(opaque_red, 0.5)
|
||||||
|
|
||||||
|
assert(semi_transparent.x == 1) -- Color unchanged
|
||||||
|
assert(semi_transparent.y == 0)
|
||||||
|
assert(semi_transparent.z == 0)
|
||||||
|
assert(semi_transparent.w == 0.5) -- Alpha changed
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("GUI Node Operations", function()
|
||||||
|
it("Should set color on GUI node", function()
|
||||||
|
-- Create a test node
|
||||||
|
local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0))
|
||||||
|
|
||||||
|
-- Test with vector4
|
||||||
|
local test_color = vmath.vector4(1, 0.5, 0, 1)
|
||||||
|
color.set_color(test_node, test_color)
|
||||||
|
|
||||||
|
-- Verify color was set (we can't easily read it back, but we can verify no errors)
|
||||||
|
assert(true) -- If we get here, no error occurred
|
||||||
|
|
||||||
|
-- Test with string color
|
||||||
|
color.set_color(test_node, "#FF0000")
|
||||||
|
assert(true) -- If we get here, no error occurred
|
||||||
|
|
||||||
|
-- Clean up
|
||||||
|
gui.delete_node(test_node)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should apply color to node using alias", function()
|
||||||
|
local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0))
|
||||||
|
|
||||||
|
-- Test the alias function
|
||||||
|
color.apply_to_node(test_node, vmath.vector4(0, 1, 0, 1))
|
||||||
|
assert(true) -- If we get here, no error occurred
|
||||||
|
|
||||||
|
gui.delete_node(test_node)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("Should set node color including alpha", function()
|
||||||
|
local test_node = gui.new_box_node(vmath.vector3(0, 0, 0), vmath.vector3(100, 100, 0))
|
||||||
|
|
||||||
|
color.set_node_color_with_alpha(test_node, vmath.vector4(1, 0, 0, 0.5))
|
||||||
|
assert(true) -- If we get here, no error occurred
|
||||||
|
|
||||||
|
gui.delete_node(test_node)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
@@ -81,7 +81,7 @@ return function()
|
|||||||
local rich_text = druid:new_rich_text(text_node)
|
local rich_text = druid:new_rich_text(text_node)
|
||||||
|
|
||||||
-- Test color tag with named color
|
-- Test color tag with named color
|
||||||
local words = rich_text:set_text("<color=red>Colored Text</color>")
|
local words = rich_text:set_text("<color=#FF0000>Colored Text</color>")
|
||||||
|
|
||||||
assert(#words > 0)
|
assert(#words > 0)
|
||||||
-- Word should have a tags field with color tag
|
-- Word should have a tags field with color tag
|
||||||
@@ -104,7 +104,7 @@ return function()
|
|||||||
local rich_text = druid:new_rich_text(text_node)
|
local rich_text = druid:new_rich_text(text_node)
|
||||||
|
|
||||||
-- Test shadow tag with named color
|
-- Test shadow tag with named color
|
||||||
local words = rich_text:set_text("<shadow=black>Shadowed Text</shadow>")
|
local words = rich_text:set_text("<shadow=#000000>Shadowed Text</shadow>")
|
||||||
|
|
||||||
assert(#words > 0)
|
assert(#words > 0)
|
||||||
assert(words[1].shadow ~= nil)
|
assert(words[1].shadow ~= nil)
|
||||||
@@ -129,7 +129,7 @@ return function()
|
|||||||
local rich_text = druid:new_rich_text(text_node)
|
local rich_text = druid:new_rich_text(text_node)
|
||||||
|
|
||||||
-- Test outline tag with named color
|
-- Test outline tag with named color
|
||||||
local words = rich_text:set_text("<outline=black>Outlined Text</outline>")
|
local words = rich_text:set_text("<outline=#000000>Outlined Text</outline>")
|
||||||
|
|
||||||
assert(#words > 0)
|
assert(#words > 0)
|
||||||
assert(words[1].outline ~= nil)
|
assert(words[1].outline ~= nil)
|
||||||
@@ -228,7 +228,7 @@ return function()
|
|||||||
local rich_text = druid:new_rich_text(text_node)
|
local rich_text = druid:new_rich_text(text_node)
|
||||||
|
|
||||||
-- Test combined tags
|
-- Test combined tags
|
||||||
local words = rich_text:set_text("<color=red><size=2>Big Red Text</size></color>")
|
local words = rich_text:set_text("<color=#FF0000><size=2>Big Red Text</size></color>")
|
||||||
|
|
||||||
assert(#words > 0)
|
assert(#words > 0)
|
||||||
assert(words[1].tags.color)
|
assert(words[1].tags.color)
|
||||||
@@ -236,7 +236,7 @@ return function()
|
|||||||
assert(words[1].relative_scale == 2)
|
assert(words[1].relative_scale == 2)
|
||||||
|
|
||||||
-- Test nested tags
|
-- Test nested tags
|
||||||
words = rich_text:set_text("<color=red>Red <size=2>Big Red</size> Red</color>")
|
words = rich_text:set_text("<color=#FF0000>Red <size=2>Big Red</size> Red</color>")
|
||||||
|
|
||||||
assert(#words >= 3)
|
assert(#words >= 3)
|
||||||
-- All words should have color tag
|
-- All words should have color tag
|
||||||
|
@@ -705,3 +705,29 @@ Please support me if you like this project! It will help me keep engaged to upda
|
|||||||
|
|
||||||
#### Druid 1.1.1
|
#### Druid 1.1.1
|
||||||
- [#309] Added max_size_x and max_size_y to container (by [astrochili](https://github.com/astrochili))
|
- [#309] Added max_size_x and max_size_y to container (by [astrochili](https://github.com/astrochili))
|
||||||
|
|
||||||
|
#### Druid 1.1.2
|
||||||
|
- [#310] Add data list matrix example (Grid 4 in row)
|
||||||
|
- [Data List] Fix for data list element amounts issue
|
||||||
|
|
||||||
|
#### Druid 1.1.3
|
||||||
|
- 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.2.0
|
||||||
|
- Fix for blocker internal enabled state depends from GUI node
|
||||||
|
- Move to druid colors for rich text
|
||||||
|
- Fix for container stretch mode (stretch and fit is not worked in init function)
|
||||||
|
- Add split_to_characters in rich text for making fancy text
|
||||||
|
- Druid GO Widgets now can wrap an events to (before only top level functions)
|
||||||
|
- Ability to pass params to Druid GO Widgets
|
||||||
|
- Update properties panel:
|
||||||
|
- Add "scenes" to manage a list of properties with back button support
|
||||||
|
- Add "refresh" button, which active a 1-sec refresh for current page
|
||||||
|
- Add "Render lua table" to easily render your lua tables with a various types support (any simple types and vector, functions, events etc)
|
||||||
|
|
||||||
|
BIN
wiki/manuals/media/memory_fps_panel.mov
Normal file
BIN
wiki/manuals/media/memory_fps_panel.mov
Normal file
Binary file not shown.
BIN
wiki/manuals/media/memory_fps_panel_add.png
Normal file
BIN
wiki/manuals/media/memory_fps_panel_add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
wiki/manuals/media/memory_fps_panel_select.png
Normal file
BIN
wiki/manuals/media/memory_fps_panel_select.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
98
wiki/manuals/widget_memory_fps_panels.md
Normal file
98
wiki/manuals/widget_memory_fps_panels.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# Memory and FPS Panel Widgets
|
||||||
|
|
||||||
|
The `Druid 1.1` comes with widgets and two included widgets are `Memory Panel` and `FPS Panel` which allow you to monitor memory and FPS in your game.
|
||||||
|
|
||||||
|
Widgets in Druid usually consists from two files: GUI, which is used to placed as a template on your GUI scene and Lua script, which is used to be created 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
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
- 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!
|
Reference in New Issue
Block a user