This commit is contained in:
Insality 2025-03-29 14:03:03 +02:00
parent b053b5044b
commit 3f22e9c542
27 changed files with 501 additions and 50 deletions

View File

@ -32,11 +32,10 @@
"Lua.diagnostics.libraryFiles": "Disable",
"Lua.runtime.version": "Lua 5.1",
"Lua.workspace.library": [
"~/Library/Application Support/Code/User/globalStorage/astronachos.defold",
"~/Library/Application Support/Code/User/workspaceStorage/72e25b7e0fdc873ee6f7baa61edbd6b1/astronachos.defold",
"~/Library/Application Support/Code/User/workspaceStorage/1446075a23c89451a63f0e82b2291def/astronachos.defold"
"~/Library/Application Support/Cursor/User/globalStorage/astronachos.defold",
"~/Library/Application Support/Cursor/User/workspaceStorage/1446075a23c89451a63f0e82b2291def/astronachos.defold"
],
"files.exclude": {
"**/*.gui": true
}
}
}

42
druid/druid.script Normal file
View File

@ -0,0 +1,42 @@
-- Place this script nearby with the gui component to able make requests
-- To the go namespace from GUI with events systems (cross context)
local defer = require("event.defer")
---Usage: defer.push("druid.get_atlas_path", {
--- texture_name = gui.get_texture(self.node),
--- sender = msg.url(),
---}, callback, [context])
---Pass texture name to get atlas info and sender url to check if the request is valid
local MESSAGE_GET_ATLAS_PATH = "druid.get_atlas_path"
---@class druid.get_atlas_path_request
---@field texture_name hash
---@field sender hash
---@param request druid.get_atlas_path_request
---@return string?
local function get_atlas_path(self, request)
local my_url = msg.url()
my_url.fragment = nil
local copy_url = msg.url(request.sender)
copy_url.fragment = nil
-- This check should works well
if my_url ~= copy_url then
return nil
end
return go.get(request.sender, "textures", { key = request.texture_name })
end
function init(self)
defer.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
end
function final(self)
defer.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path, self)
end

View File

@ -0,0 +1,83 @@
#version 140
uniform sampler2D texture_sampler;
in vec2 var_texcoord0;
in vec4 var_color;
in vec4 var_uv;
in vec4 var_repeat; // [repeat_x, repeat_y, anchor_x, anchor_y]
in vec4 var_params; // [margin_x, margin_y, offset_x, offset_y]
in vec4 var_uv_rotated;
out vec4 color_out;
void main() {
vec2 pivot = var_repeat.zw;
// Margin is a value between 0 and 1 that means offset/padding from the one image to another
vec2 margin = var_params.xy;
vec2 offset = var_params.zw;
vec2 repeat = var_repeat.xy;
// Atlas UV to local UV [0, 1]
float u = (var_texcoord0.x - var_uv.x) / (var_uv.z - var_uv.x);
float v = (var_texcoord0.y - var_uv.y) / (var_uv.w - var_uv.y);
// Adjust local UV by the pivot point. So 0:0 will be at the pivot point of node
u = u - (0.5 + pivot.x);
v = v - (0.5 - pivot.y);
// If rotated, swap UV
if (var_uv_rotated.y < 0.5) {
float temp = u;
u = v;
v = temp;
}
// Adjust repeat by the margin
repeat.x = repeat.x / (1.0 + margin.x);
repeat.y = repeat.y / (1.0 + margin.y);
// Repeat is a value between 0 and 1 that represents the number of times the texture is repeated in the atlas.
float tile_u = fract(u * repeat.x);
float tile_v = fract(v * repeat.y);
float tile_width = 1.0 / repeat.x;
float tile_height = 1.0 / repeat.y;
// Adjust tile UV by the pivot point.
// Not center is left top corner, need to adjust it to pivot point
tile_u = fract(tile_u + pivot.x + 0.5);
tile_v = fract(tile_v - pivot.y + 0.5);
// Apply offset
tile_u = fract(tile_u + offset.x);
tile_v = fract(tile_v + offset.y);
// Extend margins
margin = margin * 0.5;
tile_u = mix(0.0 - margin.x, 1.0 + margin.x, tile_u);
tile_v = mix(0.0 - margin.y, 1.0 + margin.y, tile_v);
float alpha = 0.0;
// If the tile is outside the margins, make it transparent, without IF
alpha = step(0.0, tile_u) * step(tile_u, 1.0) * step(0.0, tile_v) * step(tile_v, 1.0);
tile_u = clamp(tile_u, 0.0, 1.0); // Keep borders in the range 0-1
tile_v = clamp(tile_v, 0.0, 1.0); // Keep borders in the range 0-1
if (var_uv_rotated.y < 0.5) {
float temp = tile_u;
tile_u = tile_v;
tile_v = temp;
}
// Remap local UV to the atlas UV
vec2 uv = vec2(
mix(var_uv.x, var_uv.z, tile_u), // Get texture coordinate from the atlas
mix(var_uv.y, var_uv.w, tile_v) // Get texture coordinate from the atlas
//mix(var_uv.x, var_uv.z, tile_u * var_uv_rotated.x + tile_v * var_uv_rotated.z),
//mix(var_uv.y, var_uv.w, 1.0 - (tile_u * var_uv_rotated.y + tile_v * var_uv_rotated.x))
);
lowp vec4 tex = texture(texture_sampler, uv);
color_out = tex * var_color;
}

