mirror of
https://github.com/Insality/druid
synced 2025-11-26 19:00:52 +01:00
@@ -1 +1,2 @@
|
||||
/dist
|
||||
/dist
|
||||
/.deployer_cache
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -14,4 +14,6 @@ deployer_version_settings.txt
|
||||
.deployer_cache
|
||||
bob*.jar
|
||||
manifest.private.der
|
||||
manifest.public.der
|
||||
manifest.public.der
|
||||
/.editor_settings
|
||||
/.deployer_cache
|
||||
|
||||
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -29,7 +29,7 @@
|
||||
"utils/annotations_manual.lua"
|
||||
],
|
||||
"Lua.runtime.pathStrict": true,
|
||||
"Lua.diagnostics.libraryFiles": "Enable",
|
||||
"Lua.diagnostics.libraryFiles": "Disable",
|
||||
"Lua.runtime.version": "Lua 5.1",
|
||||
"Lua.workspace.library": [
|
||||
"~/Library/Application Support/Code/User/globalStorage/astronachos.defold",
|
||||
|
||||
107
README.md
107
README.md
@@ -17,13 +17,15 @@ In this example you can inspect a variety of **Druid** components and see how th
|
||||
|
||||
## Setup
|
||||
|
||||
### Dependency
|
||||
### [Dependency](https://www.defold.com/manuals/libraries/)
|
||||
|
||||
To integrate the **Druid** extension into your own project, add this project as a [dependency](https://www.defold.com/manuals/libraries/) in your **Defold** game. Open your `game.project` file and add the following line to the dependencies field under the project section:
|
||||
Open your `game.project` file and add the following line to the dependencies field under the project section:
|
||||
|
||||
**Druid v1.0**
|
||||
**[Druid](https://github.com/Insality/druid/archive/refs/tags/1.0.zip)**
|
||||
|
||||
> [https://github.com/Insality/druid/archive/refs/tags/1.0.zip](https://github.com/Insality/druid/archive/refs/tags/1.0.zip)
|
||||
```
|
||||
https://github.com/Insality/druid/archive/refs/tags/1.0.zip
|
||||
```
|
||||
|
||||
Here is a list of [all releases](https://github.com/Insality/druid/releases).
|
||||
|
||||
@@ -87,12 +89,60 @@ end
|
||||
function on_input(self, action_id, action)
|
||||
return self.druid:on_input(action_id, action)
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
For all **Druid** instance functions, [see here](https://insality.github.io/druid/modules/DruidInstance.html).
|
||||
|
||||
|
||||
### Default GUI Script
|
||||
|
||||
Put the following code in your GUI script to start using **Druid**.
|
||||
|
||||
```lua
|
||||
local druid = require("druid.druid")
|
||||
|
||||
function init(self)
|
||||
self.druid = druid.new(self)
|
||||
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
|
||||
```
|
||||
|
||||
|
||||
### Default Widget Template
|
||||
|
||||
Create a new lua file to create a new widget class. This widget can be created with `self.druid:new_widget(widget_class, [template], [nodes])`
|
||||
|
||||
```lua
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.button = self.druid:new_button("button", self.on_click)
|
||||
end
|
||||
|
||||
function M:on_click()
|
||||
print("Button clicked!")
|
||||
end
|
||||
|
||||
return M
|
||||
```
|
||||
|
||||
|
||||
### API Documentation
|
||||
|
||||
**Druid** offers a wide range of components and functions. To facilitate usage, **Druid** provides comprehensive API documentation with examples and annotations.
|
||||
@@ -119,45 +169,38 @@ Here is full **Druid** components list.
|
||||
|
||||
| Name | Description | Example | <div style="width:200px">Preview</div> |
|
||||
|------|-------------|---------|---------|
|
||||
| **[Button](https://insality.github.io/druid/modules/Button.html)** | Logic over GUI Node. Handle the user click interactions: click, long click, double click, etc. | [Button Example](https://insality.github.io/druid/druid/?example=general_buttons) | <img src="media/preview/button.gif" width="200" height="100"> |
|
||||
| **[Text](https://insality.github.io/druid/modules/Text.html)** | Logic over GUI Text. By default Text component fit the text inside text node size area with different adjust modes. | [Text Example](https://insality.github.io/druid/druid/?example=texts_general) | <img src="media/preview/text.gif" width="200" height="100"> |
|
||||
| **[Scroll](https://insality.github.io/druid/modules/Scroll.html)** | Logic over two GUI Nodes: input and content. Provides basic behaviour for scrollable content. | [Scroll Example](https://insality.github.io/druid/druid/?example=general_scroll) | <img src="media/preview/scroll.gif" width="200" height="100"> |
|
||||
| **[Blocker](https://insality.github.io/druid/modules/Blocker.html)** | Logic over GUI Node. Don't pass any user input below node area size. | [Blocker Example](https://insality.github.io/druid/druid/?example=timer) | <img src="media/preview/blocker.gif" width="200" height="100"> |
|
||||
| **[Back Handler](https://insality.github.io/druid/modules/BackHandler.html)** | Call callback on user "Back" action. It's a Android back button or keyboard backspace key | [Back Handler Example](https://insality.github.io/druid/druid/?example=timer) | <img src="media/preview/back_handler.gif" width="200" height="100"> |
|
||||
| **[Static Grid](https://insality.github.io/druid/modules/StaticGrid.html)** | Logic over GUI Node. Component to manage node positions with all equal node sizes. | [Static Gid Example](https://insality.github.io/druid/druid/?example=general_grid) | <img src="media/preview/static_grid.gif" width="200" height="100"> |
|
||||
| **[Hover](https://insality.github.io/druid/modules/Hover.html)** | Logic over GUI Node. Handle hover action over node. For both: mobile touch and mouse cursor. | [Hover Example](https://insality.github.io/druid/druid/?example=timer) | <img src="media/preview/hover.gif" width="200" height="100"> |
|
||||
| **[Swipe](https://insality.github.io/druid/modules/Swipe.html)** | Logic over GUI Node. Handle swipe gestures over node. | [Swipe Example](https://insality.github.io/druid/druid/?example=general_swipe) | <img src="media/preview/swipe.gif" width="200" height="100"> |
|
||||
| **[Drag](https://insality.github.io/druid/modules/Drag.html)** | Logic over GUI Node. Handle drag input actions. Can be useful to make on screen controlls. | [Drag Example](https://insality.github.io/druid/druid/?example=general_drag) | <img src="media/preview/drag.gif" width="200" height="100"> |
|
||||
| **[Button](https://insality.github.io/druid/modules/Button.html)** | Logic over GUI Node. Handle the user click interactions: click, long click, double click, etc. | [Button Example](https://insality.github.io/druid/druid/?example=ui_example_basic_button) | <img src="media/preview/button.gif" width="200" height="100"> |
|
||||
| **[Text](https://insality.github.io/druid/modules/Text.html)** | Logic over GUI Text. By default Text component fit the text inside text node size area with different adjust modes. | [Text Example](https://insality.github.io/druid/druid/?example=ui_example_basic_text) | <img src="media/preview/text.gif" width="200" height="100"> |
|
||||
| **[Scroll](https://insality.github.io/druid/modules/Scroll.html)** | Logic over two GUI Nodes: input and content. Provides basic behaviour for scrollable content. | [Scroll Example](https://insality.github.io/druid/druid/?example=ui_example_basic_scroll) | <img src="media/preview/scroll.gif" width="200" height="100"> |
|
||||
| **[Blocker](https://insality.github.io/druid/modules/Blocker.html)** | Logic over GUI Node. Don't pass any user input below node area size. | [Blocker Example](https://insality.github.io/druid/druid/?example=ui_example_basic_blocker) | <img src="media/preview/blocker.gif" width="200" height="100"> |
|
||||
| **[Back Handler](https://insality.github.io/druid/modules/BackHandler.html)** | Call callback on user "Back" action. It's a Android back button or keyboard backspace key | [Back Handler Example](https://insality.github.io/druid/druid/?example=ui_example_basic_back_handler) | <img src="media/preview/back_handler.gif" width="200" height="100"> |
|
||||
| **[Static Grid](https://insality.github.io/druid/modules/StaticGrid.html)** | Logic over GUI Node. Component to manage node positions with all equal node sizes. | [Static Gid Example](https://insality.github.io/druid/druid/?example=ui_example_basic_grid) | <img src="media/preview/static_grid.gif" width="200" height="100"> |
|
||||
| **[Hover](https://insality.github.io/druid/modules/Hover.html)** | Logic over GUI Node. Handle hover action over node. For both: mobile touch and mouse cursor. | [Hover Example](https://insality.github.io/druid/druid/?example=ui_example_basic_hover) | <img src="media/preview/hover.gif" width="200" height="100"> |
|
||||
| **[Swipe](https://insality.github.io/druid/modules/Swipe.html)** | Logic over GUI Node. Handle swipe gestures over node. | [Swipe Example](https://insality.github.io/druid/druid/?example=ui_example_basic_swipe) | <img src="media/preview/swipe.gif" width="200" height="100"> |
|
||||
| **[Drag](https://insality.github.io/druid/modules/Drag.html)** | Logic over GUI Node. Handle drag input actions. Can be useful to make on screen controlls. | [Drag Example](https://insality.github.io/druid/druid/?example=ui_example_basic_drag) | <img src="media/preview/drag.gif" width="200" height="100"> |
|
||||
|
||||
|
||||
### Extended components
|
||||
|
||||
> Extended components before usage should be registered in **Druid** with [`druid.register()`](https://insality.github.io/druid/modules/Druid.html#druid.register) function.
|
||||
> On usage of unregistered **Druid** component the next log will be shown in the console.
|
||||
```
|
||||
local data_list = require("druid.extended.data_list")
|
||||
druid.register("data_list", data_list)
|
||||
```
|
||||
|
||||
| Name | Description | Example | <div style="width:200px">Preview</div> |
|
||||
|------|-------------|---------|---------|
|
||||
| **[Data List](https://insality.github.io/druid/modules/DataList.html)** | Logic over Scroll and Grid components. Create only visible GUI nodes or components to make "infinity" scroll befaviour | [Data List Example](https://insality.github.io/druid/druid/?example=general_data_list) | <img src="media/preview/data_list.gif" width="200" height="100"> |
|
||||
| **[Input](https://insality.github.io/druid/modules/Input.html)** | Logic over GUI Node and GUI Text (or Text component). Provides basic user text input. | [Input Example](https://insality.github.io/druid/druid/?example=general_input) | <img src="media/preview/input.gif" width="200" height="100"> |
|
||||
| **[Lang text](https://insality.github.io/druid/modules/LangText.html)** | Logic over Text component to handle localization. Can be translated in real-time with `druid.on_language_change` | [Lang Text Example](https://insality.github.io/druid/druid/?example=timer) | <img src="media/preview/lang_text.gif" width="200" height="100"> |
|
||||
| **[Progress](https://insality.github.io/druid/modules/Progress.html)** | Logic over GUI Node. Handle node size and scale to handle progress node size. | [Progress Example](https://insality.github.io/druid/druid/?example=general_progress_bar) | <img src="media/preview/progress.gif" width="200" height="100"> |
|
||||
| **[Slider](https://insality.github.io/druid/modules/Slider.html)** | Logic over GUI Node. Handle draggable node with position restrictions. | [Slider Example](https://insality.github.io/druid/druid/?example=general_sliders) | <img src="media/preview/slider.gif" width="200" height="100"> |
|
||||
| **[Timer](https://insality.github.io/druid/modules/Timer.html)** | Logic over GUI Text. Handle basic timer functions. | [Timer Example](https://insality.github.io/druid/druid/?example=timer) | <img src="media/preview/timer.gif" width="200" height="100"> |
|
||||
| **[Hotkey](https://insality.github.io/druid/modules/Hotkey.html)** | Allow to set callbacks for keyboard hotkeys with key modificators. | [Hotkey Example](https://insality.github.io/druid/druid/?example=general_hotkey) | <img src="media/preview/hotkey.gif" width="200" height="100"> |
|
||||
| **[Layout](https://insality.github.io/druid/modules/Layout.html)** | Logic over GUI Node. Arrange nodes inside layout node with margin/paddings settings. | [Layout Example](https://insality.github.io/druid/druid/?example=general_layout) | <img src="media/preview/layout.gif" width="200" height="100"> |
|
||||
| **[Rich Input](https://insality.github.io/druid/modules/RichInput.html)** | Logic over GUI Node and GUI Text (or Text component). Provides rich text input with different styles and text formatting. | [Rich Input Example](https://insality.github.io/druid/druid/?example=general_rich_input) | <img src="media/preview/rich_input.gif" width="200" height="100"> |
|
||||
| **[Rich Text](https://insality.github.io/druid/modules/RichText.html)** | Logic over GUI Text. Provides rich text formatting with different styles and text formatting. | [Rich Text Example](https://insality.github.io/druid/druid/?example=general_rich_text) | <img src="media/preview/rich_text.gif" width="200" height="100"> |
|
||||
| **[Data List](https://insality.github.io/druid/modules/DataList.html)** | Logic over Scroll and Grid components. Create only visible GUI nodes or components to make "infinity" scroll befaviour | [Data List Example](https://insality.github.io/druid/druid/?example=ui_example_data_list_basic) | <img src="media/preview/data_list.gif" width="200" height="100"> |
|
||||
| **[Input](https://insality.github.io/druid/modules/Input.html)** | Logic over GUI Node and GUI Text (or Text component). Provides basic user text input. | [Input Example](https://insality.github.io/druid/druid/?example=ui_example_basic_input) | <img src="media/preview/input.gif" width="200" height="100"> |
|
||||
| **[Lang text](https://insality.github.io/druid/modules/LangText.html)** | Logic over Text component to handle localization. Can be translated in real-time with `druid.on_language_change` | [Lang Text Example](https://insality.github.io/druid/druid/?example=ui_example_window_language) | <img src="media/preview/lang_text.gif" width="200" height="100"> |
|
||||
| **[Progress](https://insality.github.io/druid/modules/Progress.html)** | Logic over GUI Node. Handle node size and scale to handle progress node size. | [Progress Example](https://insality.github.io/druid/druid/?example=ui_example_basic_progress_bar) | <img src="media/preview/progress.gif" width="200" height="100"> |
|
||||
| **[Slider](https://insality.github.io/druid/modules/Slider.html)** | Logic over GUI Node. Handle draggable node with position restrictions. | [Slider Example](https://insality.github.io/druid/druid/?example=ui_example_basic_slider) | <img src="media/preview/slider.gif" width="200" height="100"> |
|
||||
| **[Timer](https://insality.github.io/druid/modules/Timer.html)** | Logic over GUI Text. Handle basic timer functions. | [Timer Example](https://insality.github.io/druid/druid/?example=ui_example_basic_timer) | <img src="media/preview/timer.gif" width="200" height="100"> |
|
||||
| **[Hotkey](https://insality.github.io/druid/modules/Hotkey.html)** | Allow to set callbacks for keyboard hotkeys with key modificators. | [Hotkey Example](https://insality.github.io/druid/druid/?example=ui_example_basic_hotkey) | <img src="media/preview/hotkey.gif" width="200" height="100"> |
|
||||
| **[Layout](https://insality.github.io/druid/modules/Layout.html)** | Logic over GUI Node. Arrange nodes inside layout node with margin/paddings settings. | [Layout Example](https://insality.github.io/druid/druid/?example=ui_example_layout_basic) | <img src="media/preview/layout.gif" width="200" height="100"> |
|
||||
| **[Rich Input](https://insality.github.io/druid/modules/RichInput.html)** | Logic over GUI Node and GUI Text (or Text component). Provides rich text input with different styles and text formatting. | [Rich Input Example](https://insality.github.io/druid/druid/?example=ui_example_basic_rich_input) | <img src="media/preview/rich_input.gif" width="200" height="100"> |
|
||||
| **[Rich Text](https://insality.github.io/druid/modules/RichText.html)** | Logic over GUI Text. Provides rich text formatting with different styles and text formatting. | [Rich Text Example](https://insality.github.io/druid/druid/?example=ui_example_basic_rich_text) | <img src="media/preview/rich_text.gif" width="200" height="100"> |
|
||||
|
||||
For a complete overview, see: **_[components.md](docs_md/01-components.md)_**.
|
||||
|
||||
|
||||
## Druid Events
|
||||
|
||||
Any **Druid** components as callbacks use [Druid Events](https://insality.github.io/druid/modules/DruidEvent.html). In component API ([button example](https://insality.github.io/druid/modules/Button.html#on_click)) pointed list of component events. You can manually subscribe to these events with the following API:
|
||||
Any **Druid** components as callbacks use [Druid Events](https://insality.github.io/druid/modules/druid.event.html). In component API ([button example](https://insality.github.io/druid/modules/Button.html#on_click)) pointed list of component events. You can manually subscribe to these events with the following API:
|
||||
|
||||
- **event:subscribe**(callback)
|
||||
|
||||
|
||||
18
config.ld
18
config.ld
@@ -1,18 +0,0 @@
|
||||
project='Druid'
|
||||
title='Defold Druid UI Framework'
|
||||
description='Documentation for Druid Framework'
|
||||
file={"./druid",
|
||||
exclude = {
|
||||
"./druid/styles/",
|
||||
"./druid/templates/",
|
||||
"./druid/annotations.lua",
|
||||
"./druid/custom/rich_text/module",
|
||||
}
|
||||
}
|
||||
package='druid'
|
||||
sort=true
|
||||
dir='./docs'
|
||||
style='!fixed'
|
||||
topics={}
|
||||
use_markdown_titles=true
|
||||
no_space_before_args=true
|
||||
@@ -216,7 +216,7 @@ Static grid have constant node size, so it possible to calculate node positions
|
||||
Static grid can shift elements on add/remove functions.
|
||||
|
||||
### Setup
|
||||
Create component with druid: `grid = druid:new_static_grid(parent_node, prefab_node, max_in_row_elements)`
|
||||
Create component with druid: `grid = druid:new_grid(parent_node, prefab_node, max_in_row_elements)`
|
||||
|
||||
### Notes
|
||||
- On _add node_ grid will set nodeup parent to _parent_node_
|
||||
@@ -227,33 +227,6 @@ Create component with druid: `grid = druid:new_static_grid(parent_node, prefab_
|
||||
- _Prefab node_ used to get node size and anchor
|
||||
- You can point *position_function* for animations with _static_grid:set_position_function(node, pos)_ callback. Default - *gui.set_position()*
|
||||
|
||||
|
||||
## Dynamic Grid
|
||||
[Dynamic Grid API here](https://insality.github.io/druid/modules/DynamicGrid.html)
|
||||
|
||||
### Overview
|
||||
Component for manage node positions with different node sizes.
|
||||
Unlike Static Grid, Dynamic Grid can place nodes only in one row or in one column.
|
||||
Dynamic Grid can't have gaps between elements
|
||||
- you will get error, if try spawn element far away from others.
|
||||
Dynamic Grid should have __West__, __East__, __South__ or __North__ pivot (vertical or horizontal element placement)
|
||||
|
||||
### Setup
|
||||
Create component with druid: `grid = druid:new_dynamic_grid(parent_node)`
|
||||
|
||||
Check the _parent_node_ have correct pivot point. You will get the error otherwise.
|
||||
|
||||
### Notes
|
||||
- On _add node_ grid will set node parent to _parent_node_
|
||||
- You can get array of position of every element for setup points of interest in scroll component
|
||||
- You can get size of all elements for setup size in scroll component
|
||||
- You can also bind the grid to the scroll component for auto resize scroll content size
|
||||
- Pivot of parent_node matter for node placement
|
||||
- You can point *position_function* for animations with _static_grid:set_position_function(node, pos)_ callback. Default - *gui.set_position()*
|
||||
- First node placed at Grid pivot point. Other nodes placed nearby of other nodes.
|
||||
- On *add/remove* nodes always shifted. You can point the shift side in this functions (*is_shift_left* boolean argumentp
|
||||
|
||||
|
||||
## Data List
|
||||
[Data List API here](https://insality.github.io/druid/modules/DataList.html)
|
||||
|
||||
|
||||
@@ -148,7 +148,6 @@ Available keywords:
|
||||
- `text`: Adds a [Druid Text](01-components.md#text) component.
|
||||
- `lang_text`: Adds a [Druid Lang Text](01-components.md#lang-text) component.
|
||||
- `grid` or `static_grid`: Adds a [Druid Static Grid](01-components.md#static-grid) component. You should set up the Grid prefab for this component after generating the file.
|
||||
- `dynamic_grid`: Adds a [Druid Dynamic Grid](01-components.md#dynamic-grid) component.
|
||||
- `scroll_view`: Adds a [Druid Scroll](01-components.md#scroll) component. It also adds a `scroll_content` node with the same postfix. Ensure that it's the correct node.
|
||||
- `blocker`: Adds a [Druid Blocker](01-components.md#blocker) component.
|
||||
- `slider`: Adds a [Druid Slider](01-components.md#slider) component. You should adjust the end position of the Slider after generating the file.
|
||||
|
||||
@@ -61,29 +61,6 @@ no_auto_input = 1
|
||||
```
|
||||
|
||||
|
||||
## Template Name Check
|
||||
|
||||
By default, **Druid** automatically checks the parent component's template name to construct the full template name for the component. It's used in user custom components.
|
||||
|
||||
If, for some reason, you want to pass the full template name manually, you can disable this feature by setting the `druid.no_auto_template` field in the _game.project_ file:
|
||||
|
||||
```
|
||||
[druid]
|
||||
no_auto_template = 1
|
||||
```
|
||||
|
||||
|
||||
## Stencil Check
|
||||
|
||||
When creating input components inside stencil nodes, **Druid** automatically sets up `component:set_click_zone()` during the _late_init_ component step to restrict input clicks outside of the stencil zone. This is particularly useful for buttons inside scroll stencil nodes.
|
||||
|
||||
To disable this feature, add the following field to your _game.project_ file:
|
||||
```
|
||||
[druid]
|
||||
no_stencil_check = 1
|
||||
```
|
||||
|
||||
|
||||
## Code Bindings
|
||||
|
||||
Adjust **Druid** settings as needed:
|
||||
@@ -121,7 +98,7 @@ window.set_listener(on_window_callback)
|
||||
```
|
||||
|
||||
|
||||
## EmmyLua Annotations
|
||||
## Lua Annotations
|
||||
|
||||
[EmmyLua](https://emmylua.github.io/annotation.html) is a Lua annotation library. It is a useful tool for enabling Lua code autocompletion in editors such as [VSCode](https://github.com/EmmyLua/VSCode-EmmyLua) and [IntelliJ IDEA](https://github.com/EmmyLua/IntelliJ-EmmyLua).
|
||||
|
||||
|
||||
0
docs_md/api_reference/druid_button.md
Normal file
0
docs_md/api_reference/druid_button.md
Normal file
74
docs_md/quick_api_reference.md
Normal file
74
docs_md/quick_api_reference.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Quick API Reference
|
||||
|
||||
## Druid
|
||||
```lua
|
||||
local druid = require("druid.druid")
|
||||
|
||||
druid.init_window_listener()
|
||||
druid.on_language_change()
|
||||
druid.on_window_callback(window_event)
|
||||
druid.set_default_style(style)
|
||||
druid.set_sound_function(callback)
|
||||
druid.set_text_function(callback)
|
||||
|
||||
self.druid = druid.new(context, [style])
|
||||
```
|
||||
|
||||
## Druid Instance
|
||||
```lua
|
||||
self.druid:final()
|
||||
self.druid:update(dt)
|
||||
self.druid:on_input(action_id, action)
|
||||
self.druid:on_message(message_id, message, sender)
|
||||
|
||||
self.druid:new(component, ...)
|
||||
self.druid:new_back_handler([callback], [params])
|
||||
self.druid:new_blocker(node)
|
||||
self.druid:new_button(node, [callback], [params], [anim_node])
|
||||
self.druid:new_container(node, [mode], [callback])
|
||||
self.druid:new_data_list(druid_scroll, druid_grid, create_function)
|
||||
self.druid:new_drag(node, [on_drag_callback])
|
||||
self.druid:new_grid(parent_node, item, [in_row])
|
||||
self.druid:new_hotkey(keys_array, [callback], [callback_argument])
|
||||
self.druid:new_hover(node, [on_hover_callback], [on_mouse_hover_callback])
|
||||
self.druid:new_input(click_node, text_node, [keyboard_type])
|
||||
self.druid:new_lang_text(node, [locale_id], [adjust_type])
|
||||
self.druid:new_layout(node, [mode])
|
||||
self.druid:new_progress(node, key, [init_value])
|
||||
self.druid:new_rich_input(template, [nodes])
|
||||
self.druid:new_rich_text(text_node, [value])
|
||||
self.druid:new_scroll(view_node, content_node)
|
||||
self.druid:new_slider(pin_node, end_pos, [callback])
|
||||
self.druid:new_swipe(node, [on_swipe_callback])
|
||||
self.druid:new_text(node, [value], [no_adjust])
|
||||
self.druid:new_timer(node, [seconds_from], [seconds_to], [callback])
|
||||
self.druid:new_widget(widget, [template], [nodes], ...)
|
||||
self.druid:on_window_event([window_event])
|
||||
self.druid:remove(component)
|
||||
self.druid:set_blacklist(blacklist_components)
|
||||
self.druid:set_whitelist(whitelist_components)
|
||||
```
|
||||
|
||||
## Components
|
||||
### Base Component
|
||||
### Blocker
|
||||
### Button
|
||||
### Container
|
||||
### Data List
|
||||
### Drag
|
||||
### Grid
|
||||
### Hotkey
|
||||
### Hover
|
||||
### Input
|
||||
### Lang Text
|
||||
### Layout
|
||||
### Progress
|
||||
### Rich Input
|
||||
### Rich Text
|
||||
### Scroll
|
||||
### Slider
|
||||
### Swipe
|
||||
### Text
|
||||
### Timer
|
||||
|
||||
## Helper
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +1,24 @@
|
||||
-- Copyright (c) 2023 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Component with event on back and backspace button.
|
||||
-- <b># Overview #</b>
|
||||
--
|
||||
-- Back Handler is recommended to put in every game window to close it
|
||||
-- or in main screen to call settings window.
|
||||
--
|
||||
-- <b># Notes #</b>
|
||||
--
|
||||
-- • Back Handler inheritance @{BaseComponent}, you can use all of its methods in addition to those described here.
|
||||
--
|
||||
-- • Back Handler react on release action ACTION_BACK or ACTION_BACKSPACE
|
||||
-- @usage
|
||||
-- local callback = function(self, params) ... end
|
||||
--
|
||||
-- local params = {}
|
||||
-- local back_handler = self.druid:new_back_handler(callback, [params])
|
||||
-- @module BackHandler
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.back_handler
|
||||
|
||||
--- The @{DruidEvent} Event on back handler action.
|
||||
--
|
||||
-- Trigger on input action ACTION_BACK or ACTION_BACKSPACE
|
||||
-- @usage
|
||||
-- -- Subscribe additional callbacks:
|
||||
-- back_handler.on_back:subscribe(callback)
|
||||
-- @tfield DruidEvent on_back @{DruidEvent}
|
||||
|
||||
--- Custom args to pass in the callback
|
||||
-- @usage
|
||||
-- -- Replace params on runtime:
|
||||
-- back_handler.params = { ... }
|
||||
-- @tfield any|nil params
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local component = require("druid.component")
|
||||
|
||||
local BackHandler = component.create("back_handler")
|
||||
---@class druid.back_handler: druid.base_component
|
||||
---@field on_back event Trigger on back handler action, fun(self, params)
|
||||
---@field params any|nil Custom args to pass in the callback
|
||||
local M = component.create("back_handler")
|
||||
|
||||
|
||||
--- The @{BackHandler} constructor
|
||||
-- @tparam BackHandler self @{BackHandler}
|
||||
-- @tparam function callback @The callback(self, custom_args) to call on back event
|
||||
-- @tparam any|nil custom_args Button events custom arguments
|
||||
-- @local
|
||||
function BackHandler.init(self, callback, custom_args)
|
||||
self.params = custom_args
|
||||
self.on_back = Event(callback)
|
||||
---@param callback function|nil
|
||||
---@param params any|nil
|
||||
function M:init(callback, params)
|
||||
self.params = params
|
||||
self.on_back = event.create(callback)
|
||||
end
|
||||
|
||||
|
||||
--- Component input handler
|
||||
-- @tparam BackHandler self @{BackHandler}
|
||||
-- @tparam string action_id on_input action id
|
||||
-- @tparam table action on_input action
|
||||
-- @local
|
||||
function BackHandler.on_input(self, action_id, action)
|
||||
---@param action_id string
|
||||
---@param action table
|
||||
function M:on_input(action_id, action)
|
||||
if not action.released then
|
||||
return false
|
||||
end
|
||||
@@ -73,4 +32,4 @@ function BackHandler.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
return BackHandler
|
||||
return M
|
||||
|
||||
@@ -1,50 +1,22 @@
|
||||
-- Copyright (c) 2023 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Component to consume input in special zone defined by GUI node.
|
||||
-- <b># Overview #</b>
|
||||
--
|
||||
-- <b># Notes #</b>
|
||||
--
|
||||
-- Blocker consume input if `gui.pick_node` works on it.
|
||||
--
|
||||
-- • Blocker inheritance @{BaseComponent}, you can use all of its methods in addition to those described here.
|
||||
--
|
||||
-- • Blocker initial enabled state is `gui.is_enabled(node, true)`
|
||||
--
|
||||
-- • The Blocker node should be enabled to capture the input
|
||||
-- @usage
|
||||
-- local node = gui.get_node("blocker_node")
|
||||
-- local blocker = self.druid:new_blocker(node)
|
||||
-- @module Blocker
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.blocker
|
||||
|
||||
---Blocker node
|
||||
-- @tfield node node
|
||||
|
||||
---
|
||||
|
||||
local const = require("druid.const")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Blocker = component.create("blocker")
|
||||
---@class druid.blocker: druid.base_component
|
||||
---@field node node
|
||||
---@field private _is_enabled boolean
|
||||
local M = component.create("blocker")
|
||||
|
||||
|
||||
--- The @{Blocker} constructor
|
||||
-- @tparam Blocker self @{Blocker}
|
||||
-- @tparam node node Gui node
|
||||
function Blocker.init(self, node)
|
||||
---@param node node
|
||||
function M:init(node)
|
||||
self.node = self:get_node(node)
|
||||
self._is_enabled = gui.is_enabled(self.node, true)
|
||||
end
|
||||
|
||||
|
||||
--- Component input handler
|
||||
-- @tparam Blocker self @{Blocker}
|
||||
-- @tparam string action_id on_input action id
|
||||
-- @tparam table action on_input action
|
||||
-- @local
|
||||
function Blocker.on_input(self, action_id, action)
|
||||
---@param action_id string
|
||||
---@param action table
|
||||
function M:on_input(action_id, action)
|
||||
if action_id ~= const.ACTION_TOUCH and
|
||||
action_id ~= const.ACTION_MULTITOUCH and
|
||||
action_id ~= nil then
|
||||
@@ -67,22 +39,21 @@ function Blocker.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
--- Set enabled blocker component state.
|
||||
--
|
||||
-- Don't change node enabled state itself.
|
||||
-- @tparam Blocker self @{Blocker}
|
||||
-- @tparam boolean|nil state Enabled state
|
||||
function Blocker.set_enabled(self, state)
|
||||
---Set blocker enabled state
|
||||
---@param state boolean
|
||||
---@return druid.blocker self
|
||||
function M:set_enabled(state)
|
||||
self._is_enabled = state
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Return blocker enabled state
|
||||
-- @tparam Blocker self @{Blocker}
|
||||
-- @treturn boolean @True, if blocker is enabled
|
||||
function Blocker.is_enabled(self)
|
||||
---Get blocker enabled state
|
||||
---@return boolean
|
||||
function M:is_enabled()
|
||||
return self._is_enabled
|
||||
end
|
||||
|
||||
|
||||
return Blocker
|
||||
return M
|
||||
|
||||
@@ -35,16 +35,16 @@
|
||||
-- @alias druid.button
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event on successful release action over button.
|
||||
--- The event: Event on successful release action over button.
|
||||
-- @usage
|
||||
-- -- Custom args passed in Button constructor
|
||||
-- button.on_click:subscribe(function(self, custom_args, button_instance)
|
||||
-- print("On button click!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_click @{DruidEvent}
|
||||
-- @tfield event on_click event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event on repeated action over button.
|
||||
--- The event: Event on repeated action over button.
|
||||
--
|
||||
-- This callback will be triggered if user hold the button. The repeat rate pick from `input.repeat_interval` in game.project
|
||||
-- @usage
|
||||
@@ -52,10 +52,10 @@
|
||||
-- button.on_repeated_click:subscribe(function(self, custom_args, button_instance, click_count)
|
||||
-- print("On repeated Button click!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_repeated_click @{DruidEvent}
|
||||
-- @tfield event on_repeated_click event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event on long tap action over button.
|
||||
--- The event: Event on long tap action over button.
|
||||
--
|
||||
-- This callback will be triggered if user pressed the button and hold the some amount of time.
|
||||
-- The amount of time picked from button style param: LONGTAP_TIME
|
||||
@@ -64,10 +64,10 @@
|
||||
-- button.on_long_click:subscribe(function(self, custom_args, button_instance, hold_time)
|
||||
-- print("On long Button click!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_long_click @{DruidEvent}
|
||||
-- @tfield event on_long_click event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event on double tap action over button.
|
||||
--- The event: Event on double tap action over button.
|
||||
--
|
||||
-- If secondary click was too fast after previous one, the double
|
||||
-- click will be called instead usual click (if on_double_click subscriber exists)
|
||||
@@ -76,10 +76,10 @@
|
||||
-- button.on_double_click:subscribe(function(self, custom_args, button_instance, click_amount)
|
||||
-- print("On double Button click!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_double_click @{DruidEvent}
|
||||
-- @tfield event on_double_click event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event calls every frame before on_long_click event.
|
||||
--- The event: Event calls every frame before on_long_click event.
|
||||
--
|
||||
-- If long_click subscriber exists, the on_hold_callback will be called before long_click trigger.
|
||||
--
|
||||
@@ -89,10 +89,10 @@
|
||||
-- button.on_double_click:subscribe(function(self, custom_args, button_instance, time)
|
||||
-- print("On hold Button callback!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_hold_callback @{DruidEvent}
|
||||
-- @tfield event on_hold_callback event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event calls if click event was outside of button.
|
||||
--- The event: Event calls if click event was outside of button.
|
||||
--
|
||||
-- This event will be triggered for each button what was not clicked on user click action
|
||||
--
|
||||
@@ -102,16 +102,16 @@
|
||||
-- button.on_click_outside:subscribe(function(self, custom_args, button_instance)
|
||||
-- print("On click Button outside!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_click_outside @{DruidEvent}
|
||||
-- @tfield event on_click_outside event
|
||||
|
||||
|
||||
--- The @{DruidEvent}: Event triggered if button was pressed by user.
|
||||
--- The event: Event triggered if button was pressed by user.
|
||||
-- @usage
|
||||
-- -- Custom args passed in Button constructor
|
||||
-- button.on_pressed:subscribe(function(self, custom_args, button_instance)
|
||||
-- print("On Button pressed!")
|
||||
-- end)
|
||||
-- @tfield DruidEvent on_pressed @{DruidEvent}
|
||||
-- @tfield event on_pressed event
|
||||
|
||||
--- Button trigger node
|
||||
-- @tfield node node
|
||||
@@ -128,20 +128,40 @@
|
||||
---Custom args for any Button event. Setup in Button constructor
|
||||
-- @tfield any params
|
||||
|
||||
--- The @{Hover}: Button Hover component
|
||||
-- @tfield Hover hover @{Hover}
|
||||
--- The Hover: Button Hover component
|
||||
-- @tfield Hover hover Hover
|
||||
|
||||
--- Additional button click area, defined by another GUI node
|
||||
-- @tfield node|nil click_zone
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Button = component.create("button")
|
||||
---Clickable node with various interaction callbacks
|
||||
---@class druid.button: druid.base_component
|
||||
---@field on_click event function(self, custom_args, button_instance)
|
||||
---@field on_pressed event
|
||||
---@field on_repeated_click event
|
||||
---@field on_long_click event
|
||||
---@field on_double_click event
|
||||
---@field on_hold_callback event
|
||||
---@field on_click_outside event
|
||||
---@field node node
|
||||
---@field node_id hash
|
||||
---@field anim_node node
|
||||
---@field params any
|
||||
---@field hover druid.hover
|
||||
---@field click_zone node|nil
|
||||
---@field start_scale vector3
|
||||
---@field start_pos vector3
|
||||
---@field disabled boolean
|
||||
---@field key_trigger hash
|
||||
---@field style table
|
||||
local M = component.create("button")
|
||||
|
||||
|
||||
local function is_input_match(self, action_id)
|
||||
@@ -210,6 +230,7 @@ local function on_button_hold(self, press_time)
|
||||
end
|
||||
|
||||
|
||||
---@param self druid.button
|
||||
local function on_button_release(self)
|
||||
if self.is_repeated_started then
|
||||
return false
|
||||
@@ -234,10 +255,10 @@ local function on_button_release(self)
|
||||
|
||||
local time = socket.gettime()
|
||||
local is_long_click = (time - self.last_pressed_time) >= self.style.LONGTAP_TIME
|
||||
is_long_click = is_long_click and self.on_long_click:is_exist()
|
||||
is_long_click = is_long_click and not self.on_long_click:is_empty()
|
||||
|
||||
local is_double_click = (time - self.last_released_time) < self.style.DOUBLETAP_TIME
|
||||
is_double_click = is_double_click and self.on_double_click:is_exist()
|
||||
is_double_click = is_double_click and not self.on_double_click:is_empty()
|
||||
|
||||
if is_long_click then
|
||||
local is_hold_complete = (time - self.last_pressed_time) >= self.style.AUTOHOLD_TRIGGER
|
||||
@@ -260,18 +281,20 @@ end
|
||||
|
||||
|
||||
--- Component style params.
|
||||
-- You can override this component styles params in Druid styles table
|
||||
-- or create your own style
|
||||
-- @table style
|
||||
-- @tfield number|nil LONGTAP_TIME Minimum time to trigger on_hold_callback. Default: 0.4
|
||||
-- @tfield number|nil AUTOHOLD_TRIGGER Maximum hold time to trigger button release while holding. Default: 0.8
|
||||
-- @tfield number|nil DOUBLETAP_TIME Time between double taps. Default: 0.4
|
||||
-- @tfield function on_click function(self, node)
|
||||
-- @tfield function on_click_disabled function(self, node)
|
||||
-- @tfield function on_hover function(self, node, hover_state)
|
||||
-- @tfield function on_mouse_hover function(self, node, hover_state)
|
||||
-- @tfield function on_set_enabled function(self, node, enabled_state)
|
||||
function Button.on_style_change(self, style)
|
||||
---You can override this component styles params in Druid styles table
|
||||
---or create your own style
|
||||
---@class druid.button.style
|
||||
---@field LONGTAP_TIME number|nil Minimum time to trigger on_hold_callback. Default: 0.4
|
||||
---@field AUTOHOLD_TRIGGER number|nil Maximum hold time to trigger button release while holding. Default: 0.8
|
||||
---@field DOUBLETAP_TIME number|nil Time between double taps. Default: 0.4
|
||||
---@field on_click fun(self, node)|nil
|
||||
---@field on_click_disabled fun(self, node)|nil
|
||||
---@field on_hover fun(self, node, hover_state)|nil
|
||||
---@field on_mouse_hover fun(self, node, hover_state)|nil
|
||||
---@field on_set_enabled fun(self, node, enabled_state)|nil
|
||||
|
||||
---@param style druid.button.style
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.LONGTAP_TIME = style.LONGTAP_TIME or 0.4
|
||||
self.style.AUTOHOLD_TRIGGER = style.AUTOHOLD_TRIGGER or 0.8
|
||||
@@ -285,22 +308,21 @@ function Button.on_style_change(self, style)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Button} constructor
|
||||
-- @tparam Button self @{Button}
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @tparam function callback On click button callback
|
||||
-- @tparam any|nil custom_args Button events custom arguments
|
||||
-- @tparam string|node|nil anim_node Node to animate instead of trigger node.
|
||||
function Button.init(self, node, callback, custom_args, anim_node)
|
||||
---Button constructor
|
||||
---@param node_or_node_id node|string Node name or GUI Node itself.
|
||||
---@param callback fun()|nil Callback on button click
|
||||
---@param custom_args any|nil Custom args for any Button event
|
||||
---@param anim_node node|string|nil Node to animate instead of trigger node.
|
||||
function M:init(node_or_node_id, callback, custom_args, anim_node)
|
||||
self.druid = self:get_druid()
|
||||
self.node = self:get_node(node)
|
||||
self.node = self:get_node(node_or_node_id)
|
||||
self.node_id = gui.get_id(self.node)
|
||||
|
||||
self.anim_node = anim_node and self:get_node(anim_node) or self.node
|
||||
self.start_scale = gui.get_scale(self.anim_node)
|
||||
self.start_pos = gui.get_position(self.anim_node)
|
||||
self.params = custom_args
|
||||
self.hover = self.druid:new_hover(node, on_button_hover)
|
||||
self.hover = self.druid:new_hover(node_or_node_id, on_button_hover)
|
||||
self.hover.on_mouse_hover:subscribe(on_button_mouse_hover)
|
||||
self.click_zone = nil
|
||||
self.is_repeated_started = false
|
||||
@@ -315,18 +337,18 @@ function Button.init(self, node, callback, custom_args, anim_node)
|
||||
self._is_html5_listener_set = false
|
||||
|
||||
-- Events
|
||||
self.on_click = Event(callback)
|
||||
self.on_pressed = Event()
|
||||
self.on_repeated_click = Event()
|
||||
self.on_long_click = Event()
|
||||
self.on_double_click = Event()
|
||||
self.on_hold_callback = Event()
|
||||
self.on_click_outside = Event()
|
||||
self.on_click = event.create(callback)
|
||||
self.on_pressed = event.create()
|
||||
self.on_repeated_click = event.create()
|
||||
self.on_long_click = event.create()
|
||||
self.on_double_click = event.create()
|
||||
self.on_hold_callback = event.create()
|
||||
self.on_click_outside = event.create()
|
||||
end
|
||||
|
||||
|
||||
function Button.on_late_init(self)
|
||||
if not self.click_zone and const.IS_STENCIL_CHECK then
|
||||
function M:on_late_init()
|
||||
if not self.click_zone then
|
||||
local stencil_node = helper.get_closest_stencil_node(self.node)
|
||||
if stencil_node then
|
||||
self:set_click_zone(stencil_node)
|
||||
@@ -335,7 +357,7 @@ function Button.on_late_init(self)
|
||||
end
|
||||
|
||||
|
||||
function Button.on_input(self, action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
if not is_input_match(self, action_id) then
|
||||
return false
|
||||
end
|
||||
@@ -388,7 +410,7 @@ function Button.on_input(self, action_id, action)
|
||||
|
||||
-- While hold button, repeat rate pick from input.repeat_interval
|
||||
if action.repeated then
|
||||
if self.on_repeated_click:is_exist() and self.can_action then
|
||||
if not self.on_repeated_click:is_empty() and self.can_action then
|
||||
on_button_repeated_click(self)
|
||||
return is_consume
|
||||
end
|
||||
@@ -398,7 +420,7 @@ function Button.on_input(self, action_id, action)
|
||||
return on_button_release(self) and is_consume
|
||||
end
|
||||
|
||||
if self.can_action and self.on_long_click:is_exist() then
|
||||
if self.can_action and not self.on_long_click:is_empty() then
|
||||
local press_time = socket.gettime() - self.last_pressed_time
|
||||
|
||||
if self.style.AUTOHOLD_TRIGGER <= press_time then
|
||||
@@ -416,48 +438,19 @@ function Button.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Button.on_input_interrupt(self)
|
||||
function M:on_input_interrupt()
|
||||
self.can_action = false
|
||||
self.hover:set_hover(false)
|
||||
self.hover:set_mouse_hover(false)
|
||||
end
|
||||
|
||||
|
||||
function Button.on_message_input(self, node_id, message)
|
||||
if node_id ~= self.node_id or self.disabled or not gui.is_enabled(self.node) then
|
||||
return false
|
||||
end
|
||||
|
||||
if message.action == const.MESSAGE_INPUT.BUTTON_CLICK then
|
||||
on_button_click(self)
|
||||
end
|
||||
|
||||
if message.action == const.MESSAGE_INPUT.BUTTON_LONG_CLICK then
|
||||
on_button_long_click(self)
|
||||
end
|
||||
|
||||
if message.action == const.MESSAGE_INPUT.BUTTON_DOUBLE_CLICK then
|
||||
on_button_double_click(self)
|
||||
end
|
||||
|
||||
if message.action == const.MESSAGE_INPUT.BUTTON_REPEATED_CLICK then
|
||||
on_button_repeated_click(self)
|
||||
self.is_repeated_started = false
|
||||
self.last_pressed_time = socket.gettime()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set button enabled state.
|
||||
-- The style.on_set_enabled will be triggered.
|
||||
-- Disabled button is not clickable.
|
||||
-- @tparam Button self @{Button}
|
||||
-- @tparam boolean|nil state Enabled state
|
||||
-- @treturn Button Current button instance
|
||||
-- @usage
|
||||
-- button:set_enabled(false)
|
||||
-- button:set_enabled(true)
|
||||
function Button.set_enabled(self, state)
|
||||
---@param state boolean|nil Enabled state
|
||||
---@return druid.button self
|
||||
function M:set_enabled(state)
|
||||
self.disabled = not state
|
||||
self.hover:set_enabled(state)
|
||||
self.style.on_set_enabled(self, self.node, state)
|
||||
@@ -469,11 +462,8 @@ end
|
||||
--- Get button enabled state.
|
||||
--
|
||||
-- By default all Buttons is enabled on creating.
|
||||
-- @tparam Button self @{Button}
|
||||
-- @treturn boolean @True, if button is enabled now, False overwise
|
||||
-- @usage
|
||||
-- local is_enabled = button:is_enabled()
|
||||
function Button.is_enabled(self)
|
||||
---@return boolean @True, if button is enabled now, False overwise
|
||||
function M:is_enabled()
|
||||
return not self.disabled
|
||||
end
|
||||
|
||||
@@ -482,68 +472,61 @@ end
|
||||
-- Useful to restrict click outside out stencil node or scrollable content.
|
||||
--
|
||||
-- This functions calls automatically if you don't disable it in game.project: druid.no_stencil_check
|
||||
-- @tparam Button self @{Button}
|
||||
-- @tparam node|string|nil zone Gui node
|
||||
-- @treturn Button Current button instance
|
||||
-- @usage
|
||||
-- button:set_click_zone("stencil_node")
|
||||
function Button.set_click_zone(self, zone)
|
||||
self.click_zone = self:get_node(zone)
|
||||
---@param zone node|string|nil Gui node
|
||||
---@return druid.button self
|
||||
function M:set_click_zone(zone)
|
||||
self.click_zone = zone and self:get_node(zone) or nil
|
||||
self.hover:set_click_zone(zone)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set key name to trigger this button by keyboard.
|
||||
-- @tparam Button self @{Button}
|
||||
-- @tparam hash|string key The action_id of the input key
|
||||
-- @treturn Button Current button instance
|
||||
-- @usage
|
||||
-- button:set_key_trigger("key_space")
|
||||
function Button.set_key_trigger(self, key)
|
||||
self.key_trigger = hash(key)
|
||||
---Set key name to trigger this button by keyboard.
|
||||
---@param key hash|string The action_id of the input key. Example: "key_space"
|
||||
---@return druid.button self
|
||||
function M:set_key_trigger(key)
|
||||
if type(key) == "string" then
|
||||
self.key_trigger = hash(key)
|
||||
else
|
||||
self.key_trigger = key
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get current key name to trigger this button.
|
||||
-- @tparam Button self
|
||||
-- @treturn hash The action_id of the input key
|
||||
-- @usage
|
||||
-- local key_hash = button:get_key_trigger()
|
||||
function Button.get_key_trigger(self)
|
||||
---@return hash key_trigger The action_id of the input key
|
||||
function M:get_key_trigger()
|
||||
return self.key_trigger
|
||||
end
|
||||
|
||||
|
||||
--- Set function for additional check for button click availability
|
||||
-- @tparam Button self
|
||||
-- @tparam function|nil check_function Should return true or false. If true - button can be pressed.
|
||||
-- @tparam function|nil failure_callback Function will be called on button click, if check function return false
|
||||
-- @treturn Button Current button instance
|
||||
function Button.set_check_function(self, check_function, failure_callback)
|
||||
---Set function for additional check for button click availability
|
||||
---@param check_function function|nil Should return true or false. If true - button can be pressed.
|
||||
---@param failure_callback function|nil Function will be called on button click, if check function return false
|
||||
---@return druid.button self
|
||||
function M:set_check_function(check_function, failure_callback)
|
||||
self._check_function = check_function
|
||||
self._failure_callback = failure_callback
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set Button mode to work inside user HTML5 interaction event.
|
||||
--
|
||||
-- It's required to make protected things like copy & paste text, show mobile keyboard, etc
|
||||
-- The HTML5 button's doesn't call any events except on_click event.
|
||||
--
|
||||
-- If the game is not HTML, html mode will be not enabled
|
||||
-- @tparam Button self
|
||||
-- @tparam boolean|nil is_web_mode If true - button will be called inside html5 callback
|
||||
-- @treturn Button Current button instance
|
||||
-- @usage
|
||||
-- button:set_web_user_interaction(true)
|
||||
function Button.set_web_user_interaction(self, is_web_mode)
|
||||
---Set Button mode to work inside user HTML5 interaction event.
|
||||
---
|
||||
---It's required to make protected things like copy & paste text, show mobile keyboard, etc
|
||||
---The HTML5 button's doesn't call any events except on_click event.
|
||||
---
|
||||
---If the game is not HTML, html mode will be not enabled
|
||||
---@param is_web_mode boolean|nil If true - button will be called inside html5 callback
|
||||
---@return druid.button self
|
||||
function M:set_web_user_interaction(is_web_mode)
|
||||
self._is_html5_mode = not not (is_web_mode and html5)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return Button
|
||||
return M
|
||||
|
||||
@@ -14,19 +14,19 @@
|
||||
-- @tfield node node
|
||||
|
||||
--- Event on touch start callback(self)
|
||||
-- @tfield DruidEvent on_touch_start @{DruidEvent}
|
||||
-- @tfield event on_touch_start event
|
||||
|
||||
--- Event on touch end callback(self)
|
||||
-- @tfield DruidEvent on_touch_end @{DruidEvent}
|
||||
-- @tfield event on_touch_end event
|
||||
|
||||
--- Event on drag start callback(self, touch)
|
||||
-- @tfield DruidEvent on_drag_start @{DruidEvent}
|
||||
-- @tfield event on_drag_start event
|
||||
|
||||
--- on drag progress callback(self, dx, dy, total_x, total_y, touch)
|
||||
-- @tfield DruidEvent on_drag Event @{DruidEvent}
|
||||
-- @tfield event on_drag Event event
|
||||
|
||||
--- Event on drag end callback(self, total_x, total_y, touch)
|
||||
-- @tfield DruidEvent on_drag_end @{DruidEvent}
|
||||
-- @tfield event on_drag_end event
|
||||
|
||||
--- Is component now touching
|
||||
-- @tfield boolean is_touch
|
||||
@@ -57,12 +57,40 @@
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Drag = component.create("drag", const.PRIORITY_INPUT_HIGH)
|
||||
---@class druid.drag.style
|
||||
---@field DRAG_DEADZONE number Distance in pixels to start dragging. Default: 10
|
||||
---@field NO_USE_SCREEN_KOEF boolean If screen aspect ratio affects on drag values. Default: false
|
||||
|
||||
---@class druid.drag: druid.base_component
|
||||
---@field node node
|
||||
---@field on_touch_start event
|
||||
---@field on_touch_end event
|
||||
---@field on_drag_start event
|
||||
---@field on_drag event
|
||||
---@field on_drag_end event
|
||||
---@field style druid.drag.style
|
||||
---@field click_zone node|nil
|
||||
---@field is_touch boolean
|
||||
---@field is_drag boolean
|
||||
---@field can_x boolean
|
||||
---@field can_y boolean
|
||||
---@field dx number
|
||||
---@field dy number
|
||||
---@field touch_id number
|
||||
---@field x number
|
||||
---@field y number
|
||||
---@field screen_x number
|
||||
---@field screen_y number
|
||||
---@field touch_start_pos vector3
|
||||
---@field private _is_enabled boolean
|
||||
---@field private _x_koef number
|
||||
---@field private _y_koef number
|
||||
local M = component.create("drag", const.PRIORITY_INPUT_HIGH)
|
||||
|
||||
|
||||
local function start_touch(self, touch)
|
||||
@@ -177,19 +205,21 @@ end
|
||||
-- @table style
|
||||
-- @tfield number|nil DRAG_DEADZONE Distance in pixels to start dragging. Default: 10
|
||||
-- @tfield boolean|nil NO_USE_SCREEN_KOEF If screen aspect ratio affects on drag values. Default: false
|
||||
function Drag.on_style_change(self, style)
|
||||
self.style = {}
|
||||
self.style.DRAG_DEADZONE = style.DRAG_DEADZONE or 10
|
||||
self.style.NO_USE_SCREEN_KOEF = style.NO_USE_SCREEN_KOEF or false
|
||||
function M:on_style_change(style)
|
||||
self.style = {
|
||||
DRAG_DEADZONE = style.DRAG_DEADZONE or 10,
|
||||
NO_USE_SCREEN_KOEF = style.NO_USE_SCREEN_KOEF or false,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
--- The @{Drag} constructor
|
||||
-- @tparam Drag self @{Drag}
|
||||
-- @tparam node node GUI node to detect dragging
|
||||
-- @tparam function on_drag_callback Callback for on_drag_event(self, dx, dy)
|
||||
function Drag.init(self, node, on_drag_callback)
|
||||
self.node = self:get_node(node)
|
||||
---Drag constructor
|
||||
---@param node_or_node_id node|string
|
||||
---@param on_drag_callback function
|
||||
function M:init(node_or_node_id, on_drag_callback)
|
||||
self.druid = self:get_druid()
|
||||
self.node = self:get_node(node_or_node_id)
|
||||
self.hover = self.druid:new_hover(self.node)
|
||||
|
||||
self.dx = 0
|
||||
self.dy = 0
|
||||
@@ -209,18 +239,32 @@ function Drag.init(self, node, on_drag_callback)
|
||||
self._scene_scale = helper.get_scene_scale(self.node)
|
||||
|
||||
self.click_zone = nil
|
||||
self.on_touch_start = Event()
|
||||
self.on_touch_end = Event()
|
||||
self.on_drag_start = Event()
|
||||
self.on_drag = Event(on_drag_callback)
|
||||
self.on_drag_end = Event()
|
||||
self.on_touch_start = event.create()
|
||||
self.on_touch_end = event.create()
|
||||
self.on_drag_start = event.create()
|
||||
self.on_drag = event.create(on_drag_callback)
|
||||
self.on_drag_end = event.create()
|
||||
|
||||
self:on_window_resized()
|
||||
self:set_drag_cursors(true)
|
||||
end
|
||||
|
||||
|
||||
function Drag.on_late_init(self)
|
||||
if not self.click_zone and const.IS_STENCIL_CHECK then
|
||||
---Set Drag component enabled state.
|
||||
---@param is_enabled boolean
|
||||
function M:set_drag_cursors(is_enabled)
|
||||
if defos and is_enabled then
|
||||
self.hover.style.ON_HOVER_CURSOR = defos.CURSOR_CROSSHAIR
|
||||
self.hover.style.ON_MOUSE_HOVER_CURSOR = defos.CURSOR_HAND
|
||||
else
|
||||
self.hover.style.ON_HOVER_CURSOR = nil
|
||||
self.hover.style.ON_MOUSE_HOVER_CURSOR = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:on_late_init()
|
||||
if not self.click_zone then
|
||||
local stencil_node = helper.get_closest_stencil_node(self.node)
|
||||
if stencil_node then
|
||||
self:set_click_zone(stencil_node)
|
||||
@@ -229,7 +273,7 @@ function Drag.on_late_init(self)
|
||||
end
|
||||
|
||||
|
||||
function Drag.on_window_resized(self)
|
||||
function M:on_window_resized()
|
||||
local x_koef, y_koef = helper.get_screen_aspect_koef()
|
||||
self._x_koef = x_koef
|
||||
self._y_koef = y_koef
|
||||
@@ -237,14 +281,17 @@ function Drag.on_window_resized(self)
|
||||
end
|
||||
|
||||
|
||||
function Drag.on_input_interrupt(self)
|
||||
function M:on_input_interrupt()
|
||||
if self.is_drag or self.is_touch then
|
||||
end_touch(self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Drag.on_input(self, action_id, action)
|
||||
---@local
|
||||
---@param action_id string
|
||||
---@param action table
|
||||
function M:on_input(action_id, action)
|
||||
if action_id ~= const.ACTION_TOUCH and action_id ~= const.ACTION_MULTITOUCH then
|
||||
return false
|
||||
end
|
||||
@@ -321,29 +368,31 @@ function Drag.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
--- Strict drag click area. Useful for
|
||||
-- restrict events outside stencil node
|
||||
-- @tparam Drag self @{Drag}
|
||||
-- @tparam node|string|nil node Gui node
|
||||
function Drag.set_click_zone(self, node)
|
||||
self.click_zone = self:get_node(node)
|
||||
---Set Drag click zone
|
||||
---@param node node|string|nil
|
||||
---@return druid.drag self Current instance
|
||||
function M:set_click_zone(node)
|
||||
self.click_zone = node and self:get_node(node) or nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set Drag input enabled or disabled
|
||||
-- @tparam Drag self @{Drag}
|
||||
-- @tparam boolean|nil is_enabled
|
||||
function Drag.set_enabled(self, is_enabled)
|
||||
---Set Drag component enabled state.
|
||||
---@param is_enabled boolean
|
||||
---@return druid.drag self Current instance
|
||||
function M:set_enabled(is_enabled)
|
||||
self._is_enabled = is_enabled
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Check if Drag component is enabled
|
||||
-- @tparam Drag self @{Drag}
|
||||
-- @treturn boolean
|
||||
function Drag.is_enabled(self)
|
||||
---Check if Drag component is enabled
|
||||
---@return boolean
|
||||
function M:is_enabled()
|
||||
return self._is_enabled
|
||||
end
|
||||
|
||||
|
||||
return Drag
|
||||
return M
|
||||
|
||||
@@ -9,27 +9,36 @@
|
||||
-- @tfield node node
|
||||
|
||||
--- On hover callback(self, state, hover_instance)
|
||||
-- @tfield DruidEvent on_hover @{DruidEvent}
|
||||
-- @tfield event on_hover event
|
||||
|
||||
--- On mouse hover callback(self, state, hover_instance)
|
||||
-- @tfield DruidEvent on_mouse_hover @{DruidEvent}
|
||||
-- @tfield event on_mouse_hover event
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Hover = component.create("hover")
|
||||
---@class druid.hover: druid.base_component
|
||||
---@field node node
|
||||
---@field on_hover event
|
||||
---@field on_mouse_hover event
|
||||
---@field style table
|
||||
---@field click_zone node
|
||||
---@field private _is_hovered boolean|nil
|
||||
---@field private _is_mouse_hovered boolean|nil
|
||||
---@field private _is_enabled boolean|nil
|
||||
---@field private _is_mobile boolean
|
||||
local M = component.create("hover")
|
||||
|
||||
|
||||
--- The @{Hover} constructor
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @tparam node node Gui node
|
||||
-- @tparam function on_hover_callback Hover callback
|
||||
-- @tparam function on_mouse_hover On mouse hover callback
|
||||
function Hover.init(self, node, on_hover_callback, on_mouse_hover)
|
||||
--- The Hover constructor
|
||||
---@param node node Gui node
|
||||
---@param on_hover_callback function Hover callback
|
||||
---@param on_mouse_hover function On mouse hover callback
|
||||
function M:init(node, on_hover_callback, on_mouse_hover)
|
||||
self.node = self:get_node(node)
|
||||
|
||||
self._is_hovered = false
|
||||
@@ -37,13 +46,13 @@ function Hover.init(self, node, on_hover_callback, on_mouse_hover)
|
||||
self._is_enabled = true
|
||||
self._is_mobile = helper.is_mobile()
|
||||
|
||||
self.on_hover = Event(on_hover_callback)
|
||||
self.on_mouse_hover = Event(on_mouse_hover)
|
||||
self.on_hover = event.create(on_hover_callback)
|
||||
self.on_mouse_hover = event.create(on_mouse_hover)
|
||||
end
|
||||
|
||||
|
||||
function Hover.on_late_init(self)
|
||||
if not self.click_zone and const.IS_STENCIL_CHECK then
|
||||
function M:on_late_init()
|
||||
if not self.click_zone then
|
||||
local stencil_node = helper.get_closest_stencil_node(self.node)
|
||||
if stencil_node then
|
||||
self:set_click_zone(stencil_node)
|
||||
@@ -58,14 +67,14 @@ end
|
||||
-- @table style
|
||||
-- @tfield[opt] string ON_HOVER_CURSOR Mouse hover style on node hover
|
||||
-- @tfield[opt] string ON_MOUSE_HOVER_CURSOR Mouse hover style on node mouse hover
|
||||
function Hover.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.ON_HOVER_CURSOR = style.ON_HOVER_CURSOR or nil
|
||||
self.style.ON_MOUSE_HOVER_CURSOR = style.ON_MOUSE_HOVER_CURSOR or nil
|
||||
end
|
||||
|
||||
|
||||
function Hover.on_input(self, action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
if action_id ~= const.ACTION_TOUCH and action_id ~= nil then
|
||||
return false
|
||||
end
|
||||
@@ -99,15 +108,14 @@ function Hover.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Hover.on_input_interrupt(self)
|
||||
function M:on_input_interrupt()
|
||||
self:set_hover(false)
|
||||
end
|
||||
|
||||
|
||||
--- Set hover state
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @tparam boolean|nil state The hover state
|
||||
function Hover.set_hover(self, state)
|
||||
---@param state boolean|nil The hover state
|
||||
function M:set_hover(state)
|
||||
if self._is_hovered == state then
|
||||
return
|
||||
end
|
||||
@@ -122,17 +130,15 @@ end
|
||||
|
||||
|
||||
--- Return current hover state. True if touch action was on the node at current time
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @treturn boolean The current hovered state
|
||||
function Hover.is_hovered(self)
|
||||
---@return boolean The current hovered state
|
||||
function M:is_hovered()
|
||||
return self._is_hovered
|
||||
end
|
||||
|
||||
|
||||
--- Set mouse hover state
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @tparam boolean|nil state The mouse hover state
|
||||
function Hover.set_mouse_hover(self, state)
|
||||
---@param state boolean|nil The mouse hover state
|
||||
function M:set_mouse_hover(state)
|
||||
if self._is_mouse_hovered == state then
|
||||
return
|
||||
end
|
||||
@@ -147,18 +153,21 @@ end
|
||||
|
||||
|
||||
--- Return current hover state. True if nil action_id (usually desktop mouse) was on the node at current time
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @treturn boolean The current hovered state
|
||||
function Hover.is_mouse_hovered(self)
|
||||
---@return boolean The current hovered state
|
||||
function M:is_mouse_hovered()
|
||||
return self._is_mouse_hovered
|
||||
end
|
||||
|
||||
|
||||
--- Strict hover click area. Useful for
|
||||
-- no click events outside stencil node
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @tparam node|string|nil zone Gui node
|
||||
function Hover.set_click_zone(self, zone)
|
||||
---@param zone node|string|nil Gui node
|
||||
function M:set_click_zone(zone)
|
||||
if not zone then
|
||||
self.click_zone = nil
|
||||
return
|
||||
end
|
||||
|
||||
self.click_zone = self:get_node(zone)
|
||||
end
|
||||
|
||||
@@ -166,9 +175,8 @@ end
|
||||
--- Set enable state of hover component.
|
||||
-- If hover is not enabled, it will not generate
|
||||
-- any hover events
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @tparam boolean|nil state The hover enabled state
|
||||
function Hover.set_enabled(self, state)
|
||||
---@param state boolean|nil The hover enabled state
|
||||
function M:set_enabled(state)
|
||||
self._is_enabled = state
|
||||
|
||||
if not state then
|
||||
@@ -183,16 +191,16 @@ end
|
||||
|
||||
|
||||
--- Return current hover enabled state
|
||||
-- @tparam Hover self @{Hover}
|
||||
-- @treturn boolean The hover enabled state
|
||||
function Hover.is_enabled(self)
|
||||
---@return boolean The hover enabled state
|
||||
function M:is_enabled()
|
||||
return self._is_enabled
|
||||
end
|
||||
|
||||
|
||||
-- Internal cursor stack
|
||||
local cursor_stack = {}
|
||||
function Hover:_set_cursor(priority, cursor)
|
||||
---@local
|
||||
function M:_set_cursor(priority, cursor)
|
||||
if not defos then
|
||||
return
|
||||
end
|
||||
@@ -217,4 +225,4 @@ function Hover:_set_cursor(priority, cursor)
|
||||
end
|
||||
|
||||
|
||||
return Hover
|
||||
return M
|
||||
|
||||
@@ -39,13 +39,13 @@
|
||||
|
||||
|
||||
--- On scroll move callback(self, position)
|
||||
-- @tfield DruidEvent on_scroll @{DruidEvent}
|
||||
-- @tfield event on_scroll event
|
||||
|
||||
--- On scroll_to function callback(self, target, is_instant)
|
||||
-- @tfield DruidEvent on_scroll_to @{DruidEvent}
|
||||
-- @tfield event on_scroll_to event
|
||||
|
||||
--- On scroll_to_index function callback(self, index, point)
|
||||
-- @tfield DruidEvent on_point_scroll @{DruidEvent}
|
||||
-- @tfield event on_point_scroll event
|
||||
|
||||
--- Scroll view node
|
||||
-- @tfield node view_node
|
||||
@@ -75,7 +75,7 @@
|
||||
-- @tfield vector3 available_size
|
||||
|
||||
--- Drag Druid component
|
||||
-- @tfield Drag drag @{Drag}
|
||||
-- @tfield Drag drag Drag
|
||||
|
||||
--- Current index of points of interests
|
||||
-- @tfield number|nil selected
|
||||
@@ -85,12 +85,37 @@
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Scroll = component.create("scroll")
|
||||
---@class druid.scroll: druid.base_component
|
||||
---@field node node The root node
|
||||
---@field click_zone node|nil Optional click zone to restrict scroll area
|
||||
---@field on_scroll event Triggered on scroll move with (self, position)
|
||||
---@field on_scroll_to event Triggered on scroll_to with (self, target, is_instant)
|
||||
---@field on_point_scroll event Triggered on scroll_to_index with (self, index, point)
|
||||
---@field view_node node The scroll view node (static part)
|
||||
---@field view_border vector4 The scroll view borders
|
||||
---@field content_node node The scroll content node (moving part)
|
||||
---@field view_size vector3 Size of the view node
|
||||
---@field position vector3 Current scroll position
|
||||
---@field target_position vector3 Target scroll position for animations
|
||||
---@field available_pos vector4 Available content position (min_x, max_y, max_x, min_y)
|
||||
---@field available_size vector3 Size of available positions (width, height, 0)
|
||||
---@field drag druid.drag The drag component instance
|
||||
---@field selected number|nil Current selected point of interest index
|
||||
---@field is_animate boolean True if scroll is animating
|
||||
---@field private _is_inert boolean True if inertial scrolling is enabled
|
||||
---@field private inertion vector3 Current inertial movement vector
|
||||
---@field private _is_horizontal_scroll boolean True if horizontal scroll enabled
|
||||
---@field private _is_vertical_scroll boolean True if vertical scroll enabled
|
||||
---@field private _grid_on_change event Grid items change event
|
||||
---@field private _grid_on_change_callback function Grid change callback
|
||||
---@field private _offset vector3 Content start offset
|
||||
---@field private style table Component style parameters
|
||||
local M = component.create("scroll")
|
||||
|
||||
|
||||
local function inverse_lerp(min, max, current)
|
||||
@@ -138,7 +163,7 @@ end
|
||||
-- @tfield boolean|nil WHEEL_SCROLL_SPEED The scroll speed via mouse wheel scroll or touchpad. Set to 0 to disable wheel scrolling. Default: 0
|
||||
-- @tfield boolean|nil WHEEL_SCROLL_INVERTED If true, invert direction for touchpad and mouse wheel scroll. Default: false
|
||||
-- @tfield boolean|nil WHEEL_SCROLL_BY_INERTION If true, wheel will add inertion to scroll. Direct set position otherwise.. Default: false
|
||||
function Scroll.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.EXTRA_STRETCH_SIZE = style.EXTRA_STRETCH_SIZE or 0
|
||||
self.style.ANIM_SPEED = style.ANIM_SPEED or 0.2
|
||||
@@ -161,11 +186,10 @@ function Scroll.on_style_change(self, style)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Scroll} constructor
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam string|node view_node GUI view scroll node
|
||||
-- @tparam string|node content_node GUI content scroll node
|
||||
function Scroll.init(self, view_node, content_node)
|
||||
--- The Scroll constructor
|
||||
---@param view_node string|node GUI view scroll node
|
||||
---@param content_node string|node GUI content scroll node
|
||||
function M:init(view_node, content_node)
|
||||
self.druid = self:get_druid()
|
||||
|
||||
self.view_node = self:get_node(view_node)
|
||||
@@ -186,9 +210,9 @@ function Scroll.init(self, view_node, content_node)
|
||||
self.hover.on_mouse_hover:subscribe(self._on_mouse_hover)
|
||||
self._is_mouse_hover = false
|
||||
|
||||
self.on_scroll = Event()
|
||||
self.on_scroll_to = Event()
|
||||
self.on_point_scroll = Event()
|
||||
self.on_scroll = event.create()
|
||||
self.on_scroll_to = event.create()
|
||||
self.on_point_scroll = event.create()
|
||||
|
||||
self.selected = nil
|
||||
self.is_animate = false
|
||||
@@ -203,8 +227,8 @@ function Scroll.init(self, view_node, content_node)
|
||||
end
|
||||
|
||||
|
||||
function Scroll.on_late_init(self)
|
||||
if not self.click_zone and const.IS_STENCIL_CHECK then
|
||||
function M:on_late_init()
|
||||
if not self.click_zone then
|
||||
local stencil_node = helper.get_closest_stencil_node(self.node)
|
||||
if stencil_node then
|
||||
self:set_click_zone(stencil_node)
|
||||
@@ -213,15 +237,15 @@ function Scroll.on_late_init(self)
|
||||
end
|
||||
|
||||
|
||||
function Scroll.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
gui.set_position(self.content_node, self.position)
|
||||
end
|
||||
|
||||
|
||||
function Scroll.update(self, dt)
|
||||
function M:update(dt)
|
||||
if self.is_animate then
|
||||
self.position.x = gui.get(self.content_node, "position.x")
|
||||
self.position.y = gui.get(self.content_node, "position.y")
|
||||
self.position.x = gui.get(self.content_node, "position.x") --[[@as number]]
|
||||
self.position.y = gui.get(self.content_node, "position.y") --[[@as number]]
|
||||
self.on_scroll:trigger(self:get_context(), self.position)
|
||||
end
|
||||
|
||||
@@ -233,23 +257,22 @@ function Scroll.update(self, dt)
|
||||
end
|
||||
|
||||
|
||||
function Scroll.on_input(self, action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
return self:_process_scroll_wheel(action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Scroll.on_remove(self)
|
||||
function M:on_remove()
|
||||
self:bind_grid(nil)
|
||||
end
|
||||
|
||||
|
||||
--- Start scroll to target point.
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam vector3 point Target point
|
||||
-- @tparam boolean|nil is_instant Instant scroll flag
|
||||
---@param point vector3 Target point
|
||||
---@param is_instant boolean|nil Instant scroll flag
|
||||
-- @usage scroll:scroll_to(vmath.vector3(0, 50, 0))
|
||||
-- @usage scroll:scroll_to(vmath.vector3(0), true)
|
||||
function Scroll.scroll_to(self, point, is_instant)
|
||||
function M:scroll_to(point, is_instant)
|
||||
local b = self.available_pos
|
||||
local target = vmath.vector3(
|
||||
self._is_horizontal_scroll and -point.x or self.target_position.x,
|
||||
@@ -278,10 +301,9 @@ end
|
||||
|
||||
|
||||
--- Scroll to item in scroll by point index.
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam number index Point index
|
||||
-- @tparam boolean|nil skip_cb If true, skip the point callback
|
||||
function Scroll.scroll_to_index(self, index, skip_cb)
|
||||
---@param index number Point index
|
||||
---@param skip_cb boolean|nil If true, skip the point callback
|
||||
function M:scroll_to_index(index, skip_cb)
|
||||
if not self.points then
|
||||
return
|
||||
end
|
||||
@@ -301,11 +323,10 @@ end
|
||||
|
||||
|
||||
--- Start scroll to target scroll percent
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam vector3 percent target percent
|
||||
-- @tparam boolean|nil is_instant instant scroll flag
|
||||
---@param percent vector3 target percent
|
||||
---@param is_instant boolean|nil instant scroll flag
|
||||
-- @usage scroll:scroll_to_percent(vmath.vector3(0.5, 0, 0))
|
||||
function Scroll.scroll_to_percent(self, percent, is_instant)
|
||||
function M:scroll_to_percent(percent, is_instant)
|
||||
local border = self.available_pos
|
||||
|
||||
local pos = vmath.vector3(
|
||||
@@ -327,9 +348,8 @@ end
|
||||
|
||||
--- Return current scroll progress status.
|
||||
-- Values will be in [0..1] interval
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @treturn vector3 New vector with scroll progress values
|
||||
function Scroll.get_percent(self)
|
||||
---@return vector3 New vector with scroll progress values
|
||||
function M:get_percent()
|
||||
local x_perc = 1 - inverse_lerp(self.available_pos.x, self.available_pos.z, self.position.x)
|
||||
local y_perc = inverse_lerp(self.available_pos.w, self.available_pos.y, self.position.y)
|
||||
|
||||
@@ -339,11 +359,10 @@ end
|
||||
|
||||
--- Set scroll content size.
|
||||
-- It will change content gui node size
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam vector3 size The new size for content node
|
||||
-- @tparam vector3|nil offset Offset value to set, where content is starts
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_size(self, size, offset)
|
||||
---@param size vector3 The new size for content node
|
||||
---@param offset vector3|nil Offset value to set, where content is starts
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_size(size, offset)
|
||||
if offset then
|
||||
self._offset = offset
|
||||
end
|
||||
@@ -355,10 +374,9 @@ end
|
||||
|
||||
|
||||
--- Set new scroll view size in case the node size was changed.
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam vector3 size The new size for view node
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_view_size(self, size)
|
||||
---@param size vector3 The new size for view node
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_view_size(size)
|
||||
gui.set_size(self.view_node, size)
|
||||
self.view_size = size
|
||||
self.view_border = helper.get_border(self.view_node)
|
||||
@@ -369,8 +387,7 @@ end
|
||||
|
||||
|
||||
--- Refresh scroll view size
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
function Scroll.update_view_size(self)
|
||||
function M:update_view_size()
|
||||
self.view_size = helper.get_scaled_size(self.view_node)
|
||||
self.view_border = helper.get_border(self.view_node)
|
||||
self:_update_size()
|
||||
@@ -382,10 +399,9 @@ end
|
||||
--- Enable or disable scroll inert.
|
||||
-- If disabled, scroll through points (if exist)
|
||||
-- If no points, just simple drag without inertion
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam boolean|nil state Inert scroll state
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_inert(self, state)
|
||||
---@param state boolean Inert scroll state
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_inert(state)
|
||||
self._is_inert = state
|
||||
|
||||
return self
|
||||
@@ -393,19 +409,17 @@ end
|
||||
|
||||
|
||||
--- Return if scroll have inertion.
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @treturn boolean @If scroll have inertion
|
||||
function Scroll.is_inert(self)
|
||||
---@return boolean @If scroll have inertion
|
||||
function M:is_inert()
|
||||
return self._is_inert
|
||||
end
|
||||
|
||||
|
||||
--- Set extra size for scroll stretching.
|
||||
-- Set 0 to disable stretching effect
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam number|nil stretch_size Size in pixels of additional scroll area
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_extra_stretch_size(self, stretch_size)
|
||||
---@param stretch_size number|nil Size in pixels of additional scroll area
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_extra_stretch_size(stretch_size)
|
||||
self.style.EXTRA_STRETCH_SIZE = stretch_size or 0
|
||||
self:_update_size()
|
||||
|
||||
@@ -414,19 +428,17 @@ end
|
||||
|
||||
|
||||
--- Return vector of scroll size with width and height.
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @treturn vector3 Available scroll size
|
||||
function Scroll.get_scroll_size(self)
|
||||
---@return vector3 Available scroll size
|
||||
function M:get_scroll_size()
|
||||
return self.available_size
|
||||
end
|
||||
|
||||
|
||||
--- Set points of interest.
|
||||
-- Scroll will always centered on closer points
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam table points Array of vector3 points
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_points(self, points)
|
||||
---@param points table Array of vector3 points
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_points(points)
|
||||
self.points = points
|
||||
|
||||
table.sort(self.points, function(a, b)
|
||||
@@ -440,33 +452,30 @@ end
|
||||
|
||||
|
||||
--- Lock or unlock horizontal scroll
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam boolean|nil state True, if horizontal scroll is enabled
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_horizontal_scroll(self, state)
|
||||
---@param state boolean True, if horizontal scroll is enabled
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_horizontal_scroll(state)
|
||||
self._is_horizontal_scroll = state
|
||||
self.drag.can_x = self.available_size.x > 0 and state
|
||||
self.drag.can_x = self.available_size.x > 0 and state or false
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Lock or unlock vertical scroll
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam boolean|nil state True, if vertical scroll is enabled
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.set_vertical_scroll(self, state)
|
||||
---@param state boolean True, if vertical scroll is enabled
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:set_vertical_scroll(state)
|
||||
self._is_vertical_scroll = state
|
||||
self.drag.can_y = self.available_size.y > 0 and state
|
||||
self.drag.can_y = self.available_size.y > 0 and state or false
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Check node if it visible now on scroll.
|
||||
-- Extra border is not affected. Return true for elements in extra scroll zone
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam node node The node to check
|
||||
-- @treturn boolean True if node in visible scroll area
|
||||
function Scroll.is_node_in_view(self, node)
|
||||
---@param node node The node to check
|
||||
---@return boolean True if node in visible scroll area
|
||||
function M:is_node_in_view(node)
|
||||
local node_offset_for_view = gui.get_position(node)
|
||||
local parent = gui.get_parent(node)
|
||||
local is_parent_of_view = false
|
||||
@@ -504,10 +513,9 @@ end
|
||||
|
||||
--- Bind the grid component (Static or Dynamic) to recalculate
|
||||
-- scroll size on grid changes
|
||||
-- @tparam Scroll self @{Scroll}
|
||||
-- @tparam StaticGrid grid Druid grid component
|
||||
-- @treturn druid.scroll Current scroll instance
|
||||
function Scroll.bind_grid(self, grid)
|
||||
---@param grid druid.grid|nil Druid grid component
|
||||
---@return druid.scroll Current scroll instance
|
||||
function M:bind_grid(grid)
|
||||
if self._grid_on_change then
|
||||
self._grid_on_change:unsubscribe(self._grid_on_change_callback)
|
||||
|
||||
@@ -516,15 +524,16 @@ function Scroll.bind_grid(self, grid)
|
||||
end
|
||||
|
||||
if not grid then
|
||||
return
|
||||
return self
|
||||
end
|
||||
|
||||
self._grid_on_change = grid.on_change_items
|
||||
self._grid_on_change_callback = self._grid_on_change:subscribe(function()
|
||||
self._grid_on_change_callback = function()
|
||||
local size = grid:get_size()
|
||||
local offset = grid:get_offset()
|
||||
self:set_size(size, offset)
|
||||
end)
|
||||
end
|
||||
self._grid_on_change:subscribe(self._grid_on_change_callback)
|
||||
self:set_size(grid:get_size(), grid:get_offset())
|
||||
|
||||
return self
|
||||
@@ -533,14 +542,13 @@ end
|
||||
|
||||
--- Strict drag scroll area. Useful for
|
||||
-- restrict events outside stencil node
|
||||
-- @tparam Drag self
|
||||
-- @tparam node|string node Gui node
|
||||
function Scroll.set_click_zone(self, node)
|
||||
---@param node node|string Gui node
|
||||
function M:set_click_zone(node)
|
||||
self.drag:set_click_zone(node)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._on_scroll_drag(self, dx, dy)
|
||||
function M:_on_scroll_drag(dx, dy)
|
||||
local t = self.target_position
|
||||
local b = self.available_pos
|
||||
local eb = self.available_pos_extra
|
||||
@@ -581,7 +589,7 @@ function Scroll._on_scroll_drag(self, dx, dy)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._check_soft_zone(self)
|
||||
function M:_check_soft_zone()
|
||||
local target = self.target_position
|
||||
local border = self.available_pos
|
||||
local speed = self.style.BACK_SPEED
|
||||
@@ -610,7 +618,7 @@ end
|
||||
|
||||
|
||||
-- Cancel animation on other animation or input touch
|
||||
function Scroll._cancel_animate(self)
|
||||
function M:_cancel_animate()
|
||||
self.inertion.x = 0
|
||||
self.inertion.y = 0
|
||||
|
||||
@@ -624,7 +632,7 @@ function Scroll._cancel_animate(self)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._set_scroll_position(self, position_x, position_y)
|
||||
function M:_set_scroll_position(position_x, position_y)
|
||||
local available_extra = self.available_pos_extra
|
||||
position_x = helper.clamp(position_x, available_extra.x, available_extra.z)
|
||||
position_y = helper.clamp(position_y, available_extra.w, available_extra.y)
|
||||
@@ -642,8 +650,8 @@ end
|
||||
--- Find closer point of interest
|
||||
-- if no inert, scroll to next point by scroll direction
|
||||
-- if inert, find next point by scroll director
|
||||
-- @local
|
||||
function Scroll._check_points(self)
|
||||
---@private
|
||||
function M:_check_points()
|
||||
if not self.points then
|
||||
return
|
||||
end
|
||||
@@ -699,7 +707,7 @@ function Scroll._check_points(self)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._check_threshold(self)
|
||||
function M:_check_threshold()
|
||||
local is_stopped = false
|
||||
|
||||
if self.drag.can_x and math.abs(self.inertion.x) < self.style.INERT_THRESHOLD then
|
||||
@@ -717,7 +725,7 @@ function Scroll._check_threshold(self)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._update_free_scroll(self, dt)
|
||||
function M:_update_free_scroll(dt)
|
||||
if self.is_animate then
|
||||
return
|
||||
end
|
||||
@@ -742,7 +750,7 @@ function Scroll._update_free_scroll(self, dt)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._update_hand_scroll(self, dt)
|
||||
function M:_update_hand_scroll(dt)
|
||||
if self.is_animate then
|
||||
self:_cancel_animate()
|
||||
end
|
||||
@@ -757,7 +765,7 @@ function Scroll._update_hand_scroll(self, dt)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._on_touch_start(self)
|
||||
function M:_on_touch_start()
|
||||
self.inertion.x = 0
|
||||
self.inertion.y = 0
|
||||
self.target_position.x = self.position.x
|
||||
@@ -765,12 +773,12 @@ function Scroll._on_touch_start(self)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._on_touch_end(self)
|
||||
function M:_on_touch_end()
|
||||
self:_check_threshold()
|
||||
end
|
||||
|
||||
|
||||
function Scroll._update_size(self)
|
||||
function M:_update_size()
|
||||
local content_border = helper.get_border(self.content_node)
|
||||
local content_size = helper.get_scaled_size(self.content_node)
|
||||
|
||||
@@ -805,10 +813,12 @@ function Scroll._update_size(self)
|
||||
self:_set_scroll_position(self.position.x, self.position.y)
|
||||
self.target_position.x = self.position.x
|
||||
self.target_position.y = self.position.y
|
||||
|
||||
self.drag:set_drag_cursors(self.drag.can_x or self.drag.can_y)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._process_scroll_wheel(self, action_id, action)
|
||||
function M:_process_scroll_wheel(action_id, action)
|
||||
if not self._is_mouse_hover or self.style.WHEEL_SCROLL_SPEED == 0 then
|
||||
return false
|
||||
end
|
||||
@@ -845,9 +855,9 @@ function Scroll._process_scroll_wheel(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Scroll._on_mouse_hover(self, state)
|
||||
function M:_on_mouse_hover(state)
|
||||
self._is_mouse_hover = state
|
||||
end
|
||||
|
||||
|
||||
return Scroll
|
||||
return M
|
||||
|
||||
@@ -34,22 +34,22 @@
|
||||
-- <a href="https://insality.github.io/druid/druid/index.html?example=general_grid" target="_blank"><b>Example Link</b></a>
|
||||
-- @module StaticGrid
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.static_grid
|
||||
-- @alias druid.grid
|
||||
|
||||
--- On item add callback(self, node, index)
|
||||
-- @tfield DruidEvent on_add_item @{DruidEvent}
|
||||
-- @tfield event on_add_item event
|
||||
|
||||
--- On item remove callback(self, index)
|
||||
-- @tfield DruidEvent on_remove_item @{DruidEvent}
|
||||
-- @tfield event on_remove_item event
|
||||
|
||||
--- On item add, remove or change in_row callback(self, index|nil)
|
||||
-- @tfield DruidEvent on_change_items @{DruidEvent}
|
||||
-- @tfield event on_change_items event
|
||||
|
||||
--- On grid clear callback(self)
|
||||
-- @tfield DruidEvent on_clear @{DruidEvent}
|
||||
-- @tfield event on_clear event
|
||||
|
||||
--- On update item positions callback(self)
|
||||
-- @tfield DruidEvent on_update_positions @{DruidEvent}
|
||||
-- @tfield event on_update_positions event
|
||||
|
||||
--- Parent gui node
|
||||
-- @tfield node parent
|
||||
@@ -78,11 +78,27 @@
|
||||
---
|
||||
|
||||
local const = require("druid.const")
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local StaticGrid = component.create("static_grid")
|
||||
---@class druid.grid: druid.base_component
|
||||
---@field on_add_item event
|
||||
---@field on_remove_item event
|
||||
---@field on_change_items event
|
||||
---@field on_clear event
|
||||
---@field on_update_positions event
|
||||
---@field parent node
|
||||
---@field nodes node[]
|
||||
---@field first_index number
|
||||
---@field last_index number
|
||||
---@field anchor vector3
|
||||
---@field pivot vector3
|
||||
---@field node_size vector3
|
||||
---@field border vector4
|
||||
---@field in_row number
|
||||
---@field style table
|
||||
local M = component.create("static_grid")
|
||||
|
||||
|
||||
local function _extend_border(border, pos, size, pivot)
|
||||
@@ -104,23 +120,22 @@ end
|
||||
-- @table style
|
||||
-- @tfield boolean|nil IS_DYNAMIC_NODE_POSES If true, always center grid content as grid pivot sets. Default: false
|
||||
-- @tfield boolean|nil IS_ALIGN_LAST_ROW If true, always align last row of the grid as grid pivot sets. Default: false
|
||||
function StaticGrid.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.IS_DYNAMIC_NODE_POSES = style.IS_DYNAMIC_NODE_POSES or false
|
||||
self.style.IS_ALIGN_LAST_ROW = style.IS_ALIGN_LAST_ROW or false
|
||||
end
|
||||
|
||||
|
||||
--- The @{StaticGrid} constructor
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam string|node parent The GUI Node container, where grid's items will be placed
|
||||
-- @tparam node element Element prefab. Need to get it size
|
||||
-- @tparam number|nil in_row How many nodes in row can be placed. By default 1
|
||||
function StaticGrid.init(self, parent, element, in_row)
|
||||
--- The StaticGrid constructor
|
||||
---@param parent string|node The GUI Node container, where grid's items will be placed
|
||||
---@param element node Element prefab. Need to get it size
|
||||
---@param in_row number|nil How many nodes in row can be placed. By default 1
|
||||
function M:init(parent, element, in_row)
|
||||
self.parent = self:get_node(parent)
|
||||
self.nodes = {}
|
||||
|
||||
self.pivot = helper.get_pivot_offset(gui.get_pivot(self.parent))
|
||||
self.pivot = helper.get_pivot_offset(self.parent)
|
||||
self.anchor = vmath.vector3(0.5 + self.pivot.x, 0.5 - self.pivot.y, 0)
|
||||
|
||||
self.in_row = in_row or 1
|
||||
@@ -137,11 +152,11 @@ function StaticGrid.init(self, parent, element, in_row)
|
||||
|
||||
self.border = vmath.vector4(0) -- Current grid content size
|
||||
|
||||
self.on_add_item = Event()
|
||||
self.on_remove_item = Event()
|
||||
self.on_change_items = Event()
|
||||
self.on_clear = Event()
|
||||
self.on_update_positions = Event()
|
||||
self.on_add_item = event.create()
|
||||
self.on_remove_item = event.create()
|
||||
self.on_change_items = event.create()
|
||||
self.on_clear = event.create()
|
||||
self.on_update_positions = event.create()
|
||||
|
||||
self._set_position_function = gui.set_position
|
||||
end
|
||||
@@ -149,10 +164,9 @@ end
|
||||
|
||||
local _temp_pos = vmath.vector3(0)
|
||||
--- Return pos for grid node index
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam number index The grid element index
|
||||
-- @treturn vector3 @Node position
|
||||
function StaticGrid.get_pos(self, index)
|
||||
---@param index number The grid element index
|
||||
---@return vector3 @Node position
|
||||
function M:get_pos(index)
|
||||
local row = math.ceil(index / self.in_row) - 1
|
||||
local col = (index - row * self.in_row) - 1
|
||||
|
||||
@@ -167,10 +181,9 @@ end
|
||||
|
||||
|
||||
--- Return index for grid pos
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam vector3 pos The node position in the grid
|
||||
-- @treturn number The node index
|
||||
function StaticGrid.get_index(self, pos)
|
||||
---@param pos vector3 The node position in the grid
|
||||
---@return number The node index
|
||||
function M:get_index(pos)
|
||||
-- Offset to left-top corner from node pivot
|
||||
local node_offset_x = self.node_size.x * (-0.5 + self.node_pivot.x)
|
||||
local node_offset_y = self.node_size.y * (0.5 - self.node_pivot.y)
|
||||
@@ -187,10 +200,9 @@ end
|
||||
|
||||
|
||||
--- Return grid index by node
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam node node The gui node in the grid
|
||||
-- @treturn number The node index
|
||||
function StaticGrid.get_index_by_node(self, node)
|
||||
---@param node node The gui node in the grid
|
||||
---@return number|nil index The node index
|
||||
function M:get_index_by_node(node)
|
||||
for index, grid_node in pairs(self.nodes) do
|
||||
if node == grid_node then
|
||||
return index
|
||||
@@ -201,28 +213,26 @@ function StaticGrid.get_index_by_node(self, node)
|
||||
end
|
||||
|
||||
|
||||
function StaticGrid.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
self:_update(true)
|
||||
end
|
||||
|
||||
|
||||
--- Set grid anchor. Default anchor is equal to anchor of grid parent node
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam vector3 anchor Anchor
|
||||
function StaticGrid.set_anchor(self, anchor)
|
||||
---@param anchor vector3 Anchor
|
||||
function M:set_anchor(anchor)
|
||||
self.anchor = anchor
|
||||
self:_update()
|
||||
end
|
||||
|
||||
|
||||
--- Update grid content
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
function StaticGrid.refresh(self)
|
||||
function M:refresh()
|
||||
self:_update(true)
|
||||
end
|
||||
|
||||
|
||||
function StaticGrid.set_pivot(self, pivot)
|
||||
function M:set_pivot(pivot)
|
||||
local prev_pivot = helper.get_pivot_offset(gui.get_pivot(self.parent))
|
||||
self.pivot = helper.get_pivot_offset(pivot)
|
||||
|
||||
@@ -254,12 +264,11 @@ end
|
||||
|
||||
|
||||
--- Add new item to the grid
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam node item GUI node
|
||||
-- @tparam number|nil index The item position. By default add as last item
|
||||
-- @tparam number|nil shift_policy How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
-- @tparam boolean|nil is_instant If true, update node positions instantly
|
||||
function StaticGrid.add(self, item, index, shift_policy, is_instant)
|
||||
---@param item node GUI node
|
||||
---@param index number|nil The item position. By default add as last item
|
||||
---@param shift_policy number|nil How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
---@param is_instant boolean|nil If true, update node positions instantly
|
||||
function M:add(item, index, shift_policy, is_instant)
|
||||
index = index or ((self.last_index or 0) + 1)
|
||||
|
||||
helper.insert_with_shift(self.nodes, item, index, shift_policy)
|
||||
@@ -279,10 +288,9 @@ end
|
||||
|
||||
|
||||
--- Set new items to the grid. All previous items will be removed
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam node[] nodes The new grid nodes
|
||||
---@param nodes node[] The new grid nodes
|
||||
-- @tparam[opt=false] boolean is_instant If true, update node positions instantly
|
||||
function StaticGrid.set_items(self, nodes, is_instant)
|
||||
function M:set_items(nodes, is_instant)
|
||||
self.nodes = nodes
|
||||
for index = 1, #nodes do
|
||||
local item = nodes[index]
|
||||
@@ -296,12 +304,11 @@ end
|
||||
|
||||
|
||||
--- Remove the item from the grid. Note that gui node will be not deleted
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam number index The grid node index to remove
|
||||
-- @tparam number|nil shift_policy How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
-- @tparam boolean|nil is_instant If true, update node positions instantly
|
||||
-- @treturn node The deleted gui node from grid
|
||||
function StaticGrid.remove(self, index, shift_policy, is_instant)
|
||||
---@param index number The grid node index to remove
|
||||
---@param shift_policy number|nil How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
---@param is_instant boolean|nil If true, update node positions instantly
|
||||
---@return node The deleted gui node from grid
|
||||
function M:remove(index, shift_policy, is_instant)
|
||||
assert(self.nodes[index], "No grid item at given index " .. index)
|
||||
|
||||
local remove_node = self.nodes[index]
|
||||
@@ -317,9 +324,8 @@ end
|
||||
|
||||
|
||||
--- Return grid content size
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @treturn vector3 The grid content size
|
||||
function StaticGrid.get_size(self)
|
||||
---@return vector3 The grid content size
|
||||
function M:get_size()
|
||||
return vmath.vector3(
|
||||
self.border.z - self.border.x,
|
||||
self.border.y - self.border.w,
|
||||
@@ -327,7 +333,7 @@ function StaticGrid.get_size(self)
|
||||
end
|
||||
|
||||
|
||||
function StaticGrid.get_size_for(self, count)
|
||||
function M:get_size_for(count)
|
||||
if not count or count == 0 then
|
||||
return vmath.vector3(0)
|
||||
end
|
||||
@@ -350,17 +356,15 @@ end
|
||||
|
||||
|
||||
--- Return grid content borders
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @treturn vector3 The grid content borders
|
||||
function StaticGrid.get_borders(self)
|
||||
---@return vector4 The grid content borders
|
||||
function M:get_borders()
|
||||
return self.border
|
||||
end
|
||||
|
||||
|
||||
--- Return array of all node positions
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @treturn vector3[] All grid node positions
|
||||
function StaticGrid.get_all_pos(self)
|
||||
---@return vector3[] All grid node positions
|
||||
function M:get_all_pos()
|
||||
local result = {}
|
||||
for i, node in pairs(self.nodes) do
|
||||
table.insert(result, gui.get_position(node))
|
||||
@@ -372,10 +376,9 @@ end
|
||||
|
||||
--- Change set position function for grid nodes. It will call on
|
||||
-- update poses on grid elements. Default: gui.set_position
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam function callback Function on node set position
|
||||
-- @treturn druid.static_grid Current grid instance
|
||||
function StaticGrid.set_position_function(self, callback)
|
||||
---@param callback function Function on node set position
|
||||
---@return druid.grid Current grid instance
|
||||
function M:set_position_function(callback)
|
||||
self._set_position_function = callback or gui.set_position
|
||||
|
||||
return self
|
||||
@@ -384,9 +387,8 @@ end
|
||||
|
||||
--- Clear grid nodes array. GUI nodes will be not deleted!
|
||||
-- If you want to delete GUI nodes, use static_grid.nodes array before grid:clear
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @treturn druid.static_grid Current grid instance
|
||||
function StaticGrid.clear(self)
|
||||
---@return druid.grid Current grid instance
|
||||
function M:clear()
|
||||
self.border.x = 0
|
||||
self.border.y = 0
|
||||
self.border.w = 0
|
||||
@@ -403,9 +405,8 @@ end
|
||||
|
||||
|
||||
--- Return StaticGrid offset, where StaticGrid content starts.
|
||||
-- @tparam StaticGrid self @{StaticGrid} The StaticGrid instance
|
||||
-- @treturn vector3 The StaticGrid offset
|
||||
function StaticGrid:get_offset()
|
||||
---@return vector3 The StaticGrid offset
|
||||
function M:get_offset()
|
||||
local borders = self:get_borders()
|
||||
local size = self:get_size()
|
||||
|
||||
@@ -419,10 +420,9 @@ end
|
||||
|
||||
|
||||
--- Set new in_row elements for grid
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam number in_row The new in_row value
|
||||
-- @treturn druid.static_grid Current grid instance
|
||||
function StaticGrid.set_in_row(self, in_row)
|
||||
---@param in_row number The new in_row value
|
||||
---@return druid.grid Current grid instance
|
||||
function M:set_in_row(in_row)
|
||||
self.in_row = in_row
|
||||
self._grid_horizonal_offset = self.node_size.x * (self.in_row - 1) * self.anchor.x
|
||||
self._zero_offset = vmath.vector3(
|
||||
@@ -438,11 +438,10 @@ end
|
||||
|
||||
|
||||
--- Set new node size for grid
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam[opt] number width The new node width
|
||||
-- @tparam[opt] number height The new node height
|
||||
-- @treturn druid.static_grid Current grid instance
|
||||
function StaticGrid.set_item_size(self, width, height)
|
||||
---@return druid.grid Current grid instance
|
||||
function M:set_item_size(width, height)
|
||||
if width then
|
||||
self.node_size.x = width
|
||||
end
|
||||
@@ -463,20 +462,20 @@ end
|
||||
|
||||
|
||||
--- Sort grid nodes by custom comparator function
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam function comparator The comparator function. (a, b) -> boolean
|
||||
-- @treturn druid.static_grid Current grid instance
|
||||
function StaticGrid.sort_nodes(self, comparator)
|
||||
---@param comparator function The comparator function. (a, b) -> boolean
|
||||
---@return druid.grid self Current grid instance
|
||||
function M:sort_nodes(comparator)
|
||||
table.sort(self.nodes, comparator)
|
||||
self:_update(true)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Update grid inner state
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam boolean|nil is_instant If true, node position update instantly, otherwise with set_position_function callback
|
||||
-- @local
|
||||
function StaticGrid._update(self, is_instant)
|
||||
---@param is_instant boolean|nil If true, node position update instantly, otherwise with set_position_function callback
|
||||
---@private
|
||||
function M:_update(is_instant)
|
||||
self:_update_indexes()
|
||||
self:_update_borders()
|
||||
self:_update_pos(is_instant)
|
||||
@@ -484,9 +483,8 @@ end
|
||||
|
||||
|
||||
--- Update first and last indexes of grid nodes
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @local
|
||||
function StaticGrid._update_indexes(self)
|
||||
---@private
|
||||
function M:_update_indexes()
|
||||
self.first_index = nil
|
||||
self.last_index = nil
|
||||
for index in pairs(self.nodes) do
|
||||
@@ -500,9 +498,8 @@ end
|
||||
|
||||
|
||||
--- Update grid content borders, recalculate min and max values
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @local
|
||||
function StaticGrid._update_borders(self)
|
||||
---@private
|
||||
function M:_update_borders()
|
||||
if not self.first_index then
|
||||
self.border = vmath.vector4(0)
|
||||
return
|
||||
@@ -519,10 +516,9 @@ end
|
||||
|
||||
|
||||
--- Update grid nodes position
|
||||
-- @tparam StaticGrid self @{StaticGrid}
|
||||
-- @tparam boolean|nil is_instant If true, node position update instantly, otherwise with set_position_function callback
|
||||
-- @local
|
||||
function StaticGrid._update_pos(self, is_instant)
|
||||
---@param is_instant boolean|nil If true, node position update instantly, otherwise with set_position_function callback
|
||||
---@private
|
||||
function M:_update_pos(is_instant)
|
||||
local zero_offset = self:_get_zero_offset()
|
||||
|
||||
for i, node in pairs(self.nodes) do
|
||||
@@ -543,11 +539,11 @@ end
|
||||
|
||||
--- Return elements offset for correct posing nodes. Correct posing at
|
||||
-- parent pivot node (0:0) with adjusting of node sizes and anchoring
|
||||
-- @treturn vector3 The offset vector
|
||||
-- @local
|
||||
function StaticGrid:_get_zero_offset()
|
||||
---@return vector3 The offset vector
|
||||
---@private
|
||||
function M:_get_zero_offset()
|
||||
if not self.style.IS_DYNAMIC_NODE_POSES then
|
||||
return const.VECTOR_ZERO
|
||||
return vmath.vector3(0)
|
||||
end
|
||||
|
||||
-- zero offset: center pos - border size * anchor
|
||||
@@ -560,9 +556,9 @@ end
|
||||
|
||||
|
||||
--- Return offset x for last row in grid. Used to align this row accorting to grid's anchor
|
||||
-- @treturn number The offset x value
|
||||
-- @local
|
||||
function StaticGrid:_get_zero_offset_x(row_index)
|
||||
---@return number The offset x value
|
||||
---@private
|
||||
function M:_get_zero_offset_x(row_index)
|
||||
if not self.style.IS_DYNAMIC_NODE_POSES or not self.style.IS_ALIGN_LAST_ROW then
|
||||
return self._zero_offset.x
|
||||
end
|
||||
@@ -580,4 +576,4 @@ function StaticGrid:_get_zero_offset_x(row_index)
|
||||
end
|
||||
|
||||
|
||||
return StaticGrid
|
||||
return M
|
||||
|
||||
@@ -36,13 +36,13 @@
|
||||
-- @alias druid.text
|
||||
|
||||
--- On set text callback(self, text)
|
||||
-- @tfield DruidEvent on_set_text @{DruidEvent}
|
||||
-- @tfield event on_set_text event
|
||||
|
||||
--- On adjust text size callback(self, new_scale, text_metrics)
|
||||
-- @tfield DruidEvent on_update_text_scale @{DruidEvent}
|
||||
-- @tfield event on_update_text_scale event
|
||||
|
||||
--- On change pivot callback(self, pivot)
|
||||
-- @tfield DruidEvent on_set_pivot @{DruidEvent}
|
||||
-- @tfield event on_set_pivot event
|
||||
|
||||
--- Text node
|
||||
-- @tfield node node
|
||||
@@ -76,14 +76,23 @@
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local utf8_lua = require("druid.system.utf8")
|
||||
local component = require("druid.component")
|
||||
local utf8 = utf8 or utf8_lua --[[@as utf8]]
|
||||
|
||||
local Text = component.create("text")
|
||||
---@class druid.text: druid.base_component
|
||||
---@field node node
|
||||
---@field on_set_text event
|
||||
---@field on_update_text_scale event
|
||||
---@field on_set_pivot event
|
||||
---@field style table
|
||||
---@field private start_pivot userdata
|
||||
---@field private start_scale vector3
|
||||
---@field private scale vector3
|
||||
local M = component.create("text")
|
||||
|
||||
local function update_text_size(self)
|
||||
if self.scale.x == 0 or self.scale.y == 0 then
|
||||
@@ -200,6 +209,8 @@ local function update_text_area_size(self)
|
||||
end
|
||||
|
||||
|
||||
---@param self druid.text
|
||||
---@param trim_postfix string
|
||||
local function update_text_with_trim(self, trim_postfix)
|
||||
local max_width = self.text_area.x
|
||||
local text_width = self:get_text_size()
|
||||
@@ -222,6 +233,24 @@ local function update_text_with_trim(self, trim_postfix)
|
||||
end
|
||||
end
|
||||
|
||||
local function update_text_with_trim_left(self, trim_postfix)
|
||||
local max_width = self.text_area.x
|
||||
local text_width = self:get_text_size()
|
||||
local text_length = utf8.len(self.last_value)
|
||||
local trim_index = 1
|
||||
|
||||
if text_width > max_width then
|
||||
local new_text = self.last_value
|
||||
while text_width > max_width and trim_index < text_length do
|
||||
trim_index = trim_index + 1
|
||||
new_text = trim_postfix .. utf8.sub(self.last_value, trim_index, text_length)
|
||||
text_width = self:get_text_size(new_text)
|
||||
end
|
||||
|
||||
gui.set_text(self.node, new_text)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function update_text_with_anchor_shift(self)
|
||||
if self:get_text_size() >= self.text_area.x then
|
||||
@@ -232,6 +261,7 @@ local function update_text_with_anchor_shift(self)
|
||||
end
|
||||
|
||||
|
||||
---@param self druid.text
|
||||
local function update_adjust(self)
|
||||
if not self.adjust_type or self.adjust_type == const.TEXT_ADJUST.NO_ADJUST then
|
||||
reset_default_scale(self)
|
||||
@@ -246,6 +276,10 @@ local function update_adjust(self)
|
||||
update_text_with_trim(self, self.style.TRIM_POSTFIX)
|
||||
end
|
||||
|
||||
if self.adjust_type == const.TEXT_ADJUST.TRIM_LEFT then
|
||||
update_text_with_trim_left(self, self.style.TRIM_POSTFIX)
|
||||
end
|
||||
|
||||
if self.adjust_type == const.TEXT_ADJUST.DOWNSCALE_LIMITED then
|
||||
update_text_area_size(self)
|
||||
end
|
||||
@@ -258,6 +292,16 @@ local function update_adjust(self)
|
||||
update_text_area_size(self)
|
||||
update_text_with_anchor_shift(self)
|
||||
end
|
||||
|
||||
if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM then
|
||||
update_text_area_size(self)
|
||||
update_text_with_trim(self, self.style.TRIM_POSTFIX)
|
||||
end
|
||||
|
||||
if self.adjust_type == const.TEXT_ADJUST.SCALE_THEN_TRIM_LEFT then
|
||||
update_text_area_size(self)
|
||||
update_text_with_trim_left(self, self.style.TRIM_POSTFIX)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -269,7 +313,7 @@ end
|
||||
-- @tfield string|nil DEFAULT_ADJUST The default adjust type for any text component. Default: DOWNSCALE
|
||||
-- @tfield string|nil ADJUST_STEPS Amount of iterations for text adjust by height. Default: 20
|
||||
-- @tfield string|nil ADJUST_SCALE_DELTA Scale step on each height adjust step. Default: 0.02
|
||||
function Text.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.TRIM_POSTFIX = style.TRIM_POSTFIX or "..."
|
||||
self.style.DEFAULT_ADJUST = style.DEFAULT_ADJUST or const.TEXT_ADJUST.DOWNSCALE
|
||||
@@ -278,12 +322,11 @@ function Text.on_style_change(self, style)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Text} constructor
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam string|node node Node name or GUI Text Node itself
|
||||
-- @tparam string|nil value Initial text. Default value is node text from GUI scene. Default: nil
|
||||
-- @tparam string|nil adjust_type Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference. Default: DOWNSCALE
|
||||
function Text.init(self, node, value, adjust_type)
|
||||
--- The Text constructor
|
||||
---@param node string|node Node name or GUI Text Node itself
|
||||
---@param value string|nil Initial text. Default value is node text from GUI scene. Default: nil
|
||||
---@param adjust_type string|nil Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference. Default: DOWNSCALE
|
||||
function M:init(node, value, adjust_type)
|
||||
self.node = self:get_node(node)
|
||||
self.pos = gui.get_position(self.node)
|
||||
self.node_id = gui.get_id(self.node)
|
||||
@@ -300,37 +343,25 @@ function Text.init(self, node, value, adjust_type)
|
||||
self.adjust_type = adjust_type or self.style.DEFAULT_ADJUST
|
||||
self.color = gui.get_color(self.node)
|
||||
|
||||
self.on_set_text = Event()
|
||||
self.on_set_pivot = Event()
|
||||
self.on_update_text_scale = Event()
|
||||
self.on_set_text = event.create()
|
||||
self.on_set_pivot = event.create()
|
||||
self.on_update_text_scale = event.create()
|
||||
|
||||
self:set_to(value or gui.get_text(self.node))
|
||||
self:set_text(value or gui.get_text(self.node))
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function Text.on_layout_change(self)
|
||||
self:set_to(self.last_value)
|
||||
end
|
||||
|
||||
|
||||
function Text.on_message_input(self, node_id, message)
|
||||
if node_id ~= self.node_id then
|
||||
return false
|
||||
end
|
||||
|
||||
if message.action == const.MESSAGE_INPUT.TEXT_SET then
|
||||
Text.set_to(self, message.value)
|
||||
end
|
||||
function M:on_layout_change()
|
||||
self:set_text(self.last_value)
|
||||
end
|
||||
|
||||
|
||||
--- Calculate text width with font with respect to trailing space
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam string text|nil
|
||||
-- @treturn number Width
|
||||
-- @treturn number Height
|
||||
function Text.get_text_size(self, text)
|
||||
---@param text string|nil
|
||||
---@return number Width
|
||||
---@return number Height
|
||||
function M:get_text_size(text)
|
||||
text = text or self.last_value
|
||||
local font_name = gui.get_font(self.node)
|
||||
local font = gui.get_font_resource(font_name)
|
||||
@@ -351,10 +382,9 @@ end
|
||||
|
||||
|
||||
--- Get chars count by width
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam number width
|
||||
-- @treturn number Chars count
|
||||
function Text.get_text_index_by_width(self, width)
|
||||
---@param width number
|
||||
---@return number Chars count
|
||||
function M:get_text_index_by_width(width)
|
||||
local text = self.last_value
|
||||
local font_name = gui.get_font(self.node)
|
||||
local font = gui.get_font_resource(font_name)
|
||||
@@ -385,11 +415,11 @@ end
|
||||
|
||||
|
||||
--- Set text to text field
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam string set_to Text for node
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_to(self, set_to)
|
||||
set_to = set_to or ""
|
||||
---@deprecated
|
||||
---@param set_to string Text for node
|
||||
---@return druid.text Current text instance
|
||||
function M:set_to(set_to)
|
||||
set_to = tostring(set_to or "")
|
||||
|
||||
self.last_value = set_to
|
||||
gui.set_text(self.node, set_to)
|
||||
@@ -402,24 +432,35 @@ function Text.set_to(self, set_to)
|
||||
end
|
||||
|
||||
|
||||
function M:set_text(new_text)
|
||||
---@diagnostic disable-next-line: deprecated
|
||||
return self:set_to(new_text)
|
||||
end
|
||||
|
||||
|
||||
function M:get_text()
|
||||
return self.last_value
|
||||
end
|
||||
|
||||
|
||||
--- Set text area size
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam vector3 size The new text area size
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_size(self, size)
|
||||
---@param size vector3 The new text area size
|
||||
---@return druid.text self Current text instance
|
||||
function M:set_size(size)
|
||||
self.start_size = size
|
||||
self.text_area = vmath.vector3(size)
|
||||
self.text_area.x = self.text_area.x * self.start_scale.x
|
||||
self.text_area.y = self.text_area.y * self.start_scale.y
|
||||
update_adjust(self)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set color
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam vector4 color Color for node
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_color(self, color)
|
||||
---@param color vector4 Color for node
|
||||
---@return druid.text Current text instance
|
||||
function M:set_color(color)
|
||||
self.color = color
|
||||
gui.set_color(self.node, color)
|
||||
|
||||
@@ -428,10 +469,9 @@ end
|
||||
|
||||
|
||||
--- Set alpha
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam number alpha Alpha for node
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_alpha(self, alpha)
|
||||
---@param alpha number Alpha for node
|
||||
---@return druid.text Current text instance
|
||||
function M:set_alpha(alpha)
|
||||
self.color.w = alpha
|
||||
gui.set_color(self.node, self.color)
|
||||
|
||||
@@ -440,10 +480,9 @@ end
|
||||
|
||||
|
||||
--- Set scale
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam vector3 scale Scale for node
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_scale(self, scale)
|
||||
---@param scale vector3 Scale for node
|
||||
---@return druid.text Current text instance
|
||||
function M:set_scale(scale)
|
||||
self.last_scale = scale
|
||||
gui.set_scale(self.node, scale)
|
||||
|
||||
@@ -452,10 +491,9 @@ end
|
||||
|
||||
|
||||
--- Set text pivot. Text will re-anchor inside text area
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam number pivot The gui.PIVOT_* constant
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_pivot(self, pivot)
|
||||
---@param pivot userdata The gui.PIVOT_* constant
|
||||
---@return druid.text Current text instance
|
||||
function M:set_pivot(pivot)
|
||||
local prev_pivot = gui.get_pivot(self.node)
|
||||
local prev_offset = const.PIVOTS[prev_pivot]
|
||||
|
||||
@@ -477,33 +515,32 @@ function Text.set_pivot(self, pivot)
|
||||
end
|
||||
|
||||
|
||||
--- Return true, if text with line break
|
||||
-- @tparam Text self @{Text}
|
||||
-- @treturn boolean Is text node with line break
|
||||
function Text.is_multiline(self)
|
||||
---Return true, if text with line break
|
||||
---@return boolean Is text node with line break
|
||||
function M:is_multiline()
|
||||
return gui.get_line_break(self.node)
|
||||
end
|
||||
|
||||
|
||||
--- Set text adjust, refresh the current text visuals, if needed
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam string|nil adjust_type See const.TEXT_ADJUST. If pass nil - use current adjust type
|
||||
-- @tparam number|nil minimal_scale If pass nil - not use minimal scale
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_text_adjust(self, adjust_type, minimal_scale)
|
||||
---Set text adjust, refresh the current text visuals, if needed
|
||||
---Values are: "downscale", "trim", "no_adjust", "downscale_limited",
|
||||
---"scroll", "scale_then_scroll", "trim_left", "scale_then_trim", "scale_then_trim_left"
|
||||
---@param adjust_type string|nil See const.TEXT_ADJUST. If pass nil - use current adjust type
|
||||
---@param minimal_scale number|nil To remove minimal scale, use `text:set_minimal_scale(nil)`, if pass nil - not change minimal scale
|
||||
---@return druid.text self Current text instance
|
||||
function M:set_text_adjust(adjust_type, minimal_scale)
|
||||
self.adjust_type = adjust_type
|
||||
self._minimal_scale = minimal_scale
|
||||
self:set_to(self.last_value)
|
||||
self._minimal_scale = minimal_scale or self._minimal_scale
|
||||
self:set_text(self.last_value)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set minimal scale for DOWNSCALE_LIMITED or SCALE_THEN_SCROLL adjust types
|
||||
-- @tparam Text self @{Text}
|
||||
-- @tparam number minimal_scale If pass nil - not use minimal scale
|
||||
-- @treturn Text Current text instance
|
||||
function Text.set_minimal_scale(self, minimal_scale)
|
||||
---@param minimal_scale number If pass nil - not use minimal scale
|
||||
---@return druid.text Current text instance
|
||||
function M:set_minimal_scale(minimal_scale)
|
||||
self._minimal_scale = minimal_scale
|
||||
|
||||
return self
|
||||
@@ -511,10 +548,10 @@ end
|
||||
|
||||
|
||||
--- Return current text adjust type
|
||||
-- @treturn number The current text adjust type
|
||||
function Text.get_text_adjust(self, adjust_type)
|
||||
---@return string adjust_type The current text adjust type
|
||||
function M:get_text_adjust()
|
||||
return self.adjust_type
|
||||
end
|
||||
|
||||
|
||||
return Text
|
||||
return M
|
||||
|
||||
48
druid/bindings.lua
Normal file
48
druid/bindings.lua
Normal file
@@ -0,0 +1,48 @@
|
||||
local event = require("event.event")
|
||||
|
||||
local M = {}
|
||||
local WRAPPED_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 no access to nested widgets functions
|
||||
---@param widget druid.widget
|
||||
function M.set_widget(widget)
|
||||
local object = msg.url()
|
||||
object.fragment = nil
|
||||
|
||||
-- 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 })
|
||||
local parent_table = getmetatable(widget).__index
|
||||
|
||||
-- Go through all functions and wrap them in events
|
||||
for key, value in pairs(parent_table) do
|
||||
if type(value) == "function" then
|
||||
wrapped_widget[key] = event.create(function(_, ...)
|
||||
return value(widget, ...)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
WRAPPED_WIDGETS[object.socket] = WRAPPED_WIDGETS[object.socket] or {}
|
||||
WRAPPED_WIDGETS[object.socket][object.path] = wrapped_widget
|
||||
end
|
||||
|
||||
|
||||
---@param object_url string|userdata|url @root object
|
||||
---@return druid.widget|nil
|
||||
function M.get_widget(object_url)
|
||||
assert(object_url, "You must provide an object_url")
|
||||
|
||||
object_url = msg.url(object_url --[[@as string]])
|
||||
|
||||
local socket_widgets = WRAPPED_WIDGETS[object_url.socket]
|
||||
if not socket_widgets then
|
||||
return nil
|
||||
end
|
||||
|
||||
return socket_widgets[object_url.path]
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
229
druid/color.lua
Normal file
229
druid/color.lua
Normal file
@@ -0,0 +1,229 @@
|
||||
---@type table<string, table<string, vector4>>
|
||||
local PALETTE_DATA
|
||||
local CURRENT_PALETTE = "default"
|
||||
local DEFAULT_COLOR = vmath.vector4(1, 1, 1, 1)
|
||||
local COLOR_X = hash("color.x")
|
||||
local COLOR_Y = hash("color.y")
|
||||
local COLOR_Z = hash("color.z")
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
---Get color color by id
|
||||
---@param color_id string
|
||||
---@return vector4
|
||||
function M.get(color_id)
|
||||
-- Check is it hex: starts with "#" or contains only 3 or 6 hex symbols
|
||||
if type(color_id) == "string" then
|
||||
if string.sub(color_id, 1, 1) == "#" or string.match(color_id, "^[0-9a-fA-F]+$") then
|
||||
return M.hex2vector4(color_id)
|
||||
end
|
||||
end
|
||||
|
||||
return PALETTE_DATA[CURRENT_PALETTE] and PALETTE_DATA[CURRENT_PALETTE][color_id] or DEFAULT_COLOR
|
||||
end
|
||||
|
||||
|
||||
---Add palette to palette data
|
||||
---@param palette_name string
|
||||
---@param palette_data table<string, vector4>
|
||||
function M.add_palette(palette_name, palette_data)
|
||||
PALETTE_DATA[palette_name] = PALETTE_DATA[palette_name] or {}
|
||||
local palette = PALETTE_DATA[palette_name]
|
||||
|
||||
for color_id, color in pairs(palette_data) do
|
||||
if type(color) == "string" then
|
||||
palette[color_id] = M.hex2vector4(color)
|
||||
else
|
||||
palette[color_id] = color
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M.set_palette(palette_name)
|
||||
if PALETTE_DATA[palette_name] then
|
||||
CURRENT_PALETTE = palette_name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M.get_palette()
|
||||
return CURRENT_PALETTE
|
||||
end
|
||||
|
||||
|
||||
---Set color of gui node without changing alpha
|
||||
---@param gui_node node
|
||||
---@param color vector4|vector3|string Color in vector4, vector3 or color id from palette
|
||||
function M.set_color(gui_node, color)
|
||||
if type(color) == "string" then
|
||||
color = M.get(color)
|
||||
end
|
||||
|
||||
gui.set(gui_node, COLOR_X, color.x)
|
||||
gui.set(gui_node, COLOR_Y, color.y)
|
||||
gui.set(gui_node, COLOR_Z, color.z)
|
||||
end
|
||||
|
||||
|
||||
function M.get_random_color()
|
||||
return vmath.vector4(math.random(), math.random(), math.random(), 1)
|
||||
end
|
||||
|
||||
|
||||
---Lerp colors via color HSB values
|
||||
function M.lerp(t, color1, color2)
|
||||
local h1, s1, v1 = M.rgb2hsb(color1.x, color1.y, color1.z)
|
||||
local h2, s2, v2 = M.rgb2hsb(color2.x, color2.y, color2.z)
|
||||
|
||||
local h = h1 + (h2 - h1) * t
|
||||
local s = s1 + (s2 - s1) * t
|
||||
local v = v1 + (v2 - v1) * t
|
||||
|
||||
local r, g, b, a = M.hsb2rgb(h, s, v)
|
||||
a = a or 1
|
||||
return vmath.vector4(r, g, b, a)
|
||||
end
|
||||
|
||||
|
||||
---@param hex string
|
||||
---@param alpha number|nil
|
||||
---@return number, number, number, number
|
||||
function M.hex2rgb(hex, alpha)
|
||||
alpha = alpha or 1
|
||||
if alpha > 1 then
|
||||
alpha = alpha / 100
|
||||
end
|
||||
|
||||
-- Remove leading #
|
||||
if string.sub(hex, 1, 1) == "#" then
|
||||
hex = string.sub(hex, 2)
|
||||
end
|
||||
|
||||
-- Expand 3-digit hex codes to 6 digits
|
||||
if #hex == 3 then
|
||||
hex = string.rep(string.sub(hex, 1, 1), 2) ..
|
||||
string.rep(string.sub(hex, 2, 2), 2) ..
|
||||
string.rep(string.sub(hex, 3, 3), 2)
|
||||
end
|
||||
|
||||
local r = tonumber("0x" .. string.sub(hex, 1, 2)) / 255
|
||||
local g = tonumber("0x" .. string.sub(hex, 3, 4)) / 255
|
||||
local b = tonumber("0x" .. string.sub(hex, 5, 6)) / 255
|
||||
return r, g, b, alpha
|
||||
end
|
||||
|
||||
|
||||
---@param hex string
|
||||
---@param alpha number|nil
|
||||
---@return vector4
|
||||
function M.hex2vector4(hex, alpha)
|
||||
local r, g, b, a = M.hex2rgb(hex, alpha)
|
||||
return vmath.vector4(r, g, b, a)
|
||||
end
|
||||
|
||||
|
||||
---Convert hsb color to rgb color
|
||||
---@param r number @Red value
|
||||
---@param g number @Green value
|
||||
---@param b number @Blue value
|
||||
---@param alpha number|nil @Alpha value. Default is 1
|
||||
function M.rgb2hsb(r, g, b, alpha)
|
||||
alpha = alpha or 1
|
||||
local min, max = math.min(r, g, b), math.max(r, g, b)
|
||||
local delta = max - min
|
||||
local h, s, v = 0, max, max
|
||||
|
||||
s = max ~= 0 and delta / max or 0
|
||||
|
||||
if delta ~= 0 then
|
||||
if r == max then
|
||||
h = (g - b) / delta
|
||||
elseif g == max then
|
||||
h = 2 + (b - r) / delta
|
||||
else
|
||||
h = 4 + (r - g) / delta
|
||||
end
|
||||
h = (h / 6) % 1
|
||||
end
|
||||
|
||||
alpha = alpha > 1 and alpha / 100 or alpha
|
||||
|
||||
return h, s, v, alpha
|
||||
end
|
||||
|
||||
|
||||
---Convert hsb color to rgb color
|
||||
---@param h number @Hue
|
||||
---@param s number @Saturation
|
||||
---@param v number @Value
|
||||
---@param alpha number|nil @Alpha value. Default is 1
|
||||
function M.hsb2rgb(h, s, v, alpha)
|
||||
local r, g, b
|
||||
local i = math.floor(h * 6)
|
||||
local f = h * 6 - i
|
||||
local p = v * (1 - s)
|
||||
local q = v * (1 - f * s)
|
||||
local t = v * (1 - (1 - f) * s)
|
||||
|
||||
i = i % 6
|
||||
|
||||
if i == 0 then r, g, b = v, t, p
|
||||
elseif i == 1 then r, g, b = q, v, p
|
||||
elseif i == 2 then r, g, b = p, v, t
|
||||
elseif i == 3 then r, g, b = p, q, v
|
||||
elseif i == 4 then r, g, b = t, p, v
|
||||
elseif i == 5 then r, g, b = v, p, q
|
||||
end
|
||||
|
||||
return r, g, b, alpha
|
||||
end
|
||||
|
||||
|
||||
---Convert rgb color to hex color
|
||||
---@param red number @Red value
|
||||
---@param green number @Green value
|
||||
---@param blue number @Blue value
|
||||
function M.rgb2hex(red, green, blue)
|
||||
local r = string.format("%x", math.floor(red * 255))
|
||||
local g = string.format("%x", math.floor(green * 255))
|
||||
local b = string.format("%x", math.floor(blue * 255))
|
||||
return string.upper((#r == 1 and "0" or "") .. r .. (#g == 1 and "0" or "") .. g .. (#b == 1 and "0" or "") .. b)
|
||||
end
|
||||
|
||||
|
||||
function M.load_palette()
|
||||
local PALETTE_PATH = sys.get_config_string("fluid.palette")
|
||||
if PALETTE_PATH then
|
||||
PALETTE_DATA = M.load_json(PALETTE_PATH) --[[@as table<string, table<string, vector4>>]]
|
||||
end
|
||||
PALETTE_DATA = PALETTE_DATA or {}
|
||||
|
||||
for _, palette_data in pairs(PALETTE_DATA) do
|
||||
for color_id, color in pairs(palette_data) do
|
||||
if type(color) == "string" then
|
||||
palette_data[color_id] = M.hex2vector4(color)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Load JSON file from game resources folder (by relative path to game.project)
|
||||
---Return nil if file not found or error
|
||||
---@param json_path string
|
||||
---@return table|nil
|
||||
function M.load_json(json_path)
|
||||
local resource, is_error = sys.load_resource(json_path)
|
||||
if is_error or not resource then
|
||||
return nil
|
||||
end
|
||||
|
||||
return json.decode(resource)
|
||||
end
|
||||
|
||||
|
||||
M.load_palette()
|
||||
|
||||
return M
|
||||
@@ -1,83 +1,61 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Basic class for all Druid components.
|
||||
-- To create you custom component, use static function `component.create`
|
||||
-- @usage
|
||||
-- -- Create your component:
|
||||
-- local component = require("druid.component")
|
||||
--
|
||||
-- local AwesomeComponent = component.create("awesome_component")
|
||||
--
|
||||
-- function AwesomeComponent:init(template, nodes)
|
||||
-- self:set_template(template)
|
||||
-- self:set_nodes(nodes)
|
||||
-- self.druid = self:get_druid()
|
||||
-- end
|
||||
--
|
||||
-- return AwesomeComponent
|
||||
-- @module BaseComponent
|
||||
-- @alias druid.base_component
|
||||
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
|
||||
local BaseComponent = {}
|
||||
---@class druid.base_component.meta
|
||||
---@field template string
|
||||
---@field context table
|
||||
---@field nodes table<hash, node>|nil
|
||||
---@field style table|nil
|
||||
---@field druid druid_instance
|
||||
---@field input_enabled boolean
|
||||
---@field children table
|
||||
---@field parent druid.base_component|nil
|
||||
---@field instance_class table
|
||||
|
||||
---@class druid.base_component.component
|
||||
---@field name string
|
||||
---@field input_priority number
|
||||
---@field default_input_priority number
|
||||
---@field _is_input_priority_changed boolean
|
||||
---@field _uid number
|
||||
|
||||
---@class druid.base_component
|
||||
---@field druid druid_instance Druid instance to create inner components
|
||||
---@field init fun(self:druid.base_component, ...)|nil
|
||||
---@field update fun(self:druid.base_component, dt:number)|nil
|
||||
---@field on_remove fun(self:druid.base_component)|nil
|
||||
---@field on_input fun(self:druid.base_component, action_id:number, action:table)|nil
|
||||
---@field on_message fun(self:druid.base_component, message_id:hash, message:table, sender:url)|nil
|
||||
---@field on_late_init fun(self:druid.base_component)|nil
|
||||
---@field on_focus_lost fun(self:druid.base_component)|nil
|
||||
---@field on_focus_gained fun(self:druid.base_component)|nil
|
||||
---@field on_style_change fun(self:druid.base_component, style: table)|nil
|
||||
---@field on_layout_change fun(self:druid.base_component)|nil
|
||||
---@field on_window_resized fun(self:druid.base_component)|nil
|
||||
---@field on_language_change fun(self:druid.base_component)|nil
|
||||
---@field private _component druid.base_component.component
|
||||
---@field private _meta druid.base_component.meta
|
||||
local M = {}
|
||||
|
||||
local INTERESTS = {} -- Cache interests per component class in runtime
|
||||
local IS_AUTO_TEMPLATE = not (sys.get_config_int("druid.no_auto_template", 0) == 1)
|
||||
|
||||
-- Component Interests
|
||||
BaseComponent.ON_INPUT = const.ON_INPUT
|
||||
BaseComponent.ON_UPDATE = const.ON_UPDATE
|
||||
BaseComponent.ON_MESSAGE = const.ON_MESSAGE
|
||||
BaseComponent.ON_LATE_INIT = const.ON_LATE_INIT
|
||||
BaseComponent.ON_FOCUS_LOST = const.ON_FOCUS_LOST
|
||||
BaseComponent.ON_FOCUS_GAINED = const.ON_FOCUS_GAINED
|
||||
BaseComponent.ON_LAYOUT_CHANGE = const.ON_LAYOUT_CHANGE
|
||||
BaseComponent.ON_MESSAGE_INPUT = const.ON_MESSAGE_INPUT
|
||||
BaseComponent.ON_WINDOW_RESIZED = const.ON_WINDOW_RESIZED
|
||||
BaseComponent.ON_LANGUAGE_CHANGE = const.ON_LANGUAGE_CHANGE
|
||||
|
||||
BaseComponent.ALL_INTERESTS = {
|
||||
BaseComponent.ON_INPUT,
|
||||
BaseComponent.ON_UPDATE,
|
||||
BaseComponent.ON_MESSAGE,
|
||||
BaseComponent.ON_LATE_INIT,
|
||||
BaseComponent.ON_FOCUS_LOST,
|
||||
BaseComponent.ON_FOCUS_GAINED,
|
||||
BaseComponent.ON_LAYOUT_CHANGE,
|
||||
BaseComponent.ON_MESSAGE_INPUT,
|
||||
BaseComponent.ON_WINDOW_RESIZED,
|
||||
BaseComponent.ON_LANGUAGE_CHANGE,
|
||||
}
|
||||
|
||||
-- Mapping from on_message method to specific method name
|
||||
BaseComponent.SPECIFIC_UI_MESSAGES = {
|
||||
[hash("layout_changed")] = BaseComponent.ON_LAYOUT_CHANGE, -- The message_id from Defold
|
||||
[hash(BaseComponent.ON_FOCUS_LOST)] = BaseComponent.ON_FOCUS_LOST,
|
||||
[hash(BaseComponent.ON_FOCUS_GAINED)] = BaseComponent.ON_FOCUS_GAINED,
|
||||
[hash(BaseComponent.ON_WINDOW_RESIZED)] = BaseComponent.ON_WINDOW_RESIZED,
|
||||
[hash(BaseComponent.ON_MESSAGE_INPUT)] = BaseComponent.ON_MESSAGE_INPUT,
|
||||
[hash(BaseComponent.ON_LANGUAGE_CHANGE)] = BaseComponent.ON_LANGUAGE_CHANGE,
|
||||
}
|
||||
|
||||
|
||||
local uid = 0
|
||||
function BaseComponent.create_uid()
|
||||
---@private
|
||||
function M.create_uid()
|
||||
uid = uid + 1
|
||||
return uid
|
||||
end
|
||||
|
||||
|
||||
--- Set current component style table.
|
||||
--
|
||||
-- Invoke `on_style_change` on component, if exist. Component should handle
|
||||
-- their style changing and store all style params
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam table|nil druid_style Druid style module
|
||||
-- @treturn BaseComponent @{BaseComponent}
|
||||
-- @local
|
||||
function BaseComponent.set_style(self, druid_style)
|
||||
---Set component style. Pass nil to clear style
|
||||
---@generic T
|
||||
---@param self T
|
||||
---@param druid_style table|nil
|
||||
---@return T self The component itself for chaining
|
||||
function M:set_style(druid_style)
|
||||
---@cast self druid.base_component
|
||||
|
||||
self._meta.style = druid_style or {}
|
||||
local component_style = self._meta.style[self._component.name] or {}
|
||||
|
||||
@@ -89,21 +67,22 @@ function BaseComponent.set_style(self, druid_style)
|
||||
end
|
||||
|
||||
|
||||
--- Set component template name.
|
||||
--
|
||||
-- Use on all your custom components with GUI layouts used as templates.
|
||||
-- It will check parent template name to build full template name in self:get_node()
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam string template BaseComponent template name
|
||||
-- @treturn BaseComponent @{BaseComponent}
|
||||
-- @local
|
||||
function BaseComponent.set_template(self, template)
|
||||
---Set component template name. Pass nil to clear template.
|
||||
---This template id used to access nodes inside the template on GUI scene.
|
||||
---Parent template will be added automatically if exist.
|
||||
---@generic T
|
||||
---@param self T
|
||||
---@param template string|nil
|
||||
---@return T self The component itself for chaining
|
||||
function M:set_template(template)
|
||||
---@cast self druid.base_component
|
||||
|
||||
template = template or ""
|
||||
|
||||
local parent = self:get_parent_component()
|
||||
if parent and IS_AUTO_TEMPLATE then
|
||||
if parent then
|
||||
local parent_template = parent:get_template()
|
||||
if #parent_template > 0 then
|
||||
if parent_template and #parent_template > 0 then
|
||||
if #template > 0 then
|
||||
template = "/" .. template
|
||||
end
|
||||
@@ -111,106 +90,52 @@ function BaseComponent.set_template(self, template)
|
||||
end
|
||||
end
|
||||
|
||||
self._meta.template = template
|
||||
if template ~= "" then
|
||||
self._meta.template = template
|
||||
else
|
||||
self._meta.template = nil
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Get current component template name.
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn string Component full template name
|
||||
function BaseComponent.get_template(self)
|
||||
---Get full template name.
|
||||
---@return string
|
||||
function M:get_template()
|
||||
return self._meta.template
|
||||
end
|
||||
|
||||
|
||||
--- Set current component nodes.
|
||||
-- Use if your component nodes was cloned with `gui.clone_tree` and you got the node tree.
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam table nodes BaseComponent nodes table
|
||||
-- @treturn BaseComponent @{BaseComponent}
|
||||
-- @usage
|
||||
-- local nodes = gui.clone_tree(self.prefab)
|
||||
-- ... In your component:
|
||||
-- self:set_nodes(nodes)
|
||||
-- @local
|
||||
function BaseComponent.set_nodes(self, nodes)
|
||||
---Set current component nodes, returned from `gui.clone_tree` function.
|
||||
---@param nodes table<hash, node>
|
||||
---@return druid.base_component
|
||||
function M:set_nodes(nodes)
|
||||
self._meta.nodes = nodes
|
||||
|
||||
-- When we use gui.clone_tree in inner template (template inside other template)
|
||||
-- this nodes have no id. We have table: hash(correct_id) : hash("")
|
||||
-- It's wrong and we use this hack to fix this
|
||||
if nodes then
|
||||
for id, node in pairs(nodes) do
|
||||
gui.set_id(node, id)
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Context used as first arg in all Druid events
|
||||
--
|
||||
-- Context is usually self of gui_script.
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn table BaseComponent context
|
||||
function BaseComponent.get_context(self)
|
||||
---Return current component context
|
||||
---@return any context Usually it's self of script but can be any other Druid component
|
||||
function M:get_context()
|
||||
return self._meta.context
|
||||
end
|
||||
|
||||
|
||||
--- Increase input priority in input stack
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @local
|
||||
function BaseComponent.increase_input_priority(self)
|
||||
helper.deprecated("The component:increase_input_priority is deprecated. Please use component:set_input_priority(druid_const.PRIORITY_INPUT_MAX) instead")
|
||||
---Get component node by node_id. Respect to current template and nodes.
|
||||
---@param node_id string|node
|
||||
---@return node
|
||||
function M:get_node(node_id)
|
||||
return helper.get_node(node_id, self:get_template(), self:get_nodes())
|
||||
end
|
||||
|
||||
|
||||
--- Get component node by name.
|
||||
--
|
||||
-- If component has nodes, node_or_name should be string
|
||||
-- It autopick node by template name or from nodes by gui.clone_tree
|
||||
-- if they was setup via component:set_nodes, component:set_template.
|
||||
-- If node is not found, the exception will fired
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam string|node node_or_name Node name or node itself
|
||||
-- @treturn node Gui node
|
||||
function BaseComponent.get_node(self, node_or_name)
|
||||
if type(node_or_name) ~= "string" then
|
||||
-- Assume it's already node from gui.get_node
|
||||
return node_or_name
|
||||
end
|
||||
|
||||
local template_name = self:get_template()
|
||||
local nodes = self:__get_nodes()
|
||||
|
||||
if #template_name > 0 then
|
||||
template_name = template_name .. "/"
|
||||
end
|
||||
|
||||
local node
|
||||
if nodes then
|
||||
node = nodes[template_name .. node_or_name]
|
||||
else
|
||||
node = gui.get_node(template_name .. node_or_name)
|
||||
end
|
||||
|
||||
if not node then
|
||||
assert(node, "No component with name: " .. (template_name or "") .. (node_or_name or ""))
|
||||
end
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
|
||||
--- Get Druid instance for inner component creation.
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam string|nil template The template name
|
||||
-- @tparam table|nil nodes The nodes table
|
||||
-- @treturn DruidInstance Druid instance with component context
|
||||
function BaseComponent.get_druid(self, template, nodes)
|
||||
---Get Druid instance for inner component creation.
|
||||
---@param template string|nil
|
||||
---@param nodes table<hash, node>|nil
|
||||
---@return druid_instance
|
||||
function M:get_druid(template, nodes)
|
||||
local context = { _context = self }
|
||||
local druid_instance = setmetatable(context, { __index = self._meta.druid })
|
||||
|
||||
@@ -226,39 +151,33 @@ function BaseComponent.get_druid(self, template, nodes)
|
||||
end
|
||||
|
||||
|
||||
--- Return component name
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn string The component name
|
||||
function BaseComponent.get_name(self)
|
||||
return self._component.name .. BaseComponent.create_uid()
|
||||
---Get component name
|
||||
---@return string name The component name + uid
|
||||
function M:get_name()
|
||||
return self._component.name .. M.create_uid()
|
||||
end
|
||||
|
||||
|
||||
--- Return parent component name
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn string|nil The parent component name if exist or bil
|
||||
function BaseComponent.get_parent_name(self)
|
||||
---Get parent component name
|
||||
---@return string|nil parent_name The parent component name if exist or nil
|
||||
function M:get_parent_name()
|
||||
local parent = self:get_parent_component()
|
||||
return parent and parent:get_name()
|
||||
end
|
||||
|
||||
|
||||
--- Return component input priority
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn number The component input priority
|
||||
function BaseComponent.get_input_priority(self)
|
||||
---Get component input priority, the bigger number processed first. Default value: 10
|
||||
---@return number
|
||||
function M:get_input_priority()
|
||||
return self._component.input_priority
|
||||
end
|
||||
|
||||
|
||||
--- Set component input priority
|
||||
--
|
||||
-- Default value: 10
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam number value The new input priority value
|
||||
-- @tparam boolean|nil is_temporary If true, the reset input priority will return to previous value
|
||||
-- @treturn number The component input priority
|
||||
function BaseComponent.set_input_priority(self, value, is_temporary)
|
||||
---Set component input priority, the bigger number processed first. Default value: 10
|
||||
---@param value number
|
||||
---@param is_temporary boolean|nil If true, the reset input priority will return to previous value
|
||||
---@return druid.base_component self The component itself for chaining
|
||||
function M:set_input_priority(value, is_temporary)
|
||||
assert(value)
|
||||
|
||||
if self._component.input_priority == value then
|
||||
@@ -281,32 +200,27 @@ function BaseComponent.set_input_priority(self, value, is_temporary)
|
||||
end
|
||||
|
||||
|
||||
--- Reset component input priority to default value
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn number The component input priority
|
||||
function BaseComponent.reset_input_priority(self)
|
||||
---Reset component input priority to it's default value, that was set in `create` function or `set_input_priority`
|
||||
---@return druid.base_component self The component itself for chaining
|
||||
function M:reset_input_priority()
|
||||
self:set_input_priority(self._component.default_input_priority)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Return component UID.
|
||||
--
|
||||
-- UID generated in component creation order.
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn number The component uid
|
||||
function BaseComponent.get_uid(self)
|
||||
---Get component UID, unique identifier created in component creation order.
|
||||
---@return number uid The component uid
|
||||
function M:get_uid()
|
||||
return self._component._uid
|
||||
end
|
||||
|
||||
|
||||
--- Set component input state. By default it enabled
|
||||
--
|
||||
-- If input is disabled, the component will not receive input events
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam boolean|nil state The component input state
|
||||
-- @treturn BaseComponent BaseComponent itself
|
||||
function BaseComponent.set_input_enabled(self, state)
|
||||
---Set component input state. By default it's enabled.
|
||||
---If input is disabled, the component will not receive input events.
|
||||
---Recursive for all children components.
|
||||
---@param state boolean
|
||||
---@return druid.base_component self The component itself for chaining
|
||||
function M:set_input_enabled(state)
|
||||
self._meta.input_enabled = state
|
||||
|
||||
for index = 1, #self._meta.children do
|
||||
@@ -317,23 +231,21 @@ function BaseComponent.set_input_enabled(self, state)
|
||||
end
|
||||
|
||||
|
||||
--- Return the parent component if exist
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn BaseComponent|nil The druid component instance or nil
|
||||
function BaseComponent.get_parent_component(self)
|
||||
---Get parent component
|
||||
---@return druid.base_component|nil parent The parent component if exist or nil
|
||||
function M:get_parent_component()
|
||||
return self._meta.parent
|
||||
end
|
||||
|
||||
|
||||
--- Setup component context and his style table
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam table druid_instance The parent druid instance
|
||||
-- @tparam table context Druid context. Usually it is self of script
|
||||
-- @tparam table style Druid style module
|
||||
-- @tparam table instance_class The component instance class
|
||||
-- @treturn component BaseComponent itself
|
||||
-- @local
|
||||
function BaseComponent.setup_component(self, druid_instance, context, style, instance_class)
|
||||
---@param druid_instance table The parent druid instance
|
||||
---@param context table Druid context. Usually it is self of script
|
||||
---@param style table Druid style module
|
||||
---@param instance_class table The component instance class
|
||||
---@return druid.base_component BaseComponent itself
|
||||
---@private
|
||||
function M:setup_component(druid_instance, context, style, instance_class)
|
||||
self._meta = {
|
||||
template = "",
|
||||
context = context,
|
||||
@@ -342,7 +254,7 @@ function BaseComponent.setup_component(self, druid_instance, context, style, ins
|
||||
druid = druid_instance,
|
||||
input_enabled = true,
|
||||
children = {},
|
||||
parent = type(context) ~= "userdata" and context,
|
||||
parent = type(context) ~= "userdata" and context --[[@as druid.base_component]],
|
||||
instance_class = instance_class
|
||||
}
|
||||
|
||||
@@ -357,62 +269,32 @@ function BaseComponent.setup_component(self, druid_instance, context, style, ins
|
||||
end
|
||||
|
||||
|
||||
--- Print log information if debug mode is enabled
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam string message
|
||||
-- @tparam table context
|
||||
-- @local
|
||||
function BaseComponent.log_message(self, message, context)
|
||||
if not self._component.is_debug then
|
||||
return
|
||||
end
|
||||
print("[" .. self:get_name() .. "]:", message, helper.table_to_string(context))
|
||||
end
|
||||
|
||||
|
||||
--- Set debug logs for component enabled or disabled
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam boolean|nil is_debug
|
||||
-- @local
|
||||
function BaseComponent.set_debug(self, is_debug)
|
||||
self._component.is_debug = is_debug
|
||||
end
|
||||
|
||||
|
||||
--- Return true, if input priority was changed
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @local
|
||||
function BaseComponent._is_input_priority_changed(self)
|
||||
---@private
|
||||
function M:_is_input_priority_changed()
|
||||
return self._component._is_input_priority_changed
|
||||
end
|
||||
|
||||
|
||||
--- Reset is_input_priority_changed field
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @local
|
||||
function BaseComponent._reset_input_priority_changed(self)
|
||||
---@private
|
||||
function M:_reset_input_priority_changed()
|
||||
self._component._is_input_priority_changed = false
|
||||
end
|
||||
|
||||
|
||||
function BaseComponent.__tostring(self)
|
||||
return self._component.name
|
||||
end
|
||||
|
||||
|
||||
--- Get current component interests
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn table List of component interests
|
||||
-- @local
|
||||
function BaseComponent.__get_interests(self)
|
||||
---@return table List of component interests
|
||||
---@private
|
||||
function M:__get_interests()
|
||||
local instance_class = self._meta.instance_class
|
||||
if INTERESTS[instance_class] then
|
||||
return INTERESTS[instance_class]
|
||||
end
|
||||
|
||||
local interests = {}
|
||||
for index = 1, #BaseComponent.ALL_INTERESTS do
|
||||
local interest = BaseComponent.ALL_INTERESTS[index]
|
||||
for index = 1, #const.ALL_INTERESTS do
|
||||
local interest = const.ALL_INTERESTS[index]
|
||||
if self[interest] and type(self[interest]) == "function" then
|
||||
table.insert(interests, interest)
|
||||
end
|
||||
@@ -423,47 +305,52 @@ function BaseComponent.__get_interests(self)
|
||||
end
|
||||
|
||||
|
||||
--- Get current component nodes
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn table BaseComponent nodes table
|
||||
-- @local
|
||||
function BaseComponent.__get_nodes(self)
|
||||
---Get current component nodes
|
||||
---@return table<hash, node>|nil
|
||||
function M:get_nodes()
|
||||
local nodes = self._meta.nodes
|
||||
local parent = self:get_parent_component()
|
||||
if parent then
|
||||
nodes = nodes or parent:__get_nodes()
|
||||
nodes = nodes or parent:get_nodes()
|
||||
end
|
||||
|
||||
return nodes
|
||||
end
|
||||
|
||||
|
||||
--- Add child to component children list
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam component child The druid component instance
|
||||
-- @local
|
||||
function BaseComponent.__add_child(self, child)
|
||||
---Add child to component children list
|
||||
---@generic T: druid.base_component
|
||||
---@param child T The druid component instance
|
||||
---@return T self The component itself for chaining
|
||||
---@private
|
||||
function M:__add_child(child)
|
||||
table.insert(self._meta.children, child)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Remove child from component children list
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @tparam component child The druid component instance
|
||||
-- @local
|
||||
function BaseComponent.__remove_child(self, child)
|
||||
|
||||
---Remove child from component children list
|
||||
---@generic T: druid.base_component
|
||||
---@param child T The druid component instance
|
||||
---@return boolean true if child was removed
|
||||
---@private
|
||||
function M:__remove_child(child)
|
||||
for i = #self._meta.children, 1, -1 do
|
||||
if self._meta.children[i] == child then
|
||||
table.remove(self._meta.children, i)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- Return all children components, recursive
|
||||
-- @tparam BaseComponent self @{BaseComponent}
|
||||
-- @treturn table Array of childrens if the Druid component instance
|
||||
function BaseComponent.get_childrens(self)
|
||||
---@return table Array of childrens if the Druid component instance
|
||||
function M:get_childrens()
|
||||
local childrens = {}
|
||||
|
||||
for i = 1, #self._meta.children do
|
||||
@@ -477,23 +364,21 @@ function BaseComponent.get_childrens(self)
|
||||
end
|
||||
|
||||
|
||||
--- Create new component. It will inheritance from basic Druid component.
|
||||
-- @function BaseComponent.create
|
||||
-- @tparam string name BaseComponent name
|
||||
-- @tparam number|nil input_priority The input priority. The bigger number processed first
|
||||
-- @local
|
||||
function BaseComponent.create(name, input_priority)
|
||||
---Сreate a new component class, which will inherit from the base Druid component.
|
||||
---@param name string|nil The name of the component
|
||||
---@param input_priority number|nil The input priority. The bigger number processed first. Default value: 10
|
||||
---@return druid.base_component
|
||||
function M.create(name, input_priority)
|
||||
local new_class = setmetatable({}, {
|
||||
__index = BaseComponent,
|
||||
__index = M,
|
||||
__call = function(cls, ...)
|
||||
local self = setmetatable({
|
||||
_component = {
|
||||
name = name,
|
||||
name = name or "Druid Component",
|
||||
input_priority = input_priority or const.PRIORITY_INPUT,
|
||||
default_input_priority = input_priority or const.PRIORITY_INPUT,
|
||||
is_debug = false,
|
||||
_is_input_priority_changed = true, -- Default true for sort once time after GUI init
|
||||
_uid = BaseComponent.create_uid()
|
||||
_uid = M.create_uid()
|
||||
}
|
||||
}, {
|
||||
__index = cls
|
||||
@@ -506,4 +391,4 @@ function BaseComponent.create(name, input_priority)
|
||||
end
|
||||
|
||||
|
||||
return BaseComponent
|
||||
return M
|
||||
|
||||
@@ -1,10 +1,4 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Druid constants
|
||||
-- @local
|
||||
-- @module DruidConst
|
||||
-- @alias druid_const
|
||||
|
||||
---@class druid.system.const
|
||||
local M = {}
|
||||
|
||||
M.ACTION_TEXT = hash(sys.get_config_string("druid.input_text", "text"))
|
||||
@@ -23,9 +17,6 @@ M.ACTION_LSHIFT = hash(sys.get_config_string("druid.input_key_lshift", "key_lshi
|
||||
M.ACTION_LCTRL = hash(sys.get_config_string("druid.input_key_lctrl", "key_lctrl"))
|
||||
M.ACTION_LCMD = hash(sys.get_config_string("druid.input_key_lsuper", "key_lsuper"))
|
||||
|
||||
M.IS_STENCIL_CHECK = not (sys.get_config_int("druid.no_stencil_check", 0) == 1)
|
||||
|
||||
|
||||
M.ON_INPUT = "on_input"
|
||||
M.ON_UPDATE = "update"
|
||||
M.ON_MESSAGE = "on_message"
|
||||
@@ -33,23 +24,28 @@ M.ON_LATE_INIT = "on_late_init"
|
||||
M.ON_FOCUS_LOST = "on_focus_lost"
|
||||
M.ON_FOCUS_GAINED = "on_focus_gained"
|
||||
M.ON_LAYOUT_CHANGE = "on_layout_change"
|
||||
M.ON_MESSAGE_INPUT = "on_message_input"
|
||||
M.ON_WINDOW_RESIZED = "on_window_resized"
|
||||
M.ON_LANGUAGE_CHANGE = "on_language_change"
|
||||
|
||||
M.ALL_INTERESTS = {
|
||||
M.ON_INPUT,
|
||||
M.ON_UPDATE,
|
||||
M.ON_MESSAGE,
|
||||
M.ON_LATE_INIT,
|
||||
M.ON_FOCUS_LOST,
|
||||
M.ON_FOCUS_GAINED,
|
||||
M.ON_LAYOUT_CHANGE,
|
||||
M.ON_WINDOW_RESIZED,
|
||||
M.ON_LANGUAGE_CHANGE,
|
||||
}
|
||||
|
||||
M.MSG_LAYOUT_CHANGED = hash("layout_changed")
|
||||
|
||||
-- Components with higher priority value processed first
|
||||
M.PRIORITY_INPUT = 10
|
||||
M.PRIORITY_INPUT_HIGH = 20
|
||||
M.PRIORITY_INPUT_MAX = 100
|
||||
|
||||
M.MESSAGE_INPUT = {
|
||||
BUTTON_CLICK = "button_click",
|
||||
BUTTON_LONG_CLICK = "button_long_click",
|
||||
BUTTON_DOUBLE_CLICK = "button_double_click",
|
||||
BUTTON_REPEATED_CLICK = "button_repeated_click",
|
||||
TEXT_SET = "text_set",
|
||||
}
|
||||
|
||||
M.PIVOTS = {
|
||||
[gui.PIVOT_CENTER] = vmath.vector3(0),
|
||||
[gui.PIVOT_N] = vmath.vector3(0, 0.5, 0),
|
||||
@@ -83,7 +79,6 @@ M.LAYOUT_MODE = {
|
||||
STRETCH = gui.ADJUST_STRETCH,
|
||||
}
|
||||
|
||||
M.VECTOR_ZERO = vmath.vector3(0)
|
||||
M.SYS_INFO = sys.get_sys_info()
|
||||
M.CURRENT_SYSTEM_NAME = M.SYS_INFO.system_name
|
||||
|
||||
@@ -104,10 +99,13 @@ M.SHIFT = {
|
||||
|
||||
M.TEXT_ADJUST = {
|
||||
DOWNSCALE = "downscale",
|
||||
TRIM = "trim",
|
||||
NO_ADJUST = "no_adjust",
|
||||
DOWNSCALE_LIMITED = "downscale_limited",
|
||||
SCROLL = "scroll",
|
||||
TRIM = "trim",
|
||||
TRIM_LEFT = "trim_left",
|
||||
SCALE_THEN_TRIM = "scale_then_trim",
|
||||
SCALE_THEN_TRIM_LEFT = "scale_then_trim_left",
|
||||
SCALE_THEN_SCROLL = "scale_then_scroll",
|
||||
}
|
||||
|
||||
@@ -116,17 +114,5 @@ M.SIDE = {
|
||||
Y = "y"
|
||||
}
|
||||
|
||||
M.SWIPE = {
|
||||
UP = "up",
|
||||
DOWN = "down",
|
||||
LEFT = "left",
|
||||
RIGHT = "right",
|
||||
}
|
||||
|
||||
M.ERRORS = {
|
||||
GRID_DYNAMIC_ANCHOR = "The pivot of dynamic grid node should be West, East, South or North"
|
||||
}
|
||||
|
||||
M.EMPTY_FUNCTION = function() end
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,371 +1,160 @@
|
||||
script: ""
|
||||
fonts {
|
||||
name: "game"
|
||||
font: "/example/assets/fonts/game.font"
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "kenney"
|
||||
texture: "/example/assets/images/kenney.atlas"
|
||||
}
|
||||
background_color {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "kenney/empty"
|
||||
id: "root"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
layer: ""
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 190.0
|
||||
y: 45.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 0.31
|
||||
y: 0.318
|
||||
z: 0.322
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "kenney/progress_back"
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
parent: "root"
|
||||
layer: ""
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
x: 4.0
|
||||
y: 4.0
|
||||
z: 4.0
|
||||
w: 4.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 300.0
|
||||
y: 60.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
x: 380.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.9490196
|
||||
y: 0.9490196
|
||||
z: 0.9490196
|
||||
w: 1.0
|
||||
x: 0.31
|
||||
y: 0.318
|
||||
z: 0.322
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
text: "Placeholder"
|
||||
font: "game"
|
||||
font: "druid_text_bold"
|
||||
id: "placeholder_text"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
outline {
|
||||
x: 0.4
|
||||
y: 0.4
|
||||
z: 0.4
|
||||
w: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
line_break: false
|
||||
parent: "button"
|
||||
layer: ""
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
alpha: 1.0
|
||||
outline_alpha: 1.0
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
template_node_child: false
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 0.6
|
||||
y: 0.6
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 300.0
|
||||
y: 60.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
x: 380.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
text: "User input"
|
||||
font: "game"
|
||||
font: "druid_text_bold"
|
||||
id: "input_text"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
outline {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
line_break: false
|
||||
parent: "button"
|
||||
layer: ""
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
alpha: 1.0
|
||||
outline_alpha: 1.0
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
template_node_child: false
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 67.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
x: 61.0
|
||||
}
|
||||
scale {
|
||||
x: 0.6
|
||||
y: 0.6
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
x: 16.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: 0.631
|
||||
y: 0.843
|
||||
z: 0.961
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "kenney/empty"
|
||||
texture: "druid/ui_circle_16"
|
||||
id: "cursor_node"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
parent: "button"
|
||||
layer: ""
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
z: 8.0
|
||||
w: 8.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
alpha: 0.5
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 2.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
x: -1.4
|
||||
y: 4.0
|
||||
}
|
||||
size {
|
||||
x: 20.0
|
||||
y: 40.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 0.2
|
||||
y: 0.2
|
||||
z: 0.2
|
||||
w: 1.0
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
text: "|"
|
||||
font: "game"
|
||||
font: "druid_text_bold"
|
||||
id: "cursor_text"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
outline {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
line_break: false
|
||||
parent: "cursor_node"
|
||||
layer: ""
|
||||
inherit_alpha: true
|
||||
alpha: 1.0
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
template_node_child: false
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
max_nodes: 512
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
-- @alias druid.rich_input
|
||||
|
||||
--- The component druid instance
|
||||
-- @tfield DruidInstance druid @{DruidInstance}
|
||||
-- @tfield DruidInstance druid DruidInstance
|
||||
|
||||
--- Root node
|
||||
-- @tfield node root
|
||||
|
||||
--- On input field text change callback(self, input_text)
|
||||
-- @tfield Input input @{Input}
|
||||
-- @tfield Input input Input
|
||||
|
||||
--- On input field text change to empty string callback(self, input_text)
|
||||
-- @tfield node cursor
|
||||
@@ -43,17 +43,22 @@ local const = require("druid.const")
|
||||
local utf8_lua = require("druid.system.utf8")
|
||||
local utf8 = utf8 or utf8_lua
|
||||
|
||||
local input = require("druid.extended.input")
|
||||
local RichInput = component.create("druid.rich_input")
|
||||
---@class druid.rich_input: druid.base_component
|
||||
---@field root node
|
||||
---@field input druid.input
|
||||
---@field cursor node
|
||||
---@field cursor_text node
|
||||
---@field cursor_position vector3
|
||||
local M = component.create("druid.rich_input")
|
||||
|
||||
local SCHEME = {
|
||||
ROOT = "root",
|
||||
BUTTON = "button",
|
||||
PLACEHOLDER = "placeholder_text",
|
||||
INPUT = "input_text",
|
||||
CURSOR = "cursor_node",
|
||||
CURSOR_TEXT = "cursor_text",
|
||||
}
|
||||
--local SCHEME = {
|
||||
-- ROOT = "root",
|
||||
-- BUTTON = "button",
|
||||
-- PLACEHOLDER = "placeholder_text",
|
||||
-- INPUT = "input_text",
|
||||
-- CURSOR = "cursor_node",
|
||||
-- CURSOR_TEXT = "cursor_text",
|
||||
--}
|
||||
|
||||
local DOUBLE_CLICK_TIME = 0.35
|
||||
|
||||
@@ -189,13 +194,11 @@ local function on_drag_callback(self, dx, dy, x, y, touch)
|
||||
end
|
||||
|
||||
|
||||
--- The @{RichInput} constructor
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
-- @tparam string template The template string name
|
||||
-- @tparam table nodes Nodes table from gui.clone_tree
|
||||
function RichInput.init(self, template, nodes)
|
||||
---@param template string The template string name
|
||||
---@param nodes table Nodes table from gui.clone_tree
|
||||
function M:init(template, nodes)
|
||||
self.druid = self:get_druid(template, nodes)
|
||||
self.root = self:get_node(SCHEME.ROOT)
|
||||
self.root = self:get_node("root")
|
||||
|
||||
self._last_touch_info = {
|
||||
cursor_index = nil,
|
||||
@@ -204,20 +207,20 @@ function RichInput.init(self, template, nodes)
|
||||
self.is_lshift = false
|
||||
self.is_lctrl = false
|
||||
|
||||
self.input = self.druid:new(input, self:get_node(SCHEME.BUTTON), self:get_node(SCHEME.INPUT))
|
||||
self.input = self.druid:new_input("button", "input_text")
|
||||
self.is_button_input_enabled = gui.is_enabled(self.input.button.node)
|
||||
|
||||
self.cursor = self:get_node(SCHEME.CURSOR)
|
||||
self.cursor = self:get_node("cursor_node")
|
||||
self.cursor_position = gui.get_position(self.cursor)
|
||||
self.cursor_text = self:get_node(SCHEME.CURSOR_TEXT)
|
||||
self.cursor_text = self:get_node("cursor_text")
|
||||
|
||||
self.drag = self.druid:new_drag(self:get_node(SCHEME.BUTTON), on_drag_callback)
|
||||
self.drag = self.druid:new_drag("button", on_drag_callback)
|
||||
self.drag.on_touch_start:subscribe(on_touch_start_callback)
|
||||
self.drag:set_input_priority(const.PRIORITY_INPUT_MAX + 1)
|
||||
self.drag:set_enabled(false)
|
||||
|
||||
self.input:set_text("")
|
||||
self.placeholder = self.druid:new_text(self:get_node(SCHEME.PLACEHOLDER))
|
||||
self.placeholder = self.druid:new_text("placeholder_text")
|
||||
self.text_position = gui.get_position(self.input.text.node)
|
||||
|
||||
self.input.on_input_text:subscribe(update_text)
|
||||
@@ -230,7 +233,7 @@ function RichInput.init(self, template, nodes)
|
||||
end
|
||||
|
||||
|
||||
function RichInput.on_input(self, action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
if action_id == const.ACTION_LSHIFT then
|
||||
if action.pressed then
|
||||
self.is_lshift = true
|
||||
@@ -247,37 +250,38 @@ function RichInput.on_input(self, action_id, action)
|
||||
end
|
||||
end
|
||||
|
||||
if action_id == const.ACTION_LEFT and (action.pressed or action.repeated) then
|
||||
self.input:move_selection(-1, self.is_lshift, self.is_lctrl)
|
||||
end
|
||||
if self.input.is_selected then
|
||||
if action_id == const.ACTION_LEFT and (action.pressed or action.repeated) then
|
||||
self.input:move_selection(-1, self.is_lshift, self.is_lctrl)
|
||||
return true
|
||||
end
|
||||
|
||||
if action_id == const.ACTION_RIGHT and (action.pressed or action.repeated) then
|
||||
self.input:move_selection(1, self.is_lshift, self.is_lctrl)
|
||||
if action_id == const.ACTION_RIGHT and (action.pressed or action.repeated) then
|
||||
self.input:move_selection(1, self.is_lshift, self.is_lctrl)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set placeholder text
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
-- @tparam string placeholder_text The placeholder text
|
||||
function RichInput.set_placeholder(self, placeholder_text)
|
||||
self.placeholder:set_to(placeholder_text)
|
||||
---@param placeholder_text string The placeholder text
|
||||
function M:set_placeholder(placeholder_text)
|
||||
self.placeholder:set_text(placeholder_text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Select input field
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
function RichInput.select(self)
|
||||
function M:select()
|
||||
self.input:select()
|
||||
end
|
||||
|
||||
|
||||
--- Set input field text
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
-- @treturn druid.input Current input instance
|
||||
-- @tparam string text The input text
|
||||
function RichInput.set_text(self, text)
|
||||
---@param text string The input text
|
||||
---@return druid.rich_input self Current instance
|
||||
function M:set_text(text)
|
||||
self.input:set_text(text)
|
||||
gui.set_enabled(self.placeholder.node, true and #self.input:get_text() == 0)
|
||||
|
||||
@@ -286,10 +290,9 @@ end
|
||||
|
||||
|
||||
--- Set input field font
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
-- @tparam hash font The font hash
|
||||
-- @treturn druid.input Current input instance
|
||||
function RichInput.set_font(self, font)
|
||||
---@param font hash The font hash
|
||||
---@return druid.rich_input self Current instance
|
||||
function M:set_font(font)
|
||||
gui.set_font(self.input.text.node, font)
|
||||
gui.set_font(self.placeholder.node, font)
|
||||
|
||||
@@ -298,8 +301,7 @@ end
|
||||
|
||||
|
||||
--- Set input field text
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
function RichInput.get_text(self)
|
||||
function M:get_text()
|
||||
return self.input:get_text()
|
||||
end
|
||||
|
||||
@@ -307,14 +309,13 @@ end
|
||||
--- Set allowed charaters for input field.
|
||||
-- See: https://defold.com/ref/stable/string/
|
||||
-- ex: [%a%d] for alpha and numeric
|
||||
-- @tparam RichInput self @{RichInput}
|
||||
-- @tparam string characters Regulax exp. for validate user input
|
||||
-- @treturn RichInput Current instance
|
||||
function RichInput.set_allowed_characters(self, characters)
|
||||
---@param characters string Regulax exp. for validate user input
|
||||
---@return druid.rich_input Current instance
|
||||
function M:set_allowed_characters(characters)
|
||||
self.input:set_allowed_characters(characters)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return RichInput
|
||||
return M
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
-- Author: Britzl
|
||||
-- Modified by: Insality
|
||||
|
||||
--- RT
|
||||
-- @module rich_text.rt
|
||||
-- @local
|
||||
|
||||
local helper = require("druid.helper")
|
||||
local parser = require("druid.custom.rich_text.module.rt_parse")
|
||||
local utf8_lua = require("druid.system.utf8")
|
||||
@@ -185,7 +181,7 @@ function M.create(text, settings, style)
|
||||
outline = settings.outline,
|
||||
font = gui.get_font(settings.text_prefab),
|
||||
-- Image params
|
||||
---@type druid.rich_text.image
|
||||
---@type druid.rich_text.word.image
|
||||
image = nil,
|
||||
-- Tags
|
||||
br = nil,
|
||||
@@ -428,7 +424,7 @@ function M._update_nodes(lines, settings)
|
||||
gui.set_outline(node, word.outline)
|
||||
gui.set_shadow(node, word.shadow)
|
||||
gui.set_text(node, word.text)
|
||||
gui.set_color(node, word.color or word.text_color)
|
||||
gui.set_color(node, word.color)
|
||||
gui.set_font(node, word.font or settings.font)
|
||||
end
|
||||
word.node = node
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
-- @alias druid.rich_text
|
||||
|
||||
--- The component druid instance
|
||||
-- @tfield DruidInstance druid @{DruidInstance}
|
||||
-- @tfield DruidInstance druid DruidInstance
|
||||
|
||||
--- The root node of the Rich Text
|
||||
-- @tfield node root
|
||||
@@ -76,14 +76,83 @@
|
||||
local component = require("druid.component")
|
||||
local rich_text = require("druid.custom.rich_text.module.rt")
|
||||
|
||||
local RichText = component.create("rich_text")
|
||||
---@class druid.rich_text.settings
|
||||
---@field parent node
|
||||
---@field size number
|
||||
---@field fonts table<string, string>
|
||||
---@field scale vector3
|
||||
---@field color vector4
|
||||
---@field shadow vector4
|
||||
---@field outline vector4
|
||||
---@field position vector3
|
||||
---@field image_pixel_grid_snap boolean
|
||||
---@field combine_words boolean
|
||||
---@field default_animation string
|
||||
---@field text_prefab node
|
||||
---@field adjust_scale number
|
||||
---@field default_texture string
|
||||
---@field is_multiline boolean
|
||||
---@field text_leading number
|
||||
---@field font hash
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class druid.rich_text.word
|
||||
---@field node node
|
||||
---@field relative_scale number
|
||||
---@field source_text string
|
||||
---@field color vector4
|
||||
---@field position vector3
|
||||
---@field offset vector3
|
||||
---@field scale vector3
|
||||
---@field size vector3
|
||||
---@field metrics druid.rich_text.metrics
|
||||
---@field pivot userdata
|
||||
---@field text string
|
||||
---@field shadow vector4
|
||||
---@field outline vector4
|
||||
---@field font string
|
||||
---@field image druid.rich_text.word.image
|
||||
---@field br boolean
|
||||
---@field nobr boolean
|
||||
|
||||
---@class druid.rich_text.word.image
|
||||
---@field texture string
|
||||
---@field anim string
|
||||
---@field width number
|
||||
---@field height number
|
||||
|
||||
---@class druid.rich_text.style
|
||||
---@field COLORS table<string, vector4>
|
||||
---@field ADJUST_STEPS number
|
||||
---@field ADJUST_SCALE_DELTA number
|
||||
---@field ADJUST_TYPE string
|
||||
---@field ADJUST_SCALE number
|
||||
|
||||
---@class druid.rich_text.lines_metrics
|
||||
---@field text_width number
|
||||
---@field text_height number
|
||||
---@field lines table<number, druid.rich_text.metrics>
|
||||
|
||||
---@class druid.rich_text.metrics
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field offset_x number|nil
|
||||
---@field offset_y number|nil
|
||||
---@field node_size vector3|nil
|
||||
|
||||
---@class druid.rich_text: druid.base_component
|
||||
---@field root node
|
||||
---@field text_prefab node
|
||||
---@field private _last_value string
|
||||
---@field private _settings table
|
||||
local M = component.create("rich_text")
|
||||
|
||||
|
||||
--- The @{RichText} constructor
|
||||
-- @tparam RichText self @{RichText}
|
||||
-- @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)
|
||||
--- The RichText constructor
|
||||
---@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 M:init(text_node, value)
|
||||
self.root = self:get_node(text_node)
|
||||
self.text_prefab = self.root
|
||||
|
||||
@@ -98,7 +167,7 @@ function RichText.init(self, text_node, value)
|
||||
end
|
||||
|
||||
|
||||
function RichText.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
if self._last_value then
|
||||
self:set_text(self._last_value)
|
||||
end
|
||||
@@ -112,7 +181,7 @@ end
|
||||
-- @tfield table|nil COLORS Rich Text color aliases. Default: {}
|
||||
-- @tfield number|nil ADJUST_STEPS Amount steps of attemps text adjust by height. Default: 20
|
||||
-- @tfield number|nil ADJUST_SCALE_DELTA Scale step on each height adjust step. Default: 0.02
|
||||
function RichText.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.COLORS = style.COLORS or {}
|
||||
self.style.ADJUST_STEPS = style.ADJUST_STEPS or 20
|
||||
@@ -121,10 +190,9 @@ end
|
||||
|
||||
|
||||
--- Set text for Rich Text
|
||||
-- @tparam RichText self @{RichText}
|
||||
-- @tparam string|nil text The text to set
|
||||
-- @treturn druid.rich_text.word[] words
|
||||
-- @treturn druid.rich_text.lines_metrics line_metrics
|
||||
---@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
|
||||
--
|
||||
@@ -168,7 +236,7 @@ end
|
||||
-- <img=texture:image/>
|
||||
-- <img=texture:image,size/>
|
||||
-- <img=texture:image,width,height/>
|
||||
function RichText.set_text(self, text)
|
||||
function M:set_text(text)
|
||||
text = text or ""
|
||||
self:clear()
|
||||
self._last_value = text
|
||||
@@ -184,14 +252,13 @@ end
|
||||
|
||||
|
||||
--- Get current text
|
||||
-- @tparam RichText self @{RichText}
|
||||
-- @treturn string text
|
||||
function RichText.get_text(self)
|
||||
---@return string text
|
||||
function M:get_text()
|
||||
return self._last_value
|
||||
end
|
||||
|
||||
|
||||
function RichText:on_remove()
|
||||
function M:on_remove()
|
||||
gui.set_scale(self.root, self._default_scale)
|
||||
gui.set_size(self.root, self._default_size)
|
||||
self:clear()
|
||||
@@ -199,7 +266,7 @@ end
|
||||
|
||||
|
||||
--- Clear all created words.
|
||||
function RichText:clear()
|
||||
function M:clear()
|
||||
if self._words then
|
||||
rich_text.remove(self._words)
|
||||
self._words = nil
|
||||
@@ -209,12 +276,11 @@ end
|
||||
|
||||
|
||||
--- Get all words, which has a passed tag.
|
||||
-- @tparam RichText self @{RichText}
|
||||
-- @tparam string tag
|
||||
-- @treturn druid.rich_text.word[] words
|
||||
function RichText.tagged(self, tag)
|
||||
---@param tag string
|
||||
---@return druid.rich_text.word[] words
|
||||
function M:tagged(tag)
|
||||
if not self._words then
|
||||
return
|
||||
return {}
|
||||
end
|
||||
|
||||
return rich_text.tagged(self._words, tag)
|
||||
@@ -222,29 +288,28 @@ end
|
||||
|
||||
|
||||
---Split a word into it's characters
|
||||
-- @tparam RichText self @{RichText}
|
||||
-- @tparam druid.rich_text.word word
|
||||
-- @treturn druid.rich_text.word[] characters
|
||||
function RichText.characters(self, word)
|
||||
---@param word druid.rich_text.word
|
||||
---@return druid.rich_text.word[] characters
|
||||
function M:characters(word)
|
||||
return rich_text.characters(word)
|
||||
end
|
||||
|
||||
|
||||
--- Get all current words.
|
||||
-- @treturn table druid.rich_text.word[]
|
||||
function RichText:get_words()
|
||||
---@return druid.rich_text.word[]
|
||||
function M:get_words()
|
||||
return self._words
|
||||
end
|
||||
|
||||
|
||||
--- Get current line metrics
|
||||
--- @treturn druid.rich_text.lines_metrics
|
||||
function RichText:get_line_metric()
|
||||
----@return druid.rich_text.lines_metrics
|
||||
function M:get_line_metric()
|
||||
return self._line_metrics
|
||||
end
|
||||
|
||||
|
||||
function RichText:_create_settings()
|
||||
function M:_create_settings()
|
||||
local root_size = gui.get_size(self.root)
|
||||
local scale = gui.get_scale(self.root)
|
||||
|
||||
@@ -280,4 +345,4 @@ function RichText:_create_settings()
|
||||
end
|
||||
|
||||
|
||||
return RichText
|
||||
return M
|
||||
|
||||
@@ -19,4 +19,13 @@ images {
|
||||
images {
|
||||
image: "/druid/images/pixel.png"
|
||||
}
|
||||
images {
|
||||
image: "/druid/images/panels/rect_round2_width2.png"
|
||||
}
|
||||
images {
|
||||
image: "/druid/images/icons/icon_drag.png"
|
||||
}
|
||||
images {
|
||||
image: "/druid/images/icons/icon_arrow.png"
|
||||
}
|
||||
extrude_borders: 2
|
||||
|
||||
215
druid/druid.lua
215
druid/druid.lua
@@ -1,115 +1,18 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Druid UI Component Framework.
|
||||
-- <b># Overview #</b>
|
||||
--
|
||||
-- Druid - powerful Defold component UI library. Use basic and extended
|
||||
-- Druid components or make your own game-specific components to make
|
||||
-- amazing GUI in your games.
|
||||
--
|
||||
-- To start using Druid, please refer to the Usage section below.
|
||||
--
|
||||
-- <b># Notes #</b>
|
||||
--
|
||||
-- • Each Druid instance maintains the self context from the constructor and passes it to each Druid callback.
|
||||
--
|
||||
-- See next: @{DruidInstance}
|
||||
--
|
||||
-- @usage
|
||||
-- local druid = require("druid.druid")
|
||||
--
|
||||
-- local function on_play(self)
|
||||
-- print("Gonna play!")
|
||||
-- end
|
||||
--
|
||||
-- function init(self)
|
||||
-- self.druid = druid.new(self)
|
||||
-- self.druid:new_button("button_play", on_play)
|
||||
-- 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
|
||||
--
|
||||
-- @module Druid
|
||||
|
||||
local const = require("druid.const")
|
||||
local base_component = require("druid.component")
|
||||
local events = require("event.events")
|
||||
local settings = require("druid.system.settings")
|
||||
local druid_instance = require("druid.system.druid_instance")
|
||||
|
||||
local default_style = require("druid.styles.default.style")
|
||||
|
||||
---@class druid
|
||||
local M = {}
|
||||
|
||||
local _instances = {}
|
||||
|
||||
|
||||
local function clean_deleted_druid_instances()
|
||||
for i = #_instances, 1, -1 do
|
||||
if _instances[i]._deleted then
|
||||
table.remove(_instances, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function get_druid_instances()
|
||||
clean_deleted_druid_instances()
|
||||
return _instances
|
||||
end
|
||||
|
||||
|
||||
--- Register a new external Druid component.
|
||||
--
|
||||
-- You can register your own components to make new alias: the druid:new_{name} function.
|
||||
-- For example, if you want to register a component called "my_component", you can create it using druid:new_my_component(...).
|
||||
-- This can be useful if you have your own "basic" components that you don't want to re-create each time.
|
||||
-- @function druid.register
|
||||
-- @tparam string name module name
|
||||
-- @tparam table module lua table with component
|
||||
-- @usage
|
||||
-- local my_component = require("path.to.my.component")
|
||||
-- druid.register("my_component", my_component)
|
||||
-- ...
|
||||
-- local druid = druid.new(self)
|
||||
-- local component_instance = self.druid:new_my_component(...)
|
||||
function M.register(name, module)
|
||||
druid_instance["new_" .. name] = function(self, ...)
|
||||
return druid_instance.new(self, module, ...)
|
||||
end
|
||||
|
||||
return druid_instance["new_" .. name]
|
||||
end
|
||||
|
||||
|
||||
--- Create a new Druid instance for creating GUI components.
|
||||
--
|
||||
-- @function druid.new
|
||||
-- @tparam table context The Druid context. Usually, this is the self of the gui_script. It is passed into all Druid callbacks.
|
||||
-- @tparam table|nil style The Druid style table to override style parameters for this Druid instance.
|
||||
-- @treturn druid_instance The Druid instance @{DruidInstance}.
|
||||
-- @usage
|
||||
-- local druid = require("druid.druid")
|
||||
--
|
||||
-- function init(self)
|
||||
-- self.druid = druid.new(self)
|
||||
-- end
|
||||
---Create a new Druid instance for creating GUI components.
|
||||
---@param context table The Druid context. Usually, this is the self of the gui_script. It is passed into all Druid callbacks.
|
||||
---@param style table|nil The Druid style table to override style parameters for this Druid instance.
|
||||
---@return druid_instance druid_instance The new Druid instance
|
||||
function M.new(context, style)
|
||||
clean_deleted_druid_instances()
|
||||
|
||||
if settings.default_style == nil then
|
||||
M.set_default_style(default_style)
|
||||
end
|
||||
@@ -117,96 +20,66 @@ function M.new(context, style)
|
||||
local new_instance = setmetatable({}, { __index = druid_instance })
|
||||
new_instance:initialize(context, style)
|
||||
|
||||
table.insert(_instances, new_instance)
|
||||
return new_instance
|
||||
end
|
||||
|
||||
|
||||
--- Set your own default style for all Druid instances.
|
||||
--
|
||||
-- To create your own style file, copy the default style file and make changes to it.
|
||||
-- Register the new style before creating your Druid instances.
|
||||
-- @function druid.set_default_style
|
||||
-- @tparam table style Druid style module
|
||||
-- @usage
|
||||
-- local my_style = require("path.to.my.style")
|
||||
-- druid.set_default_style(my_style)
|
||||
---Register a new external Druid component.
|
||||
---Register component just makes the druid:new_{name} function.
|
||||
---For example, if you register a component called "my_component", you can create it using druid:new_my_component(...).
|
||||
---This can be useful if you have your own "basic" components that you don't want to require in every file.
|
||||
---The default way to create component is `druid_instance:new(component_class, ...)`.
|
||||
---@param name string Module name
|
||||
---@param module table Lua table with component
|
||||
function M.register(name, module)
|
||||
druid_instance["new_" .. name] = function(self, ...)
|
||||
return druid_instance.new(self, module, ...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Set the default style for all Druid instances.
|
||||
---@param style table Default style
|
||||
function M.set_default_style(style)
|
||||
settings.default_style = style or {}
|
||||
end
|
||||
|
||||
|
||||
--- Set the text function for the LangText component.
|
||||
--
|
||||
-- The Druid locale component will call this function to get translated text.
|
||||
-- After setting the text function, all existing locale components will be updated.
|
||||
-- @function druid.set_text_function
|
||||
-- @tparam function callback Get localized text function
|
||||
-- @usage
|
||||
-- druid.set_text_function(function(text_id)
|
||||
-- return lang_data[text_id] -- Replace with your real function
|
||||
-- end)
|
||||
---Set the text function for the LangText component.
|
||||
---@param callback fun(text_id: string): string Get localized text function
|
||||
function M.set_text_function(callback)
|
||||
settings.get_text = callback or const.EMPTY_FUNCTION
|
||||
settings.get_text = callback or function() end
|
||||
M.on_language_change()
|
||||
end
|
||||
|
||||
|
||||
--- Set the Druid sound function to play UI sounds if used.
|
||||
--
|
||||
-- Set a function to play a sound given a sound_id. This function is used for button clicks to play the "click" sound.
|
||||
-- It can also be used to play sounds in your custom components (see the default Druid style file for an example).
|
||||
-- @function druid.set_sound_function
|
||||
-- @tparam function callback Sound play callback
|
||||
-- @usage
|
||||
-- druid.set_sound_function(function(sound_id)
|
||||
-- sound.play(sound_id) -- Replace with your real function
|
||||
-- end)
|
||||
---Set the sound function to able components to play sounds.
|
||||
---@param callback fun(sound_id: string) Sound play callback
|
||||
function M.set_sound_function(callback)
|
||||
settings.play_sound = callback or const.EMPTY_FUNCTION
|
||||
settings.play_sound = callback or function() end
|
||||
end
|
||||
|
||||
|
||||
--- Set the window callback to enable on_focus_gain and on_focus_lost functions.
|
||||
--
|
||||
-- This is used to trigger the on_focus_lost and on_focus_gain functions in Druid components.
|
||||
-- @function druid.on_window_callback
|
||||
-- @tparam string event Event param from window listener
|
||||
-- @usage
|
||||
-- window.set_listener(function(_, event)
|
||||
-- druid.on_window_callback(event)
|
||||
-- end)
|
||||
function M.on_window_callback(event)
|
||||
local instances = get_druid_instances()
|
||||
|
||||
if event == window.WINDOW_EVENT_FOCUS_LOST then
|
||||
for i = 1, #instances do
|
||||
msg.post(instances[i].url, base_component.ON_FOCUS_LOST)
|
||||
end
|
||||
elseif event == window.WINDOW_EVENT_FOCUS_GAINED then
|
||||
for i = 1, #instances do
|
||||
msg.post(instances[i].url, base_component.ON_FOCUS_GAINED)
|
||||
end
|
||||
elseif event == window.WINDOW_EVENT_RESIZED then
|
||||
for i = 1, #instances do
|
||||
msg.post(instances[i].url, base_component.ON_WINDOW_RESIZED)
|
||||
end
|
||||
end
|
||||
---Subscribe Druid to the window listener. It will override your previous
|
||||
---window listener, so if you have one, you should call M.on_window_callback manually.
|
||||
function M.init_window_listener()
|
||||
window.set_listener(function(_, window_event)
|
||||
events.trigger("druid.window_event", window_event)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
--- Call this function when the game language changes.
|
||||
--
|
||||
-- This function will translate all current LangText components.
|
||||
-- @function druid.on_language_change
|
||||
-- @usage
|
||||
-- druid.on_language_change()
|
||||
---Set the window callback to enable Druid window events.
|
||||
---@param window_event constant Event param from window listener
|
||||
function M.on_window_callback(window_event)
|
||||
events.trigger("druid.window_event", window_event)
|
||||
end
|
||||
|
||||
|
||||
---Call this function when the game language changes.
|
||||
---It will notify all Druid instances to update the lang text components.
|
||||
function M.on_language_change()
|
||||
local instances = get_druid_instances()
|
||||
|
||||
for i = 1, #instances do
|
||||
msg.post(instances[i].url, base_component.ON_LANGUAGE_CHANGE)
|
||||
end
|
||||
events.trigger("druid.language_change")
|
||||
end
|
||||
|
||||
|
||||
|
||||
37
druid/druid.script
Normal file
37
druid/druid.script
Normal file
@@ -0,0 +1,37 @@
|
||||
-- Place this script nearby with the gui component to able make requests
|
||||
-- To the go namespace from GUI with events systems (cross context)
|
||||
|
||||
local event_queue = require("druid.event_queue")
|
||||
|
||||
---Usage: event_queue.request("druid.get_atlas_path", callback, gui.get_texture(self.node), msg.url())
|
||||
---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"
|
||||
|
||||
|
||||
---@param texture_name hash The name from gui.get_texture(node)
|
||||
---@param sender hash Just msg.url from the caller
|
||||
local function get_atlas_path(texture_name, sender)
|
||||
local my_url = msg.url()
|
||||
my_url.fragment = nil
|
||||
|
||||
local copy_url = msg.url(sender)
|
||||
copy_url.fragment = nil
|
||||
|
||||
-- This check should works well
|
||||
local is_my_url = my_url == copy_url
|
||||
if not is_my_url then
|
||||
return nil
|
||||
end
|
||||
|
||||
return go.get(sender, "textures", { key = texture_name })
|
||||
end
|
||||
|
||||
|
||||
function init(self)
|
||||
event_queue.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path)
|
||||
end
|
||||
|
||||
|
||||
function final(self)
|
||||
event_queue.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path)
|
||||
end
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
local component = require("druid.component")
|
||||
|
||||
---@class {COMPONENT_TYPE}: druid.component
|
||||
---@class {COMPONENT_TYPE}: druid.base_component
|
||||
---@field druid druid_instance{COMPONENT_ANNOTATIONS}
|
||||
local M = component.create("{COMPONENT_TYPE}")
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
# @license MIT, Insality 2021
|
||||
# @source https://github.com/Insality/druid
|
||||
|
||||
import os
|
||||
import sys
|
||||
import deftree
|
||||
|
||||
current_filepath = os.path.abspath(os.path.dirname(__file__))
|
||||
TEMPLATE_PATH = current_filepath + "/component.lua_template"
|
||||
TEMPLATE_PATH = current_filepath + "/widget.lua_template"
|
||||
|
||||
component_annotations = ""
|
||||
component_functions = ""
|
||||
@@ -44,13 +41,9 @@ def process_component(node_name, component_name):
|
||||
component_define += "\n\tself.{0} = self.druid:new_lang_text(\"{1}\", \"lang_id\")".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("grid") or node_name.startswith("static_grid"):
|
||||
component_annotations += "\n---@field {0} druid.static_grid".format(node_name)
|
||||
component_annotations += "\n---@field {0} druid.grid".format(node_name)
|
||||
component_define += "\n--TODO: Replace prefab_name with grid element prefab"
|
||||
component_define += "\n\tself.{0} = self.druid:new_static_grid(\"{1}\", \"prefab_name\", 1)".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("dynamic_grid"):
|
||||
component_annotations += "\n---@field {0} druid.dynamic_grid".format(node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_dynamic_grid(\"{1}\")".format(node_name, node_name)
|
||||
component_define += "\n\tself.{0} = self.druid:new_grid(\"{1}\", \"prefab_name\", 1)".format(node_name, node_name)
|
||||
|
||||
if node_name.startswith("scroll_view"):
|
||||
field_name = node_name.replace("_view", "")
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
--- @license MIT, Insality 2021
|
||||
--- @source https://github.com/Insality/druid
|
||||
|
||||
local M = {}
|
||||
|
||||
|
||||
@@ -27,18 +24,12 @@ function M.get_commands()
|
||||
return {
|
||||
{
|
||||
label = "Assign Layers",
|
||||
|
||||
locations = {"Edit"},
|
||||
|
||||
query = {
|
||||
selection = {type = "resource", cardinality = "one"}
|
||||
},
|
||||
|
||||
locations = { "Edit" },
|
||||
query = { selection = {type = "resource", cardinality = "one"} },
|
||||
active = function(opts)
|
||||
local path = editor.get(opts.selection, "path")
|
||||
return ends_with(path, ".gui")
|
||||
end,
|
||||
|
||||
run = function(opts)
|
||||
local file = opts.selection
|
||||
print("Run script for", editor.get(file, "path"))
|
||||
@@ -59,25 +50,19 @@ function M.get_commands()
|
||||
},
|
||||
|
||||
{
|
||||
label = "Create Druid Component",
|
||||
|
||||
locations = {"Edit"},
|
||||
|
||||
query = {
|
||||
selection = {type = "resource", cardinality = "one"}
|
||||
},
|
||||
|
||||
label = "Create Druid Widget",
|
||||
locations = { "Edit", "Assets" },
|
||||
query = { selection = {type = "resource", cardinality = "one"} },
|
||||
active = function(opts)
|
||||
local path = editor.get(opts.selection, "path")
|
||||
return ends_with(path, ".gui")
|
||||
end,
|
||||
|
||||
run = function(opts)
|
||||
local file = opts.selection
|
||||
print("Run script for", editor.get(file, "path"))
|
||||
save_file_from_dependency('/druid/editor_scripts/run_python_script_on_gui.sh', "./build/run_python_script_on_gui.sh")
|
||||
save_file_from_dependency('/druid/editor_scripts/create_druid_component.py', "./build/create_druid_component.py")
|
||||
save_file_from_dependency('/druid/editor_scripts/component.lua_template', "./build/component.lua_template")
|
||||
save_file_from_dependency('/druid/editor_scripts/widget.lua_template', "./build/widget.lua_template")
|
||||
return {
|
||||
{
|
||||
action = "shell",
|
||||
|
||||
@@ -31,7 +31,7 @@ def main():
|
||||
texture = node.get_attribute("texture")
|
||||
font = node.get_attribute("font")
|
||||
|
||||
if texture:
|
||||
if texture and texture.value:
|
||||
layer = texture.value.split("/")[0]
|
||||
node.set_attribute("layer", layer)
|
||||
|
||||
|
||||
26
druid/editor_scripts/widget.lua_template
Normal file
26
druid/editor_scripts/widget.lua_template
Normal file
@@ -0,0 +1,26 @@
|
||||
---This is a template for a {COMPONENT_NAME} Druid widget.
|
||||
---Instantiate this template with `druid.new_widget(widget_module, [template_id], [nodes])`.
|
||||
---Read more about Druid Widgets here: ...
|
||||
|
||||
---@class widget.{COMPONENT_TYPE}: druid.widget
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
-- Now we have next functions to use here:
|
||||
-- self:get_node([node_id]) -- Get node inside widget by id
|
||||
-- self.druid to access Druid Instance API, like:
|
||||
-- self.druid:new_button([node_id], [callback])
|
||||
-- self.druid:new_text([node_id], [text])
|
||||
-- And all functions from component.lua file
|
||||
self.root = self:get_node("root")
|
||||
self.button = self.druid:new_button("button", self.on_button, self)
|
||||
end
|
||||
|
||||
|
||||
function M:on_button()
|
||||
print("Root node", self.root)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
205
druid/event.lua
205
druid/event.lua
@@ -1,205 +0,0 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Druid Event Module
|
||||
--
|
||||
-- The Event module provides a simple class for handling callbacks. It is used in many Druid components.
|
||||
--
|
||||
-- You can subscribe to an event using the `:subscribe` method and unsubscribe using the `:unsubscribe` method.
|
||||
-- @module DruidEvent
|
||||
-- @alias druid.event
|
||||
|
||||
local M = {}
|
||||
M.COUNTER = 0
|
||||
|
||||
-- Forward declaration
|
||||
local EVENT_METATABLE
|
||||
|
||||
-- Local versions
|
||||
local pcall = pcall
|
||||
local tinsert = table.insert
|
||||
local tremove = table.remove
|
||||
|
||||
--- DruidEvent constructor
|
||||
-- @tparam function|nil callback Subscribe the callback on new event, if callback exist
|
||||
-- @tparam any|nil callback_context Additional context as first param to callback call
|
||||
-- @usage
|
||||
-- local Event = require("druid.event")
|
||||
-- ...
|
||||
-- local event = Event(callback)
|
||||
function M.create(callback, callback_context)
|
||||
local instance = setmetatable({}, EVENT_METATABLE)
|
||||
|
||||
if callback then
|
||||
instance:subscribe(callback, callback_context)
|
||||
end
|
||||
|
||||
M.COUNTER = M.COUNTER + 1
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
--- Check is event subscribed.
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @tparam function callback Callback itself
|
||||
-- @tparam any|nil callback_context Additional context as first param to callback call
|
||||
-- @treturn boolean, number|nil @Is event subscribed, return index of callback in event as second param
|
||||
function M.is_subscribed(self, callback, callback_context)
|
||||
if #self == 0 then
|
||||
return false, nil
|
||||
end
|
||||
|
||||
for index = 1, #self do
|
||||
local cb = self[index]
|
||||
if cb[1] == callback and cb[2] == callback_context then
|
||||
return true, index
|
||||
end
|
||||
end
|
||||
|
||||
return false, nil
|
||||
end
|
||||
|
||||
|
||||
--- Subscribe callback on event
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @tparam function callback Callback itself
|
||||
-- @tparam any|nil callback_context Additional context as first param to callback call, usually it's self
|
||||
-- @treturn boolean True if callback was subscribed
|
||||
-- @usage
|
||||
-- local function on_long_callback(self)
|
||||
-- print("Long click!")
|
||||
-- end
|
||||
-- ...
|
||||
-- local button = self.druid:new_button("button", callback)
|
||||
-- button.on_long_click:subscribe(on_long_callback, self)
|
||||
function M.subscribe(self, callback, callback_context)
|
||||
assert(type(self) == "table", "You should subscribe to event with : syntax")
|
||||
assert(callback, "A function must be passed to subscribe to an event")
|
||||
|
||||
if self:is_subscribed(callback, callback_context) then
|
||||
return false
|
||||
end
|
||||
|
||||
tinsert(self, { callback, callback_context })
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--- Unsubscribe callback on event
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @tparam function callback Callback itself
|
||||
-- @tparam any|nil callback_context Additional context as first param to callback call
|
||||
-- @usage
|
||||
-- local function on_long_callback(self)
|
||||
-- print("Long click!")
|
||||
-- end
|
||||
-- ...
|
||||
-- button.on_long_click:unsubscribe(on_long_callback, self)
|
||||
function M.unsubscribe(self, callback, callback_context)
|
||||
assert(callback, "A function must be passed to subscribe to an event")
|
||||
|
||||
local _, event_index = self:is_subscribed(callback, callback_context)
|
||||
if not event_index then
|
||||
return false
|
||||
end
|
||||
|
||||
tremove(self, event_index)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
--- Return true, if event have at lease one handler
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @treturn boolean True if event have handlers
|
||||
-- @usage
|
||||
-- local is_long_click_handler_exists = button.on_long_click:is_exist()
|
||||
function M.is_exist(self)
|
||||
return #self > 0
|
||||
end
|
||||
|
||||
|
||||
--- Return true, if event not have handler
|
||||
--- @tparam DruidEvent self @{DruidEvent}
|
||||
--- @treturn boolean True if event not have handlers
|
||||
--- @usage
|
||||
--- local is_long_click_handler_not_exists = button.on_long_click:is_empty()
|
||||
function M:is_empty()
|
||||
return #self == 0
|
||||
end
|
||||
|
||||
|
||||
--- Clear the all event handlers
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @usage
|
||||
-- button.on_long_click:clear()
|
||||
function M.clear(self)
|
||||
for index = #self, 1, -1 do
|
||||
self[index] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Trigger the event and call all subscribed callbacks
|
||||
-- @tparam DruidEvent self @{DruidEvent}
|
||||
-- @tparam any ... All event params
|
||||
-- @usage
|
||||
-- local Event = require("druid.event")
|
||||
-- ...
|
||||
-- local event = Event()
|
||||
-- event:trigger("Param1", "Param2")
|
||||
function M.trigger(self, ...)
|
||||
if #self == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local result = nil
|
||||
|
||||
local call_callback = self.call_callback
|
||||
for index = 1, #self do
|
||||
result = call_callback(self, self[index], ...)
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
-- @tparam table callback Callback data {function, context}
|
||||
-- @tparam any ... All event params
|
||||
-- @treturn any Result of the callback
|
||||
-- @local
|
||||
function M:call_callback(callback, ...)
|
||||
local event_callback = callback[1]
|
||||
local event_callback_context = callback[2]
|
||||
|
||||
-- Call callback
|
||||
local ok, result_or_error
|
||||
if event_callback_context then
|
||||
ok, result_or_error = pcall(event_callback, event_callback_context, ...)
|
||||
else
|
||||
ok, result_or_error = pcall(event_callback, ...)
|
||||
end
|
||||
|
||||
-- Handle errors
|
||||
if not ok then
|
||||
local caller_info = debug.getinfo(2)
|
||||
pprint("An error occurred during event processing", {
|
||||
trigger = caller_info.short_src .. ":" .. caller_info.currentline,
|
||||
error = result_or_error,
|
||||
})
|
||||
pprint("Traceback", debug.traceback())
|
||||
return nil
|
||||
end
|
||||
|
||||
return result_or_error
|
||||
end
|
||||
|
||||
-- Construct event metatable
|
||||
EVENT_METATABLE = {
|
||||
__index = M,
|
||||
__call = M.trigger,
|
||||
}
|
||||
|
||||
return setmetatable(M, {
|
||||
__call = function(_, callback)
|
||||
return M.create(callback)
|
||||
end,
|
||||
})
|
||||
84
druid/event_queue.lua
Normal file
84
druid/event_queue.lua
Normal file
@@ -0,0 +1,84 @@
|
||||
local event = require("event.event")
|
||||
|
||||
---@class event.queue
|
||||
local M = {}
|
||||
|
||||
local event_handlers = {}
|
||||
local pending_callbacks = {}
|
||||
|
||||
|
||||
---Request to handle a specified event and processes the queue of callbacks associated with it.
|
||||
---If event has already been triggered, the callback will be executed immediately.
|
||||
---If event not triggered yet, callback will be executed when event will be triggered.
|
||||
---It triggered only once and then removed from the queue.
|
||||
---@param event_name string The name of the event to trigger.
|
||||
---@param callback fun() The callback function to execute upon triggering.
|
||||
---@param ... any Additional arguments for the callback.
|
||||
function M.request(event_name, callback, ...)
|
||||
pending_callbacks[event_name] = pending_callbacks[event_name] or {}
|
||||
table.insert(pending_callbacks[event_name], { event.create(callback), ... })
|
||||
|
||||
M.process_pending_callbacks(event_name)
|
||||
end
|
||||
|
||||
|
||||
---Subscribes to a specified event and executes a callback when the event is triggered.
|
||||
-- If the event has already been triggered, the callback will be executed immediately.
|
||||
---@param event_name string The name of the event to subscribe to.
|
||||
---@param callback fun() The function to call when the event is triggered.
|
||||
function M.subscribe(event_name, callback)
|
||||
event_handlers[event_name] = event_handlers[event_name] or event.create()
|
||||
|
||||
if event_handlers[event_name] then
|
||||
event_handlers[event_name]:subscribe(callback)
|
||||
end
|
||||
|
||||
M.process_pending_callbacks(event_name)
|
||||
end
|
||||
|
||||
|
||||
---Unsubscribes a callback function from a specified event.
|
||||
---@param event_name string The name of the event to unsubscribe from.
|
||||
---@param callback fun() The function to remove from the event's subscription list.
|
||||
function M.unsubscribe(event_name, callback)
|
||||
if event_handlers[event_name] then
|
||||
event_handlers[event_name]:unsubscribe(callback)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---Processes the queue for a given event name, executing callbacks and handling results.
|
||||
---Processed callbacks are removed from the queue.
|
||||
---@param event_name string The name of the event for which to process the queue.
|
||||
function M.process_pending_callbacks(event_name)
|
||||
local callbacks_to_process = pending_callbacks[event_name]
|
||||
local event_handler = event_handlers[event_name]
|
||||
|
||||
if not callbacks_to_process or not event_handler then
|
||||
return
|
||||
end
|
||||
|
||||
-- Loop through the queue in reverse to prevent index errors during removal
|
||||
for i = #callbacks_to_process, 1, -1 do
|
||||
local callback_entry = callbacks_to_process[i]
|
||||
-- Better to figure out how to make it without 2 unpacks, but ok for all our cases now
|
||||
local args = { unpack(callback_entry, 2) }
|
||||
|
||||
-- Safely call the event handler and handle errors
|
||||
local success, result = pcall(event_handler.trigger, event_handler, unpack(args))
|
||||
|
||||
if success and result then
|
||||
local callback_function = callback_entry[1]
|
||||
pcall(callback_function, result) -- Safely invoke the callback, catching any errors
|
||||
table.remove(callbacks_to_process, i) -- Remove the processed callback from the queue
|
||||
end
|
||||
end
|
||||
|
||||
-- Clean up if the callback queue is empty
|
||||
if #callbacks_to_process == 0 then
|
||||
pending_callbacks[event_name] = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
@@ -13,7 +13,7 @@
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
|
||||
---@class druid.container: druid.base_component
|
||||
---@field node node
|
||||
@@ -29,7 +29,7 @@ local Event = require("druid.event")
|
||||
---@field fit_size vector3
|
||||
---@field min_size_x number|nil
|
||||
---@field min_size_y number|nil
|
||||
---@field on_size_changed druid.event @function on_size_changed(size)
|
||||
---@field on_size_changed event @function on_size_changed(size)
|
||||
---@field _parent_container druid.container
|
||||
---@field _containers table
|
||||
---@field _draggable_corners table
|
||||
@@ -50,7 +50,7 @@ local CORNER_PIVOTS = {
|
||||
--- The Container init
|
||||
---@param node node Gui node
|
||||
---@param mode string Layout mode
|
||||
---@param callback fun(self: druid.container, size: vector3) Callback on size changed
|
||||
---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed
|
||||
function M:init(node, mode, callback)
|
||||
self.node = self:get_node(node)
|
||||
self.druid = self:get_druid()
|
||||
@@ -82,7 +82,7 @@ function M:init(node, mode, callback)
|
||||
gui.set_size_mode(self.node, gui.SIZE_MODE_MANUAL)
|
||||
gui.set_adjust_mode(self.node, gui.ADJUST_FIT)
|
||||
|
||||
self.on_size_changed = Event(callback)
|
||||
self.on_size_changed = event.create(callback)
|
||||
|
||||
self.pivot_offset = helper.get_pivot_offset(gui.get_pivot(self.node))
|
||||
self.center_offset = -vmath.vector3(self.size.x * self.pivot_offset.x, self.size.y * self.pivot_offset.y, 0)
|
||||
@@ -134,8 +134,9 @@ end
|
||||
--- Set new size of layout node
|
||||
---@param width number|nil
|
||||
---@param height number|nil
|
||||
---@return druid.container @{Container}
|
||||
function M:set_size(width, height)
|
||||
---@param anchor_pivot constant|nil If set will keep the corner possition relative to the new size
|
||||
---@return druid.container Container
|
||||
function M:set_size(width, height, anchor_pivot)
|
||||
width = width or self.size.x
|
||||
height = height or self.size.y
|
||||
|
||||
@@ -149,11 +150,23 @@ function M:set_size(width, height)
|
||||
if (width and width ~= self.size.x) or (height and height ~= self.size.y) then
|
||||
self.center_offset.x = -width * self.pivot_offset.x
|
||||
self.center_offset.y = -height * self.pivot_offset.y
|
||||
local dx = self.size.x - width
|
||||
local dy = self.size.y - height
|
||||
self.size.x = width
|
||||
self.size.y = height
|
||||
self.size.z = 0
|
||||
gui.set_size(self.node, self.size)
|
||||
|
||||
if anchor_pivot then
|
||||
local pivot = gui.get_pivot(self.node)
|
||||
local pivot_offset = helper.get_pivot_offset(pivot)
|
||||
local new_pivot_offset = helper.get_pivot_offset(anchor_pivot)
|
||||
|
||||
local position_dx = dx * (pivot_offset.x - new_pivot_offset.x)
|
||||
local position_dy = dy * (pivot_offset.y - new_pivot_offset.y)
|
||||
self:set_position(self._position.x + position_dx, self._position.y - position_dy)
|
||||
end
|
||||
|
||||
self:update_child_containers()
|
||||
self.on_size_changed:trigger(self:get_context(), self.size)
|
||||
end
|
||||
@@ -162,6 +175,11 @@ function M:set_size(width, height)
|
||||
end
|
||||
|
||||
|
||||
function M:get_position()
|
||||
return self._position
|
||||
end
|
||||
|
||||
|
||||
---@param pos_x number
|
||||
---@param pos_y number
|
||||
function M:set_position(pos_x, pos_y)
|
||||
@@ -178,7 +196,7 @@ end
|
||||
---Get current size of layout node
|
||||
---@return vector3 size
|
||||
function M:get_size()
|
||||
return self.size
|
||||
return vmath.vector3(self.size)
|
||||
end
|
||||
|
||||
|
||||
@@ -191,22 +209,22 @@ end
|
||||
|
||||
--- Set size for layout node to fit inside it
|
||||
---@param target_size vector3
|
||||
---@return druid.container @{Container}
|
||||
---@return druid.container Container
|
||||
function M:fit_into_size(target_size)
|
||||
self.fit_size = target_size
|
||||
self:refresh()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set current size for layout node to fit inside it
|
||||
---@return druid.container @{Container}
|
||||
---@return druid.container Container
|
||||
function M:fit_into_window()
|
||||
return self:fit_into_size(vmath.vector3(gui.get_width(), gui.get_height(), 0))
|
||||
end
|
||||
|
||||
|
||||
---@param self druid.container
|
||||
function M:on_window_resized()
|
||||
local x_koef, y_koef = helper.get_screen_aspect_koef()
|
||||
self.x_koef = x_koef
|
||||
@@ -221,7 +239,7 @@ end
|
||||
---@param node_or_container node|string|druid.container|table
|
||||
---@param mode string|nil stretch, fit, stretch_x, stretch_y. Default: Pick from node, "fit" or "stretch"
|
||||
---@param on_resize_callback fun(self: userdata, size: vector3)|nil
|
||||
---@return druid.container @{Container} New created layout instance
|
||||
---@return druid.container Container New created layout instance
|
||||
function M:add_container(node_or_container, mode, on_resize_callback)
|
||||
local container = nil
|
||||
local node = node_or_container
|
||||
@@ -422,7 +440,7 @@ function M:update_child_containers()
|
||||
end
|
||||
|
||||
|
||||
---@return druid.container @{Container}
|
||||
---@return druid.container Container
|
||||
function M:create_draggable_corners()
|
||||
self:clear_draggable_corners()
|
||||
|
||||
@@ -452,7 +470,7 @@ function M:create_draggable_corners()
|
||||
end
|
||||
|
||||
|
||||
---@return druid.container @{Container}
|
||||
---@return druid.container Container
|
||||
function M:clear_draggable_corners()
|
||||
for index = 1, #self._draggable_corners do
|
||||
local drag_component = self._draggable_corners[index]
|
||||
@@ -505,7 +523,7 @@ end
|
||||
|
||||
--- Set node for layout node to fit inside it. Pass nil to reset
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@return druid.container @{Layout}
|
||||
---@return druid.container Layout
|
||||
function M:fit_into_node(node)
|
||||
self._fit_node = self:get_node(node)
|
||||
self:refresh_scale()
|
||||
@@ -10,10 +10,10 @@
|
||||
|
||||
|
||||
--- The Druid scroll component
|
||||
-- @tfield Scroll scroll @{Scroll}
|
||||
-- @tfield Scroll scroll Scroll
|
||||
|
||||
--- The Druid Grid component
|
||||
-- @tfield StaticGrid grid @{StaticGrid}, @{DynamicGrid}
|
||||
-- @tfield StaticGrid grid StaticGrid}, @{DynamicGrid
|
||||
|
||||
--- The current progress of scroll posititon
|
||||
-- @tfield number scroll_progress
|
||||
@@ -25,30 +25,41 @@
|
||||
-- @tfield number last_index
|
||||
|
||||
--- Event triggered when scroll progress is changed; event(self, progress_value)
|
||||
-- @tfield DruidEvent on_scroll_progress_change @{DruidEvent}
|
||||
-- @tfield event on_scroll_progress_change event
|
||||
|
||||
---On DataList visual element created Event callback(self, index, node, instance)
|
||||
-- @tfield DruidEvent on_element_add @{DruidEvent}
|
||||
-- @tfield event on_element_add event
|
||||
|
||||
---On DataList visual element created Event callback(self, index)
|
||||
-- @tfield DruidEvent on_element_remove @{DruidEvent}
|
||||
-- @tfield event on_element_remove event
|
||||
|
||||
---
|
||||
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
|
||||
local DataList = component.create("data_list")
|
||||
---@class druid.data_list: druid.base_component
|
||||
---@field scroll druid.scroll
|
||||
---@field grid druid.grid
|
||||
---@field on_scroll_progress_change event
|
||||
---@field on_element_add event
|
||||
---@field on_element_remove event
|
||||
---@field private _create_function function
|
||||
---@field private _is_use_cache boolean
|
||||
---@field private _cache table
|
||||
---@field private _data table
|
||||
---@field private _data_visual table
|
||||
---@field top_index number
|
||||
local M = component.create("data_list")
|
||||
|
||||
|
||||
--- The @{DataList} constructor
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam Scroll scroll The @{Scroll} instance for Data List component
|
||||
-- @tparam StaticGrid grid The @{StaticGrid} or @{DynamicGrid} instance for Data List component
|
||||
-- @tparam function create_function The create function callback(self, data, index, data_list). Function should return (node, [component])
|
||||
function DataList.init(self, scroll, grid, create_function)
|
||||
--- The DataList constructor
|
||||
---@param scroll druid.scroll The Scroll instance for Data List component
|
||||
---@param grid druid.grid The StaticGrid} or @{DynamicGrid instance for Data List component
|
||||
---@param create_function function The create function callback(self, data, index, data_list). Function should return (node, [component])
|
||||
function M:init(scroll, grid, create_function)
|
||||
self.scroll = scroll
|
||||
self.grid = grid
|
||||
if self.grid.style then
|
||||
@@ -68,35 +79,32 @@ function DataList.init(self, scroll, grid, create_function)
|
||||
|
||||
self.scroll.on_scroll:subscribe(self._refresh, self)
|
||||
|
||||
self.on_scroll_progress_change = Event()
|
||||
self.on_element_add = Event()
|
||||
self.on_element_remove = Event()
|
||||
self.on_scroll_progress_change = event.create()
|
||||
self.on_element_add = event.create()
|
||||
self.on_element_remove = event.create()
|
||||
end
|
||||
|
||||
|
||||
--- Druid System on_remove function
|
||||
-- @tparam DataList self @{DataList}
|
||||
function DataList.on_remove(self)
|
||||
function M:on_remove()
|
||||
self:clear()
|
||||
self.scroll.on_scroll:unsubscribe(self._refresh, self)
|
||||
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)
|
||||
---@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 M:set_use_cache(is_use_cache)
|
||||
self._is_use_cache = is_use_cache
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set new data set for DataList component
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam table data The new data array
|
||||
-- @treturn druid.data_list Current DataList instance
|
||||
function DataList.set_data(self, data)
|
||||
---@param data table The new data array
|
||||
---@return druid.data_list Current DataList instance
|
||||
function M:set_data(data)
|
||||
self._data = data or {}
|
||||
self:_refresh()
|
||||
|
||||
@@ -105,19 +113,17 @@ end
|
||||
|
||||
|
||||
--- Return current data from DataList component
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @treturn table The current data array
|
||||
function DataList.get_data(self)
|
||||
---@return table The current data array
|
||||
function M:get_data()
|
||||
return self._data
|
||||
end
|
||||
|
||||
|
||||
--- Add element to DataList. Currenly untested
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam table data
|
||||
-- @tparam number|nil index
|
||||
-- @tparam number|nil shift_policy The constant from const.SHIFT.*
|
||||
function DataList.add(self, data, index, shift_policy)
|
||||
---@param data table
|
||||
---@param index number|nil
|
||||
---@param shift_policy number|nil The constant from const.SHIFT.*
|
||||
function M:add(data, index, shift_policy)
|
||||
index = index or #self._data + 1
|
||||
shift_policy = shift_policy or const.SHIFT.RIGHT
|
||||
|
||||
@@ -127,20 +133,18 @@ end
|
||||
|
||||
|
||||
--- Remove element from DataList. Currenly untested
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam number|nil index
|
||||
-- @tparam number|nil shift_policy The constant from const.SHIFT.*
|
||||
function DataList.remove(self, index, shift_policy)
|
||||
---@param index number|nil
|
||||
---@param shift_policy number|nil The constant from const.SHIFT.*
|
||||
function M:remove(index, shift_policy)
|
||||
helper.remove_with_shift(self._data, index, shift_policy)
|
||||
self:_refresh()
|
||||
end
|
||||
|
||||
|
||||
--- Remove element from DataList by data value. Currenly untested
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam table data
|
||||
-- @tparam number|nil shift_policy The constant from const.SHIFT.*
|
||||
function DataList.remove_by_data(self, data, shift_policy)
|
||||
---@param data table
|
||||
---@param shift_policy number|nil The constant from const.SHIFT.*
|
||||
function M:remove_by_data(data, shift_policy)
|
||||
local index = helper.contains(self._data, data)
|
||||
if index then
|
||||
helper.remove_with_shift(self._data, index, shift_policy)
|
||||
@@ -150,17 +154,15 @@ end
|
||||
|
||||
|
||||
--- Clear the DataList and refresh visuals
|
||||
-- @tparam DataList self @{DataList}
|
||||
function DataList.clear(self)
|
||||
function M:clear()
|
||||
self._data = {}
|
||||
self:_refresh()
|
||||
end
|
||||
|
||||
|
||||
--- Return index for data value
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam table data
|
||||
function DataList.get_index(self, data)
|
||||
---@param data table
|
||||
function M:get_index(data)
|
||||
for index, value in pairs(self._data) do
|
||||
if value == data then
|
||||
return index
|
||||
@@ -172,9 +174,8 @@ end
|
||||
|
||||
|
||||
--- Return all currenly created nodes in DataList
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @treturn node[] List of created nodes
|
||||
function DataList.get_created_nodes(self)
|
||||
---@return node[] List of created nodes
|
||||
function M:get_created_nodes()
|
||||
local nodes = {}
|
||||
|
||||
for index, data in pairs(self._data_visual) do
|
||||
@@ -186,9 +187,8 @@ end
|
||||
|
||||
|
||||
--- Return all currenly created components in DataList
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @treturn druid.base_component[] List of created nodes
|
||||
function DataList.get_created_components(self)
|
||||
---@return druid.base_component[] List of created nodes
|
||||
function M:get_created_components()
|
||||
local components = {}
|
||||
|
||||
for index, data in pairs(self._data_visual) do
|
||||
@@ -200,19 +200,17 @@ end
|
||||
|
||||
|
||||
--- Instant scroll to element with passed index
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam number index
|
||||
function DataList.scroll_to_index(self, index)
|
||||
---@param index number
|
||||
function M:scroll_to_index(index)
|
||||
local pos = self.grid:get_pos(index)
|
||||
self.scroll:scroll_to(pos)
|
||||
end
|
||||
|
||||
|
||||
--- Add element at passed index using cache or create new
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam number index
|
||||
-- @local
|
||||
function DataList._add_at(self, index)
|
||||
---@param index number
|
||||
---@private
|
||||
function M:_add_at(index)
|
||||
if self._data_visual[index] then
|
||||
self:_remove_at(index)
|
||||
end
|
||||
@@ -243,10 +241,9 @@ end
|
||||
|
||||
|
||||
--- Remove element from passed index and add it to cache if applicable
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @tparam number index
|
||||
-- @local
|
||||
function DataList._remove_at(self, index)
|
||||
---@param index number
|
||||
---@private
|
||||
function M:_remove_at(index)
|
||||
self.grid:remove(index, const.SHIFT.NO_SHIFT)
|
||||
|
||||
local visual_data = self._data_visual[index]
|
||||
@@ -274,9 +271,8 @@ end
|
||||
|
||||
|
||||
--- Refresh all elements in DataList
|
||||
-- @tparam DataList self @{DataList}
|
||||
-- @local
|
||||
function DataList._refresh(self)
|
||||
---@private
|
||||
function M:_refresh()
|
||||
self.scroll:set_size(self.grid:get_size_for(#self._data))
|
||||
|
||||
local start_pos = -self.scroll.position --[[@as vector3]]
|
||||
@@ -313,4 +309,4 @@ function DataList._refresh(self)
|
||||
end
|
||||
|
||||
|
||||
return DataList
|
||||
return M
|
||||
|
||||
@@ -1,427 +0,0 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Component to handle placing components in row
|
||||
--
|
||||
-- <a href="https://insality.github.io/druid/druid/index.html?example=general_grid" target="_blank"><b>Example Link</b></a>
|
||||
-- @module DynamicGrid
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.dynamic_grid
|
||||
|
||||
--- On item add callback(self, node, index)
|
||||
-- @tfield DruidEvent on_add_item @{DruidEvent}
|
||||
|
||||
--- On item remove callback(self, index)
|
||||
-- @tfield DruidEvent on_remove_item @{DruidEvent}
|
||||
|
||||
--- On item add or remove callback(self, index)
|
||||
-- @tfield DruidEvent on_change_items @{DruidEvent}
|
||||
|
||||
--- On grid clear callback(self)
|
||||
-- @tfield DruidEvent on_clear @{DruidEvent}
|
||||
|
||||
--- On update item positions callback(self)
|
||||
-- @tfield DruidEvent on_update_positions @{DruidEvent}
|
||||
|
||||
--- Parent gui node
|
||||
-- @tfield node parent
|
||||
|
||||
--- List of all grid elements. Contains from node, pos, size, pivot
|
||||
-- @tfield node[] nodes
|
||||
|
||||
--- The first index of node in grid
|
||||
-- @tfield number first_index
|
||||
|
||||
--- The last index of node in grid
|
||||
-- @tfield number last_index
|
||||
|
||||
--- Item size
|
||||
-- @tfield vector3 node_size
|
||||
|
||||
--- The size of item content
|
||||
-- @tfield vector4 border
|
||||
|
||||
---
|
||||
|
||||
local const = require("druid.const")
|
||||
local Event = require("druid.event")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local DynamicGrid = component.create("dynamic_grid")
|
||||
|
||||
|
||||
local SIDE_VECTORS = {
|
||||
LEFT = vmath.vector3(-1, 0, 0),
|
||||
RIGHT = vmath.vector3(1, 0, 0),
|
||||
TOP = vmath.vector3(0, -1, 0),
|
||||
BOT = vmath.vector3(0, 1, 0),
|
||||
}
|
||||
|
||||
local AVAILABLE_PIVOTS = {
|
||||
gui.PIVOT_N,
|
||||
gui.PIVOT_S,
|
||||
gui.PIVOT_W,
|
||||
gui.PIVOT_E,
|
||||
}
|
||||
|
||||
|
||||
--- The @{DynamicGrid} constructor
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam node parent The gui node parent, where items will be placed
|
||||
function DynamicGrid.init(self, parent)
|
||||
self.parent = self:get_node(parent)
|
||||
|
||||
local parent_pivot = gui.get_pivot(self.parent)
|
||||
self.pivot = helper.get_pivot_offset(parent_pivot)
|
||||
|
||||
assert(helper.contains(AVAILABLE_PIVOTS, parent_pivot), const.ERRORS.GRID_DYNAMIC_ANCHOR)
|
||||
self.side = ((parent_pivot == gui.PIVOT_W or parent_pivot == gui.PIVOT_E)
|
||||
and const.SIDE.X or const.SIDE.Y)
|
||||
|
||||
self.nodes = {}
|
||||
self.border = vmath.vector4(0) -- Current grid content size
|
||||
|
||||
self.on_add_item = Event()
|
||||
self.on_remove_item = Event()
|
||||
self.on_change_items = Event()
|
||||
self.on_clear = Event()
|
||||
self.on_update_positions = Event()
|
||||
|
||||
self._set_position_function = gui.set_position
|
||||
end
|
||||
|
||||
|
||||
function DynamicGrid.on_layout_change(self)
|
||||
self:_update(true)
|
||||
end
|
||||
|
||||
|
||||
--- Return pos for grid node index
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam number index The grid element index
|
||||
-- @tparam node node The node to be placed
|
||||
-- @tparam number|nil origin_index Index of nearby node
|
||||
-- @treturn vector3 node position
|
||||
function DynamicGrid.get_pos(self, index, node, origin_index)
|
||||
local origin_node = self.nodes[origin_index]
|
||||
|
||||
-- If anchor node is not exist, check around nodes
|
||||
if not origin_node then
|
||||
if self.nodes[index + 1] then
|
||||
origin_index = index + 1
|
||||
end
|
||||
if self.nodes[index - 1] then
|
||||
origin_index = index - 1
|
||||
end
|
||||
origin_node = self.nodes[origin_index]
|
||||
end
|
||||
|
||||
if not origin_node then
|
||||
assert(not self.first_index, "Dynamic Grid can't have gaps between nodes. Error on grid:add")
|
||||
|
||||
-- If not origin node, so it should be first element in the grid
|
||||
local size = helper.get_scaled_size(node)
|
||||
local pivot = const.PIVOTS[gui.get_pivot(node)]
|
||||
return vmath.vector3(
|
||||
size.x * pivot.x - size.x * self.pivot.x,
|
||||
size.y * pivot.y - size.y * self.pivot.y,
|
||||
0)
|
||||
end
|
||||
|
||||
if origin_node then
|
||||
-- Other nodes spawn from other side of the origin node
|
||||
local is_forward = origin_index < index
|
||||
local delta = is_forward and 1 or -1
|
||||
return self:_get_next_node_pos(index - delta, node, self:_get_side_vector(self.side, is_forward))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Add new node to the grid
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam node node Gui node
|
||||
-- @tparam number|nil index The node position. By default add as last node
|
||||
-- @tparam number|nil shift_policy How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
-- @tparam boolean|nil is_instant If true, update node positions instantly
|
||||
function DynamicGrid.add(self, node, index, shift_policy, is_instant)
|
||||
shift_policy = shift_policy or const.SHIFT.RIGHT
|
||||
local delta = shift_policy -- -1 or 1 or 0
|
||||
|
||||
-- By default add node at end
|
||||
index = index or ((self.last_index or 0) + 1)
|
||||
|
||||
-- If node exist at index place, shifting them
|
||||
local is_shift = self.nodes[index] and shift_policy ~= const.SHIFT.NO_SHIFT
|
||||
if is_shift then
|
||||
-- We need to iterate from index to start or end grid, depends of shift side
|
||||
local start_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index
|
||||
for i = start_index, index, -delta do
|
||||
self.nodes[i + delta] = self.nodes[i]
|
||||
end
|
||||
end
|
||||
|
||||
self:_add_node(node, index, index - delta)
|
||||
|
||||
-- After shifting we should recalc node poses
|
||||
if is_shift then
|
||||
-- We need to iterate from placed node to start or end grid, depends of shift side
|
||||
local target_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index
|
||||
for i = index + delta, target_index + delta, delta do
|
||||
local move_node = self.nodes[i]
|
||||
move_node.pos = self:get_pos(i, move_node.node, i - delta)
|
||||
end
|
||||
end
|
||||
|
||||
-- Sync grid data
|
||||
self:_update(is_instant)
|
||||
|
||||
self.on_add_item:trigger(self:get_context(), node, index)
|
||||
self.on_change_items:trigger(self:get_context(), index)
|
||||
end
|
||||
|
||||
|
||||
--- Remove the item from the grid. Note that gui node will be not deleted
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam number index The grid node index to remove
|
||||
-- @tparam number|nil shift_policy How shift nodes, if required. Default: const.SHIFT.RIGHT
|
||||
-- @tparam boolean|nil is_instant If true, update node positions instantly
|
||||
-- @treturn node The deleted gui node from grid
|
||||
function DynamicGrid.remove(self, index, shift_policy, is_instant)
|
||||
shift_policy = shift_policy or const.SHIFT.RIGHT
|
||||
local delta = shift_policy -- -1 or 1 or 0
|
||||
|
||||
assert(self.nodes[index], "No grid item at given index " .. index)
|
||||
|
||||
-- Just set nil for delete node data
|
||||
local removed_node = self.nodes[index].node
|
||||
self.nodes[index] = nil
|
||||
|
||||
-- After delete node, we should shift nodes and recalc their poses, depends from is_shift_left
|
||||
if shift_policy ~= const.SHIFT.NO_SHIFT then
|
||||
local target_index = shift_policy == const.SHIFT.LEFT and self.first_index or self.last_index
|
||||
for i = index, target_index, delta do
|
||||
self.nodes[i] = self.nodes[i + delta]
|
||||
if self.nodes[i] then
|
||||
self.nodes[i].pos = self:get_pos(i, self.nodes[i].node, i - delta)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Sync grid data
|
||||
self:_update(is_instant)
|
||||
|
||||
self.on_remove_item:trigger(self:get_context(), index)
|
||||
self.on_change_items:trigger(self:get_context(), index)
|
||||
|
||||
return removed_node
|
||||
end
|
||||
|
||||
|
||||
--- Return grid content size
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam vector3 border
|
||||
-- @treturn vector3 The grid content size
|
||||
function DynamicGrid.get_size(self, border)
|
||||
border = border or self.border
|
||||
return vmath.vector3(
|
||||
border.z - border.x,
|
||||
border.y - border.w,
|
||||
0)
|
||||
end
|
||||
|
||||
|
||||
--- Return DynamicGrid offset, where DynamicGrid content starts.
|
||||
-- @tparam DynamicGrid self @{DynamicGrid} The DynamicGrid instance
|
||||
-- @treturn vector3 The DynamicGrid offset
|
||||
function DynamicGrid.get_offset(self)
|
||||
local size = self:get_size()
|
||||
local borders = self:get_borders()
|
||||
local offset = vmath.vector3(
|
||||
(borders.z + borders.x)/2 + size.x * self.pivot.x,
|
||||
(borders.y + borders.w)/2 + size.y * self.pivot.y,
|
||||
0)
|
||||
|
||||
return offset
|
||||
end
|
||||
|
||||
|
||||
--- Return grid content borders
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @treturn vector3 The grid content borders
|
||||
function DynamicGrid.get_borders(self)
|
||||
return self.border
|
||||
end
|
||||
|
||||
|
||||
--- Return grid index by node
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam node node The gui node in the grid
|
||||
-- @treturn number The node index
|
||||
function DynamicGrid.get_index_by_node(self, node)
|
||||
for index, node_info in pairs(self.nodes) do
|
||||
if node == node_info.node then
|
||||
return index
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Return array of all node positions
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @treturn vector3[] All grid node positions
|
||||
function DynamicGrid.get_all_pos(self)
|
||||
local result = {}
|
||||
for i, node in pairs(self.nodes) do
|
||||
table.insert(result, gui.get_position(node.node))
|
||||
end
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
|
||||
--- Change set position function for grid nodes. It will call on
|
||||
-- update poses on grid elements. Default: gui.set_position
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam function callback Function on node set position
|
||||
-- @treturn druid.dynamic_grid Current grid instance
|
||||
function DynamicGrid.set_position_function(self, callback)
|
||||
self._set_position_function = callback or gui.set_position
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Clear grid nodes array. GUI nodes will be not deleted!
|
||||
-- If you want to delete GUI nodes, use dynamic_grid.nodes array before grid:clear
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @treturn druid.dynamic_grid Current grid instance
|
||||
function DynamicGrid.clear(self)
|
||||
self.nodes = {}
|
||||
self:_update()
|
||||
|
||||
self.on_clear:trigger(self:get_context())
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function DynamicGrid._add_node(self, node, index, origin_index)
|
||||
self.nodes[index] = {
|
||||
node = node,
|
||||
pos = self:get_pos(index, node, origin_index),
|
||||
size = helper.get_scaled_size(node),
|
||||
pivot = const.PIVOTS[gui.get_pivot(node)]
|
||||
}
|
||||
|
||||
-- Add new item instantly in new pos
|
||||
gui.set_parent(node, self.parent)
|
||||
gui.set_position(node, self.nodes[index].pos)
|
||||
end
|
||||
|
||||
|
||||
--- Update grid inner state
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam boolean|nil is_instant If true, node position update instantly, otherwise with set_position_function callback
|
||||
-- @local
|
||||
function DynamicGrid._update(self, is_instant)
|
||||
self:_update_indexes()
|
||||
self:_update_borders()
|
||||
self:_update_pos(is_instant)
|
||||
end
|
||||
|
||||
|
||||
--- Update first and last indexes of grid nodes
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @local
|
||||
function DynamicGrid._update_indexes(self)
|
||||
self.first_index = nil
|
||||
self.last_index = nil
|
||||
for index in pairs(self.nodes) do
|
||||
self.first_index = self.first_index or index
|
||||
self.last_index = self.last_index or index
|
||||
|
||||
self.first_index = math.min(self.first_index, index)
|
||||
self.last_index = math.max(self.last_index, index)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Update grid content borders, recalculate min and max values
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @local
|
||||
function DynamicGrid._update_borders(self)
|
||||
if not self.first_index then
|
||||
self.border = vmath.vector4(0)
|
||||
return
|
||||
end
|
||||
|
||||
self.border = vmath.vector4(math.huge, -math.huge, -math.huge, math.huge)
|
||||
|
||||
for index, node in pairs(self.nodes) do
|
||||
local pos = node.pos
|
||||
local size = node.size
|
||||
local pivot = node.pivot
|
||||
|
||||
local left = pos.x - size.x/2 - (size.x * pivot.x)
|
||||
local right = pos.x + size.x/2 - (size.x * pivot.x)
|
||||
local top = pos.y + size.y/2 - (size.y * pivot.y)
|
||||
local bottom = pos.y - size.y/2 - (size.y * pivot.y)
|
||||
|
||||
self.border.x = math.min(self.border.x, left)
|
||||
self.border.y = math.max(self.border.y, top)
|
||||
self.border.z = math.max(self.border.z, right)
|
||||
self.border.w = math.min(self.border.w, bottom)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Update grid nodes position
|
||||
-- @tparam DynamicGrid self @{DynamicGrid}
|
||||
-- @tparam boolean|nil is_instant If true, node position update instantly, otherwise with set_position_function callback
|
||||
-- @local
|
||||
function DynamicGrid._update_pos(self, is_instant)
|
||||
for index, node in pairs(self.nodes) do
|
||||
if is_instant then
|
||||
gui.set_position(node.node, node.pos)
|
||||
else
|
||||
self._set_position_function(node.node, node.pos)
|
||||
end
|
||||
end
|
||||
|
||||
self.on_update_positions:trigger(self:get_context())
|
||||
end
|
||||
|
||||
|
||||
function DynamicGrid._get_next_node_pos(self, origin_node_index, new_node, place_side)
|
||||
local node = self.nodes[origin_node_index]
|
||||
|
||||
local new_node_size = helper.get_scaled_size(new_node)
|
||||
local new_pivot = const.PIVOTS[gui.get_pivot(new_node)]
|
||||
|
||||
local dist_x = (node.size.x/2 + new_node_size.x/2) * place_side.x
|
||||
local dist_y = (node.size.y/2 + new_node_size.y/2) * place_side.y
|
||||
local node_center_x = node.pos.x - node.size.x * node.pivot.x
|
||||
local node_center_y = node.pos.y - node.size.y * node.pivot.y
|
||||
|
||||
return vmath.vector3(
|
||||
node_center_x + dist_x + new_node_size.x * new_pivot.x,
|
||||
node_center_y - dist_y + new_node_size.y * new_pivot.y,
|
||||
0
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
--- Return side vector to correct node shifting
|
||||
function DynamicGrid._get_side_vector(self, side, is_forward)
|
||||
if side == const.SIDE.X then
|
||||
return is_forward and SIDE_VECTORS.RIGHT or SIDE_VECTORS.LEFT
|
||||
end
|
||||
|
||||
if side == const.SIDE.Y then
|
||||
return is_forward and SIDE_VECTORS.BOT or SIDE_VECTORS.TOP
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return DynamicGrid
|
||||
@@ -8,10 +8,10 @@
|
||||
-- @alias druid.hotkey
|
||||
|
||||
--- On hotkey released callback(self, argument)
|
||||
-- @tfield DruidEvent on_hotkey_pressed @{DruidEvent}
|
||||
-- @tfield event on_hotkey_pressed event
|
||||
|
||||
--- On hotkey released callback(self, argument)
|
||||
-- @tfield DruidEvent on_hotkey_released @{DruidEvent}
|
||||
-- @tfield event on_hotkey_released event
|
||||
|
||||
--- Visual node
|
||||
-- @tfield node node
|
||||
@@ -20,30 +20,35 @@
|
||||
-- @tfield node|nil click_node
|
||||
|
||||
--- Button component from click_node
|
||||
-- @tfield Button button @{Button}
|
||||
-- @tfield Button button Button
|
||||
|
||||
---
|
||||
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
|
||||
local Hotkey = component.create("hotkey")
|
||||
---@class druid.hotkey: druid.base_component
|
||||
---@field on_hotkey_pressed event
|
||||
---@field on_hotkey_released event
|
||||
---@field style table
|
||||
---@field private _hotkeys table
|
||||
---@field private _modificators table
|
||||
local M = component.create("hotkey")
|
||||
|
||||
|
||||
--- The @{Hotkey} constructor
|
||||
-- @tparam Hotkey self @{Hotkey}
|
||||
-- @tparam string[]|string keys The keys to be pressed for trigger callback. Should contains one key and any modificator keys
|
||||
-- @tparam function callback The callback function
|
||||
-- @tparam any|nil callback_argument The argument to pass into the callback function
|
||||
function Hotkey.init(self, keys, callback, callback_argument)
|
||||
--- The Hotkey constructor
|
||||
---@param keys string[]|string The keys to be pressed for trigger callback. Should contains one key and any modificator keys
|
||||
---@param callback function The callback function
|
||||
---@param callback_argument any|nil The argument to pass into the callback function
|
||||
function M:init(keys, callback, callback_argument)
|
||||
self.druid = self:get_druid()
|
||||
|
||||
self._hotkeys = {}
|
||||
self._modificators = {}
|
||||
|
||||
self.on_hotkey_pressed = Event()
|
||||
self.on_hotkey_released = Event(callback)
|
||||
self.on_hotkey_pressed = event.create()
|
||||
self.on_hotkey_released = event.create(callback)
|
||||
|
||||
if keys then
|
||||
self:add_hotkey(keys, callback_argument)
|
||||
@@ -56,7 +61,7 @@ end
|
||||
-- or create your own style
|
||||
-- @table style
|
||||
-- @tfield string[] MODIFICATORS The list of action_id as hotkey modificators
|
||||
function Hotkey.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.MODIFICATORS = style.MODIFICATORS or {}
|
||||
|
||||
@@ -67,11 +72,10 @@ end
|
||||
|
||||
|
||||
--- Add hotkey for component callback
|
||||
-- @tparam Hotkey self @{Hotkey}
|
||||
-- @tparam string[]|hash[]|string|hash keys that have to be pressed before key pressed to activate
|
||||
-- @tparam any|nil callback_argument The argument to pass into the callback function
|
||||
-- @treturn Hotkey Current instance
|
||||
function Hotkey.add_hotkey(self, keys, callback_argument)
|
||||
---@param keys string[]|hash[]|string|hash that have to be pressed before key pressed to activate
|
||||
---@param callback_argument any|nil The argument to pass into the callback function
|
||||
---@return druid.hotkey Current instance
|
||||
function M:add_hotkey(keys, callback_argument)
|
||||
keys = keys or {}
|
||||
if type(keys) == "string" then
|
||||
keys = { keys }
|
||||
@@ -82,7 +86,7 @@ function Hotkey.add_hotkey(self, keys, callback_argument)
|
||||
|
||||
for index = 1, #keys do
|
||||
local key_hash = hash(keys[index])
|
||||
if helper.contains(self.style.MODIFICATORS, key_hash) then
|
||||
if #keys > 1 and helper.contains(self.style.MODIFICATORS, key_hash) then
|
||||
table.insert(modificators, key_hash)
|
||||
else
|
||||
if not key then
|
||||
@@ -110,40 +114,51 @@ function Hotkey.add_hotkey(self, keys, callback_argument)
|
||||
end
|
||||
|
||||
|
||||
function Hotkey.on_focus_gained(self)
|
||||
function M:is_processing()
|
||||
for index = 1, #self._hotkeys do
|
||||
if self._hotkeys[index].is_processing then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function M:on_focus_gained()
|
||||
for k, v in pairs(self._modificators) do
|
||||
self._modificators[k] = false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function Hotkey.on_input(self, action_id, action)
|
||||
if not action_id or #self._hotkeys == 0 then
|
||||
function M:on_input(action_id, action)
|
||||
if not action_id then
|
||||
return false
|
||||
end
|
||||
|
||||
if self._modificators[action_id] ~= nil then
|
||||
if action.pressed then
|
||||
self._modificators[action_id] = true
|
||||
end
|
||||
if action.released then
|
||||
self._modificators[action_id] = false
|
||||
end
|
||||
if self._modificators[action_id] ~= nil and action.pressed then
|
||||
self._modificators[action_id] = true
|
||||
end
|
||||
|
||||
for index = 1, #self._hotkeys do
|
||||
local hotkey = self._hotkeys[index]
|
||||
if action_id == hotkey.key then
|
||||
local is_relative_key = helper.contains(self.style.MODIFICATORS, action_id) or action_id == hotkey.key
|
||||
|
||||
if is_relative_key and (action_id == hotkey.key or not hotkey.key) then
|
||||
local is_modificator_ok = true
|
||||
local is_consume = not not (hotkey.key)
|
||||
|
||||
-- Check only required modificators pressed
|
||||
for i = 1, #self.style.MODIFICATORS do
|
||||
local mod = self.style.MODIFICATORS[i]
|
||||
if helper.contains(hotkey.modificators, mod) and self._modificators[mod] == false then
|
||||
is_modificator_ok = false
|
||||
end
|
||||
if not helper.contains(hotkey.modificators, mod) and self._modificators[mod] == true then
|
||||
is_modificator_ok = false
|
||||
if hotkey.key and #hotkey.modificators > 0 then
|
||||
for i = 1, #self.style.MODIFICATORS do
|
||||
local mod = self.style.MODIFICATORS[i]
|
||||
if helper.contains(hotkey.modificators, mod) and self._modificators[mod] == false then
|
||||
is_modificator_ok = false
|
||||
end
|
||||
if not helper.contains(hotkey.modificators, mod) and self._modificators[mod] == true then
|
||||
is_modificator_ok = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -153,28 +168,31 @@ function Hotkey.on_input(self, action_id, action)
|
||||
end
|
||||
if not action.pressed and self._is_process_repeated and action.repeated and is_modificator_ok and hotkey.is_processing then
|
||||
self.on_hotkey_released:trigger(self:get_context(), hotkey.callback_argument)
|
||||
return true
|
||||
return is_consume
|
||||
end
|
||||
if action.released and is_modificator_ok and hotkey.is_processing then
|
||||
hotkey.is_processing = false
|
||||
self.on_hotkey_released:trigger(self:get_context(), hotkey.callback_argument)
|
||||
return true
|
||||
hotkey.is_processing = false
|
||||
return is_consume
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if self._modificators[action_id] ~= nil and action.released then
|
||||
self._modificators[action_id] = false
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
--- If true, the callback will be triggered on action.repeated
|
||||
-- @tparam Hotkey self @{Hotkey}
|
||||
-- @tparam bool is_enabled_repeated The flag value
|
||||
-- @treturn Hotkey
|
||||
function Hotkey.set_repeat(self, is_enabled_repeated)
|
||||
---@param is_enabled_repeated bool The flag value
|
||||
---@return druid.hotkey
|
||||
function M:set_repeat(is_enabled_repeated)
|
||||
self._is_process_repeated = is_enabled_repeated
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return Hotkey
|
||||
return M
|
||||
|
||||
@@ -10,25 +10,25 @@
|
||||
-- @alias druid.input
|
||||
|
||||
--- On input field select callback(self, input_instance)
|
||||
-- @tfield DruidEvent on_input_select @{DruidEvent}
|
||||
-- @tfield event on_input_select event
|
||||
|
||||
--- On input field unselect callback(self, input_text, input_instance)
|
||||
-- @tfield DruidEvent on_input_unselect @{DruidEvent}
|
||||
-- @tfield event on_input_unselect event
|
||||
|
||||
--- On input field text change callback(self, input_text)
|
||||
-- @tfield DruidEvent on_input_text @{DruidEvent}
|
||||
-- @tfield event on_input_text event
|
||||
|
||||
--- On input field text change to empty string callback(self, input_text)
|
||||
-- @tfield DruidEvent on_input_empty @{DruidEvent}
|
||||
-- @tfield event on_input_empty event
|
||||
|
||||
--- On input field text change to max length string callback(self, input_text)
|
||||
-- @tfield DruidEvent on_input_full @{DruidEvent}
|
||||
-- @tfield event on_input_full event
|
||||
|
||||
--- On trying user input with not allowed character callback(self, params, input_text)
|
||||
-- @tfield DruidEvent on_input_wrong @{DruidEvent}
|
||||
-- @tfield event on_input_wrong event
|
||||
|
||||
--- On cursor position change callback(self, cursor_index, start_index, end_index)
|
||||
-- @tfield DruidEvent on_select_cursor_change @{DruidEvent}
|
||||
-- @tfield event on_select_cursor_change event
|
||||
|
||||
--- The cursor index. The index of letter cursor after. Leftmost cursor - 0
|
||||
-- @tfield number cursor_index
|
||||
@@ -40,7 +40,7 @@
|
||||
-- @tfield number end_index
|
||||
|
||||
--- Text component
|
||||
-- @tfield Text text @{Text}
|
||||
-- @tfield Text text Text
|
||||
|
||||
--- Current input value
|
||||
-- @tfield string value
|
||||
@@ -61,7 +61,7 @@
|
||||
-- @tfield number marked_text_width
|
||||
|
||||
--- Button component
|
||||
-- @tfield Button button @{Button}
|
||||
-- @tfield Button button Button
|
||||
|
||||
--- Is current input selected now
|
||||
-- @tfield boolean is_selected
|
||||
@@ -80,16 +80,26 @@
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
local utf8_lua = require("druid.system.utf8")
|
||||
local utf8 = utf8 or utf8_lua
|
||||
|
||||
local Input = component.create("input")
|
||||
---@class druid.input: druid.base_component
|
||||
---@field on_input_select event
|
||||
---@field on_input_unselect event
|
||||
---@field on_input_text event
|
||||
---@field on_input_empty event
|
||||
---@field on_input_full event
|
||||
---@field on_input_wrong event
|
||||
---@field on_select_cursor_change event
|
||||
---@field style table
|
||||
---@field text druid.text
|
||||
local M = component.create("input")
|
||||
|
||||
Input.ALLOWED_ACTIONS = {
|
||||
M.ALLOWED_ACTIONS = {
|
||||
[const.ACTION_TOUCH] = true,
|
||||
[const.ACTION_TEXT] = true,
|
||||
[const.ACTION_MARKED_TEXT] = true,
|
||||
@@ -99,9 +109,9 @@ Input.ALLOWED_ACTIONS = {
|
||||
}
|
||||
|
||||
--- Mask text by replacing every character with a mask character
|
||||
-- @tparam string text
|
||||
-- @tparam string mask
|
||||
-- @treturn string Masked text
|
||||
---@param text string
|
||||
---@param mask string
|
||||
---@return string Masked text
|
||||
local function mask_text(text, mask)
|
||||
mask = mask or "*"
|
||||
local masked_text = ""
|
||||
@@ -132,7 +142,7 @@ end
|
||||
-- @tfield function on_select (self, button_node) Callback on input field selecting
|
||||
-- @tfield function on_unselect (self, button_node) Callback on input field unselecting
|
||||
-- @tfield function on_input_wrong (self, button_node) Callback on wrong user input
|
||||
function Input.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
|
||||
self.style.IS_LONGTAP_ERASE = style.IS_LONGTAP_ERASE or false
|
||||
@@ -145,12 +155,11 @@ function Input.on_style_change(self, style)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Input} constructor
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam node click_node Node to enabled input component
|
||||
-- @tparam node|Text text_node Text node what will be changed on user input. You can pass text component instead of text node name @{Text}
|
||||
-- @tparam number|nil keyboard_type Gui keyboard type for input field
|
||||
function Input.init(self, click_node, text_node, keyboard_type)
|
||||
--- The Input constructor
|
||||
---@param click_node node Node to enabled input component
|
||||
---@param text_node node|druid.text Text node what will be changed on user input. You can pass text component instead of text node name Text
|
||||
---@param keyboard_type number|nil Gui keyboard type for input field
|
||||
function M:init(click_node, text_node, keyboard_type)
|
||||
self.druid = self:get_druid()
|
||||
|
||||
if type(text_node) == "table" then
|
||||
@@ -191,18 +200,18 @@ function Input.init(self, click_node, text_node, keyboard_type)
|
||||
self.button:set_web_user_interaction(true)
|
||||
end
|
||||
|
||||
self.on_input_select = Event()
|
||||
self.on_input_unselect = Event()
|
||||
self.on_input_text = Event()
|
||||
self.on_input_empty = Event()
|
||||
self.on_input_full = Event()
|
||||
self.on_input_wrong = Event()
|
||||
self.on_select_cursor_change = Event()
|
||||
self.on_input_select = event.create()
|
||||
self.on_input_unselect = event.create()
|
||||
self.on_input_text = event.create()
|
||||
self.on_input_empty = event.create()
|
||||
self.on_input_full = event.create()
|
||||
self.on_input_wrong = event.create()
|
||||
self.on_select_cursor_change = event.create()
|
||||
end
|
||||
|
||||
|
||||
function Input.on_input(self, action_id, action)
|
||||
if not (action_id == nil or Input.ALLOWED_ACTIONS[action_id]) then
|
||||
function M:on_input(action_id, action)
|
||||
if not (action_id == nil or M.ALLOWED_ACTIONS[action_id]) then
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -299,17 +308,17 @@ function Input.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Input.on_focus_lost(self)
|
||||
function M:on_focus_lost()
|
||||
self:unselect()
|
||||
end
|
||||
|
||||
|
||||
function Input.on_input_interrupt(self)
|
||||
function M:on_input_interrupt()
|
||||
--self:unselect()
|
||||
end
|
||||
|
||||
|
||||
function Input.get_text_selected(self)
|
||||
function M:get_text_selected()
|
||||
if self.start_index == self.end_index then
|
||||
return self.value
|
||||
end
|
||||
@@ -318,10 +327,9 @@ function Input.get_text_selected(self)
|
||||
end
|
||||
|
||||
--- Replace selected text with new text
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam string text The text to replace selected text
|
||||
-- @treturn string New input text
|
||||
function Input.get_text_selected_replaced(self, text)
|
||||
---@param text string The text to replace selected text
|
||||
---@return string New input text
|
||||
function M:get_text_selected_replaced(text)
|
||||
local left_part = utf8.sub(self.value, 1, self.start_index)
|
||||
local right_part = utf8.sub(self.value, self.end_index + 1, utf8.len(self.value))
|
||||
local result = left_part .. text .. right_part
|
||||
@@ -336,9 +344,8 @@ end
|
||||
|
||||
|
||||
--- Set text for input field
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam string input_text The string to apply for input field
|
||||
function Input.set_text(self, input_text)
|
||||
---@param input_text string The string to apply for input field
|
||||
function M:set_text(input_text)
|
||||
input_text = tostring(input_text or "")
|
||||
|
||||
-- Case when update with marked text
|
||||
@@ -366,7 +373,7 @@ function Input.set_text(self, input_text)
|
||||
self.is_empty = #value == 0 and #marked_value == 0
|
||||
|
||||
local final_text = value .. marked_value
|
||||
self.text:set_to(final_text)
|
||||
self.text:set_text(final_text)
|
||||
|
||||
-- measure it
|
||||
self.text_width = self.text:get_text_size(value)
|
||||
@@ -385,8 +392,7 @@ end
|
||||
|
||||
|
||||
--- Select input field. It will show the keyboard and trigger on_select events
|
||||
-- @tparam Input self @{Input}
|
||||
function Input.select(self)
|
||||
function M:select()
|
||||
gui.reset_keyboard()
|
||||
self.marked_value = ""
|
||||
if not self.is_selected then
|
||||
@@ -410,8 +416,7 @@ end
|
||||
|
||||
|
||||
--- Remove selection from input. It will hide the keyboard and trigger on_unselect events
|
||||
-- @tparam Input self @{Input}
|
||||
function Input.unselect(self)
|
||||
function M:unselect()
|
||||
gui.reset_keyboard()
|
||||
self.marked_value = ""
|
||||
self.value = self.current_value
|
||||
@@ -429,9 +434,8 @@ end
|
||||
|
||||
|
||||
--- Return current input field text
|
||||
-- @tparam Input self @{Input}
|
||||
-- @treturn string The current input field text
|
||||
function Input.get_text(self)
|
||||
---@return string The current input field text
|
||||
function M:get_text()
|
||||
if self.marked_value ~= "" then
|
||||
return self.value .. self.marked_value
|
||||
end
|
||||
@@ -442,10 +446,9 @@ end
|
||||
|
||||
--- Set maximum length for input field.
|
||||
-- Pass nil to make input field unliminted (by default)
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam number max_length Maximum length for input text field
|
||||
-- @treturn druid.input Current input instance
|
||||
function Input.set_max_length(self, max_length)
|
||||
---@param max_length number Maximum length for input text field
|
||||
---@return druid.input Current input instance
|
||||
function M:set_max_length(max_length)
|
||||
self.max_length = max_length
|
||||
return self
|
||||
end
|
||||
@@ -454,19 +457,17 @@ end
|
||||
--- Set allowed charaters for input field.
|
||||
-- See: https://defold.com/ref/stable/string/
|
||||
-- ex: [%a%d] for alpha and numeric
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam string characters Regulax exp. for validate user input
|
||||
-- @treturn druid.input Current input instance
|
||||
function Input.set_allowed_characters(self, characters)
|
||||
---@param characters string Regulax exp. for validate user input
|
||||
---@return druid.input Current input instance
|
||||
function M:set_allowed_characters(characters)
|
||||
self.allowed_characters = characters
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Reset current input selection and return previous value
|
||||
-- @tparam Input self @{Input}
|
||||
-- @treturn druid.input Current input instance
|
||||
function Input.reset_changes(self)
|
||||
---@return druid.input Current input instance
|
||||
function M:reset_changes()
|
||||
self:set_text(self.previous_value)
|
||||
self:unselect()
|
||||
return self
|
||||
@@ -474,12 +475,11 @@ end
|
||||
|
||||
|
||||
--- Set cursor position in input field
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam number|nil cursor_index Cursor index for cursor position, if nil - will be set to the end of the text
|
||||
-- @tparam number|nil start_index Start index for cursor position, if nil - will be set to the end of the text
|
||||
-- @tparam number|nil end_index End index for cursor position, if nil - will be set to the start_index
|
||||
-- @treturn druid.input Current input instance
|
||||
function Input.select_cursor(self, cursor_index, start_index, end_index)
|
||||
---@param cursor_index number|nil Cursor index for cursor position, if nil - will be set to the end of the text
|
||||
---@param start_index number|nil Start index for cursor position, if nil - will be set to the end of the text
|
||||
---@param end_index number|nil End index for cursor position, if nil - will be set to the start_index
|
||||
---@return druid.input Current input instance
|
||||
function M:select_cursor(cursor_index, start_index, end_index)
|
||||
local len = utf8.len(self.value)
|
||||
|
||||
self.cursor_index = cursor_index or len
|
||||
@@ -497,11 +497,10 @@ end
|
||||
|
||||
|
||||
--- Change cursor position by delta
|
||||
-- @tparam Input self @{Input}
|
||||
-- @tparam number delta side for cursor position, -1 for left, 1 for right
|
||||
-- @tparam boolean is_add_to_selection (Shift key)
|
||||
-- @tparam boolean is_move_to_end (Ctrl key)
|
||||
function Input.move_selection(self, delta, is_add_to_selection, is_move_to_end)
|
||||
---@param delta number side for cursor position, -1 for left, 1 for right
|
||||
---@param is_add_to_selection boolean (Shift key)
|
||||
---@param is_move_to_end boolean (Ctrl key)
|
||||
function M:move_selection(delta, is_add_to_selection, is_move_to_end)
|
||||
local len = utf8.len(self.value)
|
||||
local cursor_index = self.cursor_index
|
||||
local start_index, end_index -- if nil, the selection will be 0 at cursor position
|
||||
@@ -559,4 +558,4 @@ function Input.move_selection(self, delta, is_add_to_selection, is_move_to_end)
|
||||
end
|
||||
|
||||
|
||||
return Input
|
||||
return M
|
||||
|
||||
@@ -18,35 +18,40 @@
|
||||
-- @alias druid.lang_text
|
||||
|
||||
--- On change text callback
|
||||
-- @tfield DruidEvent on_change @{DruidEvent}
|
||||
-- @tfield event on_change event
|
||||
|
||||
--- The text component
|
||||
-- @tfield Text text @{Text}
|
||||
-- @tfield Text text Text
|
||||
|
||||
--- Text node
|
||||
-- @tfield node node
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local settings = require("druid.system.settings")
|
||||
local component = require("druid.component")
|
||||
|
||||
local LangText = component.create("lang_text")
|
||||
---@class druid.lang_text: druid.base_component
|
||||
---@field text druid.text
|
||||
---@field node node
|
||||
---@field on_change event
|
||||
---@field private last_locale_args table
|
||||
---@field private last_locale string
|
||||
local M = component.create("lang_text")
|
||||
|
||||
|
||||
--- The @{LangText} constructor
|
||||
-- @tparam LangText self @{LangText}
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @tparam string|nil locale_id Default locale id or text from node as default
|
||||
-- @tparam string|nil adjust_type Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference
|
||||
function LangText.init(self, node, locale_id, adjust_type)
|
||||
--- The LangText constructor
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@param locale_id string|nil Default locale id or text from node as default
|
||||
---@param adjust_type string|nil Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference
|
||||
function M:init(node, locale_id, adjust_type)
|
||||
self.druid = self:get_druid()
|
||||
self.text = self.druid:new_text(node, locale_id, adjust_type)
|
||||
self.node = self.text.node
|
||||
self.last_locale_args = {}
|
||||
|
||||
self.on_change = Event()
|
||||
self.on_change = event.create()
|
||||
|
||||
self:translate(locale_id or gui.get_text(self.node))
|
||||
self.text.on_set_text:subscribe(self.on_change.trigger, self.on_change)
|
||||
@@ -55,7 +60,7 @@ function LangText.init(self, node, locale_id, adjust_type)
|
||||
end
|
||||
|
||||
|
||||
function LangText.on_language_change(self)
|
||||
function M:on_language_change()
|
||||
if self.last_locale then
|
||||
self:translate(self.last_locale, unpack(self.last_locale_args))
|
||||
end
|
||||
@@ -63,53 +68,58 @@ end
|
||||
|
||||
|
||||
--- Setup raw text to lang_text component
|
||||
-- @tparam LangText self @{LangText}
|
||||
-- @tparam string text Text for text node
|
||||
-- @treturn LangText Current instance
|
||||
function LangText.set_to(self, text)
|
||||
self.last_locale = false
|
||||
self.text:set_to(text)
|
||||
---@param text string Text for text node
|
||||
---@return druid.lang_text Current instance
|
||||
function M:set_to(text)
|
||||
self.last_locale = nil
|
||||
self.text:set_text(text)
|
||||
self.on_change:trigger()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Setup raw text to lang_text component
|
||||
---@param text string Text for text node
|
||||
---@return druid.lang_text Current instance
|
||||
function M:set_text(text)
|
||||
return self:set_to(text)
|
||||
end
|
||||
|
||||
|
||||
--- Translate the text by locale_id
|
||||
-- @tparam LangText self @{LangText}
|
||||
-- @tparam string locale_id Locale id
|
||||
-- @tparam string|nil a Optional param to string.format
|
||||
-- @tparam string|nil b Optional param to string.format
|
||||
-- @tparam string|nil c Optional param to string.format
|
||||
-- @tparam string|nil d Optional param to string.format
|
||||
-- @tparam string|nil e Optional param to string.format
|
||||
-- @tparam string|nil f Optional param to string.format
|
||||
-- @tparam string|nil g Optional param to string.format
|
||||
-- @treturn LangText Current instance
|
||||
function LangText.translate(self, locale_id, a, b, c, d, e, f, g)
|
||||
---@param locale_id string Locale id
|
||||
---@param a string|nil Optional param to string.format
|
||||
---@param b string|nil Optional param to string.format
|
||||
---@param c string|nil Optional param to string.format
|
||||
---@param d string|nil Optional param to string.format
|
||||
---@param e string|nil Optional param to string.format
|
||||
---@param f string|nil Optional param to string.format
|
||||
---@param g string|nil Optional param to string.format
|
||||
---@return druid.lang_text Current instance
|
||||
function M:translate(locale_id, a, b, c, d, e, f, g)
|
||||
self.last_locale_args = { a, b, c, d, e, f, g }
|
||||
self.last_locale = locale_id or self.last_locale
|
||||
self.text:set_to(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "")
|
||||
self.text:set_text(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Format string with new text params on localized text
|
||||
-- @tparam LangText self @{LangText}
|
||||
-- @tparam string|nil a Optional param to string.format
|
||||
-- @tparam string|nil b Optional param to string.format
|
||||
-- @tparam string|nil c Optional param to string.format
|
||||
-- @tparam string|nil d Optional param to string.format
|
||||
-- @tparam string|nil e Optional param to string.format
|
||||
-- @tparam string|nil f Optional param to string.format
|
||||
-- @tparam string|nil g Optional param to string.format
|
||||
-- @treturn LangText Current instance
|
||||
function LangText.format(self, a, b, c, d, e, f, g)
|
||||
---@param a string|nil Optional param to string.format
|
||||
---@param b string|nil Optional param to string.format
|
||||
---@param c string|nil Optional param to string.format
|
||||
---@param d string|nil Optional param to string.format
|
||||
---@param e string|nil Optional param to string.format
|
||||
---@param f string|nil Optional param to string.format
|
||||
---@param g string|nil Optional param to string.format
|
||||
---@return druid.lang_text Current instance
|
||||
function M:format(a, b, c, d, e, f, g)
|
||||
self.last_locale_args = { a, b, c, d, e, f, g }
|
||||
self.text:set_to(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "")
|
||||
self.text:set_text(settings.get_text(self.last_locale, a, b, c, d, e, f, g) or "")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
return LangText
|
||||
return M
|
||||
|
||||
@@ -1,56 +1,66 @@
|
||||
-- Copyright (c) 2024 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Layout management on node
|
||||
--
|
||||
-- <a href="https://insality.github.io/druid/druid/index.html?example=general_layout" target="_blank"><b>Example Link</b></a>
|
||||
-- @module Layout
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.layout
|
||||
|
||||
--- Layout node
|
||||
-- @tfield node node
|
||||
|
||||
--- Current layout mode
|
||||
-- @tfield string mode
|
||||
|
||||
---
|
||||
|
||||
local event = require("event.event")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
-- @class druid.layout.row_data
|
||||
-- @tfield width number
|
||||
-- @tfield height number
|
||||
-- @tfield count number
|
||||
---@alias druid.layout.mode "horizontal"|"vertical"|"horizontal_wrap"
|
||||
|
||||
-- @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 event.on_size_changed: event
|
||||
---@field subscribe fun(_, callback: fun(new_size: vector3), context: any|nil)
|
||||
|
||||
-- @class druid.layout: druid.base_component
|
||||
---@class druid.layout.row_data
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field count number
|
||||
|
||||
---@class druid.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.layout.row_data[]>
|
||||
|
||||
---@class druid.layout: druid.base_component
|
||||
---@field node node
|
||||
---@field rows_data druid.layout.rows_data Last calculated rows data
|
||||
---@field is_dirty boolean
|
||||
---@field entities node[]
|
||||
---@field margin {x: number, y: number}
|
||||
---@field padding vector4
|
||||
---@field type string
|
||||
---@field is_resize_width boolean
|
||||
---@field is_resize_height boolean
|
||||
---@field is_justify boolean
|
||||
---@field on_size_changed event.on_size_changed
|
||||
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(self, node, layout_type)
|
||||
self.node = self:get_node(node)
|
||||
---Layout component constructor
|
||||
---@local
|
||||
---@param node_or_node_id node|string
|
||||
---@param layout_type druid.layout.mode
|
||||
function M:init(node_or_node_id, layout_type)
|
||||
self.node = self:get_node(node_or_node_id)
|
||||
|
||||
self.is_dirty = true
|
||||
self.entities = {}
|
||||
self.margin = { x = 0, y = 0 }
|
||||
self.size = gui.get_size(self.node)
|
||||
|
||||
self.padding = gui.get_slice9(self.node)
|
||||
-- Grab default margins from slice9 z/w values
|
||||
self.margin = { x = self.padding.z, y = self.padding.w }
|
||||
-- Use symmetrical padding from x/z
|
||||
self.padding.z = self.padding.x
|
||||
self.padding.w = self.padding.y
|
||||
|
||||
self.type = layout_type or "horizontal"
|
||||
self.is_resize_width = false
|
||||
self.is_resize_height = false
|
||||
self.is_justify = false
|
||||
|
||||
self.on_size_changed = event.create() --[[@as event.on_size_changed]]
|
||||
end
|
||||
|
||||
|
||||
---@local
|
||||
function M:update()
|
||||
if not self.is_dirty then
|
||||
return
|
||||
@@ -60,11 +70,26 @@ function M:update()
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @tparam number|nil margin_x
|
||||
-- @tparam number|nil margin_y
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.set_margin(self, margin_x, margin_y)
|
||||
function M:get_entities()
|
||||
return self.entities
|
||||
end
|
||||
|
||||
|
||||
function M:set_node_index(node, index)
|
||||
for i = 1, #self.entities do
|
||||
if self.entities[i] == node then
|
||||
table.remove(self.entities, i)
|
||||
table.insert(self.entities, index, node)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param margin_x number|nil
|
||||
---@param margin_y number|nil
|
||||
---@return druid.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
|
||||
@@ -73,30 +98,33 @@ function M.set_margin(self, margin_x, margin_y)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @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
|
||||
---@param padding_x number|nil
|
||||
---@param padding_y number|nil
|
||||
---@param padding_z number|nil
|
||||
---@param padding_w number|nil
|
||||
---@return druid.layout
|
||||
function M:set_padding(padding_x, padding_y, padding_z, padding_w)
|
||||
self.padding.x = padding_x or self.padding.x
|
||||
self.padding.y = padding_y or self.padding.y
|
||||
self.padding.z = padding_z or self.padding.z
|
||||
self.padding.w = padding_w or self.padding.w
|
||||
self.is_dirty = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.set_dirty(self)
|
||||
---@return druid.layout
|
||||
function M:set_dirty()
|
||||
self.is_dirty = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @tparam boolean is_justify
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.set_justify(self, is_justify)
|
||||
---@param is_justify boolean
|
||||
---@return druid.layout
|
||||
function M:set_justify(is_justify)
|
||||
self.is_justify = is_justify
|
||||
self.is_dirty = true
|
||||
|
||||
@@ -104,10 +132,9 @@ function M.set_justify(self, is_justify)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @tparam string type The layout type: "horizontal", "vertical", "horizontal_wrap"
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.set_type(self, type)
|
||||
---@param type string The layout type: "horizontal", "vertical", "horizontal_wrap"
|
||||
---@return druid.layout
|
||||
function M:set_type(type)
|
||||
self.type = type
|
||||
self.is_dirty = true
|
||||
|
||||
@@ -115,11 +142,10 @@ function M.set_type(self, type)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @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)
|
||||
---@param is_hug_width boolean
|
||||
---@param is_hug_height boolean
|
||||
---@return druid.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
|
||||
@@ -128,33 +154,63 @@ function M.set_hug_content(self, is_hug_width, is_hug_height)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @tparam string|node node_or_node_id
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.add(self, node_or_node_id)
|
||||
---Add node to layout
|
||||
---@param node_or_node_id node|string node_or_node_id
|
||||
---@return druid.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
|
||||
---@cast node_or_node_id string|node
|
||||
node = self:get_node(node_or_node_id)
|
||||
end
|
||||
|
||||
-- @cast node node
|
||||
---@cast node node
|
||||
table.insert(self.entities, node)
|
||||
gui.set_parent(node, self.node)
|
||||
|
||||
self.is_dirty = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.refresh_layout(self)
|
||||
---Remove node from layout
|
||||
---@param node_or_node_id node|string node_or_node_id
|
||||
---@return druid.layout self for chaining
|
||||
function M:remove(node_or_node_id)
|
||||
local node = type(node_or_node_id) == "table" and node_or_node_id.node or self:get_node(node_or_node_id)
|
||||
|
||||
for index = #self.entities, 1, -1 do
|
||||
if self.entities[index] == node then
|
||||
table.remove(self.entities, index)
|
||||
self.is_dirty = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@return vector3
|
||||
function M:get_size()
|
||||
return self.size
|
||||
end
|
||||
|
||||
|
||||
---@return number, number
|
||||
function M:get_content_size()
|
||||
local width = self.size.x - self.padding.x - self.padding.z
|
||||
local height = self.size.y - self.padding.y - self.padding.w
|
||||
return width, height
|
||||
end
|
||||
|
||||
|
||||
---@return druid.layout
|
||||
function M:refresh_layout()
|
||||
local layout_node = self.node
|
||||
|
||||
local entities = self.entities
|
||||
@@ -168,6 +224,7 @@ function M.refresh_layout(self)
|
||||
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()
|
||||
self.rows_data = rows_data
|
||||
local rows = rows_data.rows
|
||||
local row_index = 1
|
||||
local row = rows[row_index]
|
||||
@@ -191,7 +248,7 @@ function M.refresh_layout(self)
|
||||
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
|
||||
if node_width > 0 or node_height > 0 then
|
||||
-- Calculate position for current node
|
||||
local position_x, position_y
|
||||
|
||||
@@ -281,6 +338,9 @@ function M.refresh_layout(self)
|
||||
size.y = rows_data.total_height + padding.y + padding.w
|
||||
end
|
||||
gui.set_size(layout_node, size)
|
||||
self.size = size
|
||||
|
||||
self.on_size_changed(size)
|
||||
end
|
||||
|
||||
self.is_dirty = false
|
||||
@@ -289,9 +349,8 @@ function M.refresh_layout(self)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam Layout self @{Layout}
|
||||
-- @treturn druid.layout @{Layout}
|
||||
function M.clear_layout(self)
|
||||
---@return druid.layout
|
||||
function M:clear_layout()
|
||||
for index = #self.entities, 1, -1 do
|
||||
self.entities[index] = nil
|
||||
end
|
||||
@@ -302,10 +361,9 @@ function M.clear_layout(self)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam node node
|
||||
-- @treturn number, number
|
||||
-- @local
|
||||
function M.get_node_size(node)
|
||||
---@param node node
|
||||
---@return number, number
|
||||
function M:get_node_size(node)
|
||||
if not gui.is_enabled(node, false) then
|
||||
return 0, 0
|
||||
end
|
||||
@@ -323,11 +381,10 @@ function M.get_node_size(node)
|
||||
end
|
||||
|
||||
|
||||
-- @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
|
||||
-- @local
|
||||
function M.calculate_rows_data(self)
|
||||
---Calculate rows data for layout. Contains total width, height and rows info (width, height, count of elements in row)
|
||||
---@local
|
||||
---@return druid.layout.rows_data
|
||||
function M:calculate_rows_data()
|
||||
local entities = self.entities
|
||||
local margin = self.margin
|
||||
local type = self.type
|
||||
@@ -353,12 +410,12 @@ function M.calculate_rows_data(self)
|
||||
|
||||
-- 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)
|
||||
node_width, node_height = self: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 node_width > 0 or 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)
|
||||
@@ -407,16 +464,16 @@ function M.calculate_rows_data(self)
|
||||
end
|
||||
|
||||
|
||||
-- @tparam node node
|
||||
-- @tparam number x
|
||||
-- @tparam number y
|
||||
-- @treturn node
|
||||
-- @local
|
||||
---Will reset z value to 0!
|
||||
local TEMP_VECTOR = vmath.vector3(0, 0, 0)
|
||||
---@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)
|
||||
TEMP_VECTOR.x = x
|
||||
TEMP_VECTOR.y = y
|
||||
gui.set_position(node, TEMP_VECTOR)
|
||||
|
||||
return node
|
||||
end
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
-- @alias druid.progress
|
||||
|
||||
--- On progress bar change callback(self, new_value)
|
||||
-- @tfield DruidEvent on_change @{DruidEvent}
|
||||
-- @tfield event on_change event
|
||||
|
||||
--- Progress bar fill node
|
||||
-- @tfield node node
|
||||
@@ -44,12 +44,18 @@
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Progress = component.create("progress")
|
||||
---@class druid.progress: druid.base_component
|
||||
---@field node node
|
||||
---@field on_change event
|
||||
---@field style table
|
||||
---@field key string
|
||||
---@field prop hash
|
||||
local M = component.create("progress")
|
||||
|
||||
|
||||
local function check_steps(self, from, to, exactly)
|
||||
@@ -117,19 +123,18 @@ end
|
||||
-- @table style
|
||||
-- @tfield number|nil SPEED Progress bas fill rate. More -> faster. Default: 5
|
||||
-- @tfield number|nil MIN_DELTA Minimum step to fill progress bar. Default: 0.005
|
||||
function Progress.on_style_change(self, style)
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.SPEED = style.SPEED or 5
|
||||
self.style.MIN_DELTA = style.MIN_DELTA or 0.005
|
||||
end
|
||||
|
||||
|
||||
--- The @{Progress} constructor
|
||||
-- @tparam Progress self @{Progress}
|
||||
-- @tparam string|node node Node name or GUI Node itself.
|
||||
-- @tparam string key Progress bar direction: const.SIDE.X or const.SIDE.Y
|
||||
-- @tparam number|nil init_value Initial value of progress bar. Default: 1
|
||||
function Progress.init(self, node, key, init_value)
|
||||
--- The Progress constructor
|
||||
---@param node string|node Node name or GUI Node itself.
|
||||
---@param key string Progress bar direction: const.SIDE.X or const.SIDE.Y
|
||||
---@param init_value number|nil Initial value of progress bar. Default: 1
|
||||
function M:init(node, key, init_value)
|
||||
assert(key == const.SIDE.X or const.SIDE.Y, "Progress bar key should be 'x' or 'y'")
|
||||
|
||||
self.key = key
|
||||
@@ -149,24 +154,24 @@ function Progress.init(self, node, key, init_value)
|
||||
0
|
||||
)
|
||||
|
||||
self.on_change = Event()
|
||||
self.on_change = event.create()
|
||||
|
||||
self:set_to(self.last_value)
|
||||
end
|
||||
|
||||
|
||||
function Progress.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
self:set_to(self.last_value)
|
||||
end
|
||||
|
||||
|
||||
function Progress.on_remove(self)
|
||||
function M:on_remove()
|
||||
-- Return default size
|
||||
gui.set_size(self.node, self.max_size)
|
||||
end
|
||||
|
||||
|
||||
function Progress.update(self, dt)
|
||||
function M:update(dt)
|
||||
if self.target then
|
||||
local prev_value = self.last_value
|
||||
local step = math.abs(self.last_value - self.target) * (self.style.SPEED*dt)
|
||||
@@ -187,51 +192,45 @@ end
|
||||
|
||||
|
||||
--- Fill a progress bar and stop progress animation
|
||||
-- @tparam Progress self @{Progress}
|
||||
function Progress.fill(self)
|
||||
function M:fill()
|
||||
set_bar_to(self, 1, true)
|
||||
end
|
||||
|
||||
|
||||
--- Empty a progress bar
|
||||
-- @tparam Progress self @{Progress}
|
||||
function Progress.empty(self)
|
||||
function M:empty()
|
||||
set_bar_to(self, 0, true)
|
||||
end
|
||||
|
||||
|
||||
--- Instant fill progress bar to value
|
||||
-- @tparam Progress self @{Progress}
|
||||
-- @tparam number to Progress bar value, from 0 to 1
|
||||
function Progress.set_to(self, to)
|
||||
---@param to number Progress bar value, from 0 to 1
|
||||
function M:set_to(to)
|
||||
to = helper.clamp(to, 0, 1)
|
||||
set_bar_to(self, to)
|
||||
end
|
||||
|
||||
|
||||
--- Return current progress bar value
|
||||
-- @tparam Progress self @{Progress}
|
||||
function Progress.get(self)
|
||||
function M:get()
|
||||
return self.last_value
|
||||
end
|
||||
|
||||
|
||||
--- Set points on progress bar to fire the callback
|
||||
-- @tparam Progress self @{Progress}
|
||||
-- @tparam number[] steps Array of progress bar values
|
||||
-- @tparam function callback Callback on intersect step value
|
||||
---@param steps number[] Array of progress bar values
|
||||
---@param callback function Callback on intersect step value
|
||||
-- @usage progress:set_steps({0, 0.3, 0.6, 1}, function(self, step) end)
|
||||
function Progress.set_steps(self, steps, callback)
|
||||
function M:set_steps(steps, callback)
|
||||
self.steps = steps
|
||||
self.step_callback = callback
|
||||
end
|
||||
|
||||
|
||||
--- Start animation of a progress bar
|
||||
-- @tparam Progress self @{Progress}
|
||||
-- @tparam number to value between 0..1
|
||||
-- @tparam function|nil callback Callback on animation ends
|
||||
function Progress.to(self, to, callback)
|
||||
---@param to number value between 0..1
|
||||
---@param callback function|nil Callback on animation ends
|
||||
function M:to(to, callback)
|
||||
to = helper.clamp(to, 0, 1)
|
||||
-- cause of float error
|
||||
local value = helper.round(to, 5)
|
||||
@@ -247,14 +246,13 @@ end
|
||||
|
||||
|
||||
--- Set progress bar max node size
|
||||
-- @tparam Progress self @{Progress}
|
||||
-- @tparam vector3 max_size The new node maximum (full) size
|
||||
-- @treturn Progress @{Progress}
|
||||
function Progress.set_max_size(self, max_size)
|
||||
---@param max_size vector3 The new node maximum (full) size
|
||||
---@return druid.progress Progress
|
||||
function M:set_max_size(max_size)
|
||||
self.max_size[self.key] = max_size[self.key]
|
||||
self:set_to(self.last_value)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return Progress
|
||||
return M
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-- @alias druid.slider
|
||||
|
||||
--- On change value callback(self, value)
|
||||
-- @tfield DruidEvent on_change_value @{DruidEvent}
|
||||
-- @tfield event on_change_value event
|
||||
|
||||
--- Slider pin node
|
||||
-- @tfield node node
|
||||
@@ -37,12 +37,24 @@
|
||||
---
|
||||
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local helper = require("druid.helper")
|
||||
local const = require("druid.const")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Slider = component.create("slider", const.PRIORITY_INPUT_HIGH)
|
||||
---@class druid.slider: druid.base_component
|
||||
---@field node node
|
||||
---@field on_change_value event
|
||||
---@field style table
|
||||
---@field private start_pos vector3
|
||||
---@field private pos vector3
|
||||
---@field private target_pos vector3
|
||||
---@field private end_pos vector3
|
||||
---@field private dist vector3
|
||||
---@field private is_drag boolean
|
||||
---@field private value number
|
||||
---@field private steps number[]
|
||||
local M = component.create("slider", const.PRIORITY_INPUT_HIGH)
|
||||
|
||||
|
||||
local function on_change_value(self)
|
||||
@@ -56,12 +68,11 @@ local function set_position(self, value)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Slider} constructor
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @tparam node node Gui pin node
|
||||
-- @tparam vector3 end_pos The end position of slider
|
||||
-- @tparam function|nil callback On slider change callback
|
||||
function Slider.init(self, node, end_pos, callback)
|
||||
--- The Slider constructor
|
||||
---@param node node Gui pin node
|
||||
---@param end_pos vector3 The end position of slider
|
||||
---@param callback function|nil On slider change callback
|
||||
function M:init(node, end_pos, callback)
|
||||
self.node = self:get_node(node)
|
||||
|
||||
self.start_pos = gui.get_position(self.node)
|
||||
@@ -74,25 +85,25 @@ function Slider.init(self, node, end_pos, callback)
|
||||
self.is_drag = false
|
||||
self.value = 0
|
||||
|
||||
self.on_change_value = Event(callback)
|
||||
self.on_change_value = event.create(callback)
|
||||
self:on_window_resized()
|
||||
|
||||
assert(self.dist.x == 0 or self.dist.y == 0, "Slider for now can be only vertical or horizontal")
|
||||
end
|
||||
|
||||
|
||||
function Slider.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
self:set(self.value)
|
||||
end
|
||||
|
||||
|
||||
function Slider.on_remove(self)
|
||||
function M:on_remove()
|
||||
-- Return pin to start position
|
||||
gui.set_position(self.node, self.start_pos)
|
||||
end
|
||||
|
||||
|
||||
function Slider.on_window_resized(self)
|
||||
function M:on_window_resized()
|
||||
local x_koef, y_koef = helper.get_screen_aspect_koef()
|
||||
self._x_koef = x_koef
|
||||
self._y_koef = y_koef
|
||||
@@ -100,7 +111,7 @@ function Slider.on_window_resized(self)
|
||||
end
|
||||
|
||||
|
||||
function Slider.on_input(self, action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
if action_id ~= const.ACTION_TOUCH then
|
||||
return false
|
||||
end
|
||||
@@ -185,10 +196,9 @@ end
|
||||
|
||||
|
||||
--- Set value for slider
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @tparam number value Value from 0 to 1
|
||||
-- @tparam boolean|nil is_silent Don't trigger event if true
|
||||
function Slider.set(self, value, is_silent)
|
||||
---@param value number Value from 0 to 1
|
||||
---@param is_silent boolean|nil Don't trigger event if true
|
||||
function M:set(value, is_silent)
|
||||
value = helper.clamp(value, 0, 1)
|
||||
set_position(self, value)
|
||||
self.value = value
|
||||
@@ -200,11 +210,10 @@ end
|
||||
|
||||
--- Set slider steps. Pin node will
|
||||
-- apply closest step position
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @tparam number[] steps Array of steps
|
||||
---@param steps number[] Array of steps
|
||||
-- @usage slider:set_steps({0, 0.2, 0.6, 1})
|
||||
-- @treturn Slider @{Slider}
|
||||
function Slider.set_steps(self, steps)
|
||||
---@return druid.slider Slider
|
||||
function M:set_steps(steps)
|
||||
self.steps = steps
|
||||
return self
|
||||
end
|
||||
@@ -214,29 +223,31 @@ end
|
||||
-- User can touch any place of node, pin instantly will
|
||||
-- move at this position and node drag will start.
|
||||
-- This function require the Defold version 1.3.0+
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @tparam node|string|nil input_node
|
||||
-- @treturn Slider @{Slider}
|
||||
function Slider.set_input_node(self, input_node)
|
||||
---@param input_node node|string|nil
|
||||
---@return druid.slider Slider
|
||||
function M:set_input_node(input_node)
|
||||
if not input_node then
|
||||
self._input_node = nil
|
||||
return self
|
||||
end
|
||||
|
||||
self._input_node = self:get_node(input_node)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set Slider input enabled or disabled
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @tparam boolean is_enabled
|
||||
function Slider.set_enabled(self, is_enabled)
|
||||
---@param is_enabled boolean
|
||||
function M:set_enabled(is_enabled)
|
||||
self._is_enabled = is_enabled
|
||||
end
|
||||
|
||||
|
||||
--- Check if Slider component is enabled
|
||||
-- @tparam Slider self @{Slider}
|
||||
-- @treturn boolean
|
||||
function Slider.is_enabled(self)
|
||||
---@return boolean
|
||||
function M:is_enabled()
|
||||
return self._is_enabled
|
||||
end
|
||||
|
||||
|
||||
return Slider
|
||||
return M
|
||||
|
||||
@@ -10,22 +10,32 @@
|
||||
-- @alias druid.swipe
|
||||
|
||||
--- Swipe node
|
||||
-- @tparam node node
|
||||
--@param node node
|
||||
|
||||
--- Restriction zone
|
||||
-- @tparam node|nil click_zone
|
||||
--@param click_zone node|nil
|
||||
|
||||
--- Trigger on swipe event(self, swipe_side, dist, delta_time)
|
||||
-- @tfield DruidEvent on_swipe) @{DruidEvent}
|
||||
--@param event event on_swipe
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Swipe = component.create("swipe")
|
||||
---@class druid.swipe: druid.base_component
|
||||
---@field node node
|
||||
---@field on_swipe event function(side, dist, dt), side - "left", "right", "up", "down"
|
||||
---@field style table
|
||||
---@field click_zone node
|
||||
---@field private _trigger_on_move boolean
|
||||
---@field private _swipe_start_time number
|
||||
---@field private _start_pos vector3
|
||||
---@field private _is_enabled boolean
|
||||
---@field private _is_mobile boolean
|
||||
local M = component.create("swipe")
|
||||
|
||||
|
||||
local function start_swipe(self, action)
|
||||
@@ -36,7 +46,7 @@ end
|
||||
|
||||
|
||||
local function reset_swipe(self, action)
|
||||
self._swipe_start_time = false
|
||||
self._swipe_start_time = 0
|
||||
end
|
||||
|
||||
|
||||
@@ -49,19 +59,19 @@ local function check_swipe(self, action)
|
||||
|
||||
if is_swipe then
|
||||
local is_x_swipe = math.abs(dx) >= math.abs(dy)
|
||||
local swipe_side = false
|
||||
local swipe_side = "undefined"
|
||||
|
||||
if is_x_swipe and dx > 0 then
|
||||
swipe_side = const.SWIPE.RIGHT
|
||||
swipe_side = "right"
|
||||
end
|
||||
if is_x_swipe and dx < 0 then
|
||||
swipe_side = const.SWIPE.LEFT
|
||||
swipe_side = "left"
|
||||
end
|
||||
if not is_x_swipe and dy > 0 then
|
||||
swipe_side = const.SWIPE.UP
|
||||
swipe_side = "up"
|
||||
end
|
||||
if not is_x_swipe and dy < 0 then
|
||||
swipe_side = const.SWIPE.DOWN
|
||||
swipe_side = "down"
|
||||
end
|
||||
|
||||
self.on_swipe:trigger(self:get_context(), swipe_side, dist, delta_time)
|
||||
@@ -73,11 +83,13 @@ end
|
||||
--- Component style params.
|
||||
-- You can override this component styles params in druid styles table
|
||||
-- or create your own style
|
||||
-- @table style
|
||||
-- @tfield number|nil SWIPE_TIME Maximum time for swipe trigger. Default: 0.4
|
||||
-- @tfield number|nil SWIPE_THRESHOLD Minimum distance for swipe trigger. Default: 50
|
||||
-- @tfield boolean|nil SWIPE_TRIGGER_ON_MOVE If true, trigger on swipe moving, not only release action. Default: false
|
||||
function Swipe.on_style_change(self, style)
|
||||
---@class druid.swipe.style
|
||||
---@field SWIPE_TIME number|nil Maximum time for swipe trigger. Default: 0.4
|
||||
---@field SWIPE_THRESHOLD number|nil Minimum distance for swipe trigger. Default: 50
|
||||
---@field SWIPE_TRIGGER_ON_MOVE boolean|nil If true, trigger on swipe moving, not only release action. Default: false
|
||||
|
||||
---@param style druid.swipe.style
|
||||
function M:on_style_change(style)
|
||||
self.style = {}
|
||||
self.style.SWIPE_TIME = style.SWIPE_TIME or 0.4
|
||||
self.style.SWIPE_THRESHOLD = style.SWIPE_THRESHOLD or 50
|
||||
@@ -85,24 +97,23 @@ function Swipe.on_style_change(self, style)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Swipe} constructor
|
||||
-- @tparam Swipe self @{Swipe}
|
||||
-- @tparam node node Gui node
|
||||
-- @tparam function on_swipe_callback Swipe callback for on_swipe_end event
|
||||
function Swipe.init(self, node, on_swipe_callback)
|
||||
---Swipe constructor
|
||||
---@param node_or_node_id node|string
|
||||
---@param on_swipe_callback function
|
||||
function M:init(node_or_node_id, on_swipe_callback)
|
||||
self._trigger_on_move = self.style.SWIPE_TRIGGER_ON_MOVE
|
||||
self.node = self:get_node(node)
|
||||
self.node = self:get_node(node_or_node_id)
|
||||
|
||||
self._swipe_start_time = false
|
||||
self._swipe_start_time = 0
|
||||
self._start_pos = vmath.vector3(0)
|
||||
|
||||
self.click_zone = nil
|
||||
self.on_swipe = Event(on_swipe_callback)
|
||||
self.on_swipe = event.create(on_swipe_callback)
|
||||
end
|
||||
|
||||
|
||||
function Swipe.on_late_init(self)
|
||||
if not self.click_zone and const.IS_STENCIL_CHECK then
|
||||
function M:on_late_init()
|
||||
if not self.click_zone then
|
||||
local stencil_node = helper.get_closest_stencil_node(self.node)
|
||||
if stencil_node then
|
||||
self:set_click_zone(stencil_node)
|
||||
@@ -111,7 +122,9 @@ function Swipe.on_late_init(self)
|
||||
end
|
||||
|
||||
|
||||
function Swipe.on_input(self, action_id, action)
|
||||
---@param action_id hash
|
||||
---@param action action
|
||||
function M:on_input(action_id, action)
|
||||
if action_id ~= const.ACTION_TOUCH then
|
||||
return false
|
||||
end
|
||||
@@ -126,7 +139,7 @@ function Swipe.on_input(self, action_id, action)
|
||||
return false
|
||||
end
|
||||
|
||||
if self._swipe_start_time and (self._trigger_on_move or action.released) then
|
||||
if self._swipe_start_time ~= 0 and (self._trigger_on_move or action.released) then
|
||||
check_swipe(self, action)
|
||||
end
|
||||
|
||||
@@ -142,18 +155,22 @@ function Swipe.on_input(self, action_id, action)
|
||||
end
|
||||
|
||||
|
||||
function Swipe.on_input_interrupt(self)
|
||||
function M:on_input_interrupt()
|
||||
reset_swipe(self)
|
||||
end
|
||||
|
||||
|
||||
--- Strict swipe click area. Useful for
|
||||
-- restrict events outside stencil node
|
||||
-- @tparam Swipe self @{Swipe}
|
||||
-- @tparam node|string|nil zone Gui node
|
||||
function Swipe.set_click_zone(self, zone)
|
||||
---@param zone node|string|nil Gui node
|
||||
function M:set_click_zone(zone)
|
||||
if not zone then
|
||||
self.click_zone = nil
|
||||
return
|
||||
end
|
||||
|
||||
self.click_zone = self:get_node(zone)
|
||||
end
|
||||
|
||||
|
||||
return Swipe
|
||||
return M
|
||||
|
||||
@@ -1,40 +1,18 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Component to handle GUI timers.
|
||||
-- Timer updating by game delta time. If game is not focused -
|
||||
-- timer will be not updated.
|
||||
-- @module Timer
|
||||
-- @within BaseComponent
|
||||
-- @alias druid.timer
|
||||
|
||||
--- On timer tick. Fire every second callback(self, value)
|
||||
-- @tfield DruidEvent on_tick @{DruidEvent}
|
||||
|
||||
--- On timer change enabled state callback(self, is_enabled)
|
||||
-- @tfield DruidEvent on_set_enabled @{DruidEvent}
|
||||
|
||||
--- On timer end callback
|
||||
-- @tfield DruidEvent on_timer_end(self, Timer) @{DruidEvent}
|
||||
|
||||
--- Trigger node
|
||||
-- @tfield node node
|
||||
|
||||
--- Initial timer value
|
||||
-- @tfield number from
|
||||
|
||||
--- Target timer value
|
||||
-- @tfield number target
|
||||
|
||||
--- Current timer value
|
||||
-- @tfield number value
|
||||
|
||||
---
|
||||
|
||||
local Event = require("druid.event")
|
||||
local event = require("event.event")
|
||||
local helper = require("druid.helper")
|
||||
local component = require("druid.component")
|
||||
|
||||
local Timer = component.create("timer")
|
||||
---@class druid.timer: druid.base_component
|
||||
---@field on_tick event
|
||||
---@field on_set_enabled event
|
||||
---@field on_timer_end event
|
||||
---@field style table
|
||||
---@field node node
|
||||
---@field from number
|
||||
---@field target number
|
||||
---@field value number
|
||||
---@field is_on boolean|nil
|
||||
local M = component.create("timer")
|
||||
|
||||
|
||||
local function second_string_min(sec)
|
||||
@@ -44,19 +22,18 @@ local function second_string_min(sec)
|
||||
end
|
||||
|
||||
|
||||
--- The @{Timer} constructor
|
||||
-- @tparam Timer self @{Timer}
|
||||
-- @tparam node node Gui text node
|
||||
-- @tparam number|nil seconds_from Start timer value in seconds
|
||||
-- @tparam number|nil seconds_to End timer value in seconds
|
||||
-- @tparam function|nil callback Function on timer end
|
||||
function Timer.init(self, node, seconds_from, seconds_to, callback)
|
||||
---The Timer constructor
|
||||
---@param node node Gui text node
|
||||
---@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 M:init(node, seconds_from, seconds_to, callback)
|
||||
self.node = self:get_node(node)
|
||||
seconds_to = math.max(seconds_to or 0, 0)
|
||||
|
||||
self.on_tick = Event()
|
||||
self.on_set_enabled = Event()
|
||||
self.on_timer_end = Event(callback)
|
||||
self.on_tick = event.create()
|
||||
self.on_set_enabled = event.create()
|
||||
self.on_timer_end = event.create(callback)
|
||||
|
||||
if seconds_from then
|
||||
seconds_from = math.max(seconds_from, 0)
|
||||
@@ -73,7 +50,7 @@ function Timer.init(self, node, seconds_from, seconds_to, callback)
|
||||
end
|
||||
|
||||
|
||||
function Timer.update(self, dt)
|
||||
function M:update(dt)
|
||||
if not self.is_on then
|
||||
return
|
||||
end
|
||||
@@ -96,42 +73,44 @@ function Timer.update(self, dt)
|
||||
end
|
||||
|
||||
|
||||
function Timer.on_layout_change(self)
|
||||
function M:on_layout_change()
|
||||
self:set_to(self.last_value)
|
||||
end
|
||||
|
||||
|
||||
--- Set text to text field
|
||||
-- @tparam Timer self @{Timer}
|
||||
-- @tparam number set_to Value in seconds
|
||||
function Timer.set_to(self, set_to)
|
||||
---@param set_to number Value in seconds
|
||||
---@return druid.timer self
|
||||
function M:set_to(set_to)
|
||||
self.last_value = set_to
|
||||
gui.set_text(self.node, second_string_min(set_to))
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Called when update
|
||||
-- @tparam Timer self @{Timer}
|
||||
-- @tparam boolean|nil is_on Timer enable state
|
||||
function Timer.set_state(self, is_on)
|
||||
---@param is_on boolean|nil Timer enable state
|
||||
---@return druid.timer self
|
||||
function M:set_state(is_on)
|
||||
self.is_on = is_on
|
||||
|
||||
self.on_set_enabled:trigger(self:get_context(), is_on)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Set time interval
|
||||
-- @tparam Timer self @{Timer}
|
||||
-- @tparam number from Start time in seconds
|
||||
-- @tparam number to Target time in seconds
|
||||
function Timer.set_interval(self, from, to)
|
||||
---@param from number Start time in seconds
|
||||
---@param to number Target time in seconds
|
||||
---@return druid.timer self
|
||||
function M:set_interval(from, to)
|
||||
self.from = from
|
||||
self.value = from
|
||||
self.temp = 0
|
||||
self.target = to
|
||||
self:set_state(true)
|
||||
self:set_to(from)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return Timer
|
||||
return M
|
||||
|
||||
10
druid/fonts/druid_text_bold.font
Normal file
10
druid/fonts/druid_text_bold.font
Normal file
File diff suppressed because one or more lines are too long
8
druid/fonts/druid_text_regular.font
Normal file
8
druid/fonts/druid_text_regular.font
Normal file
File diff suppressed because one or more lines are too long
@@ -1,10 +0,0 @@
|
||||
font: "/druid/fonts/Roboto-Bold.ttf"
|
||||
material: "/builtins/fonts/font-df.material"
|
||||
size: 40
|
||||
outline_alpha: 1.0
|
||||
outline_width: 2.0
|
||||
shadow_alpha: 1.0
|
||||
shadow_blur: 2
|
||||
output_format: TYPE_DISTANCE_FIELD
|
||||
render_mode: MODE_MULTI_LAYER
|
||||
characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\302\241\302\277\303\200\303\202\303\206\303\207\303\210\303\211\303\212\303\213\303\216\303\217\303\224\303\231\303\233\303\234\303\240\303\241\303\242\303\243\303\244\303\246\303\247\303\250\303\251\303\252\303\253\303\255\303\256\303\257\303\261\303\263\303\264\303\271\303\273\303\274\303\277\305\222\305\223\305\270\320\201\320\220\320\221\320\222\320\223\320\224\320\225\320\226\320\227\320\230\320\231\320\232\320\233\320\234\320\235\320\236\320\237\320\240\320\241\320\242\320\243\320\244\320\245\320\246\320\247\320\250\320\251\320\252\320\253\320\254\320\255\320\256\320\257\320\260\320\261\320\262\320\263\320\264\320\265\320\266\320\267\320\270\320\271\320\272\320\273\320\274\320\275\320\276\320\277\321\200\321\201\321\202\321\203\321\204\321\205\321\206\321\207\321\210\321\211\321\212\321\213\321\214\321\215\321\216\321\217\321\221\343\200\202\357\274\201\357\274\237"
|
||||
@@ -1,8 +0,0 @@
|
||||
font: "/druid/fonts/Roboto-Regular.ttf"
|
||||
material: "/builtins/fonts/font-df.material"
|
||||
size: 40
|
||||
outline_alpha: 1.0
|
||||
outline_width: 2.0
|
||||
output_format: TYPE_DISTANCE_FIELD
|
||||
render_mode: MODE_MULTI_LAYER
|
||||
characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\302\241\302\277\303\200\303\202\303\206\303\207\303\210\303\211\303\212\303\213\303\216\303\217\303\224\303\231\303\233\303\234\303\240\303\241\303\242\303\243\303\244\303\246\303\247\303\250\303\251\303\252\303\253\303\255\303\256\303\257\303\261\303\263\303\264\303\271\303\273\303\274\303\277\305\222\305\223\305\270\320\201\320\220\320\221\320\222\320\223\320\224\320\225\320\226\320\227\320\230\320\231\320\232\320\233\320\234\320\235\320\236\320\237\320\240\320\241\320\242\320\243\320\244\320\245\320\246\320\247\320\250\320\251\320\252\320\253\320\254\320\255\320\256\320\257\320\260\320\261\320\262\320\263\320\264\320\265\320\266\320\267\320\270\320\271\320\272\320\273\320\274\320\275\320\276\320\277\321\200\321\201\321\202\321\203\321\204\321\205\321\206\321\207\321\210\321\211\321\212\321\213\321\214\321\215\321\216\321\217\321\221\343\200\202\357\274\201\357\274\237"
|
||||
517
druid/helper.lua
517
druid/helper.lua
@@ -1,24 +1,26 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Helper module with various usefull GUI functions.
|
||||
-- @usage
|
||||
-- local helper = require("druid.helper")
|
||||
-- helper.centrate_nodes(0, node_1, node_2)
|
||||
-- @module Helper
|
||||
-- @alias druid.helper
|
||||
|
||||
local const = require("druid.const")
|
||||
|
||||
-- Localize functions for better performance
|
||||
local gui_get_node = gui.get_node
|
||||
local gui_get = gui.get
|
||||
local gui_pick_node = gui.pick_node
|
||||
|
||||
---@class druid.system.helper
|
||||
local M = {}
|
||||
|
||||
local POSITION_X = hash("position.x")
|
||||
local SCALE_X = hash("scale.x")
|
||||
local SIZE_X = hash("size.x")
|
||||
|
||||
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")
|
||||
|
||||
local function get_text_width(text_node)
|
||||
if text_node then
|
||||
local text_metrics = M.get_text_metrics_from_node(text_node)
|
||||
local text_scale = gui.get(text_node, SCALE_X)
|
||||
local text_scale = gui_get(text_node, SCALE_X)
|
||||
return text_metrics.width * text_scale
|
||||
end
|
||||
|
||||
@@ -28,7 +30,7 @@ end
|
||||
|
||||
local function get_icon_width(icon_node)
|
||||
if icon_node then
|
||||
return gui.get(icon_node, SIZE_X) * gui.get(icon_node, SCALE_X) -- icon width
|
||||
return gui_get(icon_node, SIZE_X) * gui_get(icon_node, SCALE_X) -- icon width
|
||||
end
|
||||
|
||||
return 0
|
||||
@@ -46,39 +48,36 @@ local function get_width(node)
|
||||
end
|
||||
|
||||
|
||||
--- Center two nodes.
|
||||
-- Nodes will be center around 0 x position
|
||||
-- text_node will be first (at left side)
|
||||
-- @function helper.centrate_text_with_icon
|
||||
-- @tparam text|nil text_node Gui text node
|
||||
-- @tparam box|nil icon_node Gui box node
|
||||
-- @tparam number margin Offset between nodes
|
||||
-- @local
|
||||
---Center two nodes.
|
||||
--Nodes will be center around 0 x position
|
||||
--text_node will be first (at left side)
|
||||
---@param text_node node|nil Gui text node
|
||||
---@param icon_node node|nil Gui box node
|
||||
---@param margin number Offset between nodes
|
||||
---@local
|
||||
function M.centrate_text_with_icon(text_node, icon_node, margin)
|
||||
return M.centrate_nodes(margin, text_node, icon_node)
|
||||
end
|
||||
|
||||
|
||||
--- Center two nodes.
|
||||
-- Nodes will be center around 0 x position
|
||||
-- icon_node will be first (at left side)
|
||||
-- @function helper.centrate_icon_with_text
|
||||
-- @tparam box|nil icon_node Gui box node
|
||||
-- @tparam text|nil text_node Gui text node
|
||||
-- @tparam number|nil margin Offset between nodes
|
||||
-- @local
|
||||
---Center two nodes.
|
||||
--Nodes will be center around 0 x position
|
||||
--icon_node will be first (at left side)
|
||||
---@param icon_node node|nil Gui box node
|
||||
---@param text_node node|nil Gui text node
|
||||
---@param margin number|nil Offset between nodes
|
||||
---@local
|
||||
function M.centrate_icon_with_text(icon_node, text_node, margin)
|
||||
return M.centrate_nodes(margin, icon_node, text_node)
|
||||
end
|
||||
|
||||
|
||||
--- Centerate nodes by x position with margin.
|
||||
--
|
||||
-- This functions calculate total width of nodes and set position for each node.
|
||||
-- The centrate will be around 0 x position.
|
||||
-- @function helper.centrate_nodes
|
||||
-- @tparam number|nil margin Offset between nodes
|
||||
-- @param ... Gui nodes
|
||||
---Centerate nodes by x position with margin.
|
||||
---
|
||||
---This functions calculate total width of nodes and set position for each node.
|
||||
---The centrate will be around 0 x position.
|
||||
---@param margin number|nil Offset between nodes
|
||||
---@param ... node Nodes to centrate
|
||||
function M.centrate_nodes(margin, ...)
|
||||
margin = margin or 0
|
||||
|
||||
@@ -113,36 +112,60 @@ function M.centrate_nodes(margin, ...)
|
||||
end
|
||||
|
||||
|
||||
--- Get current screen stretch multiplier for each side
|
||||
-- @function helper.get_screen_aspect_koef
|
||||
-- @treturn number stretch_x
|
||||
-- @treturn number stretch_y
|
||||
---@param node_id string|node
|
||||
---@param template string|nil @Full Path to the template
|
||||
---@param nodes table<hash, node>|nil @Nodes what created with gui.clone_tree
|
||||
---@return node
|
||||
function M.get_node(node_id, template, nodes)
|
||||
if type(node_id) ~= "string" then
|
||||
-- Assume it's already node from gui.get_node
|
||||
return node_id
|
||||
end
|
||||
|
||||
-- If template is set, then add it to the node_id
|
||||
if template and #template > 0 then
|
||||
node_id = template .. "/" .. node_id
|
||||
end
|
||||
|
||||
-- If nodes is set, then try to find node in it
|
||||
if nodes then
|
||||
return nodes[node_id]
|
||||
end
|
||||
|
||||
return gui_get_node(node_id)
|
||||
end
|
||||
|
||||
|
||||
---Get current screen stretch multiplier for each side
|
||||
---@return number stretch_x
|
||||
---@return number stretch_y
|
||||
function M.get_screen_aspect_koef()
|
||||
local window_x, window_y = window.get_size()
|
||||
|
||||
local stretch_x = window_x / gui.get_width()
|
||||
local stretch_y = window_y / gui.get_height()
|
||||
return stretch_x / math.min(stretch_x, stretch_y),
|
||||
stretch_y / math.min(stretch_x, stretch_y)
|
||||
local stretch_koef = math.min(stretch_x, stretch_y)
|
||||
|
||||
local koef_x = window_x / (stretch_koef * sys.get_config_int("display.width"))
|
||||
local koef_y = window_y / (stretch_koef * sys.get_config_int("display.height"))
|
||||
|
||||
return koef_x, koef_y
|
||||
end
|
||||
|
||||
|
||||
--- Get current GUI scale for each side
|
||||
-- @function helper.get_gui_scale
|
||||
-- @treturn number scale_x
|
||||
-- @treturn number scale_y
|
||||
---Get current GUI scale for each side
|
||||
---@return number scale_x
|
||||
function M.get_gui_scale()
|
||||
local window_x, window_y = window.get_size()
|
||||
return math.min(window_x / gui.get_width(),
|
||||
window_y / gui.get_height())
|
||||
return math.min(window_x / gui.get_width(), window_y / gui.get_height())
|
||||
end
|
||||
|
||||
|
||||
--- Move value from current to target value with step amount
|
||||
-- @function helper.step
|
||||
-- @tparam number current Current value
|
||||
-- @tparam number target Target value
|
||||
-- @tparam number step Step amount
|
||||
-- @treturn number New value
|
||||
---Move value from current to target value with step amount
|
||||
---@param current number Current value
|
||||
---@param target number Target value
|
||||
---@param step number Step amount
|
||||
---@return number New value
|
||||
function M.step(current, target, step)
|
||||
if current < target then
|
||||
return math.min(current + step, target)
|
||||
@@ -152,43 +175,44 @@ function M.step(current, target, step)
|
||||
end
|
||||
|
||||
|
||||
--- Clamp value between min and max
|
||||
-- @function helper.clamp
|
||||
-- @tparam number a Value
|
||||
-- @tparam number min Min value
|
||||
-- @tparam number max Max value
|
||||
-- @treturn number Clamped value
|
||||
function M.clamp(a, min, max)
|
||||
if min > max then
|
||||
min, max = max, min
|
||||
---Clamp value between min and max
|
||||
---@param value number Value
|
||||
---@param v1 number|nil Min value. If nil, value will be clamped to positive infinity
|
||||
---@param v2 number|nil Max value If nil, value will be clamped to negative infinity
|
||||
---@return number value Clamped value
|
||||
function M.clamp(value, v1, v2)
|
||||
if v1 and v2 then
|
||||
if v1 > v2 then
|
||||
v1, v2 = v2, v1
|
||||
end
|
||||
end
|
||||
|
||||
if a >= min and a <= max then
|
||||
return a
|
||||
elseif a < min then
|
||||
return min
|
||||
else
|
||||
return max
|
||||
if v1 and value < v1 then
|
||||
return v1
|
||||
end
|
||||
|
||||
if v2 and value > v2 then
|
||||
return v2
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
--- Calculate distance between two points
|
||||
-- @function helper.distance
|
||||
-- @tparam number x1 First point x
|
||||
-- @tparam number y1 First point y
|
||||
-- @tparam number x2 Second point x
|
||||
-- @tparam number y2 Second point y
|
||||
-- @treturn number Distance
|
||||
---Calculate distance between two points
|
||||
---@param x1 number First point x
|
||||
---@param y1 number First point y
|
||||
---@param x2 number Second point x
|
||||
---@param y2 number Second point y
|
||||
---@return number Distance
|
||||
function M.distance(x1, y1, x2, y2)
|
||||
return math.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
|
||||
end
|
||||
|
||||
|
||||
--- Return sign of value (-1, 0, 1)
|
||||
-- @function helper.sign
|
||||
-- @tparam number val Value
|
||||
-- @treturn number Sign
|
||||
---Return sign of value
|
||||
---@param val number Value
|
||||
---@return number sign Sign of value, -1, 0 or 1
|
||||
function M.sign(val)
|
||||
if val == 0 then
|
||||
return 0
|
||||
@@ -198,47 +222,42 @@ function M.sign(val)
|
||||
end
|
||||
|
||||
|
||||
--- Round number to specified decimal places
|
||||
-- @function helper.round
|
||||
-- @tparam number num Number
|
||||
-- @tparam number|nil num_decimal_places Decimal places
|
||||
-- @treturn number Rounded number
|
||||
---Round number to specified decimal places
|
||||
---@param num number Number
|
||||
---@param num_decimal_places number|nil Decimal places
|
||||
---@return number value Rounded number
|
||||
function M.round(num, num_decimal_places)
|
||||
local mult = 10^(num_decimal_places or 0)
|
||||
return math.floor(num * mult + 0.5) / mult
|
||||
end
|
||||
|
||||
|
||||
--- Lerp between two values
|
||||
-- @function helper.lerp
|
||||
-- @tparam number a First value
|
||||
-- @tparam number b Second value
|
||||
-- @tparam number t Lerp amount
|
||||
-- @treturn number Lerped value
|
||||
---Lerp between two values
|
||||
---@param a number First value
|
||||
---@param b number Second value
|
||||
---@param t number Lerp amount
|
||||
---@return number value Lerped value
|
||||
function M.lerp(a, b, t)
|
||||
return a + (b - a) * t
|
||||
end
|
||||
|
||||
|
||||
--- Check if value is in array and return index of it
|
||||
-- @function helper.contains
|
||||
-- @tparam table t Array
|
||||
-- @param value Value
|
||||
-- @treturn number|nil Index of value or nil
|
||||
function M.contains(t, value)
|
||||
for i = 1, #t do
|
||||
if t[i] == value then
|
||||
return i
|
||||
---Check if value contains in array
|
||||
---@param array any[] Array to check
|
||||
---@param value any Value
|
||||
function M.contains(array, value)
|
||||
for index = 1, #array do
|
||||
if array[index] == value then
|
||||
return index
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
--- Make a copy table with all nested tables
|
||||
-- @function helper.deepcopy
|
||||
-- @tparam table orig_table Original table
|
||||
-- @treturn table Copy of original table
|
||||
---Make a copy table with all nested tables
|
||||
---@param orig_table table Original table
|
||||
---@return table Copy of original table
|
||||
function M.deepcopy(orig_table)
|
||||
local orig_type = type(orig_table)
|
||||
local copy
|
||||
@@ -254,11 +273,10 @@ function M.deepcopy(orig_table)
|
||||
end
|
||||
|
||||
|
||||
--- Add all elements from source array to the target array
|
||||
-- @function helper.add_array
|
||||
-- @tparam any[] target Array to put elements from source
|
||||
-- @tparam any[]|nil source The source array to get elements from
|
||||
-- @treturn any[] The target array
|
||||
---Add all elements from source array to the target array
|
||||
---@param target any[] Array to put elements from source
|
||||
---@param source any[]|nil The source array to get elements from
|
||||
---@return any[] The target array
|
||||
function M.add_array(target, source)
|
||||
assert(target)
|
||||
|
||||
@@ -274,37 +292,35 @@ function M.add_array(target, source)
|
||||
end
|
||||
|
||||
|
||||
--- Make a check with gui.pick_node, but with additional node_click_area check.
|
||||
-- @function helper.pick_node
|
||||
-- @tparam node node
|
||||
-- @tparam number x
|
||||
-- @tparam number y
|
||||
-- @tparam node|nil node_click_area
|
||||
-- @local
|
||||
---Make a check with gui.pick_node, but with additional node_click_area check.
|
||||
---@param node node
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param node_click_area node|nil
|
||||
---@local
|
||||
function M.pick_node(node, x, y, node_click_area)
|
||||
local is_pick = gui.pick_node(node, x, y)
|
||||
local is_pick = gui_pick_node(node, x, y)
|
||||
|
||||
if node_click_area then
|
||||
is_pick = is_pick and gui.pick_node(node_click_area, x, y)
|
||||
is_pick = is_pick and gui_pick_node(node_click_area, x, y)
|
||||
end
|
||||
|
||||
return is_pick
|
||||
end
|
||||
|
||||
--- Get node size adjusted by scale
|
||||
-- @function helper.get_scaled_size
|
||||
-- @tparam node node GUI node
|
||||
-- @treturn vector3 Scaled size
|
||||
|
||||
---Get size of node with scale multiplier
|
||||
---@param node node GUI node
|
||||
---@return vector3 scaled_size
|
||||
function M.get_scaled_size(node)
|
||||
return vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node))
|
||||
return vmath.mul_per_elem(gui.get_size(node), gui.get_scale(node)) --[[@as vector3]]
|
||||
end
|
||||
|
||||
|
||||
--- Get cumulative parent's node scale
|
||||
-- @function helper.get_scene_scale
|
||||
-- @tparam node node Gui node
|
||||
-- @tparam boolean|nil include_passed_node_scale True if add current node scale to result
|
||||
-- @treturn vector3 The scene node scale
|
||||
---Get cumulative parent's node scale
|
||||
---@param node node Gui node
|
||||
---@param include_passed_node_scale boolean|nil True if add current node scale to result
|
||||
---@return vector3 The scene node scale
|
||||
function M.get_scene_scale(node, include_passed_node_scale)
|
||||
local scale = include_passed_node_scale and gui.get_scale(node) or vmath.vector3(1)
|
||||
local parent = gui.get_parent(node)
|
||||
@@ -317,10 +333,9 @@ function M.get_scene_scale(node, include_passed_node_scale)
|
||||
end
|
||||
|
||||
|
||||
--- Return closest non inverted clipping parent node for given node
|
||||
-- @function helper.get_closest_stencil_node
|
||||
-- @tparam node node GUI node
|
||||
-- @treturn node|nil The closest stencil node or nil
|
||||
---Return closest non inverted clipping parent node for given node
|
||||
---@param node node GUI node
|
||||
---@return node|nil stencil_node The closest stencil node or nil
|
||||
function M.get_closest_stencil_node(node)
|
||||
if not node then
|
||||
return nil
|
||||
@@ -342,37 +357,35 @@ function M.get_closest_stencil_node(node)
|
||||
end
|
||||
|
||||
|
||||
--- Get node offset for given GUI pivot.
|
||||
--
|
||||
-- Offset shown in [-0.5 .. 0.5] range, where -0.5 is left or bottom, 0.5 is right or top.
|
||||
-- @function helper.get_pivot_offset
|
||||
-- @tparam number pivot The gui.PIVOT_* constant
|
||||
-- @treturn vector3 Vector offset with [-0.5..0.5] values
|
||||
function M.get_pivot_offset(pivot)
|
||||
return const.PIVOTS[pivot]
|
||||
---Get pivot offset for given pivot or node
|
||||
---Offset shown in [-0.5 .. 0.5] range, where -0.5 is left or bottom, 0.5 is right or top.
|
||||
---@param pivot_or_node number|node GUI pivot or node
|
||||
---@return vector3 offset The pivot offset
|
||||
function M.get_pivot_offset(pivot_or_node)
|
||||
if type(pivot_or_node) == "number" then
|
||||
return const.PIVOTS[pivot_or_node]
|
||||
end
|
||||
return const.PIVOTS[gui.get_pivot(pivot_or_node)]
|
||||
end
|
||||
|
||||
|
||||
--- Check if device is native mobile (Android or iOS)
|
||||
-- @function helper.is_mobile
|
||||
-- @treturn boolean Is mobile
|
||||
---Check if device is native mobile (Android or iOS)
|
||||
---@return boolean Is mobile
|
||||
function M.is_mobile()
|
||||
return const.CURRENT_SYSTEM_NAME == const.OS.IOS or
|
||||
const.CURRENT_SYSTEM_NAME == const.OS.ANDROID
|
||||
local sys_name = const.CURRENT_SYSTEM_NAME
|
||||
return sys_name == const.OS.IOS or sys_name == const.OS.ANDROID
|
||||
end
|
||||
|
||||
|
||||
--- Check if device is HTML5
|
||||
-- @function helper.is_web
|
||||
-- @treturn boolean Is web
|
||||
---Check if device is HTML5
|
||||
---@return boolean
|
||||
function M.is_web()
|
||||
return const.CURRENT_SYSTEM_NAME == const.OS.BROWSER
|
||||
end
|
||||
|
||||
|
||||
--- Check if device is HTML5 mobile
|
||||
-- @function helper.is_web_mobile
|
||||
-- @treturn boolean Is web mobile
|
||||
---Check if device is HTML5 mobile
|
||||
---@return boolean
|
||||
function M.is_web_mobile()
|
||||
if html5 then
|
||||
return html5.run("(typeof window.orientation !== 'undefined') || (navigator.userAgent.indexOf('IEMobile') !== -1);") == "true"
|
||||
@@ -381,18 +394,16 @@ function M.is_web_mobile()
|
||||
end
|
||||
|
||||
|
||||
--- Check if device is mobile and can support multitouch
|
||||
-- @function helper.is_multitouch_supported
|
||||
-- @treturn boolean Is multitouch supported
|
||||
---Check if device is mobile and can support multitouch
|
||||
---@return boolean is_multitouch Is multitouch supported
|
||||
function M.is_multitouch_supported()
|
||||
return M.is_mobile() or M.is_web_mobile()
|
||||
end
|
||||
|
||||
|
||||
--- Simple table to one-line string converter
|
||||
-- @function helper.table_to_string
|
||||
-- @tparam table t
|
||||
-- @treturn string
|
||||
---Simple table to one-line string converter
|
||||
---@param t table
|
||||
---@return string
|
||||
function M.table_to_string(t)
|
||||
if not t then
|
||||
return ""
|
||||
@@ -411,11 +422,10 @@ function M.table_to_string(t)
|
||||
end
|
||||
|
||||
|
||||
--- Distance from node position to his borders
|
||||
-- @function helper.get_border
|
||||
-- @tparam node node GUI node
|
||||
-- @tparam vector3|nil offset Offset from node position. Pass current node position to get non relative border values
|
||||
-- @treturn vector4 Vector4 with border values (left, top, right, down)
|
||||
---Distance from node position to his borders
|
||||
---@param node node GUI node
|
||||
---@param offset vector3|nil Offset from node position. Pass current node position to get non relative border values
|
||||
---@return vector4 border Vector4 with border values (left, top, right, down)
|
||||
function M.get_border(node, offset)
|
||||
local pivot = gui.get_pivot(node)
|
||||
local pivot_offset = M.get_pivot_offset(pivot)
|
||||
@@ -438,17 +448,9 @@ function M.get_border(node, offset)
|
||||
end
|
||||
|
||||
|
||||
--- Get text metric from GUI node.
|
||||
-- @function helper.get_text_metrics_from_node
|
||||
-- @tparam node text_node
|
||||
-- @treturn GUITextMetrics
|
||||
-- @usage
|
||||
-- type GUITextMetrics = {
|
||||
-- width: number,
|
||||
-- height: number,
|
||||
-- max_ascent: number,
|
||||
-- max_descent: number
|
||||
-- }
|
||||
---Get text metric from GUI node.
|
||||
---@param text_node node
|
||||
---@return GUITextMetrics
|
||||
function M.get_text_metrics_from_node(text_node)
|
||||
local font_resource = gui.get_font_resource(gui.get_font(text_node))
|
||||
local options = {
|
||||
@@ -466,15 +468,13 @@ function M.get_text_metrics_from_node(text_node)
|
||||
end
|
||||
|
||||
|
||||
--- Add value to array with shift policy
|
||||
--
|
||||
-- Shift policy can be: left, right, no_shift
|
||||
-- @function helper.insert_with_shift
|
||||
-- @tparam table array Array
|
||||
-- @param any Item to insert
|
||||
-- @tparam number|nil index Index to insert. If nil, item will be inserted at the end of array
|
||||
-- @tparam number|nil shift_policy The druid_const.SHIFT.* constant
|
||||
-- @treturn any Inserted item
|
||||
---Add value to array with shift policy
|
||||
---Shift policy can be: left, right, no_shift
|
||||
---@param array table Array
|
||||
---@param item any Item to insert
|
||||
---@param index number|nil Index to insert. If nil, item will be inserted at the end of array
|
||||
---@param shift_policy number|nil The druid_const.SHIFT.* constant
|
||||
---@return any Inserted item
|
||||
function M.insert_with_shift(array, item, index, shift_policy)
|
||||
shift_policy = shift_policy or const.SHIFT.RIGHT
|
||||
|
||||
@@ -498,14 +498,12 @@ function M.insert_with_shift(array, item, index, shift_policy)
|
||||
end
|
||||
|
||||
|
||||
--- Remove value from array with shift policy
|
||||
--
|
||||
---Remove value from array with shift policy
|
||||
-- Shift policy can be: left, right, no_shift
|
||||
-- @function helper.remove_with_shift
|
||||
-- @tparam table array Array
|
||||
-- @tparam number|nil index Index to remove. If nil, item will be removed from the end of array
|
||||
-- @tparam number|nil shift_policy The druid_const.SHIFT.* constant
|
||||
-- @treturn any Removed item
|
||||
---@param array any[] Array
|
||||
---@param index number|nil Index to remove. If nil, item will be removed from the end of array
|
||||
---@param shift_policy number|nil The druid_const.SHIFT.* constant
|
||||
---@return any Removed item
|
||||
function M.remove_with_shift(array, index, shift_policy)
|
||||
shift_policy = shift_policy or const.SHIFT.RIGHT
|
||||
|
||||
@@ -530,30 +528,137 @@ function M.remove_with_shift(array, index, shift_policy)
|
||||
end
|
||||
|
||||
|
||||
--- Show deprecated message. Once time per message
|
||||
-- @function helper.deprecated
|
||||
-- @tparam string message The deprecated message
|
||||
-- @local
|
||||
local _deprecated_messages = {}
|
||||
function M.deprecated(message)
|
||||
if _deprecated_messages[message] then
|
||||
return
|
||||
---Get full position of node in the GUI tree
|
||||
---@param node node GUI node
|
||||
---@param root node|nil GUI root node to stop search
|
||||
function M.get_full_position(node, root)
|
||||
local position = gui.get_position(node)
|
||||
local parent = gui.get_parent(node)
|
||||
while parent and parent ~= root do
|
||||
local parent_position = gui.get_position(parent)
|
||||
position.x = position.x + parent_position.x
|
||||
position.y = position.y + parent_position.y
|
||||
parent = gui.get_parent(parent)
|
||||
end
|
||||
|
||||
print("[Druid]: " .. message)
|
||||
_deprecated_messages[message] = true
|
||||
return position
|
||||
end
|
||||
|
||||
|
||||
--- Show message to require component
|
||||
-- @local
|
||||
function M.require_component_message(component_name, component_type)
|
||||
component_type = component_type or "extended"
|
||||
---@class druid.animation_data
|
||||
---@field frames table<number, table<string, number>> @List of frames with uv coordinates and size
|
||||
---@field width number @Width of the animation
|
||||
---@field height number @Height of the animation
|
||||
---@field fps number @Frames per second
|
||||
---@field current_frame number @Current frame
|
||||
---@field node node @Node with flipbook animation
|
||||
---@field v vector4 @Vector with UV coordinates and size
|
||||
|
||||
print(string.format("[Druid]: The component %s is %s component. You have to register it via druid.register to use it", component_name, component_type))
|
||||
print("[Druid]: Use next code:")
|
||||
print(string.format('local %s = require("druid.%s.%s")', component_name, component_type, component_name))
|
||||
print(string.format('druid.register("%s", %s)', component_name, component_name))
|
||||
---@param node node
|
||||
---@param atlas_path string @Path to the atlas
|
||||
---@return druid.animation_data
|
||||
function M.get_animation_data_from_node(node, atlas_path)
|
||||
local atlas_data = resource.get_atlas(atlas_path)
|
||||
local tex_info = resource.get_texture_info(atlas_data.texture)
|
||||
local tex_w = tex_info.width
|
||||
local tex_h = tex_info.height
|
||||
|
||||
local animation_data
|
||||
|
||||
local sprite_image_id = gui.get_flipbook(node)
|
||||
for _, animation in ipairs(atlas_data.animations) do
|
||||
if hash(animation.id) == sprite_image_id then
|
||||
animation_data = animation
|
||||
break
|
||||
end
|
||||
end
|
||||
assert(animation_data, "Unable to find image " .. sprite_image_id)
|
||||
|
||||
local frames = {}
|
||||
for index = animation_data.frame_start, animation_data.frame_end - 1 do
|
||||
local uvs = atlas_data.geometries[index].uvs
|
||||
assert(#uvs == 8, "Sprite trim mode should be disabled for the images.")
|
||||
|
||||
-- UV texture coordinates
|
||||
-- 1
|
||||
-- ^ V
|
||||
-- |
|
||||
-- |
|
||||
-- | U
|
||||
-- 0-------> 1
|
||||
|
||||
-- uvs = {
|
||||
-- 0, 0,
|
||||
-- 0, height,
|
||||
-- width, height,
|
||||
-- width, 0
|
||||
-- },
|
||||
-- Point indeces (Point number {uv_index_x, uv_index_y})
|
||||
-- geometries.indices = {0 (1,2), 1(3,4), 2(5,6), 0(1,2), 2(5,6), 3(7,8)}
|
||||
-- 1------2
|
||||
-- | / |
|
||||
-- | A / |
|
||||
-- | / B |
|
||||
-- | / |
|
||||
-- 0------3
|
||||
|
||||
local width = uvs[5] - uvs[1] -- Width of sprite region
|
||||
local height = uvs[2] - uvs[4] -- Height of sprite region
|
||||
local is_rotated = height < 0 -- In case of rotated sprite
|
||||
|
||||
local x_left = uvs[1]
|
||||
local y_bottom = uvs[2]
|
||||
local x_right = uvs[5]
|
||||
local y_top = uvs[6]
|
||||
|
||||
-- Okay now it's correct for non rotated
|
||||
local uv_coord = vmath.vector4(
|
||||
x_left / tex_w,
|
||||
(tex_h - y_bottom) / tex_h,
|
||||
x_right / tex_w,
|
||||
(tex_h - y_top) / tex_h
|
||||
)
|
||||
|
||||
if is_rotated then
|
||||
-- In case the atlas has clockwise rotated sprite.
|
||||
-- 0---------------1
|
||||
-- | \ A |
|
||||
-- | \ |
|
||||
-- | \ |
|
||||
-- | B \ |
|
||||
-- 3---------------2
|
||||
height = -height
|
||||
|
||||
uv_coord.x, uv_coord.y, uv_coord.z, uv_coord.w = uv_coord.y, uv_coord.z, uv_coord.w, uv_coord.x
|
||||
|
||||
-- Update uv_coord
|
||||
--uv_coord = vmath.vector4(
|
||||
-- u1 / tex_w,
|
||||
-- (tex_h - v2) / tex_h,
|
||||
-- u2 / tex_w,
|
||||
-- (tex_h - v1) / tex_h
|
||||
--)
|
||||
end
|
||||
|
||||
local frame = {
|
||||
uv_coord = uv_coord,
|
||||
w = width,
|
||||
h = height,
|
||||
uv_rotated = is_rotated and vmath.vector4(0, 1, 0, 0) or vmath.vector4(1, 0, 0, 0)
|
||||
}
|
||||
|
||||
table.insert(frames, frame)
|
||||
end
|
||||
|
||||
return {
|
||||
frames = frames,
|
||||
width = animation_data.width,
|
||||
height = animation_data.height,
|
||||
fps = animation_data.fps,
|
||||
v = vmath.vector4(1, 1, animation_data.width, animation_data.height),
|
||||
current_frame = 1,
|
||||
node = node,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
|
||||
BIN
druid/images/icons/icon_arrow.png
Normal file
BIN
druid/images/icons/icon_arrow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 B |
BIN
druid/images/icons/icon_drag.png
Normal file
BIN
druid/images/icons/icon_drag.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
BIN
druid/images/panels/rect_round2_width2.png
Normal file
BIN
druid/images/panels/rect_round2_width2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 183 B |
84
druid/materials/gui_repeat/gui_repeat.fp
Normal file
84
druid/materials/gui_repeat/gui_repeat.fp
Normal file
@@ -0,0 +1,84 @@
|
||||
#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_perspective;
|
||||
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;
|
||||
}
|
||||
43
druid/materials/gui_repeat/gui_repeat.material
Normal file
43
druid/materials/gui_repeat/gui_repeat.material
Normal file
@@ -0,0 +1,43 @@
|
||||
name: "repeat"
|
||||
tags: "gui"
|
||||
vertex_program: "/druid/materials/gui_repeat/gui_repeat.vp"
|
||||
fragment_program: "/druid/materials/gui_repeat/gui_repeat.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: "perspective"
|
||||
type: CONSTANT_TYPE_USER
|
||||
value {
|
||||
}
|
||||
}
|
||||
vertex_constants {
|
||||
name: "uv_rotated"
|
||||
type: CONSTANT_TYPE_USER
|
||||
value {
|
||||
x: 1.0
|
||||
}
|
||||
}
|
||||
50
druid/materials/gui_repeat/gui_repeat.vp
Normal file
50
druid/materials/gui_repeat/gui_repeat.vp
Normal file
@@ -0,0 +1,50 @@
|
||||
#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]
|
||||
vec4 perspective; // [perspective_x, perspective_y, zoom, 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_perspective;
|
||||
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_perspective = perspective;
|
||||
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
|
||||
);
|
||||
|
||||
// Matrix Info = mat4(
|
||||
// scale_x, skew_x, 0, offset_x,
|
||||
// skew_y, scale_y, 0, offset_y,
|
||||
// 0, 0, scale_z, offset_z,
|
||||
// perspective_x, perspective_y, perspective_z, zoom
|
||||
//)
|
||||
|
||||
gl_Position = view_proj * vec4(position.xyz, 1.0) * transform;
|
||||
}
|
||||
10
druid/materials/gui_world/gui_world.fp
Normal file
10
druid/materials/gui_world/gui_world.fp
Normal file
@@ -0,0 +1,10 @@
|
||||
varying mediump vec2 var_texcoord0;
|
||||
varying lowp vec4 var_color;
|
||||
|
||||
uniform lowp sampler2D texture_sampler;
|
||||
|
||||
void main()
|
||||
{
|
||||
lowp vec4 tex = texture2D(texture_sampler, var_texcoord0.xy);
|
||||
gl_FragColor = tex * var_color;
|
||||
}
|
||||
8
druid/materials/gui_world/gui_world.material
Normal file
8
druid/materials/gui_world/gui_world.material
Normal file
@@ -0,0 +1,8 @@
|
||||
name: "gui_world"
|
||||
tags: "tile"
|
||||
vertex_program: "/druid/materials/gui_world/gui_world.vp"
|
||||
fragment_program: "/druid/materials/gui_world/gui_world.fp"
|
||||
vertex_constants {
|
||||
name: "view_proj"
|
||||
type: CONSTANT_TYPE_VIEWPROJ
|
||||
}
|
||||
16
druid/materials/gui_world/gui_world.vp
Normal file
16
druid/materials/gui_world/gui_world.vp
Normal file
@@ -0,0 +1,16 @@
|
||||
uniform highp mat4 view_proj;
|
||||
|
||||
// positions are in world space
|
||||
attribute highp vec3 position;
|
||||
attribute mediump vec2 texcoord0;
|
||||
attribute lowp vec4 color;
|
||||
|
||||
varying mediump vec2 var_texcoord0;
|
||||
varying lowp vec4 var_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
var_texcoord0 = texcoord0;
|
||||
var_color = vec4(color.rgb * color.a, color.a);
|
||||
gl_Position = view_proj * vec4(position.xyz, 1.0);
|
||||
}
|
||||
18
druid/materials/skew/gui_skew.fp
Normal file
18
druid/materials/skew/gui_skew.fp
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 140
|
||||
|
||||
uniform sampler2D texture_sampler;
|
||||
|
||||
in vec2 var_texcoord0;
|
||||
in vec4 var_color;
|
||||
|
||||
out vec4 color_out;
|
||||
|
||||
void main() {
|
||||
lowp vec4 tex = texture(texture_sampler, var_texcoord0.xy);
|
||||
if (tex.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// Final color of stencil texture
|
||||
color_out = tex * var_color;
|
||||
}
|
||||
8
druid/materials/skew/gui_skew.material
Normal file
8
druid/materials/skew/gui_skew.material
Normal file
@@ -0,0 +1,8 @@
|
||||
name: "repeat"
|
||||
tags: "gui"
|
||||
vertex_program: "/druid/materials/stencil/gui_stencil.vp"
|
||||
fragment_program: "/druid/materials/stencil/gui_stencil.fp"
|
||||
vertex_constants {
|
||||
name: "view_proj"
|
||||
type: CONSTANT_TYPE_VIEWPROJ
|
||||
}
|
||||
20
druid/materials/skew/gui_skew.vp
Normal file
20
druid/materials/skew/gui_skew.vp
Normal file
@@ -0,0 +1,20 @@
|
||||
#version 140
|
||||
|
||||
uniform vertex_inputs {
|
||||
highp mat4 view_proj;
|
||||
};
|
||||
|
||||
// positions are in world space
|
||||
in mediump vec3 position;
|
||||
in mediump vec2 texcoord0;
|
||||
in lowp vec4 color;
|
||||
|
||||
out mediump vec2 var_texcoord0;
|
||||
out lowp vec4 var_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
var_texcoord0 = texcoord0;
|
||||
var_color = vec4(color.rgb * color.a, color.a);
|
||||
gl_Position = view_proj * vec4(position.xyz, 1.0);
|
||||
}
|
||||
18
druid/materials/stencil/gui_stencil.fp
Normal file
18
druid/materials/stencil/gui_stencil.fp
Normal file
@@ -0,0 +1,18 @@
|
||||
#version 140
|
||||
|
||||
uniform sampler2D texture_sampler;
|
||||
|
||||
in vec2 var_texcoord0;
|
||||
in vec4 var_color;
|
||||
|
||||
out vec4 color_out;
|
||||
|
||||
void main() {
|
||||
lowp vec4 tex = texture(texture_sampler, var_texcoord0.xy);
|
||||
if (tex.a < 0.5) {
|
||||
discard;
|
||||
}
|
||||
|
||||
// Final color of stencil texture
|
||||
color_out = tex * var_color;
|
||||
}
|
||||
8
druid/materials/stencil/gui_stencil.material
Normal file
8
druid/materials/stencil/gui_stencil.material
Normal file
@@ -0,0 +1,8 @@
|
||||
name: "repeat"
|
||||
tags: "gui"
|
||||
vertex_program: "/druid/materials/stencil/gui_stencil.vp"
|
||||
fragment_program: "/druid/materials/stencil/gui_stencil.fp"
|
||||
vertex_constants {
|
||||
name: "view_proj"
|
||||
type: CONSTANT_TYPE_VIEWPROJ
|
||||
}
|
||||
20
druid/materials/stencil/gui_stencil.vp
Normal file
20
druid/materials/stencil/gui_stencil.vp
Normal file
@@ -0,0 +1,20 @@
|
||||
#version 140
|
||||
|
||||
uniform vertex_inputs {
|
||||
highp mat4 view_proj;
|
||||
};
|
||||
|
||||
// positions are in world space
|
||||
in mediump vec3 position;
|
||||
in mediump vec2 texcoord0;
|
||||
in lowp vec4 color;
|
||||
|
||||
out mediump vec2 var_texcoord0;
|
||||
out lowp vec4 var_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
var_texcoord0 = texcoord0;
|
||||
var_color = vec4(color.rgb * color.a, color.a);
|
||||
gl_Position = view_proj * vec4(position.xyz, 1.0);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
local const = require("druid.const")
|
||||
local settings = require("druid.system.settings")
|
||||
|
||||
@@ -63,7 +61,7 @@ M["hover"] = {
|
||||
}
|
||||
|
||||
M["drag"] = {
|
||||
DRAG_DEADZONE = 10, -- Size in pixels of drag deadzone
|
||||
DRAG_DEADZONE = 4, -- Size in pixels of drag deadzone
|
||||
NO_USE_SCREEN_KOEF = false,
|
||||
}
|
||||
|
||||
|
||||
29
druid/system/druid_annotations.lua
Normal file
29
druid/system/druid_annotations.lua
Normal file
@@ -0,0 +1,29 @@
|
||||
---@class druid.widget: druid.base_component
|
||||
---@field druid druid_instance Ready to use druid instance
|
||||
---@field root node
|
||||
|
||||
---@class GUITextMetrics
|
||||
---@field width number
|
||||
---@field height number
|
||||
---@field max_ascent number
|
||||
---@field max_descent number
|
||||
---@field offset_x number
|
||||
---@field offset_y number
|
||||
|
||||
---@class utf8
|
||||
---@field len fun(s: string):number
|
||||
---@field sub fun(s: string, start_index: number, length: number)
|
||||
---@field reverse fun()
|
||||
---@field char fun()
|
||||
---@field unicode fun()
|
||||
---@field gensub fun()
|
||||
---@field byte fun()
|
||||
---@field find fun()
|
||||
---@field match fun(s: string, m: string)
|
||||
---@field gmatch fun(s: string, m: string)
|
||||
---@field gsub fun()
|
||||
---@field dump fun()
|
||||
---@field format fun()
|
||||
---@field lower fun()
|
||||
---@field upper fun()
|
||||
---@field rep fun()
|
||||
@@ -1,95 +1,24 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Druid Instance which you use for component creation.
|
||||
--
|
||||
-- # Component List #
|
||||
--
|
||||
-- For a list of all available components, please refer to the "See Also" section.
|
||||
--
|
||||
-- <b># Notes #</b>
|
||||
--
|
||||
-- Please review the following API pages:
|
||||
--
|
||||
-- @{Helper} - A useful set of functions for working with GUI nodes, such as centering nodes, get GUI scale ratio, etc
|
||||
--
|
||||
-- @{DruidEvent} - The core event system in Druid. Learn how to subscribe to any event in every Druid component.
|
||||
--
|
||||
-- @{BaseComponent} - The parent class of all Druid components. You can find all default component methods there.
|
||||
--
|
||||
-- # Tech Info #
|
||||
--
|
||||
-- • To use Druid, you need to create a Druid instance first. This instance is used to spawn components.
|
||||
--
|
||||
-- • When using Druid components, provide the node name as a string argument directly. Avoid calling gui.get_node() before passing it to the component. Because Druid can get nodes from template and cloned gui nodes.
|
||||
--
|
||||
-- • All Druid and component methods are called using the colon operator (e.g., self.druid:new_button()).
|
||||
-- @usage
|
||||
-- local druid = require("druid.druid")
|
||||
--
|
||||
-- local function close_window(self)
|
||||
-- print("Yeah! You closed the game!")
|
||||
-- end
|
||||
--
|
||||
-- function init(self)
|
||||
-- self.druid = druid.new(self)
|
||||
--
|
||||
-- -- Call all druid instance function with ":" syntax:
|
||||
-- local text = self.druid:new_text("text_header", "Hello Druid!")
|
||||
-- local button = self.druid:new_button("button_close", close_window)
|
||||
--
|
||||
-- -- You not need to save component reference if not need it
|
||||
-- self.druid:new_back_handler(close_window)
|
||||
-- end
|
||||
--
|
||||
-- @module DruidInstance
|
||||
-- @alias druid_instance
|
||||
-- @see BackHandler
|
||||
-- @see Blocker
|
||||
-- @see Button
|
||||
-- @see Checkbox
|
||||
-- @see CheckboxGroup
|
||||
-- @see DataList
|
||||
-- @see Drag
|
||||
-- @see DynamicGrid
|
||||
-- @see Hotkey
|
||||
-- @see Hover
|
||||
-- @see Input
|
||||
-- @see LangText
|
||||
-- @see Layout
|
||||
-- @see Progress
|
||||
-- @see RadioGroup
|
||||
-- @see RichInput
|
||||
-- @see RichText
|
||||
-- @see Scroll
|
||||
-- @see Slider
|
||||
-- @see StaticGrid
|
||||
-- @see Swipe
|
||||
-- @see Text
|
||||
-- @see Timer
|
||||
|
||||
local events = require("event.events")
|
||||
local const = require("druid.const")
|
||||
local helper = require("druid.helper")
|
||||
local settings = require("druid.system.settings")
|
||||
local base_component = require("druid.component")
|
||||
|
||||
local drag = require("druid.base.drag")
|
||||
local text = require("druid.base.text")
|
||||
local hover = require("druid.base.hover")
|
||||
local scroll = require("druid.base.scroll")
|
||||
local button = require("druid.base.button")
|
||||
local blocker = require("druid.base.blocker")
|
||||
local static_grid = require("druid.base.static_grid")
|
||||
local back_handler = require("druid.base.back_handler")
|
||||
|
||||
-- To use this components, you should register them first
|
||||
-- local input = require("druid.extended.input")
|
||||
-- local swipe = require("druid.extended.swipe")
|
||||
-- local slider = require("druid.extended.slider")
|
||||
-- local progress = require("druid.extended.progress")
|
||||
-- local data_list = require("druid.extended.data_list")
|
||||
-- local lang_text = require("druid.extended.lang_text")
|
||||
-- local timer_component = require("druid.extended.timer")
|
||||
|
||||
local DruidInstance = {}
|
||||
---@class druid_instance
|
||||
---@field components_all druid.base_component[] All created components
|
||||
---@field components_interest table<string, druid.base_component[]> All components sorted by interest
|
||||
---@field url url
|
||||
---@field private _context table Druid context
|
||||
---@field private _style table Druid style table
|
||||
---@field private _deleted boolean
|
||||
---@field private _is_late_remove_enabled boolean
|
||||
---@field private _late_remove druid.base_component[]
|
||||
---@field private _input_blacklist druid.base_component[]|nil
|
||||
---@field private _input_whitelist druid.base_component[]|nil
|
||||
---@field private input_inited boolean
|
||||
---@field private _late_init_timer_id number
|
||||
---@field private _input_components druid.base_component[]
|
||||
local M = {}
|
||||
|
||||
local MSG_ADD_FOCUS = hash("acquire_input_focus")
|
||||
local MSG_REMOVE_FOCUS = hash("release_input_focus")
|
||||
@@ -106,7 +35,7 @@ end
|
||||
|
||||
|
||||
-- The a and b - two Druid components
|
||||
-- @local
|
||||
---@private
|
||||
local function sort_input_comparator(a, b)
|
||||
local a_priority = a:get_input_priority()
|
||||
local b_priority = b:get_input_priority()
|
||||
@@ -120,7 +49,7 @@ end
|
||||
|
||||
|
||||
local function sort_input_stack(self)
|
||||
local input_components = self.components_interest[base_component.ON_INPUT]
|
||||
local input_components = self.components_interest[const.ON_INPUT]
|
||||
if not input_components then
|
||||
return
|
||||
end
|
||||
@@ -146,7 +75,55 @@ local function create(self, instance_class)
|
||||
end
|
||||
|
||||
|
||||
-- Before processing any input check if we need to update input stack
|
||||
local WIDGET_METATABLE = { __index = base_component }
|
||||
|
||||
---Create the Druid component instance
|
||||
---@param self druid_instance
|
||||
---@param widget_class druid.base_component
|
||||
local function create_widget(self, widget_class)
|
||||
local instance = setmetatable({}, {
|
||||
__index = setmetatable(widget_class, WIDGET_METATABLE)
|
||||
})
|
||||
|
||||
instance._component = {
|
||||
_uid = base_component.create_uid(),
|
||||
name = "Druid Widget",
|
||||
input_priority = const.PRIORITY_INPUT,
|
||||
default_input_priority = const.PRIORITY_INPUT,
|
||||
_is_input_priority_changed = true, -- Default true for sort once time after GUI init
|
||||
}
|
||||
instance._meta = {
|
||||
druid = self,
|
||||
template = "",
|
||||
nodes = nil,
|
||||
context = self._context,
|
||||
style = nil,
|
||||
input_enabled = true,
|
||||
children = {},
|
||||
parent = type(self._context) ~= "userdata" and self._context,
|
||||
instance_class = widget_class
|
||||
}
|
||||
|
||||
-- Register
|
||||
if instance._meta.parent then
|
||||
instance._meta.parent:__add_child(instance)
|
||||
end
|
||||
|
||||
table.insert(self.components_all, instance)
|
||||
|
||||
local register_to = instance:__get_interests()
|
||||
for i = 1, #register_to do
|
||||
local interest = register_to[i]
|
||||
table.insert(self.components_interest[interest], instance)
|
||||
end
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
---Before processing any input check if we need to update input stack
|
||||
---@param self druid_instance
|
||||
---@param components table[]
|
||||
local function check_sort_input_stack(self, components)
|
||||
if not components or #components == 0 then
|
||||
return
|
||||
@@ -168,8 +145,10 @@ local function check_sort_input_stack(self, components)
|
||||
end
|
||||
|
||||
|
||||
--- Check whitelists and blacklists for input components
|
||||
local function can_use_input_component(self, component)
|
||||
---Check whitelists and blacklists for input components
|
||||
---@param component druid.base_component
|
||||
---@return boolean
|
||||
function M:_can_use_input_component(component)
|
||||
local can_by_whitelist = true
|
||||
local can_by_blacklist = true
|
||||
|
||||
@@ -185,13 +164,13 @@ local function can_use_input_component(self, component)
|
||||
end
|
||||
|
||||
|
||||
local function process_input(self, action_id, action, components)
|
||||
function M:_process_input(action_id, action, components)
|
||||
local is_input_consumed = false
|
||||
|
||||
for i = #components, 1, -1 do
|
||||
local component = components[i]
|
||||
local meta = component._meta
|
||||
if meta.input_enabled and can_use_input_component(self, component) then
|
||||
if meta.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
|
||||
@@ -219,36 +198,35 @@ end
|
||||
|
||||
|
||||
--- Druid class constructor
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam table context Druid context. Usually it is self of gui script
|
||||
-- @tparam table style Druid style table
|
||||
-- @local
|
||||
function DruidInstance.initialize(self, context, style)
|
||||
---@param context table Druid context. Usually it is self of gui script
|
||||
---@param style table? Druid style table
|
||||
function M:initialize(context, style)
|
||||
self._context = context
|
||||
self._style = style or settings.default_style
|
||||
self._deleted = false
|
||||
self._is_late_remove_enabled = false
|
||||
self._late_remove = {}
|
||||
self._is_debug = false
|
||||
self.url = msg.url()
|
||||
|
||||
self._input_blacklist = nil
|
||||
self._input_whitelist = nil
|
||||
|
||||
self.components_all = {}
|
||||
self.components_interest = {}
|
||||
for i = 1, #base_component.ALL_INTERESTS do
|
||||
self.components_interest[base_component.ALL_INTERESTS[i]] = {}
|
||||
for i = 1, #const.ALL_INTERESTS do
|
||||
self.components_interest[const.ALL_INTERESTS[i]] = {}
|
||||
end
|
||||
|
||||
events.subscribe("druid.window_event", self.on_window_event, self)
|
||||
events.subscribe("druid.language_change", self.on_language_change, self)
|
||||
end
|
||||
|
||||
|
||||
-- Create new component.
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam BaseComponent component Component module
|
||||
-- @tparam any ... Other component params to pass it to component:init function
|
||||
-- @treturn BaseComponent Component instance
|
||||
function DruidInstance.new(self, component, ...)
|
||||
---Create new Druid component instance
|
||||
---@generic T: druid.base_component
|
||||
---@param component T
|
||||
---@vararg any
|
||||
---@return T
|
||||
function M:new(component, ...)
|
||||
local instance = create(self, component)
|
||||
|
||||
if instance.init then
|
||||
@@ -263,8 +241,7 @@ end
|
||||
|
||||
|
||||
--- Call this in gui_script final function.
|
||||
-- @tparam DruidInstance self
|
||||
function DruidInstance.final(self)
|
||||
function M:final()
|
||||
local components = self.components_all
|
||||
|
||||
for i = #components, 1, -1 do
|
||||
@@ -276,16 +253,19 @@ function DruidInstance.final(self)
|
||||
self._deleted = true
|
||||
|
||||
set_input_state(self, false)
|
||||
|
||||
events.unsubscribe("druid.window_event", self.on_window_event, self)
|
||||
events.unsubscribe("druid.language_change", self.on_language_change, self)
|
||||
end
|
||||
|
||||
|
||||
--- Remove created component from Druid instance.
|
||||
--
|
||||
-- Component `on_remove` function will be invoked, if exist.
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam BaseComponent component Component instance
|
||||
-- @treturn boolean True if component was removed
|
||||
function DruidInstance.remove(self, component)
|
||||
---@generic T: druid.base_component
|
||||
---@param component T Component instance
|
||||
---@return boolean True if component was removed
|
||||
function M:remove(component)
|
||||
if self._is_late_remove_enabled then
|
||||
table.insert(self._late_remove, component)
|
||||
return false
|
||||
@@ -334,31 +314,27 @@ end
|
||||
--- Druid late update function called after initialization and before the regular update step
|
||||
-- This function is used to check the GUI state and perform actions after all components and nodes have been created.
|
||||
-- An example use case is performing an auto stencil check in the GUI hierarchy for input components.
|
||||
-- @tparam DruidInstance self
|
||||
-- @local
|
||||
function DruidInstance.late_init(self)
|
||||
local late_init_components = self.components_interest[base_component.ON_LATE_INIT]
|
||||
---@private
|
||||
function M:late_init()
|
||||
local late_init_components = self.components_interest[const.ON_LATE_INIT]
|
||||
while late_init_components[1] do
|
||||
late_init_components[1]:on_late_init()
|
||||
table.remove(late_init_components, 1)
|
||||
end
|
||||
|
||||
if not self.input_inited and #self.components_interest[base_component.ON_INPUT] > 0 then
|
||||
if not self.input_inited and #self.components_interest[const.ON_INPUT] > 0 then
|
||||
-- Input init on late init step, to be sure it goes after user go acquire input
|
||||
set_input_state(self, true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Call this in gui_script update function.
|
||||
--
|
||||
-- Used for: scroll, progress, timer components
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam number dt Delta time
|
||||
function DruidInstance.update(self, dt)
|
||||
---Call this in gui_script update function.
|
||||
---@param dt number Delta time
|
||||
function M:update(dt)
|
||||
self._is_late_remove_enabled = true
|
||||
|
||||
local components = self.components_interest[base_component.ON_UPDATE]
|
||||
local components = self.components_interest[const.ON_UPDATE]
|
||||
for i = 1, #components do
|
||||
components[i]:update(dt)
|
||||
end
|
||||
@@ -368,19 +344,16 @@ function DruidInstance.update(self, dt)
|
||||
end
|
||||
|
||||
|
||||
--- Call this in gui_script on_input function.
|
||||
--
|
||||
-- Used for almost all components
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam hash action_id Action_id from on_input
|
||||
-- @tparam table action Action from on_input
|
||||
-- @treturn boolean The boolean value is input was consumed
|
||||
function DruidInstance.on_input(self, action_id, action)
|
||||
---Call this in gui_script on_input function.
|
||||
---@param action_id hash Action_id from on_input
|
||||
---@param action table Action from on_input
|
||||
---@return boolean The boolean value is input was consumed
|
||||
function M:on_input(action_id, action)
|
||||
self._is_late_remove_enabled = true
|
||||
|
||||
local components = self.components_interest[base_component.ON_INPUT]
|
||||
local components = self.components_interest[const.ON_INPUT]
|
||||
check_sort_input_stack(self, components)
|
||||
local is_input_consumed = process_input(self, action_id, action, components)
|
||||
local is_input_consumed = self:_process_input(action_id, action, components)
|
||||
|
||||
self._is_late_remove_enabled = false
|
||||
self:_clear_late_remove()
|
||||
@@ -390,38 +363,19 @@ end
|
||||
|
||||
|
||||
--- Call this in gui_script on_message function.
|
||||
--
|
||||
-- Used for special actions. See SPECIFIC_UI_MESSAGES table
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam hash message_id Message_id from on_message
|
||||
-- @tparam table message Message from on_message
|
||||
-- @tparam url sender Sender from on_message
|
||||
function DruidInstance.on_message(self, message_id, message, sender)
|
||||
local specific_ui_message = base_component.SPECIFIC_UI_MESSAGES[message_id]
|
||||
|
||||
if specific_ui_message == base_component.ON_MESSAGE_INPUT then
|
||||
-- ON_MESSAGE_INPUT is special message, need to perform additional logic
|
||||
local components = self.components_interest[base_component.ON_MESSAGE_INPUT]
|
||||
if components then
|
||||
for i = 1, #components do
|
||||
local component = components[i]
|
||||
if can_use_input_component(self, component) then
|
||||
component[specific_ui_message](component, hash(message.node_id), message)
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif specific_ui_message then
|
||||
---@param message_id hash Message_id from on_message
|
||||
---@param message table Message from on_message
|
||||
---@param sender url Sender from on_message
|
||||
function M:on_message(message_id, message, sender)
|
||||
if message_id == const.MSG_LAYOUT_CHANGED then
|
||||
-- Resend special message to all components with the related interest
|
||||
local components = self.components_interest[specific_ui_message]
|
||||
if components then
|
||||
for i = 1, #components do
|
||||
local component = components[i]
|
||||
component[specific_ui_message](component, message, sender)
|
||||
end
|
||||
local components = self.components_interest[const.ON_LAYOUT_CHANGE]
|
||||
for i = 1, #components do
|
||||
components[i]:on_layout_change()
|
||||
end
|
||||
else
|
||||
-- Resend message to all components with on_message interest
|
||||
local components = self.components_interest[base_component.ON_MESSAGE]
|
||||
local components = self.components_interest[const.ON_MESSAGE]
|
||||
for i = 1, #components do
|
||||
components[i]:on_message(message_id, message, sender)
|
||||
end
|
||||
@@ -429,26 +383,22 @@ function DruidInstance.on_message(self, message_id, message, sender)
|
||||
end
|
||||
|
||||
|
||||
--- Calls the on_focus_lost function in all related components
|
||||
-- This one called by on_window_callback by global window listener
|
||||
-- @tparam DruidInstance self
|
||||
-- @local
|
||||
function DruidInstance.on_focus_lost(self)
|
||||
local components = self.components_interest[base_component.ON_FOCUS_LOST]
|
||||
for i = 1, #components do
|
||||
components[i]:on_focus_lost()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Calls the on_focus_gained function in all related components
|
||||
-- This one called by on_window_callback by global window listener
|
||||
-- @tparam DruidInstance self
|
||||
-- @local
|
||||
function DruidInstance.on_focus_gained(self)
|
||||
local components = self.components_interest[base_component.ON_FOCUS_GAINED]
|
||||
for i = 1, #components do
|
||||
components[i]:on_focus_gained()
|
||||
function M:on_window_event(window_event)
|
||||
if window_event == window.WINDOW_EVENT_FOCUS_LOST then
|
||||
local components = self.components_interest[const.ON_FOCUS_LOST]
|
||||
for i = 1, #components do
|
||||
components[i]:on_focus_lost()
|
||||
end
|
||||
elseif window_event == window.WINDOW_EVENT_FOCUS_GAINED then
|
||||
local components = self.components_interest[const.ON_FOCUS_GAINED]
|
||||
for i = 1, #components do
|
||||
components[i]:on_focus_gained()
|
||||
end
|
||||
elseif window_event == window.WINDOW_EVENT_RESIZED then
|
||||
local components = self.components_interest[const.ON_WINDOW_RESIZED]
|
||||
for i = 1, #components do
|
||||
components[i]:on_window_resized()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -456,24 +406,21 @@ end
|
||||
--- Calls the on_language_change function in all related components
|
||||
-- This one called by global druid.on_language_change, but can be
|
||||
-- call manualy to update all translations
|
||||
-- @tparam DruidInstance self
|
||||
-- @local
|
||||
function DruidInstance.on_language_change(self)
|
||||
local components = self.components_interest[base_component.ON_LANGUAGE_CHANGE]
|
||||
---@private
|
||||
function M:on_language_change()
|
||||
local components = self.components_interest[const.ON_LANGUAGE_CHANGE]
|
||||
for i = 1, #components do
|
||||
components[i]:on_language_change()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Set whitelist components for input processing.
|
||||
--
|
||||
-- If whitelist is not empty and component not contains in this list,
|
||||
-- component will be not processed on input step
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam table|BaseComponent|nil whitelist_components The array of component to whitelist
|
||||
-- @treturn self @{DruidInstance}
|
||||
function DruidInstance.set_whitelist(self, whitelist_components)
|
||||
---Set whitelist components for input processing.
|
||||
---If whitelist is not empty and component not contains in this list,
|
||||
---component will be not processed on input step
|
||||
---@param whitelist_components table|druid.base_component[] The array of component to whitelist
|
||||
---@return druid_instance
|
||||
function M:set_whitelist(whitelist_components)
|
||||
if whitelist_components and whitelist_components._component then
|
||||
whitelist_components = { whitelist_components }
|
||||
end
|
||||
@@ -488,14 +435,12 @@ function DruidInstance.set_whitelist(self, whitelist_components)
|
||||
end
|
||||
|
||||
|
||||
--- Set blacklist components for input processing.
|
||||
--
|
||||
-- If blacklist is not empty and component contains in this list,
|
||||
-- component will be not processed on input step
|
||||
-- @tparam DruidInstance self @{DruidInstance}
|
||||
-- @tparam table|BaseComponent|nil blacklist_components The array of component to blacklist
|
||||
-- @treturn self @{DruidInstance}
|
||||
function DruidInstance.set_blacklist(self, blacklist_components)
|
||||
---Set blacklist components for input processing.
|
||||
---If blacklist is not empty and component contains in this list,
|
||||
---component will be not processed on input step DruidInstance
|
||||
---@param blacklist_components table|druid.base_component[] The array of component to blacklist
|
||||
---@return druid_instance
|
||||
function M:set_blacklist(blacklist_components)
|
||||
if blacklist_components and blacklist_components._component then
|
||||
blacklist_components = { blacklist_components }
|
||||
end
|
||||
@@ -510,35 +455,9 @@ function DruidInstance.set_blacklist(self, blacklist_components)
|
||||
end
|
||||
|
||||
|
||||
--- Set debug mode for current Druid instance. It's enable debug log messages
|
||||
-- @tparam DruidInstance self @{DruidInstance}
|
||||
-- @tparam boolean|nil is_debug
|
||||
-- @treturn self @{DruidInstance}
|
||||
-- @local
|
||||
function DruidInstance.set_debug(self, is_debug)
|
||||
self._is_debug = is_debug
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Log message, if is_debug mode is enabled
|
||||
-- @tparam DruidInstance self @{DruidInstance}
|
||||
-- @tparam string message
|
||||
-- @tparam table|nil context
|
||||
-- @local
|
||||
function DruidInstance.log_message(self, message, context)
|
||||
if not self._is_debug then
|
||||
return
|
||||
end
|
||||
|
||||
print("[Druid]:", message, helper.table_to_string(context))
|
||||
end
|
||||
|
||||
|
||||
--- Remove all components on late remove step
|
||||
-- @tparam DruidInstance self @{DruidInstance}
|
||||
-- @local
|
||||
function DruidInstance._clear_late_remove(self)
|
||||
--- Remove all components on late remove step DruidInstance
|
||||
---@private
|
||||
function M:_clear_late_remove()
|
||||
if #self._late_remove == 0 then
|
||||
return
|
||||
end
|
||||
@@ -550,229 +469,245 @@ function DruidInstance._clear_late_remove(self)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Button} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @tparam function|nil callback Button callback
|
||||
-- @tparam any|nil params Button callback params
|
||||
-- @tparam node|string|nil anim_node Button anim node (node, if not provided)
|
||||
-- @treturn Button @{Button} component
|
||||
function DruidInstance.new_button(self, node, callback, params, anim_node)
|
||||
return DruidInstance.new(self, button, node, callback, params, anim_node)
|
||||
---Create new Druid widget instance
|
||||
---@generic T: druid.base_component
|
||||
---@param widget T
|
||||
---@param template string|nil The template name used by widget
|
||||
---@param nodes table<hash, node>|node|nil The nodes table from gui.clone_tree or prefab node to use for clone
|
||||
---@vararg any
|
||||
---@return T
|
||||
function M:new_widget(widget, template, nodes, ...)
|
||||
local instance = create_widget(self, widget)
|
||||
|
||||
if type(nodes) == "userdata" then
|
||||
nodes = gui.clone_tree(nodes) --[[@as table<hash, node>]]
|
||||
end
|
||||
|
||||
instance.druid = instance:get_druid(template, nodes)
|
||||
|
||||
if instance.init then
|
||||
instance:init(...)
|
||||
end
|
||||
if instance.on_late_init or (not self.input_inited and instance.on_input) then
|
||||
schedule_late_init(self)
|
||||
end
|
||||
|
||||
return instance
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Blocker} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @treturn Blocker @{Blocker} component
|
||||
function DruidInstance.new_blocker(self, node)
|
||||
return DruidInstance.new(self, blocker, node)
|
||||
local button = require("druid.base.button")
|
||||
---Create Button component
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@param callback function|nil Button callback
|
||||
---@param params any|nil Button callback params
|
||||
---@param anim_node node|string|nil Button anim node (node, if not provided)
|
||||
---@return druid.button Button component
|
||||
function M:new_button(node, callback, params, anim_node)
|
||||
return self:new(button, node, callback, params, anim_node)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{BackHandler} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam function|nil callback @The callback(self, custom_args) to call on back event
|
||||
-- @tparam any|nil params Callback argument
|
||||
-- @treturn BackHandler @{BackHandler} component
|
||||
function DruidInstance.new_back_handler(self, callback, params)
|
||||
return DruidInstance.new(self, back_handler, callback, params)
|
||||
local blocker = require("druid.base.blocker")
|
||||
---Create Blocker component
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@return druid.blocker component Blocker component
|
||||
function M:new_blocker(node)
|
||||
return self:new(blocker, node)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Hover} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @tparam function|nil on_hover_callback Hover callback
|
||||
-- @tparam function|nil on_mouse_hover_callback Mouse hover callback
|
||||
-- @treturn Hover @{Hover} component
|
||||
function DruidInstance.new_hover(self, node, on_hover_callback, on_mouse_hover_callback)
|
||||
return DruidInstance.new(self, hover, node, on_hover_callback, on_mouse_hover_callback)
|
||||
local back_handler = require("druid.base.back_handler")
|
||||
---Create BackHandler component
|
||||
---@param callback function|nil The callback(self, custom_args) to call on back event
|
||||
---@param params any|nil Callback argument
|
||||
---@return druid.back_handler component BackHandler component
|
||||
function M:new_back_handler(callback, params)
|
||||
return self:new(back_handler, callback, params)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Text} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id)
|
||||
-- @tparam string|nil value Initial text. Default value is node text from GUI scene.
|
||||
-- @tparam boolean|nil no_adjust If true, text will be not auto-adjust size
|
||||
-- @treturn Text @{Text} component
|
||||
function DruidInstance.new_text(self, node, value, no_adjust)
|
||||
return DruidInstance.new(self, text, node, value, no_adjust)
|
||||
local hover = require("druid.base.hover")
|
||||
---Create Hover component
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@param on_hover_callback function|nil Hover callback
|
||||
---@param on_mouse_hover_callback function|nil Mouse hover callback
|
||||
---@return druid.hover component Hover component
|
||||
function M:new_hover(node, on_hover_callback, on_mouse_hover_callback)
|
||||
return self:new(hover, node, on_hover_callback, on_mouse_hover_callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{StaticGrid} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node parent_node The node_id or gui.get_node(node_id). Parent of all Grid items.
|
||||
-- @tparam node item Element prefab. Required to get grid's item size. Can be adjusted separately.
|
||||
-- @tparam number|nil in_row How many nodes in row can be placed
|
||||
-- @treturn StaticGrid @{StaticGrid} component
|
||||
-- @local
|
||||
function DruidInstance.new_grid(self, parent_node, item, in_row)
|
||||
return DruidInstance.new(self, static_grid, parent_node, item, in_row)
|
||||
local text = require("druid.base.text")
|
||||
---Create Text component
|
||||
---@param node string|node The node_id or gui.get_node(node_id)
|
||||
---@param value string|nil Initial text. Default value is node text from GUI scene.
|
||||
---@param adjust_type string|nil Adjust type for text. By default is DOWNSCALE. Look const.TEXT_ADJUST for reference
|
||||
---@return druid.text component Text component
|
||||
function M:new_text(node, value, adjust_type)
|
||||
return self:new(text, node, value, adjust_type)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{StaticGrid} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node parent_node The node_id or gui.get_node(node_id). Parent of all Grid items.
|
||||
-- @tparam string|node item Item prefab. Required to get grid's item size. Can be adjusted separately.
|
||||
-- @tparam number|nil in_row How many nodes in row can be placed
|
||||
-- @treturn StaticGrid @{StaticGrid} component
|
||||
function DruidInstance.new_static_grid(self, parent_node, item, in_row)
|
||||
return DruidInstance.new(self, static_grid, parent_node, item, in_row)
|
||||
local static_grid = require("druid.base.static_grid")
|
||||
---Create Grid component
|
||||
---@param parent_node string|node The node_id or gui.get_node(node_id). Parent of all Grid items.
|
||||
---@param item string|node Item prefab. Required to get grid's item size. Can be adjusted separately.
|
||||
---@param in_row number|nil How many nodes in row can be placed
|
||||
---@return druid.grid component Grid component
|
||||
function M:new_grid(parent_node, item, in_row)
|
||||
return self:new(static_grid, parent_node, item, in_row)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Scroll} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node view_node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
-- @tparam string|node content_node The node_id or gui.get_node(node_id). Will used as scrollable node inside view_node.
|
||||
-- @treturn Scroll @{Scroll} component
|
||||
function DruidInstance.new_scroll(self, view_node, content_node)
|
||||
return DruidInstance.new(self, scroll, view_node, content_node)
|
||||
local scroll = require("druid.base.scroll")
|
||||
---Create Scroll component
|
||||
---@param view_node string|node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
---@param content_node string|node The node_id or gui.get_node(node_id). Will used as scrollable node inside view_node.
|
||||
---@return druid.scroll component Scroll component
|
||||
function M:new_scroll(view_node, content_node)
|
||||
return self:new(scroll, view_node, content_node)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Drag} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
-- @tparam function|nil on_drag_callback Callback for on_drag_event(self, dx, dy)
|
||||
-- @treturn Drag @{Drag} component
|
||||
function DruidInstance.new_drag(self, node, on_drag_callback)
|
||||
return DruidInstance.new(self, drag, node, on_drag_callback)
|
||||
local drag = require("druid.base.drag")
|
||||
---Create Drag component
|
||||
---@param node string|node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
---@param on_drag_callback function|nil Callback for on_drag_event(self, dx, dy)
|
||||
---@return druid.drag component Drag component
|
||||
function M:new_drag(node, on_drag_callback)
|
||||
return self:new(drag, node, on_drag_callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Swipe} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
-- @tparam function|nil on_swipe_callback Swipe callback for on_swipe_end event
|
||||
-- @treturn Swipe @{Swipe} component
|
||||
function DruidInstance.new_swipe(self, node, on_swipe_callback)
|
||||
return helper.require_component_message("swipe")
|
||||
local swipe = require("druid.extended.swipe")
|
||||
---Create Swipe component
|
||||
---@param node string|node The node_id or gui.get_node(node_id). Will used as user input node.
|
||||
---@param on_swipe_callback function|nil Swipe callback for on_swipe_end event
|
||||
---@return druid.swipe component Swipe component
|
||||
function M:new_swipe(node, on_swipe_callback)
|
||||
return self:new(swipe, node, on_swipe_callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{DynamicGrid} component
|
||||
-- Deprecated
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node parent_node The node_id or gui.get_node(node_id). Parent of all Grid items.
|
||||
-- @treturn DynamicGrid @{DynamicGrid} component
|
||||
function DruidInstance.new_dynamic_grid(self, parent_node)
|
||||
return helper.require_component_message("dynamic_grid")
|
||||
local lang_text = require("druid.extended.lang_text")
|
||||
---Create LangText component
|
||||
---@param node string|node The_node id or gui.get_node(node_id)
|
||||
---@param locale_id string|nil Default locale id or text from node as default
|
||||
---@param adjust_type string|nil Adjust type for text node. Default: const.TEXT_ADJUST.DOWNSCALE
|
||||
---@return druid.lang_text component LangText component
|
||||
function M:new_lang_text(node, locale_id, adjust_type)
|
||||
return self:new(lang_text, node, locale_id, adjust_type)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{LangText} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The_node id or gui.get_node(node_id)
|
||||
-- @tparam string|nil locale_id Default locale id or text from node as default
|
||||
-- @tparam string|nil adjust_type Adjust type for text node. Default: const.TEXT_ADJUST.DOWNSCALE
|
||||
-- @treturn LangText @{LangText} component
|
||||
function DruidInstance.new_lang_text(self, node, locale_id, adjust_type)
|
||||
return helper.require_component_message("lang_text")
|
||||
local slider = require("druid.extended.slider")
|
||||
---Create Slider component
|
||||
---@param pin_node string|node The_node id or gui.get_node(node_id).
|
||||
---@param end_pos vector3 The end position of slider
|
||||
---@param callback function|nil On slider change callback
|
||||
---@return druid.slider component Slider component
|
||||
function M:new_slider(pin_node, end_pos, callback)
|
||||
return self:new(slider, pin_node, end_pos, callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Slider} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node pin_node The_node id or gui.get_node(node_id).
|
||||
-- @tparam vector3 end_pos The end position of slider
|
||||
-- @tparam function|nil callback On slider change callback
|
||||
-- @treturn Slider @{Slider} component
|
||||
function DruidInstance.new_slider(self, pin_node, end_pos, callback)
|
||||
return helper.require_component_message("slider")
|
||||
local input = require("druid.extended.input")
|
||||
---Create Input component
|
||||
---@param click_node string|node Button node to enabled input component
|
||||
---@param text_node string|node|druid.text Text node what will be changed on user input
|
||||
---@param keyboard_type number|nil Gui keyboard type for input field
|
||||
---@return druid.input component Input component
|
||||
function M:new_input(click_node, text_node, keyboard_type)
|
||||
return self:new(input, click_node, text_node, keyboard_type)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Input} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node click_node Button node to enabled input component
|
||||
-- @tparam string|node|druid.text text_node Text node what will be changed on user input
|
||||
-- @tparam number|nil keyboard_type Gui keyboard type for input field
|
||||
-- @treturn Input @{Input} component
|
||||
function DruidInstance.new_input(self, click_node, text_node, keyboard_type)
|
||||
return helper.require_component_message("input")
|
||||
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 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)
|
||||
return self:new(data_list, druid_scroll, druid_grid, create_function)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{DataList} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam Scroll druid_scroll The Scroll instance for Data List component
|
||||
-- @tparam StaticGrid druid_grid The @{StaticGrid} or @{DynamicGrid} instance for Data List component
|
||||
-- @tparam function create_function The create function callback(self, data, index, data_list). Function should return (node, [component])
|
||||
-- @treturn DataList @{DataList} component
|
||||
function DruidInstance.new_data_list(self, druid_scroll, druid_grid, create_function)
|
||||
return helper.require_component_message("data_list")
|
||||
local timer_component = require("druid.extended.timer")
|
||||
---Create Timer component
|
||||
---@param node string|node Gui text node
|
||||
---@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
|
||||
---@return druid.timer component Timer component
|
||||
function M:new_timer(node, seconds_from, seconds_to, callback)
|
||||
return self:new(timer_component, node, seconds_from, seconds_to, callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Timer} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node Gui text node
|
||||
-- @tparam number seconds_from Start timer value in seconds
|
||||
-- @tparam number|nil seconds_to End timer value in seconds
|
||||
-- @tparam function|nil callback Function on timer end
|
||||
-- @treturn Timer @{Timer} component
|
||||
function DruidInstance.new_timer(self, node, seconds_from, seconds_to, callback)
|
||||
return helper.require_component_message("timer")
|
||||
local progress = require("druid.extended.progress")
|
||||
---Create Progress component
|
||||
---@param node string|node Progress bar fill node or node name
|
||||
---@param key string Progress bar direction: const.SIDE.X or const.SIDE.Y
|
||||
---@param init_value number|nil Initial value of progress bar. Default: 1
|
||||
---@return druid.progress component Progress component
|
||||
function M:new_progress(node, key, init_value)
|
||||
return self:new(progress, node, key, init_value)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Progress} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node Progress bar fill node or node name
|
||||
-- @tparam string key Progress bar direction: const.SIDE.X or const.SIDE.Y
|
||||
-- @tparam number|nil init_value Initial value of progress bar. Default: 1
|
||||
-- @treturn Progress @{Progress} component
|
||||
function DruidInstance.new_progress(self, node, key, init_value)
|
||||
return helper.require_component_message("progress")
|
||||
local layout = require("druid.extended.layout")
|
||||
---Create Layout component
|
||||
---@param node string|node The_node id or gui.get_node(node_id).
|
||||
---@param mode string|nil vertical|horizontal|horizontal_wrap. Default: horizontal
|
||||
---@return druid.layout component Layout component
|
||||
function M:new_layout(node, mode)
|
||||
return self:new(layout, node, mode)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Layout} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node node The_node id or gui.get_node(node_id).
|
||||
-- @tparam string mode The layout mode
|
||||
-- @treturn Layout @{Layout} component
|
||||
function DruidInstance.new_layout(self, node, mode)
|
||||
return helper.require_component_message("layout")
|
||||
local container = require("druid.extended.container")
|
||||
---Create Container component
|
||||
---@param node string|node The_node id or gui.get_node(node_id).
|
||||
---@param mode string|nil Layout mode
|
||||
---@param callback fun(self: druid.container, size: vector3)|nil Callback on size changed
|
||||
---@return druid.container container component
|
||||
function M:new_container(node, mode, callback)
|
||||
return self:new(container, node, mode, callback)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{Hotkey} component
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|string[] keys_array Keys for trigger action. Should contains one action key and any amount of modificator keys
|
||||
-- @tparam function callback The callback function
|
||||
-- @tparam any|nil callback_argument The argument to pass into the callback function
|
||||
-- @treturn Hotkey @{Hotkey} component
|
||||
function DruidInstance.new_hotkey(self, keys_array, callback, callback_argument)
|
||||
return helper.require_component_message("hotkey")
|
||||
local hotkey = require("druid.extended.hotkey")
|
||||
---Create Hotkey component
|
||||
---@param keys_array string|string[] Keys for trigger action. Should contains one action key and any amount of modificator keys
|
||||
---@param callback function|nil The callback function
|
||||
---@param callback_argument any|nil The argument to pass into the callback function
|
||||
---@return druid.hotkey component Hotkey component
|
||||
function M:new_hotkey(keys_array, callback, callback_argument)
|
||||
return self:new(hotkey, keys_array, callback, callback_argument)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{RichText} component.
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string|node 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)
|
||||
-- @treturn RichText @{RichText} component
|
||||
function DruidInstance.new_rich_text(self, text_node, value)
|
||||
return helper.require_component_message("rich_text", "custom")
|
||||
local rich_text = require("druid.custom.rich_text.rich_text")
|
||||
---Create RichText component.
|
||||
---@param text_node string|node The text node to make Rich Text
|
||||
---@param value string|nil The initial text value. Default will be gui.get_text(text_node)
|
||||
---@return druid.rich_text component RichText component
|
||||
function M:new_rich_text(text_node, value)
|
||||
return self:new(rich_text, text_node, value)
|
||||
end
|
||||
|
||||
|
||||
--- Create @{RichInput} component.
|
||||
local rich_input = require("druid.custom.rich_input.rich_input")
|
||||
---Create RichInput component.
|
||||
-- As a template please check rich_input.gui layout.
|
||||
-- @tparam DruidInstance self
|
||||
-- @tparam string template The template string name
|
||||
-- @tparam table nodes Nodes table from gui.clone_tree
|
||||
-- @treturn RichInput @{RichInput} component
|
||||
function DruidInstance.new_rich_input(self, template, nodes)
|
||||
return helper.require_component_message("rich_input", "custom")
|
||||
---@param template string The template string name
|
||||
---@param nodes table|nil Nodes table from gui.clone_tree
|
||||
---@return druid.rich_input component RichInput component
|
||||
function M:new_rich_input(template, nodes)
|
||||
return self:new(rich_input, template, nodes)
|
||||
end
|
||||
|
||||
|
||||
return DruidInstance
|
||||
return M
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
-- Copyright (c) 2021 Maksim Tuprikov <insality@gmail.com>. This code is licensed under MIT license
|
||||
|
||||
--- Druid settings file
|
||||
-- @module settings
|
||||
-- @local
|
||||
|
||||
---@class druid.system.settings
|
||||
local M = {}
|
||||
|
||||
M.default_style = nil
|
||||
|
||||
|
||||
function M.get_text(name, a, b, c, d, e, f, g)
|
||||
---@param text_id string
|
||||
---@vararg any
|
||||
function M.get_text(text_id, ...)
|
||||
return "[Druid]: locales not inited"
|
||||
end
|
||||
|
||||
|
||||
function M.play_sound(name)
|
||||
function M.play_sound(sound_id)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
local component = require("druid.component")
|
||||
|
||||
---@class component_name : druid.base_component
|
||||
local Component = component.create("component_name")
|
||||
---@class widget.TEMPLATE: druid.widget
|
||||
local M = {}
|
||||
|
||||
|
||||
-- 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("root")
|
||||
|
||||
self.button = self.druid:new_button("button", function() end)
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.button = self.druid:new_button("button", self.on_button, self)
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call on component remove or on druid:final
|
||||
function Component:on_remove()
|
||||
function M:on_button()
|
||||
print("Root node", self.root)
|
||||
end
|
||||
|
||||
|
||||
return Component
|
||||
return M
|
||||
@@ -1,10 +1,10 @@
|
||||
local component = require("druid.component")
|
||||
|
||||
---@class component_name : druid.base_component
|
||||
local Component = component.create("component_name")
|
||||
---@class new_component: druid.base_component
|
||||
local M = component.create("new_component")
|
||||
|
||||
-- Component constructor. Template name and nodes are optional. Pass it if you use it in your component
|
||||
function Component:init(template, nodes)
|
||||
function M:init(template, nodes)
|
||||
-- If your component is gui template, pass the template name and set it
|
||||
-- If your component is cloned my gui.clone_tree, pass nodes to component and set it
|
||||
-- Use inner druid instance to create components inside this component
|
||||
@@ -17,55 +17,55 @@ end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call every update step
|
||||
function Component:update(dt)
|
||||
function M:update(dt)
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call default on_input from gui script
|
||||
function Component:on_input(action_id, action)
|
||||
function M:on_input(action_id, action)
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call on component creation and on component:set_style() function
|
||||
function Component:on_style_change(style)
|
||||
function M:on_style_change(style)
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call default on_message from gui script
|
||||
function Component:on_message(message_id, message, sender)
|
||||
function M:on_message(message_id, message, sender)
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call if druid has triggered on_language_change
|
||||
function Component:on_language_change()
|
||||
function M:on_language_change()
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call if game layout has changed and need to restore values in component
|
||||
function Component:on_layout_change()
|
||||
function M:on_layout_change()
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call, if input was capturing before this component
|
||||
-- Example: scroll is start scrolling, so you need unhover button
|
||||
function Component:on_input_interrupt()
|
||||
function M:on_input_interrupt()
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call, if game lost focus
|
||||
function Component:on_focus_lost()
|
||||
function M:on_focus_lost()
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call, if game gained focus
|
||||
function Component:on_focus_gained()
|
||||
function M:on_focus_gained()
|
||||
end
|
||||
|
||||
|
||||
-- [OPTIONAL] Call on component remove or on druid:final
|
||||
function Component:on_remove()
|
||||
function M:on_remove()
|
||||
end
|
||||
|
||||
|
||||
return Component
|
||||
return M
|
||||
|
||||
222
druid/widget/fps_panel/fps_panel.gui
Normal file
222
druid/widget/fps_panel/fps_panel.gui
Normal file
@@ -0,0 +1,222 @@
|
||||
fonts {
|
||||
name: "druid_text_regular"
|
||||
font: "/druid/fonts/druid_text_regular.font"
|
||||
}
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 200.0
|
||||
y: 140.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "mini_graph"
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/mini_graph/mini_graph.gui"
|
||||
}
|
||||
nodes {
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/root"
|
||||
parent: "mini_graph"
|
||||
overridden_fields: 5
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/header"
|
||||
parent: "mini_graph/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
text: "FPS"
|
||||
id: "mini_graph/text_header"
|
||||
parent: "mini_graph/header"
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/icon_drag"
|
||||
parent: "mini_graph/header"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/content"
|
||||
parent: "mini_graph/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
color {
|
||||
x: 0.525
|
||||
y: 0.525
|
||||
z: 0.525
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/prefab_line"
|
||||
parent: "mini_graph/content"
|
||||
overridden_fields: 5
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
color {
|
||||
x: 0.957
|
||||
y: 0.608
|
||||
z: 0.608
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/color_low"
|
||||
parent: "mini_graph/content"
|
||||
overridden_fields: 5
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "content"
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -96.0
|
||||
y: 12.0
|
||||
}
|
||||
scale {
|
||||
x: 0.3
|
||||
y: 0.3
|
||||
}
|
||||
size {
|
||||
x: 260.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "12 FPS"
|
||||
font: "druid_text_regular"
|
||||
id: "text_min_fps"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: 12.0
|
||||
}
|
||||
scale {
|
||||
x: 0.3
|
||||
y: 0.3
|
||||
}
|
||||
size {
|
||||
x: 260.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "60 FPS"
|
||||
font: "druid_text_bold"
|
||||
id: "text_fps"
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -33.4
|
||||
y: 30.0
|
||||
}
|
||||
size {
|
||||
x: 3.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "line_second_1"
|
||||
pivot: PIVOT_N
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 33.2
|
||||
y: 30.0
|
||||
}
|
||||
size {
|
||||
x: 3.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "line_second_2"
|
||||
pivot: PIVOT_N
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
101
druid/widget/fps_panel/fps_panel.lua
Normal file
101
druid/widget/fps_panel/fps_panel.lua
Normal file
@@ -0,0 +1,101 @@
|
||||
local helper = require("druid.helper")
|
||||
local mini_graph = require("druid.widget.mini_graph.mini_graph")
|
||||
|
||||
---@class widget.fps_panel: druid.widget
|
||||
---@field root node
|
||||
local M = {}
|
||||
|
||||
local TARGET_FPS = sys.get_config_int("display.update_frequency", 60)
|
||||
if TARGET_FPS == 0 then
|
||||
TARGET_FPS = 60
|
||||
end
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
|
||||
self.delta_time = 0.1 -- in seconds
|
||||
self.collect_time = 3 -- in seconds
|
||||
self.collect_time_counter = 0
|
||||
self.graph_samples = self.collect_time / self.delta_time
|
||||
|
||||
-- Store frame time in seconds last collect_time seconds
|
||||
self.fps_samples = {}
|
||||
|
||||
self.mini_graph = self.druid:new_widget(mini_graph, "mini_graph")
|
||||
self.mini_graph:set_samples(self.graph_samples) -- show last 30 seconds
|
||||
self.mini_graph:set_max_value(TARGET_FPS)
|
||||
|
||||
do -- Set parent manually
|
||||
local parent_node = self.mini_graph.content
|
||||
local position = helper.get_full_position(parent_node, self.mini_graph.root)
|
||||
local content = self:get_node("content")
|
||||
gui.set_parent(content, self.mini_graph.content)
|
||||
gui.set_position(content, -position)
|
||||
end
|
||||
|
||||
self.text_min_fps = self.druid:new_text("text_min_fps")
|
||||
self.text_fps = self.druid:new_text("text_fps")
|
||||
|
||||
self.timer_id = timer.delay(self.delta_time, true, function()
|
||||
self:push_fps_value()
|
||||
end)
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container(self.mini_graph.container)
|
||||
end
|
||||
|
||||
|
||||
function M:on_remove()
|
||||
timer.cancel(self.timer_id)
|
||||
end
|
||||
|
||||
|
||||
function M:update(dt)
|
||||
if not self.previous_time then
|
||||
self.previous_time = socket.gettime()
|
||||
return
|
||||
end
|
||||
|
||||
local current_time = socket.gettime()
|
||||
local delta_time = current_time - self.previous_time
|
||||
self.previous_time = current_time
|
||||
self.collect_time_counter = self.collect_time_counter + delta_time
|
||||
|
||||
table.insert(self.fps_samples, 1, delta_time)
|
||||
|
||||
while self.collect_time_counter > self.collect_time do
|
||||
-- Remove last
|
||||
local removed_value = table.remove(self.fps_samples)
|
||||
self.collect_time_counter = self.collect_time_counter - removed_value
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:push_fps_value()
|
||||
if #self.fps_samples == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local max_frame_time = 0
|
||||
local average_frame_time = 0
|
||||
local average_samples_count = self.delta_time
|
||||
local average_collected = 0
|
||||
for index = 1, #self.fps_samples do
|
||||
if average_frame_time < average_samples_count then
|
||||
average_frame_time = average_frame_time + self.fps_samples[index]
|
||||
average_collected = average_collected + 1
|
||||
end
|
||||
max_frame_time = math.max(max_frame_time, self.fps_samples[index])
|
||||
end
|
||||
|
||||
average_frame_time = average_frame_time / average_collected
|
||||
|
||||
self.mini_graph:push_line_value(1 / average_frame_time)
|
||||
|
||||
self.text_fps:set_text(tostring(math.ceil(1 / average_frame_time) .. " FPS"))
|
||||
local lowest_value = math.ceil(self.mini_graph:get_lowest_value())
|
||||
self.text_min_fps:set_text(lowest_value .. " lowest")
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
242
druid/widget/memory_panel/memory_panel.gui
Normal file
242
druid/widget/memory_panel/memory_panel.gui
Normal file
@@ -0,0 +1,242 @@
|
||||
fonts {
|
||||
name: "druid_text_regular"
|
||||
font: "/druid/fonts/druid_text_regular.font"
|
||||
}
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 200.0
|
||||
y: 140.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "mini_graph"
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/mini_graph/mini_graph.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/root"
|
||||
parent: "mini_graph"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/header"
|
||||
parent: "mini_graph/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
text: "Memory"
|
||||
id: "mini_graph/text_header"
|
||||
parent: "mini_graph/header"
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/icon_drag"
|
||||
parent: "mini_graph/header"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/content"
|
||||
parent: "mini_graph/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/prefab_line"
|
||||
parent: "mini_graph/content"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "mini_graph/color_low"
|
||||
parent: "mini_graph/content"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "content"
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -96.0
|
||||
y: 12.0
|
||||
}
|
||||
scale {
|
||||
x: 0.3
|
||||
y: 0.3
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "120.23 KB"
|
||||
font: "druid_text_regular"
|
||||
id: "text_max_value"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 96.0
|
||||
y: 12.0
|
||||
}
|
||||
scale {
|
||||
x: 0.3
|
||||
y: 0.3
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "120 KB/s"
|
||||
font: "druid_text_regular"
|
||||
id: "text_per_second"
|
||||
pivot: PIVOT_E
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -33.4
|
||||
y: 30.0
|
||||
}
|
||||
size {
|
||||
x: 3.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "line_second_1"
|
||||
pivot: PIVOT_N
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 33.2
|
||||
y: 30.0
|
||||
}
|
||||
size {
|
||||
x: 3.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "line_second_2"
|
||||
pivot: PIVOT_N
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: 12.0
|
||||
}
|
||||
scale {
|
||||
x: 0.3
|
||||
y: 0.3
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "120 KB"
|
||||
font: "druid_text_bold"
|
||||
id: "text_memory"
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
96
druid/widget/memory_panel/memory_panel.lua
Normal file
96
druid/widget/memory_panel/memory_panel.lua
Normal file
@@ -0,0 +1,96 @@
|
||||
local helper = require("druid.helper")
|
||||
local mini_graph = require("druid.widget.mini_graph.mini_graph")
|
||||
|
||||
---@class widget.memory_panel: druid.widget
|
||||
---@field root node
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.delta_time = 0.1
|
||||
self.samples_count = 30
|
||||
self.memory_limit = 100
|
||||
|
||||
self.mini_graph = self.druid:new_widget(mini_graph, "mini_graph")
|
||||
self.mini_graph:set_samples(self.samples_count)
|
||||
|
||||
-- This one is not works with scaled root
|
||||
--gui.set_parent(self:get_node("content"), self.mini_graph.content, true)
|
||||
|
||||
do -- Set parent manually
|
||||
local parent_node = self.mini_graph.content
|
||||
local position = helper.get_full_position(parent_node, self.mini_graph.root)
|
||||
local content = self:get_node("content")
|
||||
gui.set_parent(content, self.mini_graph.content)
|
||||
gui.set_position(content, -position)
|
||||
end
|
||||
|
||||
self.max_value = self.druid:new_text("text_max_value")
|
||||
self.text_per_second = self.druid:new_text("text_per_second")
|
||||
self.text_memory = self.druid:new_text("text_memory")
|
||||
|
||||
self.memory = collectgarbage("count")
|
||||
self.memory_samples = {}
|
||||
|
||||
self:update_text_memory()
|
||||
|
||||
self.timer_id = timer.delay(self.delta_time, true, function()
|
||||
self:push_next_value()
|
||||
end)
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container(self.mini_graph.container)
|
||||
end
|
||||
|
||||
|
||||
function M:on_remove()
|
||||
timer.cancel(self.timer_id)
|
||||
end
|
||||
|
||||
|
||||
function M:set_low_memory_limit(limit)
|
||||
self.memory_limit = limit
|
||||
end
|
||||
|
||||
|
||||
function M:push_next_value()
|
||||
local memory = collectgarbage("count")
|
||||
local diff = math.max(0, memory - self.memory)
|
||||
self.memory = memory
|
||||
self:update_text_memory()
|
||||
|
||||
table.insert(self.memory_samples, diff)
|
||||
if #self.memory_samples > self.samples_count then
|
||||
table.remove(self.memory_samples, 1)
|
||||
end
|
||||
|
||||
self.mini_graph:push_line_value(diff)
|
||||
|
||||
local max_value = math.max(unpack(self.memory_samples))
|
||||
max_value = math.max(max_value, self.memory_limit) -- low limit to display
|
||||
self.mini_graph:set_max_value(max_value)
|
||||
|
||||
local max_memory = math.ceil(self.mini_graph:get_highest_value())
|
||||
self.max_value:set_text(max_memory .. " KB")
|
||||
|
||||
local last_second = 0
|
||||
local last_second_samples = math.ceil(1 / self.delta_time)
|
||||
for index = #self.memory_samples - last_second_samples + 1, #self.memory_samples do
|
||||
last_second = last_second + (self.memory_samples[index] or 0)
|
||||
end
|
||||
self.text_per_second:set_text(math.ceil(last_second) .. " KB/s")
|
||||
end
|
||||
|
||||
|
||||
function M:update_text_memory()
|
||||
local memory = math.ceil(collectgarbage("count")) -- in KB
|
||||
if memory > 1024 then
|
||||
memory = memory / 1024
|
||||
self.text_memory:set_text(string.format("%.2f", memory) .. " MB")
|
||||
else
|
||||
self.text_memory:set_text(memory .. " KB")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
176
druid/widget/mini_graph/mini_graph.gui
Normal file
176
druid/widget/mini_graph/mini_graph.gui
Normal file
@@ -0,0 +1,176 @@
|
||||
fonts {
|
||||
name: "druid_text_regular"
|
||||
font: "/druid/fonts/druid_text_regular.font"
|
||||
}
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 200.0
|
||||
y: 140.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_16"
|
||||
id: "root"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
z: 8.0
|
||||
w: 8.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: 70.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "header"
|
||||
pivot: PIVOT_N
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -92.0
|
||||
y: -8.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 260.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Mini Graph"
|
||||
font: "druid_text_bold"
|
||||
id: "text_header"
|
||||
pivot: PIVOT_NW
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "header"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 96.0
|
||||
y: -4.0
|
||||
}
|
||||
color {
|
||||
x: 0.306
|
||||
y: 0.31
|
||||
z: 0.314
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/icon_drag"
|
||||
id: "icon_drag"
|
||||
pivot: PIVOT_NE
|
||||
parent: "header"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -70.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
}
|
||||
color {
|
||||
x: 0.129
|
||||
y: 0.141
|
||||
z: 0.157
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_16"
|
||||
id: "content"
|
||||
pivot: PIVOT_S
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
z: 8.0
|
||||
w: 8.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_STENCIL
|
||||
material: "gui_stencil"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 8.0
|
||||
y: 70.0
|
||||
}
|
||||
color {
|
||||
x: 0.957
|
||||
y: 0.608
|
||||
z: 0.608
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "prefab_line"
|
||||
pivot: PIVOT_S
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -10.0
|
||||
y: 4.0
|
||||
}
|
||||
size {
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.557
|
||||
y: 0.835
|
||||
z: 0.62
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "color_low"
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
materials {
|
||||
name: "gui_stencil"
|
||||
material: "/druid/materials/stencil/gui_stencil.material"
|
||||
}
|
||||
162
druid/widget/mini_graph/mini_graph.lua
Normal file
162
druid/widget/mini_graph/mini_graph.lua
Normal file
@@ -0,0 +1,162 @@
|
||||
local color = require("druid.color")
|
||||
local helper = require("druid.helper")
|
||||
|
||||
---@class widget.mini_graph: druid.widget
|
||||
local M = {}
|
||||
|
||||
local SIZE_Y = hash("size.y")
|
||||
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.text_header = self.druid:new_text("text_header")
|
||||
|
||||
self.druid:new_drag("header", self.on_drag_widget)
|
||||
self.druid:new_button("icon_drag", self.toggle_hide)
|
||||
:set_style(nil)
|
||||
|
||||
self.content = self:get_node("content")
|
||||
self.layout = self.druid:new_layout(self.content, "horizontal")
|
||||
:set_margin(0, 0)
|
||||
:set_padding(0, 0, 0, 0)
|
||||
|
||||
self.prefab_line = self:get_node("prefab_line")
|
||||
gui.set_enabled(self.prefab_line, false)
|
||||
|
||||
local node_color_low = self:get_node("color_low")
|
||||
self.color_zero = gui.get_color(node_color_low)
|
||||
self.color_one = gui.get_color(self.prefab_line)
|
||||
gui.set_enabled(node_color_low, false)
|
||||
|
||||
self.is_hidden = false
|
||||
self.max_value = 1 -- in this value line will be at max height
|
||||
self.lines = {}
|
||||
self.values = {}
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("header")
|
||||
self.default_size = self.container:get_size()
|
||||
end
|
||||
|
||||
|
||||
function M:on_remove()
|
||||
self:clear()
|
||||
end
|
||||
|
||||
|
||||
function M:clear()
|
||||
self.layout:clear_layout()
|
||||
for index = 1, #self.lines do
|
||||
gui.delete_node(self.lines[index])
|
||||
end
|
||||
|
||||
self.lines = {}
|
||||
end
|
||||
|
||||
|
||||
function M:set_samples(samples)
|
||||
self.samples = samples
|
||||
self:clear()
|
||||
|
||||
local line_width = self.layout:get_size().x / self.samples
|
||||
for index = 1, self.samples do
|
||||
local line = gui.clone(self.prefab_line)
|
||||
gui.set_enabled(line, true)
|
||||
gui.set(line, "size.x", line_width)
|
||||
self.layout:add(line)
|
||||
table.insert(self.lines, line)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:get_samples()
|
||||
return self.samples
|
||||
end
|
||||
|
||||
|
||||
---Set normalized to control the color of the line
|
||||
--- for index = 1, mini_graph:get_samples() do
|
||||
--- mini_graph:set_line_value(index, math.random())
|
||||
--- end
|
||||
---@param index number
|
||||
---@param value number The normalized value from 0 to 1
|
||||
function M:set_line_value(index, value)
|
||||
local line = self.lines[index]
|
||||
if not line then
|
||||
return
|
||||
end
|
||||
|
||||
self.values[index] = value
|
||||
|
||||
local normalized = helper.clamp(value/self.max_value, 0, 1)
|
||||
local target_color = color.lerp(normalized, self.color_zero, self.color_one)
|
||||
gui.set_color(line, target_color)
|
||||
self:set_line_height(index)
|
||||
end
|
||||
|
||||
|
||||
---@return number
|
||||
function M:get_line_value(index)
|
||||
return self.values[index] or 0
|
||||
end
|
||||
|
||||
|
||||
function M:push_line_value(value)
|
||||
for index = 1, self.samples - 1 do
|
||||
self:set_line_value(index, self:get_line_value(index + 1))
|
||||
end
|
||||
|
||||
self:set_line_value(self.samples, value)
|
||||
end
|
||||
|
||||
|
||||
function M:set_max_value(max_value)
|
||||
if self.max_value == max_value then
|
||||
return
|
||||
end
|
||||
|
||||
self.max_value = max_value
|
||||
for index = 1, self.samples do
|
||||
self:set_line_height(index)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:set_line_height(index)
|
||||
local value = self.values[index] or 0
|
||||
local normalized = helper.clamp(value / self.max_value, 0, 1)
|
||||
local size_y = normalized * 70
|
||||
gui.set(self.lines[index], SIZE_Y, size_y)
|
||||
end
|
||||
|
||||
|
||||
function M:get_lowest_value()
|
||||
return math.min(unpack(self.values))
|
||||
end
|
||||
|
||||
|
||||
function M:get_highest_value()
|
||||
return math.max(unpack(self.values))
|
||||
end
|
||||
|
||||
|
||||
function M:on_drag_widget(dx, dy)
|
||||
local position = self.container:get_position()
|
||||
self.container:set_position(position.x + dx, position.y + dy)
|
||||
end
|
||||
|
||||
|
||||
function M:toggle_hide()
|
||||
self.is_hidden = not self.is_hidden
|
||||
local hidden_size = gui.get_size(self:get_node("header"))
|
||||
|
||||
local new_size = self.is_hidden and hidden_size or self.default_size
|
||||
self.container:set_size(new_size.x, new_size.y, gui.PIVOT_N)
|
||||
|
||||
gui.set_enabled(self.content, not self.is_hidden)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
195
druid/widget/node_repeat/node_repeat.lua
Normal file
195
druid/widget/node_repeat/node_repeat.lua
Normal file
@@ -0,0 +1,195 @@
|
||||
local helper = require("druid.helper")
|
||||
local event_queue = require("druid.event_queue")
|
||||
|
||||
---@class druid.node_repeat: druid.widget
|
||||
---@field animation table
|
||||
---@field node node
|
||||
---@field params vector4
|
||||
---@field time number
|
||||
local M = {}
|
||||
|
||||
function M:init(node)
|
||||
self.node = self:get_node(node)
|
||||
self.animation = nil
|
||||
gui.set_material(self.node, hash("gui_repeat"))
|
||||
self.time = 0
|
||||
self.margin = 0
|
||||
|
||||
self.params = gui.get(self.node, "params") --[[@as vector4]]
|
||||
self:get_atlas_path(function(atlas_path)
|
||||
self.is_inited = self:init_tiling_animation(atlas_path)
|
||||
local repeat_x, repeat_y = self:get_repeat()
|
||||
self:animate(repeat_x, repeat_y)
|
||||
end)
|
||||
|
||||
--self.druid.events.on_node_property_changed:subscribe(self.on_node_property_changed, self)
|
||||
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, helper.PROP_SIZE_X)
|
||||
local size_y = gui.get(self.node, helper.PROP_SIZE_Y)
|
||||
local scale_x = gui.get(self.node, helper.PROP_SCALE_X)
|
||||
local scale_y = gui.get(self.node, helper.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
|
||||
|
||||
|
||||
function M:get_atlas_path(callback)
|
||||
event_queue.request("druid.get_atlas_path", callback, gui.get_texture(self.node), msg.url())
|
||||
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 -- X factor
|
||||
-- @param repeat_y -- Y factor
|
||||
function M:animate(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
|
||||
-- @param repeat_y
|
||||
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_perpective(perspective_x, perspective_y)
|
||||
if perspective_x then
|
||||
gui.set(self.node, "perspective.x", perspective_x)
|
||||
end
|
||||
|
||||
if perspective_y then
|
||||
gui.set(self.node, "perspective.y", perspective_y)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function M:set_perpective_offset(offset_x, offset_y)
|
||||
if offset_x then
|
||||
gui.set(self.node, "perspective.z", offset_x)
|
||||
end
|
||||
|
||||
if offset_y then
|
||||
gui.set(self.node, "perspective.w", offset_y)
|
||||
end
|
||||
|
||||
return self
|
||||
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, helper.PROP_SCALE_X)
|
||||
local current_scale_y = gui.get(self.node, helper.PROP_SCALE_Y)
|
||||
local current_size_x = gui.get(self.node, helper.PROP_SIZE_X)
|
||||
local current_size_y = gui.get(self.node, helper.PROP_SIZE_Y)
|
||||
|
||||
local delta_scale_x = scale / current_scale_x
|
||||
local delta_scale_y = scale / current_scale_y
|
||||
gui.set(self.node, helper.PROP_SCALE_X, scale)
|
||||
gui.set(self.node, helper.PROP_SCALE_Y, 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
|
||||
|
||||
|
||||
return M
|
||||
155
druid/widget/properties_panel/properties/property_button.gui
Normal file
155
druid/widget/properties_panel/properties/property_button.gui
Normal file
@@ -0,0 +1,155 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 350.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Button"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -100.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 5.0
|
||||
y: 5.0
|
||||
z: 5.0
|
||||
w: 5.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 380.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Button"
|
||||
font: "druid_text_bold"
|
||||
id: "text_button"
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
58
druid/widget/properties_panel/properties/property_button.lua
Normal file
58
druid/widget/properties_panel/properties/property_button.lua
Normal file
@@ -0,0 +1,58 @@
|
||||
local color = require("druid.color")
|
||||
|
||||
---@class widget.property_button: druid.widget
|
||||
---@field root node
|
||||
---@field container druid.container
|
||||
---@field text_name druid.text
|
||||
---@field button druid.button
|
||||
---@field text_button druid.text
|
||||
---@field druid druid_instance
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.selected = self:get_node("selected")
|
||||
gui.set_alpha(self.selected, 0)
|
||||
|
||||
self.button = self.druid:new_button("button", self.on_click)
|
||||
self.text_button = self.druid:new_text("text_button")
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name", nil, function(_, size)
|
||||
self.text_button:set_size(size)
|
||||
end)
|
||||
self.container:add_container("E_Anchor")
|
||||
end
|
||||
|
||||
|
||||
function M:on_click()
|
||||
gui.set_alpha(self.selected, 1)
|
||||
gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16)
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_button
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_button
|
||||
function M:set_text_button(text)
|
||||
self.text_button:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function M:set_color(color_value)
|
||||
color.set_color(self:get_node("button"), color_value)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
134
druid/widget/properties_panel/properties/property_checkbox.gui
Normal file
134
druid/widget/properties_panel/properties/property_checkbox.gui
Normal file
@@ -0,0 +1,134 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 360.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Checkbox"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -180.0
|
||||
}
|
||||
size {
|
||||
x: 40.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 5.0
|
||||
y: 5.0
|
||||
z: 5.0
|
||||
w: 5.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_16"
|
||||
id: "icon"
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 40.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
@@ -0,0 +1,76 @@
|
||||
local event = require("event.event")
|
||||
|
||||
---@class widget.property_checkbox: druid.widget
|
||||
---@field root node
|
||||
---@field druid druid_instance
|
||||
---@field text_name druid.text
|
||||
---@field button druid.button
|
||||
---@field selected node
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
|
||||
self.icon = self:get_node("icon")
|
||||
gui.set_enabled(self.icon, false)
|
||||
|
||||
self.selected = self:get_node("selected")
|
||||
gui.set_alpha(self.selected, 0)
|
||||
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.button = self.druid:new_button("button", self.on_click)
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name")
|
||||
self.container:add_container("E_Anchor")
|
||||
|
||||
self.on_change_value = event.create()
|
||||
end
|
||||
|
||||
|
||||
---@param value boolean
|
||||
function M:set_value(value, is_instant)
|
||||
if self._value == value then
|
||||
return
|
||||
end
|
||||
|
||||
self._value = value
|
||||
gui.set_enabled(self.icon, value)
|
||||
self.on_change_value:trigger(value)
|
||||
|
||||
if not is_instant then
|
||||
gui.set_alpha(self.selected, 1)
|
||||
gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@return boolean
|
||||
function M:get_value()
|
||||
return self._value
|
||||
end
|
||||
|
||||
|
||||
function M:on_click()
|
||||
self:set_value(not self:get_value())
|
||||
end
|
||||
|
||||
|
||||
--- Set the text property of the checkbox
|
||||
---@param text string
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
end
|
||||
|
||||
|
||||
--- Set the callback function for when the checkbox value changes
|
||||
---@param callback function
|
||||
function M:on_change(callback)
|
||||
self.on_change_value:subscribe(callback)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
143
druid/widget/properties_panel/properties/property_input.gui
Normal file
143
druid/widget/properties_panel/properties/property_input.gui
Normal file
@@ -0,0 +1,143 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 350.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Input"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -100.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "rich_input"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
template: "/druid/custom/rich_input/rich_input.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "rich_input/root"
|
||||
parent: "rich_input"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "rich_input/button"
|
||||
parent: "rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input/placeholder_text"
|
||||
parent: "rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input/input_text"
|
||||
parent: "rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "rich_input/cursor_node"
|
||||
parent: "rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input/cursor_text"
|
||||
parent: "rich_input/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -100.0
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
46
druid/widget/properties_panel/properties/property_input.lua
Normal file
46
druid/widget/properties_panel/properties/property_input.lua
Normal file
@@ -0,0 +1,46 @@
|
||||
---@class widget.property_input: druid.widget
|
||||
---@field root node
|
||||
---@field container druid.container
|
||||
---@field text_name druid.text
|
||||
---@field button druid.button
|
||||
---@field druid druid_instance
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.selected = self:get_node("selected")
|
||||
gui.set_alpha(self.selected, 0)
|
||||
|
||||
self.rich_input = self.druid:new_rich_input("rich_input")
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name")
|
||||
self.container:add_container("E_Anchor")
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_input
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_input
|
||||
function M:set_text_value(text)
|
||||
self.rich_input:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function M:on_change(callback, callback_context)
|
||||
self.rich_input.input.on_input_unselect:subscribe(callback, callback_context)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,212 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 360.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Left Right Selector"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -180.0
|
||||
}
|
||||
size {
|
||||
x: 40.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button_left"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 5.0
|
||||
y: 5.0
|
||||
z: 5.0
|
||||
w: 5.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
rotation {
|
||||
z: 180.0
|
||||
}
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/icon_arrow"
|
||||
id: "icon_left"
|
||||
parent: "button_left"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -20.0
|
||||
}
|
||||
size {
|
||||
x: 40.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button_right"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 5.0
|
||||
y: 5.0
|
||||
z: 5.0
|
||||
w: 5.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/icon_arrow"
|
||||
id: "icon_right"
|
||||
parent: "button_right"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -100.0
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 120.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -100.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 220.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "42"
|
||||
font: "druid_text_bold"
|
||||
id: "text_value"
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
@@ -0,0 +1,211 @@
|
||||
local event = require("event.event")
|
||||
|
||||
---@class widget.property_left_right_selector: druid.widget
|
||||
---@field root node
|
||||
---@field druid druid_instance
|
||||
---@field text_name druid.text
|
||||
---@field button druid.button
|
||||
---@field selected node
|
||||
---@field value string|number
|
||||
---@field on_change_value event fun(value: string|number)
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.selected = self:get_node("selected")
|
||||
gui.set_alpha(self.selected, 0)
|
||||
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.text_value = self.druid:new_text("text_value")
|
||||
self.button_left = self.druid:new_button("button_left", self.on_button_left)
|
||||
self.button_left.on_repeated_click:subscribe(self.on_button_left, self)
|
||||
|
||||
self.button_right = self.druid:new_button("button_right", self.on_button_right)
|
||||
self.button_right.on_repeated_click:subscribe(self.on_button_right, self)
|
||||
|
||||
self.on_change_value = event.create()
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name")
|
||||
self.container:add_container("E_Anchor")
|
||||
end
|
||||
|
||||
|
||||
function M:set_text(text)
|
||||
self.text_name:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---Helper to cycle number in range
|
||||
---@param value number Current value
|
||||
---@param min number Min range value
|
||||
---@param max number Max range value
|
||||
---@param step number Step size
|
||||
---@param is_loop boolean Is looped
|
||||
---@return number Cycled value
|
||||
local function step_number(value, min, max, step, is_loop)
|
||||
local range = max - min + 1
|
||||
if is_loop then
|
||||
-- Normalize step within range
|
||||
local effective_step = step
|
||||
if math.abs(step) >= range then
|
||||
effective_step = step % range
|
||||
if effective_step == 0 then
|
||||
effective_step = step > 0 and range or -range
|
||||
end
|
||||
end
|
||||
|
||||
value = value + effective_step
|
||||
-- Handle wrapping
|
||||
if max then
|
||||
while value > max do
|
||||
value = min + (value - max - 1)
|
||||
end
|
||||
end
|
||||
if min then
|
||||
while value < min do
|
||||
value = max - (min - value - 1)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Clamp values
|
||||
value = value + step
|
||||
if max and value > max then
|
||||
return max
|
||||
elseif min and value < min then
|
||||
return min
|
||||
end
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
|
||||
---Helper to cycle array index with proper step wrapping
|
||||
---@param array table Array to cycle through
|
||||
---@param current_value any Current value to find index for
|
||||
---@param step number Step direction
|
||||
---@param is_loop boolean If true, cycle values. If false, clamp at ends
|
||||
---@return any Next value in cycle
|
||||
local function step_array(array, current_value, step, is_loop)
|
||||
local index = 1
|
||||
for i, v in ipairs(array) do
|
||||
if v == current_value then
|
||||
index = i
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if is_loop then
|
||||
-- Normalize step within array length
|
||||
local range = #array
|
||||
local effective_step = step
|
||||
if math.abs(step) >= range then
|
||||
effective_step = step % range
|
||||
if effective_step == 0 then
|
||||
effective_step = step > 0 and range or -range
|
||||
end
|
||||
end
|
||||
|
||||
index = index + effective_step
|
||||
-- Handle wrapping
|
||||
while index > range do
|
||||
index = 1 + (index - range - 1)
|
||||
end
|
||||
while index < 1 do
|
||||
index = range - (1 - index - 1)
|
||||
end
|
||||
else
|
||||
-- Clamp values
|
||||
index = index + step
|
||||
if index > #array then
|
||||
index = #array
|
||||
elseif index < 1 then
|
||||
index = 1
|
||||
end
|
||||
end
|
||||
|
||||
return array[index]
|
||||
end
|
||||
|
||||
|
||||
function M:on_button_left()
|
||||
self:add_step(-1)
|
||||
end
|
||||
|
||||
function M:on_button_right()
|
||||
self:add_step(1)
|
||||
end
|
||||
|
||||
|
||||
---@param koef number -1 0 1, on 0 will not move
|
||||
function M:add_step(koef)
|
||||
local array_type = self.array_type
|
||||
if array_type then
|
||||
local value = self.value
|
||||
local new_value = step_array(array_type.array, value, koef * array_type.steps, array_type.is_loop)
|
||||
self:set_value(new_value)
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
local number_type = self.number_type
|
||||
if number_type then
|
||||
local value = tonumber(self.value) --[[@as number]]
|
||||
local new_value = step_number(value, number_type.min, number_type.max, koef * number_type.steps, number_type.is_loop)
|
||||
self:set_value(new_value)
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:set_number_type(min, max, is_loop, steps)
|
||||
self.number_type = {
|
||||
min = min,
|
||||
max = max,
|
||||
steps = steps or 1,
|
||||
is_loop = is_loop,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function M:set_array_type(array, is_loop, steps)
|
||||
self.array_type = {
|
||||
array = array,
|
||||
steps = steps or 1,
|
||||
is_loop = is_loop,
|
||||
}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param value string|number
|
||||
function M:set_value(value, is_instant)
|
||||
if self.value == value then
|
||||
return
|
||||
end
|
||||
|
||||
self.value = value
|
||||
self.text_value:set_text(tostring(value))
|
||||
self.on_change_value:trigger(value)
|
||||
|
||||
if not is_instant then
|
||||
gui.set_alpha(self.selected, 1)
|
||||
gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@return string|number
|
||||
function M:get_value()
|
||||
return self.value
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
222
druid/widget/properties_panel/properties/property_slider.gui
Normal file
222
druid/widget/properties_panel/properties/property_slider.gui
Normal file
@@ -0,0 +1,222 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 380.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Slider"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -133.0
|
||||
}
|
||||
size {
|
||||
x: 134.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.129
|
||||
y: 0.141
|
||||
z: 0.157
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "slider"
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 134.0
|
||||
y: 8.0
|
||||
}
|
||||
color {
|
||||
x: 0.129
|
||||
y: 0.141
|
||||
z: 0.157
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_8"
|
||||
id: "slider_back"
|
||||
parent: "slider"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 4.0
|
||||
y: 4.0
|
||||
z: 4.0
|
||||
w: 4.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -55.0
|
||||
}
|
||||
size {
|
||||
x: 24.0
|
||||
y: 24.0
|
||||
}
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_8"
|
||||
id: "slider_pin"
|
||||
parent: "slider"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 4.0
|
||||
y: 4.0
|
||||
z: 4.0
|
||||
w: 4.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 60.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/rect_round2_width2"
|
||||
id: "button"
|
||||
pivot: PIVOT_E
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 4.0
|
||||
y: 4.0
|
||||
z: 4.0
|
||||
w: 4.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 60.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected"
|
||||
pivot: PIVOT_SE
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -30.0
|
||||
}
|
||||
scale {
|
||||
x: 0.55
|
||||
y: 0.55
|
||||
}
|
||||
size {
|
||||
x: 100.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "25 %"
|
||||
font: "druid_text_bold"
|
||||
id: "text_value"
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "button"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
125
druid/widget/properties_panel/properties/property_slider.lua
Normal file
125
druid/widget/properties_panel/properties/property_slider.lua
Normal file
@@ -0,0 +1,125 @@
|
||||
local event = require("event.event")
|
||||
local helper = require("druid.helper")
|
||||
|
||||
---@class widget.property_slider: druid.widget
|
||||
---@field root node
|
||||
---@field container druid.container
|
||||
---@field druid druid_instance
|
||||
---@field text_name druid.text
|
||||
---@field text_value druid.text
|
||||
---@field slider druid.slider
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.selected = self:get_node("selected")
|
||||
gui.set_alpha(self.selected, 0)
|
||||
self._value = 0
|
||||
|
||||
self.min = 0
|
||||
self.max = 1
|
||||
self.step = 0.01
|
||||
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.text_value = self.druid:new_text("text_value")
|
||||
self.slider = self.druid:new_slider("slider_pin", vmath.vector3(55, 0, 0), self.update_value) --[[@as druid.slider]]
|
||||
self.slider:set_input_node("slider")
|
||||
|
||||
self:set_text_function(function(value)
|
||||
return math.floor(value * 100) .. "%"
|
||||
end)
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name")
|
||||
self.container:add_container("E_Anchor")
|
||||
|
||||
self.on_change_value = event.create()
|
||||
end
|
||||
|
||||
|
||||
---@param callback fun(value:number):string
|
||||
function M:set_text_function(callback)
|
||||
self._text_function = callback
|
||||
self.text_value:set_text(self._text_function(self._value))
|
||||
end
|
||||
|
||||
|
||||
--- Sets the text property of the slider
|
||||
---@param text string
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
end
|
||||
|
||||
|
||||
--- Sets the callback function for when the slider value changes
|
||||
---@param callback fun(value:number)
|
||||
function M:on_change(callback)
|
||||
self.on_change_value:subscribe(callback)
|
||||
end
|
||||
|
||||
|
||||
---@param value number
|
||||
function M:set_value(value, is_instant)
|
||||
local diff = math.abs(self.max - self.min)
|
||||
self.slider:set((value - self.min) / diff, true)
|
||||
|
||||
local is_changed = self._value ~= value
|
||||
if not is_changed then
|
||||
return
|
||||
end
|
||||
|
||||
self._value = value
|
||||
self.text_value:set_text(self._text_function(value))
|
||||
self.on_change_value:trigger(value)
|
||||
|
||||
if not is_instant then
|
||||
gui.set_alpha(self.selected, 1)
|
||||
gui.animate(self.selected, "color.w", 0, gui.EASING_INSINE, 0.16)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@return number
|
||||
function M:get_value()
|
||||
return self._value
|
||||
end
|
||||
|
||||
|
||||
function M:update_value(value)
|
||||
local current_value = self._value
|
||||
|
||||
local diff = math.abs(self.max - self.min)
|
||||
-- [0..1] To range
|
||||
value = value * diff + self.min
|
||||
|
||||
-- Round to steps value (0.1, or 5. Should be divided on this value)
|
||||
value = math.floor(value / self.step + 0.5) * self.step
|
||||
|
||||
value = helper.clamp(value, self.min, self.max)
|
||||
|
||||
self:set_value(value)
|
||||
end
|
||||
|
||||
|
||||
function M:set_number_type(min, max, step)
|
||||
self.min = min or 0
|
||||
self.max = max or 1
|
||||
self.step = step
|
||||
|
||||
self:set_text_function(function(value)
|
||||
return tostring(value)
|
||||
end)
|
||||
|
||||
self:set_value(self._value, true)
|
||||
end
|
||||
|
||||
|
||||
function M:_on_slider_change_by_user(value)
|
||||
self:set_value(value)
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
96
druid/widget/properties_panel/properties/property_text.gui
Normal file
96
druid/widget/properties_panel/properties/property_text.gui
Normal file
@@ -0,0 +1,96 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Text"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 350.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.722
|
||||
y: 0.741
|
||||
z: 0.761
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Text"
|
||||
font: "druid_text_bold"
|
||||
id: "text_right"
|
||||
pivot: PIVOT_E
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
42
druid/widget/properties_panel/properties/property_text.lua
Normal file
42
druid/widget/properties_panel/properties/property_text.lua
Normal file
@@ -0,0 +1,42 @@
|
||||
---@class widget.property_text: druid.widget
|
||||
---@field root node
|
||||
---@field container druid.container
|
||||
---@field text_name druid.text
|
||||
---@field text_right druid.text
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_when_trim_left", 0.3)
|
||||
|
||||
self.text_right = self.druid:new_text("text_right", "")
|
||||
--:set_text_adjust("scale_when_trim_left", 0.3) -- TODO: not works? why?
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name", nil, function(_, size)
|
||||
self.text_name:set_size(size)
|
||||
end)
|
||||
self.container:add_container("text_right", nil, function(_, size)
|
||||
self.text_right:set_size(size)
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_text
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param text string|nil
|
||||
---@return widget.property_text
|
||||
function M:set_text_value(text)
|
||||
self.text_right:set_text(text or "")
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
490
druid/widget/properties_panel/properties/property_vector3.gui
Normal file
490
druid/widget/properties_panel/properties/property_vector3.gui
Normal file
@@ -0,0 +1,490 @@
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
fonts {
|
||||
name: "druid_text_regular"
|
||||
font: "/druid/fonts/druid_text_regular.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "root"
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
inherit_alpha: true
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 350.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Vector3"
|
||||
font: "druid_text_bold"
|
||||
id: "text_name"
|
||||
pivot: PIVOT_W
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "E_Anchor"
|
||||
pivot: PIVOT_E
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
}
|
||||
size {
|
||||
x: 66.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "field_x"
|
||||
pivot: PIVOT_W
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 7.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 30.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.31
|
||||
y: 0.318
|
||||
z: 0.322
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "X"
|
||||
font: "druid_text_regular"
|
||||
id: "text_x"
|
||||
parent: "field_x"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "rich_input_x"
|
||||
parent: "field_x"
|
||||
inherit_alpha: true
|
||||
template: "/druid/custom/rich_input/rich_input.gui"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_x/root"
|
||||
parent: "rich_input_x"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_x/button"
|
||||
parent: "rich_input_x/root"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_x/placeholder_text"
|
||||
parent: "rich_input_x/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "20.0"
|
||||
id: "rich_input_x/input_text"
|
||||
parent: "rich_input_x/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 18.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_x/cursor_node"
|
||||
parent: "rich_input_x/root"
|
||||
overridden_fields: 1
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_x/cursor_text"
|
||||
parent: "rich_input_x/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 50.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected_x"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "field_x"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -132.0
|
||||
}
|
||||
size {
|
||||
x: 66.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "field_y"
|
||||
pivot: PIVOT_W
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 7.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 30.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.31
|
||||
y: 0.318
|
||||
z: 0.322
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Y"
|
||||
font: "druid_text_regular"
|
||||
id: "text_y"
|
||||
parent: "field_y"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "rich_input_y"
|
||||
parent: "field_y"
|
||||
inherit_alpha: true
|
||||
template: "/druid/custom/rich_input/rich_input.gui"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_y/root"
|
||||
parent: "rich_input_y"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_y/button"
|
||||
parent: "rich_input_y/root"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_y/placeholder_text"
|
||||
parent: "rich_input_y/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "20.0"
|
||||
id: "rich_input_y/input_text"
|
||||
parent: "rich_input_y/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 18.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_y/cursor_node"
|
||||
parent: "rich_input_y/root"
|
||||
overridden_fields: 1
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_y/cursor_text"
|
||||
parent: "rich_input_y/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 50.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected_y"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "field_y"
|
||||
inherit_alpha: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -66.0
|
||||
}
|
||||
size {
|
||||
x: 66.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "field_z"
|
||||
pivot: PIVOT_W
|
||||
parent: "E_Anchor"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 7.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 30.0
|
||||
y: 40.0
|
||||
}
|
||||
color {
|
||||
x: 0.31
|
||||
y: 0.318
|
||||
z: 0.322
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Z"
|
||||
font: "druid_text_regular"
|
||||
id: "text_z"
|
||||
parent: "field_z"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "rich_input_z"
|
||||
parent: "field_z"
|
||||
inherit_alpha: true
|
||||
template: "/druid/custom/rich_input/rich_input.gui"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_z/root"
|
||||
parent: "rich_input_z"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 50.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_z/button"
|
||||
parent: "rich_input_z/root"
|
||||
overridden_fields: 4
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_z/placeholder_text"
|
||||
parent: "rich_input_z/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 70.0
|
||||
y: 50.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "20.0"
|
||||
id: "rich_input_z/input_text"
|
||||
parent: "rich_input_z/root"
|
||||
overridden_fields: 4
|
||||
overridden_fields: 8
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 18.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "rich_input_z/cursor_node"
|
||||
parent: "rich_input_z/root"
|
||||
overridden_fields: 1
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "rich_input_z/cursor_text"
|
||||
parent: "rich_input_z/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 40.0
|
||||
y: -20.0
|
||||
}
|
||||
size {
|
||||
x: 50.0
|
||||
y: 4.0
|
||||
}
|
||||
color {
|
||||
x: 0.894
|
||||
y: 0.506
|
||||
z: 0.333
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "selected_z"
|
||||
pivot: PIVOT_S
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "field_z"
|
||||
inherit_alpha: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
@@ -0,0 +1,75 @@
|
||||
local event = require("event.event")
|
||||
|
||||
|
||||
---@class widget.property_vector3: druid.widget
|
||||
---@field root node
|
||||
---@field container druid.container
|
||||
---@field text_name druid.text
|
||||
---@field button druid.button
|
||||
---@field druid druid_instance
|
||||
local M = {}
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.text_name = self.druid:new_text("text_name")
|
||||
:set_text_adjust("scale_then_trim", 0.3)
|
||||
|
||||
self.selected_x = self:get_node("selected_x")
|
||||
gui.set_alpha(self.selected_x, 0)
|
||||
|
||||
self.selected_y = self:get_node("selected_y")
|
||||
gui.set_alpha(self.selected_y, 0)
|
||||
|
||||
self.selected_z = self:get_node("selected_z")
|
||||
gui.set_alpha(self.selected_z, 0)
|
||||
|
||||
self.rich_input_x = self.druid:new_rich_input("rich_input_x")
|
||||
self.rich_input_y = self.druid:new_rich_input("rich_input_y")
|
||||
self.rich_input_z = self.druid:new_rich_input("rich_input_z")
|
||||
|
||||
self.value = vmath.vector3(0)
|
||||
|
||||
self.rich_input_x.input.on_input_unselect:subscribe(function()
|
||||
self.value.x = tonumber(self.rich_input_x.input:get_text()) or 0
|
||||
self.on_change:trigger(self.value)
|
||||
end)
|
||||
|
||||
self.rich_input_y.input.on_input_unselect:subscribe(function()
|
||||
self.value.y = tonumber(self.rich_input_y.input:get_text()) or 0
|
||||
self.on_change:trigger(self.value)
|
||||
end)
|
||||
|
||||
self.rich_input_z.input.on_input_unselect:subscribe(function()
|
||||
self.value.z = tonumber(self.rich_input_z.input:get_text()) or 0
|
||||
self.on_change:trigger(self.value)
|
||||
end)
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("text_name")
|
||||
self.container:add_container("E_Anchor")
|
||||
|
||||
self.on_change = event.create()
|
||||
end
|
||||
|
||||
|
||||
---@param text string
|
||||
---@return widget.property_vector3
|
||||
function M:set_text_property(text)
|
||||
self.text_name:set_text(text)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param x number
|
||||
---@param y number
|
||||
---@param z number
|
||||
---@return widget.property_vector3
|
||||
function M:set_value(x, y, z)
|
||||
self.rich_input_x:set_text(tostring(x))
|
||||
self.rich_input_y:set_text(tostring(y))
|
||||
self.rich_input_z:set_text(tostring(z))
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
702
druid/widget/properties_panel/properties_panel.gui
Normal file
702
druid/widget/properties_panel/properties_panel.gui
Normal file
@@ -0,0 +1,702 @@
|
||||
fonts {
|
||||
name: "druid_text_regular"
|
||||
font: "/druid/fonts/druid_text_regular.font"
|
||||
}
|
||||
fonts {
|
||||
name: "druid_text_bold"
|
||||
font: "/druid/fonts/druid_text_bold.font"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/druid/druid.atlas"
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 240.0
|
||||
}
|
||||
color {
|
||||
x: 0.173
|
||||
y: 0.184
|
||||
z: 0.204
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/ui_circle_16"
|
||||
id: "root"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
z: 8.0
|
||||
w: 8.0
|
||||
}
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: 120.0
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 40.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "header"
|
||||
pivot: PIVOT_N
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -192.0
|
||||
y: -8.0
|
||||
}
|
||||
scale {
|
||||
x: 0.5
|
||||
y: 0.5
|
||||
}
|
||||
size {
|
||||
x: 500.0
|
||||
y: 50.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
text: "Properties"
|
||||
font: "druid_text_regular"
|
||||
id: "text_header"
|
||||
pivot: PIVOT_NW
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
}
|
||||
parent: "header"
|
||||
inherit_alpha: true
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 192.0
|
||||
y: -4.0
|
||||
}
|
||||
color {
|
||||
x: 0.306
|
||||
y: 0.31
|
||||
z: 0.314
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/icon_drag"
|
||||
id: "icon_drag"
|
||||
pivot: PIVOT_NE
|
||||
parent: "header"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -120.0
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 190.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
id: "content"
|
||||
pivot: PIVOT_S
|
||||
parent: "root"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
y: 190.0
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 190.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "scroll_view"
|
||||
xanchor: XANCHOR_LEFT
|
||||
pivot: PIVOT_NW
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
clipping_mode: CLIPPING_MODE_STENCIL
|
||||
}
|
||||
nodes {
|
||||
size {
|
||||
x: 400.0
|
||||
y: 190.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/pixel"
|
||||
id: "scroll_content"
|
||||
pivot: PIVOT_NW
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "scroll_view"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 8.0
|
||||
y: 8.0
|
||||
w: 6.0
|
||||
}
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: 170.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
texture: "druid/empty"
|
||||
id: "propeties"
|
||||
parent: "content"
|
||||
inherit_alpha: true
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
visible: false
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_slider"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_slider.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/root"
|
||||
parent: "property_slider"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_slider/text_name"
|
||||
parent: "property_slider/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/E_Anchor"
|
||||
parent: "property_slider/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/slider"
|
||||
parent: "property_slider/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/slider_back"
|
||||
parent: "property_slider/slider"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/slider_pin"
|
||||
parent: "property_slider/slider"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/button"
|
||||
parent: "property_slider/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_slider/selected"
|
||||
parent: "property_slider/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_slider/text_value"
|
||||
parent: "property_slider/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -50.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_checkbox"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_checkbox.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_checkbox/root"
|
||||
parent: "property_checkbox"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_checkbox/text_name"
|
||||
parent: "property_checkbox/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_checkbox/E_Anchor"
|
||||
parent: "property_checkbox/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_checkbox/button"
|
||||
parent: "property_checkbox/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_checkbox/icon"
|
||||
parent: "property_checkbox/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_checkbox/selected"
|
||||
parent: "property_checkbox/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -100.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_button"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_button.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_button/root"
|
||||
parent: "property_button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_button/text_name"
|
||||
parent: "property_button/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_button/E_Anchor"
|
||||
parent: "property_button/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_button/button"
|
||||
parent: "property_button/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_button/selected"
|
||||
parent: "property_button/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_button/text_button"
|
||||
parent: "property_button/button"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -150.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_input"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_input.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/root"
|
||||
parent: "property_input"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_input/text_name"
|
||||
parent: "property_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/E_Anchor"
|
||||
parent: "property_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_input/rich_input"
|
||||
parent: "property_input/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/rich_input/root"
|
||||
parent: "property_input/rich_input"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/rich_input/button"
|
||||
parent: "property_input/rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_input/rich_input/placeholder_text"
|
||||
parent: "property_input/rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_input/rich_input/input_text"
|
||||
parent: "property_input/rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/rich_input/cursor_node"
|
||||
parent: "property_input/rich_input/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_input/rich_input/cursor_text"
|
||||
parent: "property_input/rich_input/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_input/selected"
|
||||
parent: "property_input/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -200.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_text"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_text.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_text/root"
|
||||
parent: "property_text"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_text/text_name"
|
||||
parent: "property_text/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_text/text_right"
|
||||
parent: "property_text/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -250.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_left_right_selector"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_left_right_selector.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/root"
|
||||
parent: "property_left_right_selector"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_left_right_selector/text_name"
|
||||
parent: "property_left_right_selector/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/E_Anchor"
|
||||
parent: "property_left_right_selector/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/button_left"
|
||||
parent: "property_left_right_selector/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/icon_left"
|
||||
parent: "property_left_right_selector/button_left"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/button_right"
|
||||
parent: "property_left_right_selector/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/icon_right"
|
||||
parent: "property_left_right_selector/button_right"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_left_right_selector/selected"
|
||||
parent: "property_left_right_selector/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_left_right_selector/text_value"
|
||||
parent: "property_left_right_selector/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
y: -300.0
|
||||
}
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_vector3"
|
||||
parent: "propeties"
|
||||
inherit_alpha: true
|
||||
template: "/druid/widget/properties_panel/properties/property_vector3.gui"
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/root"
|
||||
parent: "property_vector3"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/text_name"
|
||||
parent: "property_vector3/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/E_Anchor"
|
||||
parent: "property_vector3/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/field_x"
|
||||
parent: "property_vector3/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/text_x"
|
||||
parent: "property_vector3/field_x"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_vector3/rich_input_x"
|
||||
parent: "property_vector3/field_x"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_x/root"
|
||||
parent: "property_vector3/rich_input_x"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_x/button"
|
||||
parent: "property_vector3/rich_input_x/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_x/placeholder_text"
|
||||
parent: "property_vector3/rich_input_x/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_x/input_text"
|
||||
parent: "property_vector3/rich_input_x/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_x/cursor_node"
|
||||
parent: "property_vector3/rich_input_x/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_x/cursor_text"
|
||||
parent: "property_vector3/rich_input_x/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/selected_x"
|
||||
parent: "property_vector3/field_x"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/field_y"
|
||||
parent: "property_vector3/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/text_y"
|
||||
parent: "property_vector3/field_y"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_vector3/rich_input_y"
|
||||
parent: "property_vector3/field_y"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_y/root"
|
||||
parent: "property_vector3/rich_input_y"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_y/button"
|
||||
parent: "property_vector3/rich_input_y/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_y/placeholder_text"
|
||||
parent: "property_vector3/rich_input_y/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_y/input_text"
|
||||
parent: "property_vector3/rich_input_y/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_y/cursor_node"
|
||||
parent: "property_vector3/rich_input_y/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_y/cursor_text"
|
||||
parent: "property_vector3/rich_input_y/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/selected_y"
|
||||
parent: "property_vector3/field_y"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/field_z"
|
||||
parent: "property_vector3/E_Anchor"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/text_z"
|
||||
parent: "property_vector3/field_z"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEMPLATE
|
||||
id: "property_vector3/rich_input_z"
|
||||
parent: "property_vector3/field_z"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_z/root"
|
||||
parent: "property_vector3/rich_input_z"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_z/button"
|
||||
parent: "property_vector3/rich_input_z/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_z/placeholder_text"
|
||||
parent: "property_vector3/rich_input_z/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_z/input_text"
|
||||
parent: "property_vector3/rich_input_z/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/rich_input_z/cursor_node"
|
||||
parent: "property_vector3/rich_input_z/root"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_TEXT
|
||||
id: "property_vector3/rich_input_z/cursor_text"
|
||||
parent: "property_vector3/rich_input_z/cursor_node"
|
||||
template_node_child: true
|
||||
}
|
||||
nodes {
|
||||
type: TYPE_BOX
|
||||
id: "property_vector3/selected_z"
|
||||
parent: "property_vector3/field_z"
|
||||
template_node_child: true
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
319
druid/widget/properties_panel/properties_panel.lua
Normal file
319
druid/widget/properties_panel/properties_panel.lua
Normal file
@@ -0,0 +1,319 @@
|
||||
local property_checkbox = require("druid.widget.properties_panel.properties.property_checkbox")
|
||||
local property_slider = require("druid.widget.properties_panel.properties.property_slider")
|
||||
local property_button = require("druid.widget.properties_panel.properties.property_button")
|
||||
local property_input = require("druid.widget.properties_panel.properties.property_input")
|
||||
local property_text = require("druid.widget.properties_panel.properties.property_text")
|
||||
local property_left_right_selector = require("druid.widget.properties_panel.properties.property_left_right_selector")
|
||||
local property_vector3 = require("druid.widget.properties_panel.properties.property_vector3")
|
||||
|
||||
---@class widget.properties_panel: druid.widget
|
||||
---@field root node
|
||||
---@field scroll druid.scroll
|
||||
---@field layout druid.layout
|
||||
---@field container druid.container
|
||||
---@field container_content druid.container
|
||||
---@field container_scroll_view druid.container
|
||||
---@field contaienr_scroll_content druid.container
|
||||
---@field text_header druid.text
|
||||
---@field paginator widget.property_left_right_selector
|
||||
---@field properties druid.widget[] List of created properties
|
||||
---@field properties_constructors fun()[] List of properties functions to create a new widget. Used to not spawn non-visible widgets but keep the reference
|
||||
local M = {}
|
||||
|
||||
|
||||
function M:init()
|
||||
self.root = self:get_node("root")
|
||||
self.content = self:get_node("content")
|
||||
|
||||
self.container = self.druid:new_container(self.root)
|
||||
self.container:add_container("header")
|
||||
self.container_content = self.container:add_container("content")
|
||||
self.container_scroll_view = self.container_content:add_container("scroll_view")
|
||||
self.contaienr_scroll_content = self.container_scroll_view:add_container("scroll_content")
|
||||
|
||||
self.default_size = self.container:get_size()
|
||||
|
||||
self.properties = {}
|
||||
self.properties_constructors = {}
|
||||
self.current_page = 1
|
||||
self.properties_per_page = 15
|
||||
|
||||
self.text_header = self.druid:new_text("text_header")
|
||||
self.scroll = self.druid:new_scroll("scroll_view", "scroll_content")
|
||||
self.layout = self.druid:new_layout("scroll_content", "vertical")
|
||||
:set_hug_content(false, true)
|
||||
:set_padding(nil, 0)
|
||||
|
||||
self.layout.on_size_changed:subscribe(self.on_size_changed, self)
|
||||
|
||||
self.druid:new_drag("header", self.on_drag_widget)
|
||||
self.druid:new_button("icon_drag", self.toggle_hide)
|
||||
:set_style(nil)
|
||||
|
||||
self.property_checkbox_prefab = self:get_node("property_checkbox/root")
|
||||
gui.set_enabled(self.property_checkbox_prefab, false)
|
||||
|
||||
self.property_slider_prefab = self:get_node("property_slider/root")
|
||||
gui.set_enabled(self.property_slider_prefab, false)
|
||||
|
||||
self.property_button_prefab = self:get_node("property_button/root")
|
||||
gui.set_enabled(self.property_button_prefab, false)
|
||||
|
||||
self.property_input_prefab = self:get_node("property_input/root")
|
||||
gui.set_enabled(self.property_input_prefab, false)
|
||||
|
||||
self.property_text_prefab = self:get_node("property_text/root")
|
||||
gui.set_enabled(self.property_text_prefab, false)
|
||||
|
||||
self.property_left_right_selector_prefab = self:get_node("property_left_right_selector/root")
|
||||
gui.set_enabled(self.property_left_right_selector_prefab, false)
|
||||
|
||||
self.property_vector3_prefab = self:get_node("property_vector3/root")
|
||||
gui.set_enabled(self.property_vector3_prefab, false)
|
||||
|
||||
-- We not using as a part of properties, since it handled in a way to be paginable
|
||||
self.paginator = self.druid:new_widget(property_left_right_selector, "property_left_right_selector", self.property_left_right_selector_prefab)
|
||||
self.paginator:set_text("Page")
|
||||
self.paginator:set_number_type(1, 1, true)
|
||||
self.paginator:set_value(self.current_page)
|
||||
self.paginator.on_change_value:subscribe(function(value)
|
||||
self:set_page(value)
|
||||
end)
|
||||
local width = self.layout:get_content_size()
|
||||
self.paginator.container:set_size(width)
|
||||
|
||||
gui.set_enabled(self.paginator.root, false)
|
||||
end
|
||||
|
||||
|
||||
function M:on_remove()
|
||||
self:clear()
|
||||
end
|
||||
|
||||
|
||||
function M:on_drag_widget(dx, dy)
|
||||
local position = self.container:get_position()
|
||||
self.container:set_position(position.x + dx, position.y + dy)
|
||||
end
|
||||
|
||||
|
||||
function M:clear_created_properties()
|
||||
for index = 1, #self.properties do
|
||||
local property = self.properties[index]
|
||||
|
||||
-- If prefab used clone nodes we can remove it
|
||||
if property:get_nodes() then
|
||||
gui.delete_node(property.root)
|
||||
else
|
||||
-- Probably we have component placed on scene directly
|
||||
gui.set_enabled(property.root, false)
|
||||
end
|
||||
|
||||
self.druid:remove(self.properties[index])
|
||||
end
|
||||
self.properties = {}
|
||||
|
||||
self.layout:clear_layout()
|
||||
|
||||
-- Use paginator as "pinned" widget
|
||||
self.layout:add(self.paginator.root)
|
||||
end
|
||||
|
||||
|
||||
function M:clear()
|
||||
self:clear_created_properties()
|
||||
self.properties_constructors = {}
|
||||
end
|
||||
|
||||
|
||||
function M:on_size_changed(new_size)
|
||||
self.container_content:set_size(new_size.x, new_size.y, gui.PIVOT_N)
|
||||
|
||||
self.default_size = vmath.vector3(new_size.x, new_size.y + 50, 0)
|
||||
if not self.is_hidden then
|
||||
self.container:set_size(self.default_size.x, self.default_size.y, gui.PIVOT_N)
|
||||
end
|
||||
|
||||
local width = self.layout:get_size().x - self.layout.padding.x - self.layout.padding.z
|
||||
for index = 1, #self.properties do
|
||||
local property = self.properties[index]
|
||||
if property.container then
|
||||
property.container:set_size(width)
|
||||
end
|
||||
end
|
||||
self.paginator.container:set_size(width)
|
||||
end
|
||||
|
||||
|
||||
function M:update(dt)
|
||||
if self.is_dirty then
|
||||
self.is_dirty = false
|
||||
|
||||
self:clear_created_properties()
|
||||
|
||||
local properties_count = #self.properties_constructors
|
||||
|
||||
-- Render all current properties
|
||||
local start_index = (self.current_page - 1) * self.properties_per_page + 1
|
||||
local end_index = start_index + self.properties_per_page - 1
|
||||
end_index = math.min(end_index, properties_count)
|
||||
|
||||
local is_paginator_visible = properties_count > self.properties_per_page
|
||||
gui.set_enabled(self.paginator.root, is_paginator_visible)
|
||||
self.paginator:set_number_type(1, math.ceil(properties_count / self.properties_per_page), true)
|
||||
self.paginator.text_value:set_text(self.current_page .. " / " .. math.ceil(properties_count / self.properties_per_page))
|
||||
|
||||
for index = start_index, end_index do
|
||||
self.properties_constructors[index]()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(checkbox: widget.property_checkbox)|nil
|
||||
---@return widget.properties_panel
|
||||
function M:add_checkbox(on_create)
|
||||
return self:add_inner_widget(property_checkbox, "property_checkbox", self.property_checkbox_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(slider: widget.property_slider)|nil
|
||||
---@return widget.properties_panel
|
||||
function M:add_slider(on_create)
|
||||
return self:add_inner_widget(property_slider, "property_slider", self.property_slider_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(button: widget.property_button)|nil
|
||||
---@return widget.properties_panel
|
||||
function M:add_button(on_create)
|
||||
return self:add_inner_widget(property_button, "property_button", self.property_button_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(input: widget.property_input)|nil
|
||||
---@return widget.properties_panel
|
||||
function M:add_input(on_create)
|
||||
return self:add_inner_widget(property_input, "property_input", self.property_input_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(text: widget.property_text)|nil
|
||||
function M:add_text(on_create)
|
||||
return self:add_inner_widget(property_text, "property_text", self.property_text_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(selector: widget.property_left_right_selector)|nil
|
||||
function M:add_left_right_selector(on_create)
|
||||
return self:add_inner_widget(property_left_right_selector, "property_left_right_selector", self.property_left_right_selector_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@param on_create fun(vector3: widget.property_vector3)|nil
|
||||
function M:add_vector3(on_create)
|
||||
return self:add_inner_widget(property_vector3, "property_vector3", self.property_vector3_prefab, on_create)
|
||||
end
|
||||
|
||||
|
||||
---@generic T: druid.widget
|
||||
---@param widget_class T
|
||||
---@param template string|nil
|
||||
---@param nodes table<hash, node>|node|nil
|
||||
---@param on_create fun(widget: T)|nil
|
||||
---@return widget.properties_panel
|
||||
function M:add_inner_widget(widget_class, template, nodes, on_create)
|
||||
table.insert(self.properties_constructors, function()
|
||||
local widget = self.druid:new_widget(widget_class, template, nodes)
|
||||
|
||||
self:add_property(widget)
|
||||
if on_create then
|
||||
on_create(widget)
|
||||
end
|
||||
end)
|
||||
|
||||
self.is_dirty = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param create_widget_callback fun(): druid.widget
|
||||
---@return widget.properties_panel
|
||||
function M:add_widget(create_widget_callback)
|
||||
table.insert(self.properties_constructors, function()
|
||||
local widget = create_widget_callback()
|
||||
self:add_property(widget)
|
||||
end)
|
||||
|
||||
self.is_dirty = true
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
function M:create_from_prefab(widget_class, template, nodes)
|
||||
return self:add_property(self.druid:new_widget(widget_class, template, nodes))
|
||||
end
|
||||
|
||||
|
||||
---@private
|
||||
function M:add_property(widget)
|
||||
gui.set_enabled(widget.root, true)
|
||||
table.insert(self.properties, widget)
|
||||
local width = self.layout:get_content_size()
|
||||
widget.container:set_size(width)
|
||||
|
||||
self.layout:add(widget.root)
|
||||
|
||||
return widget
|
||||
end
|
||||
|
||||
|
||||
function M:remove(widget)
|
||||
for index = 1, #self.properties do
|
||||
if self.properties[index] == widget then
|
||||
self.druid:remove(widget)
|
||||
self.layout:remove(widget.root)
|
||||
|
||||
-- If prefab used clone nodes we can remove it
|
||||
if widget:get_nodes() then
|
||||
gui.delete_node(widget.root)
|
||||
else
|
||||
-- Probably we have component placed on scene directly
|
||||
gui.set_enabled(widget.root, false)
|
||||
end
|
||||
|
||||
table.remove(self.properties, index)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M:toggle_hide()
|
||||
self.is_hidden = not self.is_hidden
|
||||
local hidden_size = gui.get_size(self:get_node("header"))
|
||||
|
||||
local new_size = self.is_hidden and hidden_size or self.default_size
|
||||
self.container:set_size(new_size.x, new_size.y, gui.PIVOT_N)
|
||||
|
||||
gui.set_enabled(self.content, not self.is_hidden)
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
---@param properties_per_page number
|
||||
function M:set_properties_per_page(properties_per_page)
|
||||
self.properties_per_page = properties_per_page
|
||||
end
|
||||
|
||||
|
||||
function M:set_page(page)
|
||||
self.current_page = page
|
||||
self.is_dirty = true
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
@@ -1,4 +1,3 @@
|
||||
script: ""
|
||||
fonts {
|
||||
name: "text_regular"
|
||||
font: "/example/assets/fonts/text_regular.font"
|
||||
@@ -8,434 +7,129 @@ textures {
|
||||
texture: "/example/assets/druid_logo.atlas"
|
||||
}
|
||||
textures {
|
||||
name: "druid"
|
||||
texture: "/example/assets/druid.atlas"
|
||||
}
|
||||
background_color {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
name: "druid_example"
|
||||
texture: "/example/assets/druid_example.atlas"
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 170.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 0.129
|
||||
y: 0.141
|
||||
z: 0.157
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "druid/pixel"
|
||||
texture: "druid_example/pixel"
|
||||
id: "root"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
layer: "druid"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_STENCIL
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 200.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 16.0
|
||||
y: 16.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: ""
|
||||
id: "E_Anchor"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_E
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "root"
|
||||
layer: ""
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: false
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 10.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "druid_logo/icon_druid"
|
||||
id: "icon_druid_right"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
parent: "E_Anchor"
|
||||
layer: "druid_logo"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 0.5
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -200.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 16.0
|
||||
y: 16.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: ""
|
||||
id: "W_Anchor"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_W
|
||||
adjust_mode: ADJUST_MODE_STRETCH
|
||||
parent: "root"
|
||||
layer: ""
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_MANUAL
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: false
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: -10.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "druid_logo/icon_druid"
|
||||
id: "icon_druid_left"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
parent: "W_Anchor"
|
||||
layer: "druid_logo"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 0.5
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 200.0
|
||||
y: 100.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_BOX
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
texture: "druid_logo/logo_druid"
|
||||
id: "icon_logo"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
parent: "root"
|
||||
layer: "druid_logo"
|
||||
inherit_alpha: true
|
||||
slice9 {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 0.0
|
||||
}
|
||||
clipping_mode: CLIPPING_MODE_NONE
|
||||
clipping_visible: true
|
||||
clipping_inverted: false
|
||||
alpha: 1.0
|
||||
template_node_child: false
|
||||
size_mode: SIZE_MODE_AUTO
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
nodes {
|
||||
position {
|
||||
x: 0.0
|
||||
y: -50.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
rotation {
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
scale {
|
||||
x: 0.6
|
||||
y: 0.6
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
size {
|
||||
x: 400.0
|
||||
y: 50.0
|
||||
z: 0.0
|
||||
w: 1.0
|
||||
}
|
||||
color {
|
||||
x: 0.463
|
||||
y: 0.475
|
||||
z: 0.49
|
||||
w: 1.0
|
||||
}
|
||||
type: TYPE_TEXT
|
||||
blend_mode: BLEND_MODE_ALPHA
|
||||
text: "Defold UI Framework"
|
||||
font: "text_regular"
|
||||
id: "text_description"
|
||||
xanchor: XANCHOR_NONE
|
||||
yanchor: YANCHOR_NONE
|
||||
pivot: PIVOT_CENTER
|
||||
outline {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
shadow {
|
||||
x: 1.0
|
||||
y: 1.0
|
||||
z: 1.0
|
||||
w: 1.0
|
||||
}
|
||||
adjust_mode: ADJUST_MODE_FIT
|
||||
line_break: false
|
||||
parent: "root"
|
||||
layer: "text_regular"
|
||||
inherit_alpha: true
|
||||
alpha: 1.0
|
||||
outline_alpha: 0.0
|
||||
shadow_alpha: 0.0
|
||||
template_node_child: false
|
||||
text_leading: 1.0
|
||||
text_tracking: 0.0
|
||||
custom_type: 0
|
||||
enabled: true
|
||||
visible: true
|
||||
material: ""
|
||||
}
|
||||
layers {
|
||||
name: "druid"
|
||||
@@ -448,4 +142,3 @@ layers {
|
||||
}
|
||||
material: "/builtins/materials/gui.material"
|
||||
adjust_reference: ADJUST_REFERENCE_PARENT
|
||||
max_nodes: 512
|
||||
|
||||
@@ -2,7 +2,6 @@ local panthera = require("panthera.panthera")
|
||||
local component = require("druid.component")
|
||||
|
||||
local druid_logo_panthera = require("example.components.druid_logo.druid_logo_panthera")
|
||||
local container = require("example.components.container.container")
|
||||
|
||||
---@class druid_logo: druid.base_component
|
||||
---@field root druid.container
|
||||
@@ -16,7 +15,7 @@ local DruidLogo = component.create("druid_logo")
|
||||
function DruidLogo:init(template, nodes)
|
||||
self.druid = self:get_druid(template, nodes)
|
||||
|
||||
self.root = self.druid:new(container, "root") --[[@as druid.container]]
|
||||
self.root = self.druid:new_container("root") --[[@as druid.container]]
|
||||
self.root:add_container("E_Anchor")
|
||||
self.root:add_container("W_Anchor")
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user