Update docs, config generations, fix linter

This commit is contained in:
Insality
2024-10-15 19:34:07 +03:00
parent 2f5336fa4f
commit 5848921cba
44 changed files with 1880 additions and 1336 deletions

View File

@@ -367,6 +367,12 @@ function druid__data_list.scroll_to_index(self, index) end
---@return druid.data_list Current DataList instance
function druid__data_list.set_data(self, data) end
--- Set refresh function for DataList component
---@param self druid.data_list @{DataList}
---@param is_use_cache boolean Use cache version of DataList. Requires make setup of components in on_element_add callback and clean in on_element_remove
---@return druid.data_list Current DataList instance
function druid__data_list.set_use_cache(self, is_use_cache) end
---@class druid.drag : druid.base_component
---@field can_x boolean Is drag component process vertical dragging.
@@ -374,8 +380,8 @@ function druid__data_list.set_data(self, data) end
---@field is_drag boolean Is component now dragging
---@field is_touch boolean Is component now touching
---@field node node Drag node
---@field on_drag druid.event on drag progress callback(self, dx, dy, total_x, total_y)
---@field on_drag_end druid.event Event on drag end callback(self, total_x, total_y)
---@field on_drag druid.event on drag progress callback(self, dx, dy, total_x, total_y, touch)
---@field on_drag_end druid.event Event on drag end callback(self, total_x, total_y, touch)
---@field on_drag_start druid.event Event on drag start callback(self, touch)
---@field on_touch_end druid.event Event on touch end callback(self)
---@field on_touch_start druid.event Event on touch start callback(self)
@@ -559,7 +565,8 @@ function druid__event.unsubscribe(self, callback, callback_context) end
---@field button druid.button Button component from click_node
---@field click_node node|nil Button trigger node
---@field node node Visual node
---@field on_change_state druid.event On change state callback(self, state)
---@field on_hotkey_pressed druid.event On hotkey released callback(self, argument)
---@field on_hotkey_released druid.event On hotkey released callback(self, argument)
---@field style druid.hotkey.style Component style params.
local druid__hotkey = {}
@@ -803,67 +810,8 @@ function druid__lang_text.translate(self, locale_id, a, b, c, d, e, f, g) end
---@class druid.layout : druid.base_component
---@field mode string Current layout mode
---@field node node Layout node
---@field on_size_changed druid.event On window resize callback(self, new_size)
local druid__layout = {}
--- Set node for layout node to fit inside it.
--- Pass nil to reset
---@param self druid.layout @{Layout}
---@param node node|nil
---@return druid.layout @{Layout}
function druid__layout.fit_into_node(self, node) end
--- Set size for layout node to fit inside it
---@param self druid.layout @{Layout}
---@param target_size vector3
---@return druid.layout @{Layout}
function druid__layout.fit_into_size(self, target_size) end
--- Set current size for layout node to fit inside it
---@param self druid.layout @{Layout}
---@return druid.layout @{Layout}
function druid__layout.fit_into_window(self) end
--- The @{Layout} constructor
---@param self druid.layout @{Layout}
---@param node node Gui node
---@param mode string The layout mode (from const.LAYOUT_MODE)
---@param on_size_changed_callback function|nil The callback on window resize
function druid__layout.init(self, node, mode, on_size_changed_callback) end
--- Set max gui upscale for FIT adjust mode (or side).
--- It happens on bigger render gui screen
---@param self druid.layout @{Layout}
---@param max_gui_upscale number
---@return druid.layout @{Layout}
function druid__layout.set_max_gui_upscale(self, max_gui_upscale) end
--- Set maximum size of layout node
---@param self druid.layout @{Layout}
---@param max_size vector3
---@return druid.layout @{Layout}
function druid__layout.set_max_size(self, max_size) end
--- Set minimal size of layout node
---@param self druid.layout @{Layout}
---@param min_size vector3
---@return druid.layout @{Layout}
function druid__layout.set_min_size(self, min_size) end
--- Set new origin position of layout node.
--- You should apply this on node movement
---@param self druid.layout @{Layout}
---@param new_origin_position vector3
---@return druid.layout @{Layout}
function druid__layout.set_origin_position(self, new_origin_position) end
--- Set new origin size of layout node.
--- You should apply this on node manual size change
---@param self druid.layout @{Layout}
---@param new_origin_size vector3
---@return druid.layout @{Layout}
function druid__layout.set_origin_size(self, new_origin_size) end
---@class druid.pin_knob : druid.base_component
---@field druid druid_instance The component druid instance
@@ -1056,9 +1004,9 @@ function druid__rich_text.get_words() end
--- The @{RichText} constructor
---@param self druid.rich_text @{RichText}
---@param template string The Rich Text template name
---@param nodes table The node table, if prefab was copied by gui.clone_tree()
function druid__rich_text.init(self, template, nodes) end
---@param text_node node|string The text node to make Rich Text
---@param value string|nil The initial text value. Default will be gui.get_text(text_node)
function druid__rich_text.init(self, text_node, value) end
--- Set text for Rich Text
---@param self druid.rich_text @{RichText}
@@ -1097,6 +1045,7 @@ local druid__rich_text__style = {}
---@field style druid.scroll.style Component style params.
---@field target_position vector3 Current scroll target position
---@field view_node node Scroll view node
---@field view_size vector3 Scroll view size
local druid__scroll = {}
--- Bind the grid component (Static or Dynamic) to recalculate scroll size on grid changes
@@ -1205,6 +1154,10 @@ function druid__scroll.set_vertical_scroll(self, state) end
---@return druid.scroll Current scroll instance
function druid__scroll.set_view_size(self, size) end
--- Refresh scroll view size
---@param self druid.scroll @{Scroll}
function druid__scroll.update_view_size(self) end
---@class druid.scroll.style
---@field ANIM_SPEED number|nil Scroll gui.animation speed for scroll_to function. Default: 2
@@ -1550,7 +1503,7 @@ local druid__timer = {}
--- The @{Timer} constructor
---@param self druid.timer @{Timer}
---@param node node Gui text node
---@param seconds_from number Start timer value in seconds
---@param seconds_from number|nil Start timer value in seconds
---@param seconds_to number|nil End timer value in seconds
---@param callback function|nil Function on timer end
function druid__timer.init(self, node, seconds_from, seconds_to, callback) end
@@ -1579,14 +1532,6 @@ local druid_instance = {}
---@param self druid_instance
function druid_instance.final(self) end
--- Create new component.
---@generic T
---@param self druid_instance
---@param component T Component module
---@param ... any Other component params to pass it to component:init function
---@return T Component instance
function druid_instance.new(self, component, ...) end
--- Create @{BackHandler} component
---@param self druid_instance
---@param callback function|nil @The callback(self, custom_args) to call on back event
@@ -1683,9 +1628,8 @@ function druid_instance.new_lang_text(self, node, locale_id, adjust_type) end
---@param self druid_instance
---@param node string|node The_node id or gui.get_node(node_id).
---@param mode string The layout mode
---@param on_size_changed_callback function|nil The callback on window resize
---@return druid.layout @{Layout} component
function druid_instance.new_layout(self, node, mode, on_size_changed_callback) end
function druid_instance.new_layout(self, node, mode) end
--- Create @{Progress} component
---@param self druid_instance
@@ -1752,7 +1696,7 @@ function druid_instance.new_text(self, node, value, no_adjust) end
--- Create @{Timer} component
---@param self druid_instance
---@param node string|node Gui text node
---@param seconds_from number|nil Start timer value in seconds
---@param seconds_from number Start timer value in seconds
---@param seconds_to number|nil End timer value in seconds
---@param callback function|nil Function on timer end
---@return druid.timer @{Timer} component
@@ -2021,3 +1965,22 @@ function helper.table_to_string(t) end
---@field height number
---@field max_ascent number
---@field max_descent number
---@class utf8
---@field len fun(string: string): number
---@field sub fun(string: string, i: number, j: number): string
---@field gmatch fun(string: string, pattern: string): fun(): string
---@field gsub fun(string: string, pattern: string, repl: string, n: number): string
---@field char fun(...: number): string
---@field byte fun(string: string, i: number, j: number): number
---Add generics to some functions.
---Create new component.
---@generic T: druid.base_component
---@param self druid_instance
---@param component T Component module
---@param ... any Other component params to pass it to component:init function
---@return T Component instance
function druid_instance.new(self, component, ...) end