View File

@ -0,0 +1,37 @@
name: "repeat"
tags: "gui"
vertex_program: "/druid/materials/gui_tiling_node/gui_tiling_node.vp"
fragment_program: "/druid/materials/gui_tiling_node/gui_tiling_node.fp"
vertex_constants {
name: "view_proj"
type: CONSTANT_TYPE_VIEWPROJ
}
vertex_constants {
name: "uv_coord"
type: CONSTANT_TYPE_USER
value {
z: 1.0
w: 1.0
}
}
vertex_constants {
name: "uv_repeat"
type: CONSTANT_TYPE_USER
value {
x: 1.0
y: 1.0
}
}
vertex_constants {
name: "params"
type: CONSTANT_TYPE_USER
value {
}
}
vertex_constants {
name: "uv_rotated"
type: CONSTANT_TYPE_USER
value {
x: 1.0
}
}

View File

@ -0,0 +1,40 @@
#version 140
in mediump vec3 position;
in mediump vec2 texcoord0;
in lowp vec4 color;
uniform vertex_inputs
{
highp mat4 view_proj;
highp vec4 uv_coord;
highp vec4 uv_repeat; // [repeat_x, repeat_y, pivot_x, pivot_y]
vec4 uv_rotated;
vec4 params; // [margin_x, margin_y, offset_x, offset_y]
};
out mediump vec2 var_texcoord0;
out lowp vec4 var_color;
out highp vec4 var_uv;
out highp vec4 var_repeat;
out vec4 var_params;
out vec4 var_uv_rotated;
void main()
{
var_texcoord0 = texcoord0;
var_color = vec4(color.rgb * color.a, color.a);
var_uv = uv_coord;
var_repeat = uv_repeat;
var_params = params;
var_uv_rotated = uv_rotated;
mat4 transform = mat4(
1.0, 0, 0, 0.0,
0, 1.0, 0, 0.0,
0, 0, 1, 0,
0.0, position.z, 0, 1.0
);
gl_Position = view_proj * vec4(position.xyz, 1.0) * transform;
}

View File

