This commit is contained in:
Insality
2025-03-13 23:39:43 +02:00
parent c13a31711f
commit 1aa96d8dbc
29 changed files with 1278 additions and 291 deletions

View File

@@ -277,7 +277,7 @@ function M:update_view_size()
end
---Enable or disable scroll inert.
---Enable or disable scroll inert
-- If disabled, scroll through points (if exist)
-- If no points, just simple drag without inertion
---@param state boolean Inert scroll state
@@ -289,14 +289,14 @@ function M:set_inert(state)
end
---Return if scroll have inertion.
---@return boolean @If scroll have inertion
---Return if scroll have inertion
---@return boolean is_inert If scroll have inertion
function M:is_inert()
return self._is_inert
end
---Set extra size for scroll stretching.
---Set extra size for scroll stretching
-- Set 0 to disable stretching effect
---@param stretch_size number|nil Size in pixels of additional scroll area
---@return druid.scroll Current scroll instance

View File

@@ -72,7 +72,7 @@ end
local _temp_pos = vmath.vector3(0)
---Return pos for grid node index
---@param index number The grid element index
---@return vector3 @Node position
---@return vector3 position Node position
function M:get_pos(index)
local row = math.ceil(index / self.in_row) - 1
local col = (index - row * self.in_row) - 1
@@ -191,7 +191,7 @@ end
---Set new items to the grid. All previous items will be removed
---@param nodes node[] The new grid nodes
-- @tparam[opt=false] boolean is_instant If true, update node positions instantly
---@param is_instant boolean|nil If true, update node positions instantly
function M:set_items(nodes, is_instant)
self.nodes = nodes
for index = 1, #nodes do
@@ -340,8 +340,8 @@ end
---Set new node size for grid
-- @tparam[opt] number width The new node width
-- @tparam[opt] number height The new node height
---@param width number|nil The new node width
---@param height number|nil The new node height
---@return druid.grid Current grid instance
function M:set_item_size(width, height)
if width then

View File

@@ -1,42 +1,3 @@
-- Copyright (c) 2022 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
---Druid Rich Input custom component.
-- It's wrapper on Input component with cursor and placeholder text
-- @module RichInput
-- @alias druid.rich_input
---The component druid instance
-- @tfield DruidInstance druid DruidInstance
---Root node
-- @tfield node root
---On input field text change callback(self, input_text)
-- @tfield Input input Input
---On input field text change to empty string callback(self, input_text)
-- @tfield node cursor
---On input field text change to empty string callback(self, input_text)
-- @tfield node cursor_text
---On input field text change to empty string callback(self, input_text)
-- @tfield vector3 cursor_position
---On input field text change to empty string callback(self, input_text)
-- @tfield druid.text input_text
---On input field text change to empty string callback(self, input_text)
-- @tfield druid.drag drag
---On input field text change to empty string callback(self, input_text)
-- @tfield druid.text placeholder
---On input field text change to empty string callback(self, input_text)
-- @tfield vector3 text_position
---
local component = require("druid.component")
local helper = require("druid.helper")
local const = require("druid.const")

View File

@@ -51,9 +51,9 @@ end
---Get the length of a text ignoring any tags except image tags
-- which are treated as having a length of 1
-- @param text String with text or a list of words (from richtext.create)
-- @return Length of text
---which are treated as having a length of 1
---@param text string|table<string, any> String with text or a list of words (from richtext.create)
---@return number Length of text
function M.length(text)
assert(text)
if type(text) == "string" then
@@ -523,9 +523,9 @@ end
---Get all words with a specific tag
-- @param words The words to search (as received from richtext.create)
-- @param tag The tag to search for. Nil to search for words without a tag
-- @return Words matching the tag
---@param words druid.rich_text.word[] The words to search (as received from richtext.create)
---@param tag string|nil The tag to search for. Nil to search for words without a tag
---@return druid.rich_text.word[] Words matching the tag
function M.tagged(words, tag)
local tagged = {}
for i = 1, #words do

View File