View File

@@ -344,10 +344,6 @@ function Button.on_input(self, action_id, action)
return false
end
if not self:is_enabled() then
return false
end
local is_consume = true
local is_pick = true
local is_key_trigger = (action_id == self.key_trigger)

View File

@@ -22,10 +22,10 @@
--- Event on drag start callback(self, touch)
-- @tfield DruidEvent on_drag_start @{DruidEvent}
--- on drag progress callback(self, dx, dy, total_x, total_y)
--- on drag progress callback(self, dx, dy, total_x, total_y, touch)
-- @tfield DruidEvent on_drag Event @{DruidEvent}
--- Event on drag end callback(self, total_x, total_y)
--- Event on drag end callback(self, total_x, total_y, touch)
-- @tfield DruidEvent on_drag_end @{DruidEvent}
--- Is component now touching

View File

@@ -50,6 +50,9 @@
--- Scroll view node
-- @tfield node view_node
--- Scroll view size
-- @tfield vector3 view_size
--- Scroll content node
-- @tfield node content_node

View File

@@ -211,6 +211,9 @@ local function update_text_with_trim(self, trim_postfix)
text_length = text_length - 1
new_text = utf8.sub(self.last_value, 1, text_length)
text_width = self:get_text_size(new_text .. trim_postfix)
if text_length == 0 then
break
end
end
gui.set_text(self.node, new_text .. trim_postfix)

View File

@@ -227,7 +227,7 @@ function M._fill_properties(word, metrics, settings)
else
-- Text properties
word.scale = settings.scale * word.relative_scale * settings.adjust_scale
word.pivot = gui.PIVOT_W -- With this pivot adjustments works correctly, but with another some misalignment
word.pivot = gui.PIVOT_SW -- With this pivot adjustments works more correctly than with other pivots
word.size = vmath.vector3(metrics.width, metrics.height, 0)
word.offset = vmath.vector3(metrics.offset_x, metrics.offset_y, 0)
end

View File