@ -0,0 +1,168 @@
local helper = require("druid.helper")
local defer = require("event.defer")
---@class druid.tiling_node: druid.widget
---@field animation table
---@field node node
---@field params vector4
---@field time number
local M = {}
M.PROP_SIZE_X = hash("size.x")
M.PROP_SIZE_Y = hash("size.y")
M.PROP_SCALE_X = hash("scale.x")
M.PROP_SCALE_Y = hash("scale.y")
function M:init(node)
self.node = self:get_node(node)
self.animation = nil
self.time = 0
self.margin = 0
self.params = gui.get(self.node, "params") --[[@as vector4]]
defer.push("druid.get_atlas_path", {
texture_name = gui.get_texture(self.node),
sender = msg.url(),
}, self.on_get_atlas_path, self)
end
function M:on_get_atlas_path(atlas_path)
self.is_inited = self:init_tiling_animation(atlas_path)
local repeat_x, repeat_y = self:get_repeat()
self:refresh(repeat_x, repeat_y)
end
function M:on_node_property_changed(node, property)
if not self.is_inited or node ~= self.node then
return
end
if property == "size" or property == "scale" then
local repeat_x, repeat_y = self:get_repeat()
self:set_repeat(repeat_x, repeat_y)
end
end
function M:get_repeat()
if not self.is_inited then
return 1, 1
end
local size_x = gui.get(self.node, M.PROP_SIZE_X)
local size_y = gui.get(self.node, M.PROP_SIZE_Y)
local scale_x = gui.get(self.node, M.PROP_SCALE_X)
local scale_y = gui.get(self.node, M.PROP_SCALE_Y)
local repeat_x = (size_x / self.animation.width) / scale_x
local repeat_y = (size_y / self.animation.height) / scale_y
return repeat_x, repeat_y
end
---@return boolean
function M:init_tiling_animation(atlas_path)
if not atlas_path then
print("No atlas path found for node", gui.get_id(self.node), gui.get_texture(self.node))
print("Probably you should add druid.script at window collection to access resources")
return false
end
self.animation = helper.get_animation_data_from_node(self.node, atlas_path)
return true
end
-- Start our repeat shader work
---@param repeat_x number X factor
---@param repeat_y number Y factor
function M:refresh(repeat_x, repeat_y)
if not self.is_inited then
return
end
local node = self.node
local animation = self.animation
local frame = animation.frames[1]
gui.set(node, "uv_coord", frame.uv_coord)
self:set_repeat(repeat_x, repeat_y)
if #animation.frames > 1 and animation.fps > 0 then
animation.handle =
timer.delay(1/animation.fps, true, function(self, handle, time_elapsed)
local next_rame = animation.frames[animation.current_frame]
gui.set(node, "uv_coord", next_rame.uv_coord)
animation.current_frame = animation.current_frame + 1
if animation.current_frame > #animation.frames then
animation.current_frame = 1
end
end)
end
end
function M:final()
local animation = self.animation
if animation.handle then
timer.cancel(animation.handle)
animation.handle = nil
end
end
---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
animation.v.y = repeat_y or animation.v.y
local anchor = helper.get_pivot_offset(gui.get_pivot(self.node))
animation.v.z = anchor.x
animation.v.w = anchor.y
gui.set(self.node, "uv_repeat", animation.v)
end
function M:set_offset(offset_perc_x, offset_perc_y)
self.params.z = offset_perc_x or self.params.z
self.params.w = offset_perc_y or self.params.w
gui.set(self.node, "params", self.params)
return self
end
function M:set_margin(margin_x, margin_y)
self.params.x = margin_x or self.params.x
self.params.y = margin_y or self.params.y
gui.set(self.node, "params", self.params)
return self
end
---@param scale number
function M:set_scale(scale)
local current_scale_x = gui.get(self.node, M.PROP_SCALE_X)
local current_scale_y = gui.get(self.node, M.PROP_SCALE_Y)
local current_size_x = gui.get(self.node, M.PROP_SIZE_X)
local current_size_y = gui.get(self.node, M.PROP_SIZE_Y)
local delta_scale_x = scale / current_scale_x
local delta_scale_y = scale / current_scale_y
gui.set(self.node, M.PROP_SCALE_X, scale)
gui.set(self.node, M.PROP_SCALE_Y, scale)
gui.set(self.node, M.PROP_SIZE_X, current_size_x / delta_scale_x)
gui.set(self.node, M.PROP_SIZE_Y, current_size_y / delta_scale_y)
return self
end
return M

View File

@ -110,7 +110,7 @@ end
function M:update_components()
---@diagnostic disable-next-line: undefined-field
---@diagnostic disable-next-line, invisible
local components = #self.druid.components_all
self.text_components_amount:set_text(tostring(components))

View File

@ -1,18 +1,12 @@
local component = require("druid.component")
---@class property_button: druid.component
---@class property_button: druid.widget
---@field root node
---@field text_name druid.lang_text
---@field button druid.button
---@field text_button druid.text
---@field druid druid.instance
local M = component.create("property_button")
local M = {}
---@param template string
---@param nodes table<hash, node>
function M:init(template, nodes)
self.druid = self:get_druid(template, nodes)
function M:init()
self.root = self:get_node("root")
self.text_name = self.druid:new_lang_text("text_name") --[[@as druid.lang_text]]
self.selected = self:get_node("selected")

View File

@ -1,18 +1,12 @@
local component = require("druid.component")
---@class property_checkbox: druid.component
---@field druid druid.instance
---@class property_checkbox: druid.widget
---@field root druid.container
---@field text_name druid.lang_text
---@field button druid.button
---@field selected node
local M = component.create("property_checkbox")
local M = {}
---@param template string
---@param nodes table<hash, node>
function M:init(template, nodes)
self.druid = self:get_druid(template, nodes)
function M:init()
self.root = self.druid:new_container("root") --[[@as druid.container]]
self.icon = self:get_node("icon")