@@ -34,7 +34,7 @@ function M.parse_decimal(dec)
local r,g,b,a = dec:match("(%d*%.?%d*),(%d*%.?%d*),(%d*%.?%d*),(%d*%.?%d*)")
if r and g and b and a then
local color = vmath.vector4(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local color = vmath.vector4(tonumber(r) or 0, tonumber(g) or 0, tonumber(b) or 0, tonumber(a) or 1)
cache[dec] = color
return color
end

View File

@@ -108,10 +108,10 @@ end
---Parse the text into individual words
-- @param text The text to parse
-- @param default_settings Default settings for each word
-- @param color_aliases Color aliases table
-- @return List of all words
---@param text string The text to parse
---@param default_settings table<string, any> Default settings for each word
---@param style table<string, any> Style settings
---@return table<string, any> List of all words
function M.parse(text, default_settings, style)
assert(text)
assert(default_settings)
@@ -185,6 +185,8 @@ end
---Get the length of a text, excluding any tags (except image and spine tags)
---@param text string The text to get the length of
---@return number The length of the text
function M.length(text)
return utf8.len(text:gsub("<img.-/>", " "):gsub("<.->", ""))
end

View File

@@ -26,12 +26,11 @@ function M.register(tag, fn)
end
-- Split string at first occurrence of token
-- If the token doesn't exist the whole string is returned
-- @param s The string to split
-- @param token The token to split string on
-- @return before The string before the token or the whole string if token doesn't exist
-- @return after The string after the token or nul
---Split string at first occurrence of token
---@param s string The string to split
---@param token string The token to split string on
---@return string before The string before the token or the whole string if token doesn't exist
---@return string after The string after the token or nil
local function split(s, token)
if not s then return nil, nil end
local before, after = s:match("(.-)" .. token .. "(.*)")

View File

@@ -1,78 +1,3 @@
-- Copyright (c) 2022 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
---Druid Rich Text Custom Component.
-- <b># Overview #</b>
--
-- This custom component is inspired by <a href="https://github.com/britzl/defold-richtext" target="_blank">defold-richtext</a> by britzl.
-- It uses a similar syntax for tags but currently supports fewer tags.
--
-- Create Rich Text on your GUI Text Node. All properties of the text node will be used as default for the text.
--
-- <b># Notes #</b>
--
-- • Nested tags are supported
--
-- <a href="https://insality.github.io/druid/druid/index.html?example=custom_rich_text" target="_blank"><b>Example Link</b></a>
-- @usage
-- local RichText = require("druid.custom.rich_text.rich_text")
-- ...
-- self.rich_text = self.druid:new(RichText, "rich_text")
-- self.rich_text:set_text("Hello, Druid Rich Text!")
-- @usage
-- type druid.rich_text.word = {
-- node: Node,
-- relative_scale: number,
-- color: vector4,
-- position: vector3,
-- offset: vector3,
-- scale: vector3,
-- size: vector3,
-- metrics: druid.rich_text.metrics,
-- pivot: Pivot,
-- text: string,
-- shadow: vector4,
-- outline: vector4,
-- font: string,
-- image: druid.rich_text.image,
-- br: boolean,
-- nobr: boolean,
-- }
--
-- type druid.rich_text.word.image = {
-- texture: string,
-- anim: string,
-- width: number,
-- height: number,
-- }
--
-- type druid.rich_text.lines_metrics = {
-- text_width: number,
-- text_height: number,
-- lines: table<number, druid.rich_text.metrics>,
-- }
--
-- type druid.rich_text.metrics = {
-- width: number,
-- height: number,
-- offset_x: number|nil,
-- offset_y: number|nil,
-- node_size: vector3|nil @For images only,
-- }
-- @module RichText
-- @within BaseComponent
-- @alias druid.rich_text
---The component druid instance
-- @tfield DruidInstance druid DruidInstance
---The root node of the Rich Text
-- @tfield node root
---The text prefab node
-- @tfield node text_prefab
--
local component = require("druid.component")
local rich_text = require("druid.custom.rich_text.module.rt")
@@ -183,52 +108,28 @@ end
---Set text for Rich Text
--- rich_text:set_text("color=redFoobar/color")
--- rich_text:set_text("color=1.0,0,0,1.0Foobar/color")
--- rich_text:set_text("color=#ff0000Foobar/color")
--- rich_text:set_text("color=#ff0000ffFoobar/color")
--- rich_text:set_text("shadow=redFoobar/shadow")
--- rich_text:set_text("shadow=1.0,0,0,1.0Foobar/shadow")
--- rich_text:set_text("shadow=#ff0000Foobar/shadow")
--- rich_text:set_text("shadow=#ff0000ffFoobar/shadow")
--- rich_text:set_text("outline=redFoobar/outline")
--- rich_text:set_text("outline=1.0,0,0,1.0Foobar/outline")
--- rich_text:set_text("outline=#ff0000Foobar/outline")
--- rich_text:set_text("outline=#ff0000ffFoobar/outline")
--- rich_text:set_text("font=MyCoolFontFoobar/font")
--- rich_text:set_text("size=2Twice as large/size")
--- rich_text:set_text("br/Insert a line break")
--- rich_text:set_text("nobrPrevent the text from breaking")
--- rich_text:set_text("img=texture:imageDisplay image")
--- rich_text:set_text("img=texture:image,sizeDisplay image with size")
--- rich_text:set_text("img=texture:image,width,heightDisplay image with width and height")
---@param text string|nil The text to set
---@return druid.rich_text.word[] words
---@return druid.rich_text.lines_metrics line_metrics
-- @usage
-- • color: Change text color
--
-- <color=red>Foobar</color>
-- <color=1.0,0,0,1.0>Foobar</color>
-- <color=#ff0000>Foobar</color>
-- <color=#ff0000ff>Foobar</color>
--
-- • shadow: Change text shadow
--
-- <shadow=red>Foobar</shadow>
-- <shadow=1.0,0,0,1.0>Foobar</shadow>
-- <shadow=#ff0000>Foobar</shadow>
-- <shadow=#ff0000ff>Foobar</shadow>
--
-- • outline: Change text shadow
--
-- <outline=red>Foobar</outline>
-- <outline=1.0,0,0,1.0>Foobar</outline>
-- <outline=#ff0000>Foobar</outline>
-- <outline=#ff0000ff>Foobar</outline>
--
-- • font: Change font
--
-- <font=MyCoolFont>Foobar</font>
--
-- • size: Change text size, relative to default size
--
-- <size=2>Twice as large</size>
--
-- • br: Insert a line break
--
-- <br/>
--
-- • nobr: Prevent the text from breaking
--
-- Words <nobr>inside tag</nobr> won't break
--
-- • img: Display image
--
-- <img=texture:image/>
-- <img=texture:image,size/>
-- <img=texture:image,width,height/>
function M:set_text(text)
text = text or ""
self:clear()

27
druid/druid.gui_script Normal file
View File

@@ -0,0 +1,27 @@
local druid = require("druid.druid")
function init(self)
self.druid = druid.new(self)
druid.register_gui_widget(self.druid)
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

View File

@@ -112,16 +112,14 @@ function M.on_language_change()
end
local WRAPPED_WIDGETS = {}
local REGISTERED_GUI_WIDGETS = {}
---Set a widget to the current game object. The game object can acquire the widget by calling `bindings.get_widget`
---It wraps with events only top level functions cross-context, so you will have no access to nested widgets functions
---Only one widget can be set per game object.
---@param widget druid.widget
function M.set_widget(widget)
local object = msg.url()
object.fragment = nil
---@return druid.widget
local function wrap_widget(widget)
-- Make a copy of the widget with all functions wrapped in events
-- It makes available to call gui functions from game objects
local wrapped_widget = setmetatable({}, { __index = widget })
@@ -136,52 +134,45 @@ function M.set_widget(widget)
end
end
WRAPPED_WIDGETS[object.socket] = WRAPPED_WIDGETS[object.socket] or {}
WRAPPED_WIDGETS[object.socket][object.path] = wrapped_widget
return wrapped_widget
end
---Get a binded widget to the current game object.
---@param object_url string|userdata|url|nil Root object, if nil current object will be used
---@return druid.widget|nil
function M.get_widget(object_url)
object_url = object_url or msg.url()
if object_url then
object_url = msg.url(object_url --[[@as string]])
end
local socket_widgets = WRAPPED_WIDGETS[object_url.socket]
if not socket_widgets then
---@generic T: druid.widget
---@param widget_class T The class of the widget to return
---@param gui_url_string string GUI url, if nil current gui will be used
---@return T|nil
function M.get_widget(widget_class, gui_url_string)
local gui_url = msg.url(gui_url_string)
local guis = REGISTERED_GUI_WIDGETS[gui_url.socket]
if not guis then
return nil
end
return socket_widgets[object_url.path]
for index = 1, #guis do
local gui = guis[index]
if gui.fragment == gui_url.fragment and gui.path == gui_url.path then
return gui.new_widget:trigger(widget_class)
end
end
return nil
end
---Release a binded widget to the current game object.
---@param object_url string|userdata|url|nil Root object, if nil current object will be used
---@return boolean is_released True if the widget was released, false if it was not found
function M.release_widget(object_url)
object_url = object_url or msg.url()
if object_url then
object_url = msg.url(object_url --[[@as string]])
end
local socket_widgets = WRAPPED_WIDGETS[object_url.socket]
if not socket_widgets then
return false
end
socket_widgets[object_url.path] = nil
-- Remove the socket if it's empty
if next(socket_widgets) == nil then
WRAPPED_WIDGETS[object_url.socket] = nil
end
return true
---Register a widget to the current game object.
---@param druid druid.instance The druid instance to register
function M.register_gui_widget(druid)
local gui_url = msg.url()
REGISTERED_GUI_WIDGETS[gui_url.socket] = REGISTERED_GUI_WIDGETS[gui_url.socket] or {}
table.insert(REGISTERED_GUI_WIDGETS[gui_url.socket], {
path = gui_url.path,
fragment = gui_url.fragment,
new_widget = event.create(function(widget_class)
return wrap_widget(druid:new_widget(widget_class))
end),
})
end
return M

View File

@@ -233,7 +233,6 @@ function M:_remove_at(index)
end
---Refresh all elements in DataList
---@private
function M:_refresh()

View File

@@ -1,10 +1,13 @@
varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;
#version 140
uniform lowp sampler2D texture_sampler;
in mediump vec2 var_texcoord0;
in mediump vec4 var_color;
out vec4 out_fragColor;
uniform mediump sampler2D texture_sampler;
void main()
{
lowp vec4 tex = texture2D(texture_sampler, var_texcoord0.xy);
gl_FragColor = tex * var_color;
out_fragColor = texture(texture_sampler, var_texcoord0.xy) * var_color;
}

View File

@@ -1,12 +1,16 @@
uniform highp mat4 view_proj;
#version 140
// positions are in world space
attribute highp vec3 position;
attribute mediump vec2 texcoord0;
attribute lowp vec4 color;
in mediump vec3 position;
in mediump vec2 texcoord0;
in mediump vec4 color;
varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;
out mediump vec2 var_texcoord0;
out mediump vec4 var_color;
uniform vs_uniforms
{
mediump mat4 view_proj;
};
void main()
{

View File

@@ -1,5 +1,6 @@
-- Hello, Defolder! Wish you a good day!
local event = require("event.event")
local events = require("event.events")
local const = require("druid.const")
local helper = require("druid.helper")
@@ -165,33 +166,6 @@ function M:_can_use_input_component(component)
end
---Process input for components
---@param action_id hash Action_id from on_input
---@param action table Action from on_input
---@param components druid.component[] Components to process input
---@return boolean The boolean value is input was consumed
function M:_process_input(action_id, action, components)
local is_input_consumed = false
for i = #components, 1, -1 do
local component = components[i]
local input_enabled = component:get_input_enabled()
if input_enabled and self:_can_use_input_component(component) then
if not is_input_consumed then
is_input_consumed = component:on_input(action_id, action) or false
else
if component.on_input_interrupt then
component:on_input_interrupt(action_id, action)
end
end
end
end
return is_input_consumed
end
local function schedule_late_init(self)
if self._late_init_timer_id then
return
@@ -228,6 +202,9 @@ function M.create_druid_instance(context, style)
events.subscribe("druid.window_event", self.on_window_event, self)
events.subscribe("druid.language_change", self.on_language_change, self)
-- And we can rid of several bindings by this?
--self.on_node_size_changed = event.create()
return self
end
@@ -379,7 +356,23 @@ function M:on_input(action_id, action)
local components = self.components_interest[const.ON_INPUT]
check_sort_input_stack(self, components)
local is_input_consumed = self:_process_input(action_id, action, components)
local is_input_consumed = false
for i = #components, 1, -1 do
local component = components[i]
local input_enabled = component:get_input_enabled()
if input_enabled and self:_can_use_input_component(component) then
if not is_input_consumed then
is_input_consumed = component:on_input(action_id, action) or false
else
if component.on_input_interrupt then
component:on_input_interrupt(action_id, action)
end
end
end
end
self._is_late_remove_enabled = false
self:_clear_late_remove()
@@ -652,7 +645,7 @@ end
local data_list = require("druid.extended.data_list")
---Create DataList component
---@param druid_scroll druid.scroll The Scroll instance for Data List component
---@param druid_grid druid.grid The StaticGrid} or @{DynamicGrid instance for Data List component
---@param druid_grid druid.grid The Grid instance for Data List component
---@param create_function function The create function callback(self, data, index, data_list). Function should return (node, [component])
---@return druid.data_list component DataList component
function M:new_data_list(druid_scroll, druid_grid, create_function)

View File

@@ -41,11 +41,11 @@ function M:init()
self:push_fps_value()
end)
self.container = self.druid:new_container(self.root)
self.container:add_container(self.mini_graph.container)
local container_content = self.container:add_container("content")
container_content:add_container("text_min_fps")
container_content:add_container("text_fps")
--self.container = self.druid:new_container(self.root)
--self.container:add_container(self.mini_graph.container)
--local container_content = self.container:add_container("content")
--container_content:add_container("text_min_fps")
--container_content:add_container("text_fps")
end

View File

@@ -39,11 +39,11 @@ function M:init()
self:push_next_value()
end)
self.container = self.druid:new_container(self.root)
self.container:add_container(self.mini_graph.container)
local container_content = self.container:add_container("content")
container_content:add_container("text_max_value")
container_content:add_container("text_per_second")
--self.container = self.druid:new_container(self.root)
--self.container:add_container(self.mini_graph.container)
--local container_content = self.container:add_container("content")
--container_content:add_container("text_max_value")
--container_content:add_container("text_per_second")
end

View File

@@ -72,8 +72,8 @@ function M:init_tiling_animation(atlas_path)
end
-- Start our repeat shader work
-- @param repeat_x -- X factor
-- @param repeat_y -- Y factor
---@param repeat_x number X factor
---@param repeat_y number Y factor
function M:animate(repeat_x, repeat_y)
if not self.is_inited then
return
@@ -110,9 +110,9 @@ function M:final()
end
-- Update repeat factor values
-- @param repeat_x
-- @param repeat_y
---Update repeat factor values
---@param repeat_x number X factor
---@param repeat_y number Y factor
function M:set_repeat(repeat_x, repeat_y)
local animation = self.animation
animation.v.x = repeat_x or animation.v.x
@@ -182,12 +182,6 @@ function M:set_scale(scale)
gui.set(self.node, helper.PROP_SIZE_X, current_size_x / delta_scale_x)
gui.set(self.node, helper.PROP_SIZE_Y, current_size_y / delta_scale_y)
--self.druid:on_node_property_changed(self.node, "scale")
--self.druid:on_node_property_changed(self.node, "size")
--local repeat_x, repeat_y = self:get_repeat()
--self:set_repeat(repeat_x, repeat_y)
return self
end