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]])
---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
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
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

View File

@ -95,6 +95,7 @@ function M:add_example(examples, druid_example)
else
instance = druid_example.druid:new(example_data.component_class, example_data.template)
end
---@cast instance druid.component|druid.widget
self.selected_example = {
data = example_data,

View File

@ -26,9 +26,9 @@ local druid_examples = require("example.examples.druid_examples")
---@field container_output druid.container
---@field container_center druid.container
---@field container_status druid.container
---@field druid_logo druid_logo
---@field druid_logo examples.druid_logo
---@field panel_information panel_information
---@field example_scene example_scene
---@field example_scene examples.example_scene
---@field panel_druid_profiler panel_druid_profiler
---@field examples_list_view examples_list_view
---@field properties_panel properties_panel
@ -74,13 +74,13 @@ end
---@param self druid.example
local function setup_components(self)
self.druid_logo = self.druid:new(druid_logo, "druid_logo") --[[@as druid_logo]]
self.druid_logo = self.druid:new(druid_logo, "druid_logo") --[[@as examples.druid_logo]]
self.container_logo:add_container(self.druid_logo.root)
self.panel_information = self.druid:new(panel_information, "panel_information") --[[@as panel_information]]
self.container_info:add_container(self.panel_information.root)
self.example_scene = self.druid:new(example_scene, "example_scene") --[[@as example_scene]]
self.example_scene = self.druid:new(example_scene, "example_scene") --[[@as examples.example_scene]]
self.container_center:add_container(self.example_scene.root)
self.panel_druid_profiler = self.druid:new(panel_druid_profiler, "panel_druid_profiler") --[[@as panel_druid_profiler]]

View File

@ -36,6 +36,66 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_stick.gui"
}
nodes {
type: TYPE_BOX
id: "stick_left/root"
parent: "stick_left"
template_node_child: true
}
nodes {
type: TYPE_PIE
id: "stick_left/background_mask"
parent: "stick_left/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/background"
parent: "stick_left/background_mask"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/stick_root"
parent: "stick_left/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/stick_shadow"
parent: "stick_left/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/stick"
parent: "stick_left/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/dot_1"
parent: "stick_left/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/dot_2"
parent: "stick_left/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/dot_3"
parent: "stick_left/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_left/dot_4"
parent: "stick_left/stick"
template_node_child: true
}
nodes {
position {
x: 200.0
@ -47,6 +107,66 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_stick.gui"
}
nodes {
type: TYPE_BOX
id: "stick_right/root"
parent: "stick_right"
template_node_child: true
}
nodes {
type: TYPE_PIE
id: "stick_right/background_mask"
parent: "stick_right/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/background"
parent: "stick_right/background_mask"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/stick_root"
parent: "stick_right/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/stick_shadow"
parent: "stick_right/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/stick"
parent: "stick_right/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/dot_1"
parent: "stick_right/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/dot_2"
parent: "stick_right/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/dot_3"
parent: "stick_right/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "stick_right/dot_4"
parent: "stick_right/stick"
template_node_child: true
}
nodes {
position {
x: -330.0
@ -73,6 +193,30 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_left/button"
parent: "button_left"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_left/text"
parent: "button_left/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
rotation {
z: 180.0
}
type: TYPE_BOX
id: "button_left/icon"
parent: "button_left/button"
overridden_fields: 2
template_node_child: true
}
nodes {
position {
y: 100.0
@ -83,6 +227,30 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_up/button"
parent: "button_up"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_up/text"
parent: "button_up/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
rotation {
z: 90.0
}
type: TYPE_BOX
id: "button_up/icon"
parent: "button_up/button"
overridden_fields: 2
template_node_child: true
}
nodes {
position {
x: 100.0
@ -93,6 +261,26 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_right/button"
parent: "button_right"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_right/text"
parent: "button_right/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
type: TYPE_BOX
id: "button_right/icon"
parent: "button_right/button"
template_node_child: true
}
nodes {
position {
y: -100.0
@ -103,6 +291,30 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_down/button"
parent: "button_down"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_down/text"
parent: "button_down/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
rotation {
z: -90.0
}
type: TYPE_BOX
id: "button_down/icon"
parent: "button_down/button"
overridden_fields: 2
template_node_child: true
}
nodes {
position {
x: 330.0
@ -129,6 +341,26 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_x/button"
parent: "button_x"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_x/text"
parent: "button_x/button"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_x/icon"
parent: "button_x/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
y: 100.0
@ -139,6 +371,28 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_y/button"
parent: "button_y"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "Y"
id: "button_y/text"
parent: "button_y/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_y/icon"
parent: "button_y/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
x: 100.0
@ -149,6 +403,28 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_b/button"
parent: "button_b"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "B"
id: "button_b/text"
parent: "button_b/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_b/icon"
parent: "button_b/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
y: -100.0
@ -159,6 +435,28 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_a/button"
parent: "button_a"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "A"
id: "button_a/text"
parent: "button_a/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_a/icon"
parent: "button_a/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
y: 160.0
@ -184,6 +482,33 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
size {
x: 170.0
y: 90.0
}
type: TYPE_BOX
id: "button_back/button"
parent: "button_back"
overridden_fields: 4
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "Back"
id: "button_back/text"
parent: "button_back/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_back/icon"
parent: "button_back/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
x: 110.0
@ -194,6 +519,33 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
size {
x: 170.0
y: 90.0
}
type: TYPE_BOX
id: "button_start/button"
parent: "button_start"
overridden_fields: 4
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "Start"
id: "button_start/text"
parent: "button_start/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_start/icon"
parent: "button_start/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
x: -330.0
@ -221,6 +573,28 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_l1/button"
parent: "button_l1"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "L1"
id: "button_l1/text"
parent: "button_l1/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_l1/icon"
parent: "button_l1/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
y: 60.0
@ -231,6 +605,26 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_trigger.gui"
}
nodes {
type: TYPE_BOX
id: "button_l2/button"
parent: "button_l2"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_l2/fill"
parent: "button_l2/button"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "L2"
id: "button_l2/text"
parent: "button_l2/button"
overridden_fields: 8
template_node_child: true
}
nodes {
position {
x: 330.0
@ -257,6 +651,28 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_r1/button"
parent: "button_r1"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "R1"
id: "button_r1/text"
parent: "button_r1/button"
overridden_fields: 8
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_r1/icon"
parent: "button_r1/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
nodes {
position {
y: 60.0
@ -267,5 +683,25 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_trigger.gui"
}
nodes {
type: TYPE_BOX
id: "button_r2/button"
parent: "button_r2"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_r2/fill"
parent: "button_r2/button"
template_node_child: true
}
nodes {
type: TYPE_TEXT
text: "R2"
id: "button_r2/text"
parent: "button_r2/button"
overridden_fields: 8
template_node_child: true
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT

View File

@ -133,6 +133,66 @@ nodes {
parent: "on_screen_input/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/root"
parent: "on_screen_input/on_screen_stick"
template_node_child: true
}
nodes {
type: TYPE_PIE
id: "on_screen_input/on_screen_stick/background_mask"
parent: "on_screen_input/on_screen_stick/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/background"
parent: "on_screen_input/on_screen_stick/background_mask"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/stick_root"
parent: "on_screen_input/on_screen_stick/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/stick_shadow"
parent: "on_screen_input/on_screen_stick/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/stick"
parent: "on_screen_input/on_screen_stick/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/dot_1"
parent: "on_screen_input/on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/dot_2"
parent: "on_screen_input/on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/dot_3"
parent: "on_screen_input/on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_stick/dot_4"
parent: "on_screen_input/on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/on_screen_button"
@ -145,5 +205,23 @@ nodes {
parent: "on_screen_input/on_screen_button"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/button_action/button"
parent: "on_screen_input/button_action"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "on_screen_input/button_action/text"
parent: "on_screen_input/button_action/button"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_input/button_action/icon"
parent: "on_screen_input/button_action/button"
template_node_child: true
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT

View File

@ -22,6 +22,66 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_stick.gui"
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/root"
parent: "on_screen_stick"
template_node_child: true
}
nodes {
type: TYPE_PIE
id: "on_screen_stick/background_mask"
parent: "on_screen_stick/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/background"
parent: "on_screen_stick/background_mask"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/stick_root"
parent: "on_screen_stick/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/stick_shadow"
parent: "on_screen_stick/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/stick"
parent: "on_screen_stick/stick_root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/dot_1"
parent: "on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/dot_2"
parent: "on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/dot_3"
parent: "on_screen_stick/stick"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "on_screen_stick/dot_4"
parent: "on_screen_stick/stick"
template_node_child: true
}
nodes {
position {
x: 300.0
@ -43,5 +103,25 @@ nodes {
inherit_alpha: true
template: "/example/examples/gamepad/gamepad_tester/templates/gamepad_button.gui"
}
nodes {
type: TYPE_BOX
id: "button_action/button"
parent: "button_action"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "button_action/text"
parent: "button_action/button"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "button_action/icon"
parent: "button_action/button"
overridden_fields: 45
template_node_child: true
enabled: false
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT

View File

@ -0,0 +1,33 @@
name: "go_bindings"
scale_along_z: 0
embedded_instances {
id: "go"
data: "components {\n"
" id: \"go_widget\"\n"
" component: \"/example/other/go_bindings/go_widget.gui\"\n"
"}\n"
"components {\n"
" id: \"go_bindings\"\n"
" component: \"/example/other/go_bindings/go_bindings.script\"\n"
"}\n"
"embedded_components {\n"
" id: \"sprite\"\n"
" type: \"sprite\"\n"
" data: \"default_animation: \\\"ui_circle_8\\\"\\n"
"material: \\\"/builtins/materials/sprite.material\\\"\\n"
"textures {\\n"
" sampler: \\\"texture_sampler\\\"\\n"
" texture: \\\"/druid/druid.atlas\\\"\\n"
"}\\n"
"\"\n"
" rotation {\n"
" z: 0.70710677\n"
" w: 0.70710677\n"
" }\n"
"}\n"
""
position {
x: 776.0
y: 366.0
}
}

View File

@ -0,0 +1,25 @@
local panthera = require("panthera.panthera")
local animation = require("example.other.go_bindings.go_bindings_panthera")
local druid = require("druid.druid")
local widget = require("example.other.go_bindings.go_widget")
function init(self)
self.go_widget = druid.get_widget(widget, "#go_widget")
self.go_widget:play_animation()
self.go_widget:set_position(go.get_position())
self.animation = panthera.create_go(animation)
panthera.play(self.animation, "default", {
is_loop = true,
})
msg.post(".", "acquire_input_focus")
end
function update(self, dt)
self.go_widget:set_position(go.get_position())
end

View File

@ -0,0 +1,79 @@
return {
data = {
animations = {
{
animation_id = "default",
animation_keys = {
{
duration = 1.13,
easing = "outback",
end_value = 706,
key_type = "tween",
node_id = "go",
property_id = "position_x",
start_value = 776,
},
{
duration = 1.46,
easing = "outback",
end_value = 271,
key_type = "tween",
node_id = "go",
property_id = "position_y",
start_time = 1.13,
start_value = 366,
},
{
duration = 1.44,
easing = "outback",
end_value = 826,
key_type = "tween",
node_id = "go",
property_id = "position_x",
start_time = 2.59,
start_value = 706,
},
{
duration = 1.17,
easing = "outback",
end_value = 366,
key_type = "tween",
node_id = "go",
property_id = "position_y",
start_time = 4.02,
start_value = 271,
},
{
duration = 0.81,
easing = "outback",
end_value = 776,
key_type = "tween",
node_id = "go",
property_id = "position_x",
start_time = 5.19,
start_value = 826,
},
},
duration = 6,
},
},
metadata = {
fps = 60,
gizmo_steps = {
},
gui_path = "/example/other/go_bindings/go_bindings.collection",
layers = {
},
settings = {
font_size = 30,
},
template_animation_paths = {
},
},
nodes = {
},
},
format = "json",
type = "animation_editor",
version = 1,
}

View File

@ -0,0 +1,156 @@
script: "/druid/druid.gui_script"
fonts {
name: "druid_text_bold"
font: "/druid/fonts/druid_text_bold.font"
}
textures {
name: "druid"
texture: "/druid/druid.atlas"
}
nodes {
type: TYPE_BOX
texture: "druid/empty"
id: "root"
inherit_alpha: true
size_mode: SIZE_MODE_AUTO
}
nodes {
position {
y: 20.0
}
size {
x: 32.0
y: 120.0
}
type: TYPE_BOX
texture: "druid/ui_circle_32"
id: "circle"
pivot: PIVOT_S
parent: "root"
inherit_alpha: true
slice9 {
x: 16.0
y: 16.0
z: 16.0
w: 16.0
}
}
nodes {
position {
y: 190.0
}
size {
x: 200.0
y: 100.0
}
type: TYPE_TEXT
text: "20"
font: "druid_text_bold"
id: "text"
parent: "root"
inherit_alpha: true
outline_alpha: 0.0
shadow_alpha: 0.0
}
nodes {
position {
x: 233.0
y: 127.0
}
type: TYPE_TEMPLATE
id: "memory_panel"
inherit_alpha: true
template: "/druid/widget/memory_panel/memory_panel.gui"
}
nodes {
type: TYPE_BOX
id: "memory_panel/root"
parent: "memory_panel"
template_node_child: true
}
nodes {
type: TYPE_TEMPLATE
id: "memory_panel/mini_graph"
parent: "memory_panel/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/root"
parent: "memory_panel/mini_graph"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/header"
parent: "memory_panel/mini_graph/root"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "memory_panel/mini_graph/text_header"
parent: "memory_panel/mini_graph/header"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/icon_drag"
parent: "memory_panel/mini_graph/header"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/content"
parent: "memory_panel/mini_graph/root"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/prefab_line"
parent: "memory_panel/mini_graph/content"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/mini_graph/color_low"
parent: "memory_panel/mini_graph/content"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/content"
parent: "memory_panel/root"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "memory_panel/text_max_value"
parent: "memory_panel/content"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "memory_panel/text_per_second"
parent: "memory_panel/content"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/line_second_1"
parent: "memory_panel/content"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "memory_panel/line_second_2"
parent: "memory_panel/content"
template_node_child: true
}
nodes {
type: TYPE_TEXT
id: "memory_panel/text_memory"
parent: "memory_panel/content"
template_node_child: true
}
material: "/druid/materials/gui_world/gui_world.material"
adjust_reference: ADJUST_REFERENCE_DISABLED

View File

@ -0,0 +1,39 @@
local panthera = require("panthera.panthera")
local animation = require("example.other.go_bindings.go_widget_panthera")
local memory_panel = require("druid.widget.memory_panel.memory_panel")
---@class widget.go_widget: druid.widget
local M = {}
function M:init()
print("init widget", msg.url())
self.root = self:get_node("root")
self.circle = self:get_node("circle")
self.animation = panthera.create_gui(animation)
self.counter = 0
self.text_counter = self:get_node("text")
gui.set_text(self.text_counter, 0)
self.memory_panel = self.druid:new_widget(memory_panel, "memory_panel")
end
function M:play_animation()
panthera.play(self.animation, "default", {
is_loop = true,
callback = function()
self.counter = self.counter + 1
gui.set_text(self.text_counter, self.counter)
end
})
end
---@param position vector3 The position to set
function M:set_position(position)
gui.set_position(self.root, position)
end
return M

View File

@ -0,0 +1,186 @@
return {
data = {
animations = {
{
animation_id = "default",
animation_keys = {
{
easing = "outsine",
end_value = 1.6,
key_type = "tween",
node_id = "text",
property_id = "scale_x",
start_value = 1,
},
{
easing = "outsine",
end_value = 1.6,
key_type = "tween",
node_id = "text",
property_id = "scale_y",
start_value = 1,
},
{
duration = 0.15,
easing = "outsine",
end_value = 1,
key_type = "tween",
node_id = "text",
property_id = "scale_x",
start_value = 1.6,
},
{
duration = 0.15,
easing = "outsine",
end_value = 1,
key_type = "tween",
node_id = "text",
property_id = "scale_y",
start_value = 1.6,
},
{
duration = 0.39,
easing = "outsine",
end_value = 400,
key_type = "tween",
node_id = "text",
property_id = "position_y",
start_value = 190,
},
{
duration = 0.43,
easing = "outsine",
end_value = 300,
key_type = "tween",
node_id = "circle",
property_id = "size_y",
start_value = 120,
},
{
duration = 1,
easing = "inoutexpo",
end_value = 720,
key_type = "tween",
node_id = "root",
property_id = "rotation_z",
},
{
duration = 0.24,
easing = "outsine",
end_value = 2,
key_type = "tween",
node_id = "text",
property_id = "scale_x",
start_time = 0.15,
start_value = 1,
},
{
duration = 0.24,
easing = "outsine",
end_value = 2,
key_type = "tween",
node_id = "text",
property_id = "scale_y",
start_time = 0.15,
start_value = 1,
},
{
duration = 0.3,
easing = "outsine",
key_type = "tween",
node_id = "circle",
property_id = "color_b",
start_time = 0.31,
start_value = 1,
},
{
duration = 0.3,
easing = "outsine",
key_type = "tween",
node_id = "circle",
property_id = "color_g",
start_time = 0.31,
start_value = 1,
},
{
duration = 0.61,
easing = "outsine",
end_value = 1,
key_type = "tween",
node_id = "text",
property_id = "scale_x",
start_time = 0.39,
start_value = 2,
},
{
duration = 0.61,
easing = "outsine",
end_value = 1,
key_type = "tween",
node_id = "text",
property_id = "scale_y",
start_time = 0.39,
start_value = 2,
},
{
duration = 0.26,
easing = "insine",
end_value = 1,
key_type = "tween",
node_id = "circle",
property_id = "color_b",
start_time = 0.61,
},
{
duration = 0.26,
easing = "insine",
end_value = 1,
key_type = "tween",
node_id = "circle",
property_id = "color_g",
start_time = 0.61,
},
{
duration = 0.36,
easing = "outsine",
end_value = 190,
key_type = "tween",
node_id = "text",
property_id = "position_y",
start_time = 0.62,
start_value = 400,
},
{
duration = 0.35,
easing = "outsine",
end_value = 120,
key_type = "tween",
node_id = "circle",
property_id = "size_y",
start_time = 0.65,
start_value = 300,
},
},
duration = 1,
},
},
metadata = {
fps = 60,
gizmo_steps = {
},
gui_path = "/example/other/go_bindings/go_widget.gui",
layers = {
},
settings = {
font_size = 30,
},
template_animation_paths = {
},
},
nodes = {
},
},
format = "json",
type = "animation_editor",
version = 1,
}

View File

@ -21,7 +21,7 @@ custom_resources = /example/locales
dependencies#0 = https://github.com/britzl/deftest/archive/refs/tags/2.8.0.zip
dependencies#1 = https://github.com/Insality/defold-saver/archive/refs/tags/1.zip
dependencies#2 = https://github.com/Insality/defold-tweener/archive/refs/tags/3.zip
dependencies#3 = https://github.com/Insality/panthera/archive/refs/tags/runtime.4.zip
dependencies#3 = https://github.com/Insality/panthera/archive/refs/heads/develop.zip
dependencies#4 = https://github.com/Insality/defold-lang/archive/refs/tags/3.zip
dependencies#5 = https://github.com/Insality/defold-event/archive/refs/tags/10.zip
dependencies#6 = https://github.com/subsoap/defos/archive/refs/tags/v2.8.0.zip