View File

@ -1,19 +1,12 @@
local component = require("druid.component")
---@class property_slider: druid.component
---@field druid druid.instance
---@class property_slider: druid.widget
---@field root druid.container
---@field text_name druid.lang_text
---@field text_value druid.text
---@field slider druid.slider
local M = component.create("property_slider")
local M = {}
---@param template string
---@param nodes table<hash, node>
function M:init(template, nodes)
self.druid = self:get_druid(template, nodes)
function M:init()
self.root = self.druid:new_container("root") --[[@as druid.container]]
self.selected = self:get_node("selected")
gui.set_alpha(self.selected, 0)

View File

@ -87,7 +87,7 @@ end
---@return property_slider
function M:add_slider(text_id, initial_value, on_change_callback)
local nodes = gui.clone_tree(self.property_slider_prefab)
local instance = self.druid:new(property_slider, "property_slider", nodes) --[[@as property_slider]]
local instance = self.druid:new_widget(property_slider, "property_slider", nodes) --[[@as property_slider]]
instance.text_name:translate(text_id)
instance:set_value(initial_value, true)

View File

@ -6,5 +6,9 @@ embedded_instances {
" id: \"druid\"\n"
" component: \"/example/druid.gui\"\n"
"}\n"
"components {\n"
" id: \"druid1\"\n"
" component: \"/druid/druid.script\"\n"
"}\n"
""
}

View File

@ -4785,6 +4785,25 @@ nodes {
parent: "property_input/E_Anchor"
template_node_child: true
}
nodes {
type: TYPE_TEMPLATE
id: "example_tiling_node"
parent: "widgets"
inherit_alpha: true
template: "/example/examples/widgets/tiling_node/example_tiling_node.gui"
}
nodes {
type: TYPE_BOX
id: "example_tiling_node/root"
parent: "example_tiling_node"
template_node_child: true
}
nodes {
type: TYPE_BOX
id: "example_tiling_node/tiling_node"
parent: "example_tiling_node/root"
template_node_child: true
}
nodes {
position {
x: -20.0

View File

@ -3,7 +3,7 @@ local intro_panthera = require("example.examples.intro.intro.intro_panthera")
---@class examples.intro: druid.widget
---@field root node
---@field animation panthera.instance
---@field animation panthera.animation
local M = {}

View File

@ -7,9 +7,9 @@ local character_animation_blend = require("example.examples.panthera.animation_b
---@class examples.animation_blend: druid.widget
---@field root node
---@field root_size vector3
---@field animation_idle panthera.instance
---@field animation_vertical panthera.instance
---@field animation_horizontal panthera.instance
---@field animation_idle panthera.animation
---@field animation_vertical panthera.animation
---@field animation_horizontal panthera.animation
---@field rich_text druid.rich_text
---@field on_update event
local M = {}

View File

@ -3,7 +3,7 @@ local panthera = require("panthera.panthera")
local basic_animation_panthera = require("example.examples.panthera.basic_animation.basic_animation_panthera")
---@class examples.basic_animation: druid.widget
---@field animation panthera.instance
---@field animation panthera.animation
---@field button druid.button
local M = {}

View File

@ -177,6 +177,37 @@ function M.get_examples()
end
end)
end,
},
{
name_id = "ui_example_widget_tiling_node",
information_text_id = "ui_example_widget_tiling_node_description",
template = "example_tiling_node",
root = "example_tiling_node/root",
code_url = "example/examples/widgets/tiling_node/example_tiling_node.lua",
widget_class = require("example.examples.widgets.tiling_node.example_tiling_node"),
properties_control = function(instance, properties_panel)
---@cast instance examples.example_tiling_node
properties_panel:add_slider("Repeat X", 0, function(value)
local repeat_x = math.floor(value * 10)
instance.tiling_node:refresh(repeat_x, nil)
end)
properties_panel:add_slider("Repeat Y", 0, function(value)
local repeat_y = math.floor(value * 10)
instance.tiling_node:refresh(nil, repeat_y)
end)
properties_panel:add_slider("Offset X", 0, function(value)
instance.tiling_node:set_offset(value, nil)
end)
properties_panel:add_slider("Offset Y", 0, function(value)
instance.tiling_node:set_offset(nil, value)
end)
properties_panel:add_slider("Margin X", 0, function(value)
instance.tiling_node:set_margin(value, nil)
end)
properties_panel:add_slider("Margin Y", 0, function(value)
instance.tiling_node:set_margin(nil, value)
end)
end,
}
}
end