@@ -1,45 +0,0 @@
fonts {
name: "game"
font: "/example/assets/fonts/game.font"
}
textures {
name: "items"
texture: "/example/assets/images/kenney.atlas"
}
nodes {
size {
x: 400.0
y: 100.0
}
type: TYPE_BOX
id: "root"
inherit_alpha: true
visible: false
}
nodes {
position {
x: -200.0
}
size {
x: 400.0
y: 100.0
}
type: TYPE_TEXT
text: "Rich text"
font: "game"
id: "text_prefab"
pivot: PIVOT_W
parent: "root"
inherit_alpha: true
outline_alpha: 0.0
shadow_alpha: 0.0
}
nodes {
type: TYPE_BOX
id: "icon_prefab"
parent: "root"
inherit_alpha: true
size_mode: SIZE_MODE_AUTO
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT

View File

@@ -109,8 +109,8 @@ local RichText = component.create("rich_text")
--- The @{RichText} constructor
-- @tparam RichText self @{RichText}
-- @tparam string template The Rich Text template name
-- @tparam table nodes The node table, if prefab was copied by gui.clone_tree()
-- @tparam node|string text_node The text node to make Rich Text
-- @tparam string|nil value The initial text value. Default will be gui.get_text(text_node)
function RichText.init(self, text_node, value)
self.root = self:get_node(text_node)
self.text_prefab = self.root

View File

@@ -25,35 +25,6 @@ end
function M.get_commands()
return {
{
label = "Print GUI Scheme",
locations = { "Outline" },
query = {
selection = {type = "outline", cardinality = "many"}
},
active = function(opts)
return true
end,
run = function(opts)
print("local SCHEME = {")
for i = 1, #opts.selection do
local file = opts.selection[i]
if editor.can_get(file, "id") then
local id = editor.get(file, "id")
print("\t" .. string.upper(id) .. " = \"" .. id .. "\",")
end
end
print("}")
print("")
end
},
{
label = "Assign layers",

View File

@@ -84,6 +84,8 @@ end
--- Set refresh function for DataList component
-- @tparam DataList self @{DataList}
-- @tparam boolean is_use_cache Use cache version of DataList. Requires make setup of components in on_element_add callback and clean in on_element_remove
-- @treturn druid.data_list Current DataList instance
function DataList.set_use_cache(self, is_use_cache)
self._is_use_cache = is_use_cache
return self
@@ -280,7 +282,7 @@ end
function DataList._refresh(self)
self.scroll:set_size(self.grid:get_size_for(#self._data))
local start_pos = -self.scroll.position
local start_pos = -self.scroll.position --[[@as vector3]]
local start_index = self.grid:get_index(start_pos)
start_index = math.max(1, start_index)

View File

@@ -1,392 +0,0 @@
local helper = require("druid.helper")
local component = require("druid.component")
---@class druid.figma_layout.row_data
---@field width number
---@field height number
---@field count number
---@class druid.figma_layout.rows_data
---@field total_width number
---@field total_height number
---@field nodes_width table<node, number>
---@field nodes_height table<node, number>
---@field rows druid.figma_layout.row_data[]>
---@class druid.figma_layout: druid.base_component
local M = component.create("layout")
--- The @{Layout} constructor
-- @tparam Layout self @{Layout}
-- @tparam node node Gui node
-- @tparam string layout_type The layout mode (from const.LAYOUT_MODE)
-- @tparam function|nil on_size_changed_callback The callback on window resize
function M:init(node, layout_type)
self.node = self:get_node(node)
print(self.node)
self.is_dirty = true
self.entities = {}
self.margin = { x = 0, y = 0 }
self.padding = gui.get_slice9(self.node)
self.type = layout_type or "horizontal"
self.is_resize_width = false
self.is_resize_height = false
self.is_justify = false
end
function M:update()
if not self.is_dirty then
return
end
self:refresh_layout()
end
---@param margin_x number|nil
---@param margin_y number|nil
---@return druid.figma_layout
function M:set_margin(margin_x, margin_y)
self.margin.x = margin_x or self.margin.x
self.margin.y = margin_y or self.margin.y
self.is_dirty = true
return self
end
---@param padding vector4 The vector4 with padding values, where x - left, y - top, z - right, w - bottom
function M:set_padding(padding)
self.padding = padding
self.is_dirty = true
return self
end
function M:set_dirty()
self.is_dirty = true
return self
end
---@param is_justify boolean
---@return druid.figma_layout
function M:set_justify(is_justify)
self.is_justify = is_justify
self.is_dirty = true
return self
end
---@param type string The layout type: "horizontal", "vertical", "horizontal_wrap"
function M:set_type(type)
self.type = type
self.is_dirty = true
return self
end
---@param is_hug_width boolean
---@param is_hug_height boolean
---@return druid.figma_layout
function M:set_hug_content(is_hug_width, is_hug_height)
self.is_resize_width = is_hug_width or false
self.is_resize_height = is_hug_height or false
self.is_dirty = true
return self
end
---@param node_or_node_id string|node
---@return druid.figma_layout
function M:add(node_or_node_id)
-- Acquire node from entity or by id
local node = node_or_node_id
if type(node_or_node_id) == "table" then
assert(node_or_node_id.node, "The entity should have a node")
node = node_or_node_id.node
else
---@cast node_or_node_id string|node
node = self:get_node(node_or_node_id)
end
---@cast node node
table.insert(self.entities, node)
gui.set_parent(node, self.node)
self.is_dirty = true
return self
end
---@return druid.figma_layout
function M:refresh_layout()
local layout_node = self.node
local entities = self.entities
local type = self.type -- vertical, horizontal, horizontal_wrap
local margin = self.margin -- {x: horizontal, y: vertical} in pixels, between elements
local padding = self.padding -- {x: left, y: top, z: right, w: bottom} in pixels
local is_justify = self.is_justify
local size = gui.get_size(layout_node)
local max_width = size.x - padding.x - padding.z
local max_height = size.y - padding.y - padding.w
local layout_pivot_offset = helper.get_pivot_offset(gui.get_pivot(layout_node)) -- {x: -0.5, y: -0.5} - is left bot, {x: 0.5, y: 0.5} - is right top
local rows_data = self:calculate_rows_data()
local rows = rows_data.rows
local row_index = 1
local row = rows[row_index]
-- Current x and Current y is a top left corner of the node
local current_x = -row.width * (0.5 + layout_pivot_offset.x)
local current_y = rows_data.total_height * (0.5 - layout_pivot_offset.y)
if is_justify then
if (type == "horizontal" or type == "horizontal_wrap") and row.count > 1 then
current_x = -max_width * (0.5 + layout_pivot_offset.x)
end
if type == "vertical" then
current_y = max_height * (0.5 - layout_pivot_offset.y)
end
end
for index = 1, #entities do
local node = entities[index]
local node_width = rows_data.nodes_width[node]
local node_height = rows_data.nodes_height[node]
local pivot_offset = helper.get_pivot_offset(gui.get_pivot(node))
if node_width > 0 and node_height > 0 then
-- Calculate position for current node
local position_x, position_y
if type == "horizontal" then
position_x = current_x + node_width * (0.5 + pivot_offset.x)
position_y = current_y - row.height * (0.5 - pivot_offset.y)
local node_margin = margin.x
if is_justify and row.count > 1 then
node_margin = (max_width - row.width) / (row.count - 1) + margin.x
end
current_x = current_x + node_width + node_margin
end
if type == "vertical" then
position_x = current_x + row.width * (0.5 - pivot_offset.x)
position_y = current_y - node_height * (0.5 + pivot_offset.y)
local node_margin = margin.y
if is_justify then
node_margin = (max_height - rows_data.total_height) / (#rows - 1) + margin.y
end
current_y = current_y - node_height - node_margin
end
if type == "horizontal_wrap" then
local width = row.width
if is_justify and row.count > 0 then
width = math.max(row.width, max_width)
end
local new_row_width = width * (0.5 - layout_pivot_offset.x)
-- Compare with eps due the float loss and element flickering
if current_x + node_width - new_row_width > 0.0001 then
if row_index < #rows then
row_index = row_index + 1
row = rows[row_index]
end
current_x = -row.width * (0.5 + layout_pivot_offset.x)
current_y = current_y - row.height - margin.y
if is_justify and row.count > 1 then
current_x = -max_width * (0.5 + layout_pivot_offset.x)
end
end
position_x = current_x + node_width * (0.5 + pivot_offset.x)
position_y = current_y - row.height * (0.5 - pivot_offset.y)
local node_margin = margin.x
if is_justify and row.count > 1 then
node_margin = (max_width - row.width) / (row.count - 1) + margin.x
end
current_x = current_x + node_width + node_margin
end
do -- Padding offset
if layout_pivot_offset.x == -0.5 then
position_x = position_x + padding.x
end
if layout_pivot_offset.y == 0.5 then
position_y = position_y - padding.y
end
if layout_pivot_offset.x == 0.5 then
position_x = position_x - padding.z
end
if layout_pivot_offset.y == -0.5 then
position_y = position_y + padding.w
end
end
self:set_node_position(node, position_x, position_y)
end
end
if self.is_resize_width or self.is_resize_height then
if self.is_resize_width then
size.x = rows_data.total_width + padding.x + padding.z
end
if self.is_resize_height then
size.y = rows_data.total_height + padding.y + padding.w
end
gui.set_size(layout_node, size)
end
self.is_dirty = false
return self
end
---@return druid.figma_layout
function M:clear_layout()
for index = #self.entities, 1, -1 do
self.entities[index] = nil
end
self.is_dirty = true
return self
end
---@param node node
---@return number, number
function M.get_node_size(node)
if not gui.is_enabled(node, false) then
return 0, 0
end
local scale = gui.get_scale(node)
-- If node has text - get text size instead of node size
if gui.get_text(node) then
local text_metrics = helper.get_text_metrics_from_node(node)
return text_metrics.width * scale.x, text_metrics.height * scale.y
end
local size = gui.get_size(node)
return size.x * scale.x, size.y * scale.y
end
---Calculate rows data for layout. Contains total width, height and rows info (width, height, count of elements in row)
---@private
---@return druid.figma_layout.rows_data
function M:calculate_rows_data()
local entities = self.entities
local margin = self.margin
local type = self.type
local padding = self.padding
local size = gui.get_size(self.node)
local max_width = size.x - padding.x - padding.z
-- Collect rows info about width, height and count of elements in row
local current_row = { width = 0, height = 0, count = 0 }
local rows_data = {
total_width = 0,
total_height = 0,
nodes_width = {},
nodes_height = {},
rows = { current_row }
}
for index = 1, #entities do
local node = entities[index]
local node_width = rows_data.nodes_width[node]
local node_height = rows_data.nodes_height[node]
-- Get node size if it's not calculated yet
if not node_width or not node_height then
node_width, node_height = M.get_node_size(node)
rows_data.nodes_width[node] = node_width
rows_data.nodes_height[node] = node_height
end
if node_width > 0 and node_height > 0 then
if type == "horizontal" then
current_row.width = current_row.width + node_width + margin.x
current_row.height = math.max(current_row.height, node_height)
current_row.count = current_row.count + 1
end
if type == "vertical" then
if current_row.count > 0 then
current_row = { width = 0, height = 0, count = 0 }
table.insert(rows_data.rows, current_row)
end
current_row.width = math.max(current_row.width, node_width + margin.x)
current_row.height = node_height
current_row.count = current_row.count + 1
end
if type == "horizontal_wrap" then
if current_row.width + node_width > max_width and current_row.count > 0 then
current_row = { width = 0, height = 0, count = 0 }
table.insert(rows_data.rows, current_row)
end
current_row.width = current_row.width + node_width + margin.x
current_row.height = math.max(current_row.height, node_height)
current_row.count = current_row.count + 1
end
end
end
-- Remove last margin of each row
-- Calculate total width and height
local rows_count = #rows_data.rows
for index = 1, rows_count do
local row = rows_data.rows[index]
if row.width > 0 then
row.width = row.width - margin.x
end
rows_data.total_width = math.max(rows_data.total_width, row.width)
rows_data.total_height = rows_data.total_height + row.height
end
rows_data.total_height = rows_data.total_height + margin.y * (rows_count - 1)
return rows_data
end
---@private
---@param node node
---@param x number
---@param y number
---@return node
function M:set_node_position(node, x, y)
local position = gui.get_position(node)
position.x = x
position.y = y
gui.set_position(node, position)
return node
end
return M

View File

@@ -27,7 +27,6 @@
local helper = require("druid.helper")
local component = require("druid.component")
local Event = require("druid.event")
local const = require("druid.const")
local Hotkey = component.create("hotkey")

View File

@@ -1,4 +1,4 @@
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
-- Copyright (c) 2024 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
--- Layout management on node
--
@@ -13,205 +13,406 @@
--- Current layout mode
-- @tfield string mode
---On window resize callback(self, new_size)
-- @tfield DruidEvent on_size_changed @{DruidEvent}
---
local const = require("druid.const")
local helper = require("druid.helper")
local component = require("druid.component")
local Event = require("druid.event")
-- @class druid.layout.row_data
-- @tfield width number
-- @tfield height number
-- @tfield count number
local Layout = component.create("layout")
-- @class druid.layout.rows_data
-- @tfield total_width number
-- @tfield total_height number
-- @tfield nodes_width table<node, number>
-- @tfield nodes_height table<node, number>
-- @tfield rows druid.layout.row_data[]>
-- @class druid.layout: druid.base_component
local M = component.create("layout")
--- The @{Layout} constructor
-- The @{Layout} constructor
-- @tparam Layout self @{Layout}
-- @tparam node node Gui node
-- @tparam string mode The layout mode (from const.LAYOUT_MODE)
-- @tparam string layout_type The layout mode (from const.LAYOUT_MODE)
-- @tparam function|nil on_size_changed_callback The callback on window resize
function Layout.init(self, node, mode, on_size_changed_callback)
function M.init(self, node, layout_type)
self.node = self:get_node(node)
self._min_size = nil
self._max_size = nil
self._current_size = vmath.vector3(0)
self._inited = false
self._max_gui_upscale = nil
self._fit_node = nil
self._anchors = {}
self.mode = mode or const.LAYOUT_MODE.FIT
self.on_size_changed = Event(on_size_changed_callback)
self.is_dirty = true
self.entities = {}
self.margin = { x = 0, y = 0 }
self.padding = gui.get_slice9(self.node)
self.type = layout_type or "horizontal"
self.is_resize_width = false
self.is_resize_height = false
self.is_justify = false
end
function Layout.on_late_init(self)
self._inited = true
self.origin_size = self.origin_size or gui.get_size(self.node)
self.fit_size = self.fit_size or vmath.vector3(self.origin_size)
self.pivot = helper.get_pivot_offset(gui.get_pivot(self.node))
self.origin_position = gui.get_position(self.node)
self.position = vmath.vector3(self.origin_position)
gui.set_size_mode(self.node, gui.SIZE_MODE_MANUAL)
gui.set_adjust_mode(self.node, gui.ADJUST_FIT)
self:on_window_resized()
end
function Layout.on_window_resized(self)
if not self._inited then
function M:update()
if not self.is_dirty then
return
end
local x_koef, y_koef = helper.get_screen_aspect_koef()
local revert_scale = 1
if self._max_gui_upscale then
revert_scale = self._max_gui_upscale / helper.get_gui_scale()
revert_scale = math.min(revert_scale, 1)
end
gui.set_scale(self.node, vmath.vector3(revert_scale))
if self._fit_node then
self.fit_size = gui.get_size(self._fit_node)
self.fit_size.x = self.fit_size.x / x_koef
self.fit_size.y = self.fit_size.y / y_koef
end
x_koef = self.fit_size.x / self.origin_size.x * x_koef
y_koef = self.fit_size.y / self.origin_size.y * y_koef
local new_size = vmath.vector3(self.origin_size)
if self.mode == const.LAYOUT_MODE.STRETCH then
new_size.x = new_size.x * x_koef / revert_scale
new_size.y = new_size.y * y_koef / revert_scale
end
if self.mode == const.LAYOUT_MODE.STRETCH_X then
new_size.x = new_size.x * x_koef / revert_scale
end
if self.mode == const.LAYOUT_MODE.STRETCH_Y then
new_size.y = new_size.y * y_koef / revert_scale
end
-- Fit to the stretched container (node size or other defined)
if self.mode == const.LAYOUT_MODE.ZOOM_MIN then
new_size = new_size * math.min(x_koef, y_koef)
end
if self.mode == const.LAYOUT_MODE.ZOOM_MAX then
new_size = new_size * math.max(x_koef, y_koef)
end
if self._min_size then
new_size.x = math.max(new_size.x, self._min_size.x)
new_size.y = math.max(new_size.y, self._min_size.y)
end
if self._max_size then
new_size.x = math.min(new_size.x, self._max_size.x)
new_size.y = math.min(new_size.y, self._max_size.y)
end
self._current_size = new_size
gui.set_size(self.node, new_size)
self.position.x = self.origin_position.x + self.origin_position.x * (x_koef - 1)
self.position.y = self.origin_position.y + self.origin_position.y * (y_koef - 1)
gui.set_position(self.node, self.position)
self.on_size_changed:trigger(self:get_context(), new_size)
self:refresh_layout()
end
--- Set minimal size of layout node
-- @tparam Layout self @{Layout}
-- @tparam vector3 min_size
-- @treturn Layout @{Layout}
function Layout.set_min_size(self, min_size)
self._min_size = min_size
-- @tparam number|nil margin_x
-- @tparam number|nil margin_y
-- @treturn druid.layout @{Layout}
function M.set_margin(self, margin_x, margin_y)
self.margin.x = margin_x or self.margin.x
self.margin.y = margin_y or self.margin.y
self.is_dirty = true
return self
end
--- Set maximum size of layout node
-- @tparam Layout self @{Layout}
-- @tparam vector3 max_size
-- @treturn Layout @{Layout}
function Layout.set_max_size(self, max_size)
self._max_size = max_size
-- @tparam vector4 padding The vector4 with padding values, where x - left, y - top, z - right, w - bottom
-- @treturn druid.layout @{Layout}
function M.set_padding(self, padding)
self.padding = padding
self.is_dirty = true
return self
end
--- Set new origin position of layout node. You should apply this on node movement
-- @tparam Layout self @{Layout}
-- @tparam vector3 new_origin_position
-- @treturn Layout @{Layout}
function Layout.set_origin_position(self, new_origin_position)
self.origin_position = new_origin_position or self.origin_position
self:on_window_resized()
-- @treturn druid.layout @{Layout}
function M.set_dirty(self)
self.is_dirty = true
return self
end
--- Set new origin size of layout node. You should apply this on node manual size change
-- @tparam Layout self @{Layout}
-- @tparam vector3 new_origin_size
-- @treturn Layout @{Layout}
function Layout.set_origin_size(self, new_origin_size)
self.origin_size = new_origin_size or self.origin_size
self:on_window_resized()
-- @tparam boolean is_justify
-- @treturn druid.layout @{Layout}
function M.set_justify(self, is_justify)
self.is_justify = is_justify
self.is_dirty = true
return self
end
--- Set max gui upscale for FIT adjust mode (or side). It happens on bigger render gui screen
-- @tparam Layout self @{Layout}
-- @tparam number max_gui_upscale
-- @treturn Layout @{Layout}
function Layout.set_max_gui_upscale(self, max_gui_upscale)
self._max_gui_upscale = max_gui_upscale
self:on_window_resized()
end
-- @tparam string type The layout type: "horizontal", "vertical", "horizontal_wrap"
-- @treturn druid.layout @{Layout}
function M.set_type(self, type)
self.type = type
self.is_dirty = true
--- Set size for layout node to fit inside it
-- @tparam Layout self @{Layout}
-- @tparam vector3 target_size
-- @treturn Layout @{Layout}
function Layout.fit_into_size(self, target_size)
self.fit_size = target_size
self:on_window_resized()
return self
end
--- Set node for layout node to fit inside it. Pass nil to reset
-- @tparam Layout self @{Layout}
-- @tparam node|nil node
-- @treturn Layout @{Layout}
function Layout.fit_into_node(self, node)
self._fit_node = node
self:on_window_resized()
-- @tparam boolean is_hug_width
-- @tparam boolean is_hug_height
-- @treturn druid.layout @{Layout}
function M.set_hug_content(self, is_hug_width, is_hug_height)
self.is_resize_width = is_hug_width or false
self.is_resize_height = is_hug_height or false
self.is_dirty = true
return self
end
--- Set current size for layout node to fit inside it
-- @tparam Layout self @{Layout}
-- @treturn Layout @{Layout}
function Layout.fit_into_window(self)
return self:fit_into_size(vmath.vector3(
gui.get_width(),
gui.get_height(),
0))
-- @tparam string|node node_or_node_id
-- @treturn druid.layout @{Layout}
function M.add(self, node_or_node_id)
-- Acquire node from entity or by id
local node = node_or_node_id
if type(node_or_node_id) == "table" then
assert(node_or_node_id.node, "The entity should have a node")
node = node_or_node_id.node
else
-- @cast node_or_node_id string|node
node = self:get_node(node_or_node_id)
end
-- @cast node node
table.insert(self.entities, node)
gui.set_parent(node, self.node)
self.is_dirty = true
return self
end
return Layout
-- @tparam Layout self @{Layout}
-- @treturn druid.layout @{Layout}
function M.refresh_layout(self)
local layout_node = self.node
local entities = self.entities
local type = self.type -- vertical, horizontal, horizontal_wrap
local margin = self.margin -- {x: horizontal, y: vertical} in pixels, between elements
local padding = self.padding -- {x: left, y: top, z: right, w: bottom} in pixels
local is_justify = self.is_justify
local size = gui.get_size(layout_node)
local max_width = size.x - padding.x - padding.z
local max_height = size.y - padding.y - padding.w
local layout_pivot_offset = helper.get_pivot_offset(gui.get_pivot(layout_node)) -- {x: -0.5, y: -0.5} - is left bot, {x: 0.5, y: 0.5} - is right top
local rows_data = self:calculate_rows_data()
local rows = rows_data.rows
local row_index = 1
local row = rows[row_index]
-- Current x and Current y is a top left corner of the node
local current_x = -row.width * (0.5 + layout_pivot_offset.x)
local current_y = rows_data.total_height * (0.5 - layout_pivot_offset.y)
if is_justify then
if (type == "horizontal" or type == "horizontal_wrap") and row.count > 1 then
current_x = -max_width * (0.5 + layout_pivot_offset.x)
end
if type == "vertical" then
current_y = max_height * (0.5 - layout_pivot_offset.y)
end
end
for index = 1, #entities do
local node = entities[index]
local node_width = rows_data.nodes_width[node]
local node_height = rows_data.nodes_height[node]
local pivot_offset = helper.get_pivot_offset(gui.get_pivot(node))
if node_width > 0 and node_height > 0 then
-- Calculate position for current node
local position_x, position_y
if type == "horizontal" then
position_x = current_x + node_width * (0.5 + pivot_offset.x)
position_y = current_y - row.height * (0.5 - pivot_offset.y)
local node_margin = margin.x
if is_justify and row.count > 1 then
node_margin = (max_width - row.width) / (row.count - 1) + margin.x
end
current_x = current_x + node_width + node_margin
end
if type == "vertical" then
position_x = current_x + row.width * (0.5 - pivot_offset.x)
position_y = current_y - node_height * (0.5 + pivot_offset.y)
local node_margin = margin.y
if is_justify then
node_margin = (max_height - rows_data.total_height) / (#rows - 1) + margin.y
end
current_y = current_y - node_height - node_margin
end
if type == "horizontal_wrap" then
local width = row.width
if is_justify and row.count > 0 then
width = math.max(row.width, max_width)
end
local new_row_width = width * (0.5 - layout_pivot_offset.x)
-- Compare with eps due the float loss and element flickering
if current_x + node_width - new_row_width > 0.0001 then
if row_index < #rows then
row_index = row_index + 1
row = rows[row_index]
end
current_x = -row.width * (0.5 + layout_pivot_offset.x)
current_y = current_y - row.height - margin.y
if is_justify and row.count > 1 then
current_x = -max_width * (0.5 + layout_pivot_offset.x)
end
end
position_x = current_x + node_width * (0.5 + pivot_offset.x)
position_y = current_y - row.height * (0.5 - pivot_offset.y)
local node_margin = margin.x
if is_justify and row.count > 1 then
node_margin = (max_width - row.width) / (row.count - 1) + margin.x
end
current_x = current_x + node_width + node_margin
end
do -- Padding offset
if layout_pivot_offset.x == -0.5 then
position_x = position_x + padding.x
end
if layout_pivot_offset.y == 0.5 then
position_y = position_y - padding.y
end
if layout_pivot_offset.x == 0.5 then
position_x = position_x - padding.z
end
if layout_pivot_offset.y == -0.5 then
position_y = position_y + padding.w
end
end
self:set_node_position(node, position_x, position_y)
end
end
if self.is_resize_width or self.is_resize_height then
if self.is_resize_width then
size.x = rows_data.total_width + padding.x + padding.z
end
if self.is_resize_height then
size.y = rows_data.total_height + padding.y + padding.w
end
gui.set_size(layout_node, size)
end
self.is_dirty = false
return self
end
-- @tparam Layout self @{Layout}
-- @treturn druid.layout @{Layout}
function M.clear_layout(self)
for index = #self.entities, 1, -1 do
self.entities[index] = nil
end
self.is_dirty = true
return self
end
-- @private
-- @tparam node node
-- @treturn number, number
function M.get_node_size(node)
if not gui.is_enabled(node, false) then
return 0, 0
end
local scale = gui.get_scale(node)
-- If node has text - get text size instead of node size
if gui.get_text(node) then
local text_metrics = helper.get_text_metrics_from_node(node)
return text_metrics.width * scale.x, text_metrics.height * scale.y
end
local size = gui.get_size(node)
return size.x * scale.x, size.y * scale.y
end
-- @private
-- @tparam Layout self @{Layout}
-- Calculate rows data for layout. Contains total width, height and rows info (width, height, count of elements in row)
-- @treturn druid.layout.rows_data
function M.calculate_rows_data(self)
local entities = self.entities
local margin = self.margin
local type = self.type
local padding = self.padding
local size = gui.get_size(self.node)
local max_width = size.x - padding.x - padding.z
-- Collect rows info about width, height and count of elements in row
local current_row = { width = 0, height = 0, count = 0 }
local rows_data = {
total_width = 0,
total_height = 0,
nodes_width = {},
nodes_height = {},
rows = { current_row }
}
for index = 1, #entities do
local node = entities[index]
local node_width = rows_data.nodes_width[node]
local node_height = rows_data.nodes_height[node]
-- Get node size if it's not calculated yet
if not node_width or not node_height then
node_width, node_height = M.get_node_size(node)
rows_data.nodes_width[node] = node_width
rows_data.nodes_height[node] = node_height
end
if node_width > 0 and node_height > 0 then
if type == "horizontal" then
current_row.width = current_row.width + node_width + margin.x
current_row.height = math.max(current_row.height, node_height)
current_row.count = current_row.count + 1
end
if type == "vertical" then
if current_row.count > 0 then
current_row = { width = 0, height = 0, count = 0 }
table.insert(rows_data.rows, current_row)
end
current_row.width = math.max(current_row.width, node_width + margin.x)
current_row.height = node_height
current_row.count = current_row.count + 1
end
if type == "horizontal_wrap" then
if current_row.width + node_width > max_width and current_row.count > 0 then
current_row = { width = 0, height = 0, count = 0 }
table.insert(rows_data.rows, current_row)
end
current_row.width = current_row.width + node_width + margin.x
current_row.height = math.max(current_row.height, node_height)
current_row.count = current_row.count + 1
end
end
end
-- Remove last margin of each row
-- Calculate total width and height
local rows_count = #rows_data.rows
for index = 1, rows_count do
local row = rows_data.rows[index]
if row.width > 0 then
row.width = row.width - margin.x
end
rows_data.total_width = math.max(rows_data.total_width, row.width)
rows_data.total_height = rows_data.total_height + row.height
end
rows_data.total_height = rows_data.total_height + margin.y * (rows_count - 1)
return rows_data
end
-- @private
-- @tparam node node
-- @tparam number x
-- @tparam number y
-- @treturn node
function M:set_node_position(node, x, y)
local position = gui.get_position(node)
position.x = x
position.y = y
gui.set_position(node, position)
return node
end
return M

View File

@@ -151,6 +151,8 @@ function Slider.on_input(self, action_id, action)
self.value = (self.target_pos.y - self.start_pos.y) / self.dist.y
end
self.value = math.abs(self.value)
if self.steps then
local closest_dist = 1000
local closest = nil

View File

@@ -42,15 +42,15 @@ M["button"] = {
end,
on_click_disabled = function(self, node)
settings.play_sound(M.button.BTN_SOUND_DISABLED)
local start_pos = self.start_pos
gui.animate(node, "position.x", start_pos.x - 3, gui.EASING_OUTSINE, 0.05, 0, function()
gui.animate(node, "position.x", start_pos.x + 3, gui.EASING_OUTSINE, 0.1, 0, function()
gui.animate(node, "position.x", start_pos.x, gui.EASING_OUTSINE, 0.05)
end)
end)
end,
on_set_enabled = function(self, node, state)
if state then
gui.set_color(node, M.button.ENABLED_COLOR)
else
gui.set_color(node, M.button.DISABLED_COLOR)
end
end
}

View File

@@ -247,7 +247,7 @@ function DruidInstance.initialize(self, context, style)
end
--- Create new component.
-- Create new component.
-- @tparam DruidInstance self
-- @tparam BaseComponent component Component module
-- @tparam any ... Other component params to pass it to component:init function
@@ -776,9 +776,8 @@ end
-- @tparam DruidInstance self
-- @tparam string|node node The_node id or gui.get_node(node_id).
-- @tparam string mode The layout mode
-- @tparam function|nil on_size_changed_callback The callback on window resize
-- @treturn Layout @{Layout} component
function DruidInstance.new_layout(self, node, mode, on_size_changed_callback)
function DruidInstance.new_layout(self, node, mode)
return helper.require_component_message("layout")
end

View File

@@ -688,7 +688,8 @@ local function matcherGenerator(regex, plain)
local sum = 0
local bc, ec = utf8sub(str, 1, 1), utf8sub(str, 2, 2)
local skip = len(bc) + len(ec)
bc, ec = utf8unicode(bc), utf8unicode(ec)
bc = utf8unicode(bc) --[[@as string]]
ec = utf8unicode(ec) --[[@as string]]
return function(cC)
if cC == ec and sum > 0 then
sum = sum - 1

View File

@@ -3,18 +3,13 @@ local component = require("druid.component")
---@class component_name : druid.base_component
local Component = component.create("component_name")
local SCHEME = {
ROOT = "root",
BUTTON = "button",
}
-- Component constructor. Template name and nodes are optional. Pass it if you use it in your component
function Component:init(template, nodes)
self.druid = self:get_druid(template, nodes)
self.root = self:get_node(SCHEME.ROOT)
self.root = self:get_node("root")
self.button = self.druid:new_button(SCHEME.BUTTON, function() end)
self.button = self.druid:new_button("button", function() end)
end

View File

@@ -3,13 +3,6 @@ local component = require("druid.component")
---@class component_name : druid.base_component
local Component = component.create("component_name")
-- Scheme of component gui nodes
local SCHEME = {
ROOT = "root",
BUTTON = "button",
}
-- Component constructor. Template name and nodes are optional. Pass it if you use it in your component
function Component:init(template, nodes)
-- If your component is gui template, pass the template name and set it
@@ -18,7 +11,7 @@ function Component:init(template, nodes)
self.druid = self:get_druid(template, nodes)
-- self:get_node will auto process component template and nodes
self.root = self:get_node(SCHEME.ROOT)
self.root = self:get_node("root")
end