View File

@ -0,0 +1,33 @@
textures {
name: "tiling_texture"
texture: "/example/examples/widgets/tiling_node/tiling_texture.atlas"
}
nodes {
size {
x: 200.0
y: 100.0
}
type: TYPE_BOX
id: "root"
inherit_alpha: true
size_mode: SIZE_MODE_AUTO
visible: false
}
nodes {
size {
x: 500.0
y: 500.0
}
type: TYPE_BOX
texture: "tiling_texture/pattern_0004"
id: "tiling_node"
parent: "root"
inherit_alpha: true
material: "gui_tiling_node"
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
materials {
name: "gui_tiling_node"
material: "/druid/materials/gui_tiling_node/gui_tiling_node.material"
}

View File

@ -0,0 +1,12 @@
local tiling_node = require("druid.widget.tiling_node.tiling_node")
---@class examples.example_tiling_node: druid.widget
local M = {}
function M:init()
self.tiling_node = self.druid:new_widget(tiling_node, nil, nil, self:get_node("tiling_node"))
end
return M

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,3 @@
images {
image: "/example/examples/widgets/tiling_node/pattern_0004.png"
}

View File

@ -10,7 +10,7 @@ local window_animation_panthera = require("example.examples.windows.window_anima
---@field button_close druid.button
---@field button_accept druid.button
---@field button_decline druid.button
---@field animation panthera.instance
---@field animation panthera.animation
local M = {}

View File

@ -8,7 +8,7 @@ local window_animation_panthera = require("example.examples.windows.window_anima
---@field text_description druid.lang_text
---@field button_close druid.button
---@field button_accept druid.button
---@field animation panthera.instance
---@field animation panthera.animation
local M = {}

View File

@ -11,7 +11,7 @@ local window_animation_panthera = require("example.examples.windows.window_anima
---@field lang_buttons table<string, druid.button>
---@field grid druid.grid
---@field on_language_change event
---@field animation panthera.instance
---@field animation panthera.animation
local M = {}

View File

@ -58,7 +58,7 @@ cssfile = /builtins/manifests/web/dark_theme.css
show_console_banner = 0
[native_extension]
app_manifest =
app_manifest =
[graphics]
texture_profiles = /builtins/graphics/default.texture_profiles

View File

@ -3,7 +3,7 @@ local mock = require("deftest.mock.mock")
local M = {}
-- Userdata type instead of script self
---@return vector3|vector4
---@return vector
function M.get_context()
return vmath.vector({})
end

View File

@ -586,20 +586,19 @@ Hello! Druid 1.1 is here! It's brings a lot of new features and improvements. Le
**Changelog 1.1**
- Remove external annotations, remove old HTML API page
- Fully annotated code and new API readme page (hope more comfortable to use)
- Fully annotated code and new API README page (hope more comfortable to use)
- Widgets here!
- A replacement for `custom_component`. Basically it's the same, but widgets contains no boilerplate code and more convinient to use.
- A replacement for `custom_components`. Basically it's the same, but `widgets` contains no boilerplate code and more convinient to use.
- Now I can include a kind of `widgets` with Druid and you can use it almost instantly in your project.
- Removed `druid.register()`. Now all components are available by default and available with `self.druid:new_*` functions
- This means the druid will be bigger in size, but it's much comfortable to use
- This means the Druid will be bigger in size, but it's much comfortable to use
- In case you want to delete components you not using, you can do it in fork in `druid.lua` file
- Any additional widgets, color library will be not included until you use it
- Remove `druid.event`, replaced with defold-event library. Now it required to double dependency to use Druid.
- Remove `druid.event`, replaced with `defold-event` library. Now it required to double dependency to use Druid.
- Add Druid UI kit, contains atlas so now you can use Druid GUI files in your projects.
- Contains mostly basic shapes for my UI and can contains several icons. Atlas is a small, only `128x128` size and will be included in build only if you use it.
- Contains mostly basic shapes for the UI and can contains several icons. Atlas is a small, only `128x128` size and will be included in build only if you use it.
- [Text]: Add `trim_left` and `scale_then_trim_left` text adjust modes
- [Text]: Add `set_text` function instead `set_to` (now it deprecated)
- Add `druid.bindings` module to handle cross-context widgets
- Add `druid.color` module to work with colors and palettes
- Add `container` component to handle more complex adaptive layouts
- [Shaders] Add repeat, hard image stencil and world gui materials