mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
f7f589626c
commit
795ff8725c
13 changed files with 145 additions and 314 deletions
|
|
@ -15,6 +15,14 @@ to maintain. Vide achieves this using a reactive style of programming which
|
||||||
allows you to focus on the flow of data through your application without
|
allows you to focus on the flow of data through your application without
|
||||||
worrying about manually updating UI instances.
|
worrying about manually updating UI instances.
|
||||||
|
|
||||||
|
Some of the main focuses behind Vide's design choices:
|
||||||
|
|
||||||
|
- Concise syntax to reduce verbosity as much as possible.
|
||||||
|
- Reducing the amount of imports needed for usage by using Luau's syntax and
|
||||||
|
semantics.
|
||||||
|
- Being completely typecheckable.
|
||||||
|
- Flexibility, particularly with integrating other libraries.
|
||||||
|
|
||||||
## Creating UI Instances
|
## Creating UI Instances
|
||||||
|
|
||||||
Instances are created using [`create()`](../api/creation#create).
|
Instances are created using [`create()`](../api/creation#create).
|
||||||
|
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
# Vide Reactive Graph
|
|
||||||
|
|
||||||
Details on how Vide's reactive graph works.
|
|
||||||
|
|
||||||
## Nodes
|
|
||||||
|
|
||||||
A "node" refers to a point on the reactive graph.
|
|
||||||
|
|
||||||
- Nodes can have parents and children.
|
|
||||||
- Updating a node will mark all descendant nodes for update.
|
|
||||||
- Each Vide state object acts as a node on the reactive graph.
|
|
||||||
|
|
||||||
Vide's reactive graph uses a *lazy evaluation* model, meaning that
|
|
||||||
if a node with children is updated, the new value for the child node
|
|
||||||
is not recalculated immediately. Only when something attempts to access
|
|
||||||
the child's value is it recalculated.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
Below is an (*overengineered*) example to demonstrate how the reactive graph functions.
|
|
||||||
States are used here to model the various transforms done on two inputs, `health` and `maxHealth`
|
|
||||||
to represent player health for UI.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local health = wrap(90)
|
|
||||||
local maxHealth = wrap(100)
|
|
||||||
|
|
||||||
local healthTweened = spring(health, 0.5)
|
|
||||||
local text = "Health: " .. healthTweened
|
|
||||||
|
|
||||||
local ratio = health / maxHealth
|
|
||||||
|
|
||||||
local barSize = derive(function(from)
|
|
||||||
return UDim2.fromScale(from(ratio), 1)
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Below is a graphical representation of the reactive graph formed by the above code.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A(( ))
|
|
||||||
B(( ))
|
|
||||||
A --> health
|
|
||||||
B --> maxHealth
|
|
||||||
|
|
||||||
health --> healthTweened
|
|
||||||
healthTweened --> text
|
|
||||||
|
|
||||||
health --> ratio
|
|
||||||
maxHealth --> ratio
|
|
||||||
ratio --> barSize
|
|
||||||
```
|
|
||||||
|
|
||||||
When states are initially derived, all values are known.
|
|
||||||
|
|
||||||
Say if the player is damaged, and the `health` node changes value.
|
|
||||||
All descendant nodes from `health` will be marked as updated.
|
|
||||||
The nodes marked as updated are represented by the broken lines below.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A(( ))
|
|
||||||
B(( ))
|
|
||||||
A --> health
|
|
||||||
B --> maxHealth
|
|
||||||
|
|
||||||
health .-x healthTweened
|
|
||||||
healthTweened .-x text
|
|
||||||
|
|
||||||
health .-x ratio
|
|
||||||
maxHealth --> ratio
|
|
||||||
ratio .-x barSize
|
|
||||||
```
|
|
||||||
|
|
||||||
When something tries to read the value of the node `text`, a recalculation occurs.
|
|
||||||
While `text` is being recalculated, `healthTweened` will be read from, causing it to be recalculated as well.
|
|
||||||
This results in a chain that propogates up the reactive graph until all ancestors are up to date.
|
|
||||||
|
|
||||||
Below is what the graph will look like after `text` has been recalculated.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
A(( ))
|
|
||||||
B(( ))
|
|
||||||
A --> health
|
|
||||||
B --> maxHealth
|
|
||||||
|
|
||||||
health --> healthTweened
|
|
||||||
healthTweened --> text
|
|
||||||
|
|
||||||
health .-x ratio
|
|
||||||
maxHealth --> ratio
|
|
||||||
ratio .-x barSize
|
|
||||||
```
|
|
||||||
|
|
||||||
Lazy evaluation is a useful model as it saves unecessary calculation, only calculating when needed.
|
|
||||||
|
|
||||||
Looking at stateful code as a reactive graph is a good way to mentally picture how your data maps to UI.
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
```lua
|
|
||||||
local function Text(args)
|
|
||||||
return create("TextLabel") {
|
|
||||||
[Layout] = {
|
|
||||||
Size = scale(1),
|
|
||||||
args[Layout]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
Text {
|
|
||||||
[Layout] = {
|
|
||||||
Position = scale(0.5, 0.1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
|
||||||
a
|
|
||||||
```
|
|
||||||
25
src/action.luau
Normal file
25
src/action.luau
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
type Action = {
|
||||||
|
priority: number,
|
||||||
|
callback: (Instance) -> ()
|
||||||
|
}
|
||||||
|
|
||||||
|
local ActionMT = {}
|
||||||
|
|
||||||
|
local function is_action(v: any)
|
||||||
|
return getmetatable(v) == ActionMT
|
||||||
|
end
|
||||||
|
|
||||||
|
local function action(callback: (Instance) -> (), priority: number?): Action
|
||||||
|
local t = {
|
||||||
|
priority = priority or 1,
|
||||||
|
callback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
setmetatable(t :: any, ActionMT)
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
return function()
|
||||||
|
return action, is_action
|
||||||
|
end
|
||||||
|
|
@ -8,15 +8,30 @@ type Node<T> = graph.Node<T>
|
||||||
|
|
||||||
local throw = require(script.Parent.throw)
|
local throw = require(script.Parent.throw)
|
||||||
local bind = require(script.Parent.bind)
|
local bind = require(script.Parent.bind)
|
||||||
|
local _, is_action = require(script.Parent.action)()
|
||||||
|
|
||||||
local function recurse(instance: Instance, properties: { [unknown]: unknown }, event_buffer)
|
local event_buffer: { [string]: () -> () } = {}
|
||||||
|
local action_buffers = {} :: { { () -> () } }
|
||||||
|
|
||||||
|
setmetatable(action_buffers :: any, {
|
||||||
|
__index = function(_, i: number)
|
||||||
|
action_buffers[i] = {}
|
||||||
|
return action_buffers[i]
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
local function recurse(instance: Instance, properties: { [unknown]: unknown })
|
||||||
for property, value in properties do
|
for property, value in properties do
|
||||||
if type(value) == "table" then
|
if type(value) == "table" then
|
||||||
recurse(instance, value :: {}, event_buffer)
|
if is_action(value) then
|
||||||
|
table.insert(action_buffers[(value :: any).priority], (value :: any).callback :: () -> ())
|
||||||
|
else
|
||||||
|
recurse(instance, value :: {})
|
||||||
|
end
|
||||||
elseif type(property) == "string" then
|
elseif type(property) == "string" then
|
||||||
if type(value) == "function" then
|
if type(value) == "function" then
|
||||||
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
|
if typeof((instance :: any)[property]) == "RBXScriptSignal" then
|
||||||
event_buffer[property] = value
|
event_buffer[property] = value :: () -> ()
|
||||||
else
|
else
|
||||||
bind.property(instance, property, value :: () -> ())
|
bind.property(instance, property, value :: () -> ())
|
||||||
end
|
end
|
||||||
|
|
@ -37,14 +52,23 @@ local function apply<T>(instance: T & Instance, properties: { [unknown]: unknown
|
||||||
local parent: unknown = properties.Parent
|
local parent: unknown = properties.Parent
|
||||||
if parent then properties.Parent = nil end
|
if parent then properties.Parent = nil end
|
||||||
|
|
||||||
local event_buffer: { [string]: () -> () } = {} -- connect events after setting properties
|
table.clear(event_buffer)
|
||||||
|
for _, buffer in next, action_buffers do
|
||||||
|
table.clear(buffer)
|
||||||
|
end
|
||||||
|
|
||||||
recurse(instance, properties, event_buffer)
|
recurse(instance, properties)
|
||||||
|
|
||||||
for event, fn in next, event_buffer do
|
for event, fn in next, event_buffer do
|
||||||
(instance :: any)[event]:Connect(fn)
|
(instance :: any)[event]:Connect(fn)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for _, buffer in next, action_buffers do
|
||||||
|
for _, callback in next, buffer do
|
||||||
|
callback()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if parent then
|
if parent then
|
||||||
if type(parent) == "function" then
|
if type(parent) == "function" then
|
||||||
error("cannot set parent to state")
|
error("cannot set parent to state")
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,11 @@ local get = graph.get
|
||||||
local capture_and_link = graph.capture_and_link
|
local capture_and_link = graph.capture_and_link
|
||||||
|
|
||||||
local function derive<T>(fn: () -> T): () -> T
|
local function derive<T>(fn: () -> T): () -> T
|
||||||
local node = create((nil :: any) :: T)
|
local node, node_get = create((nil :: any) :: T)
|
||||||
|
|
||||||
node.cache = capture_and_link(node, fn)
|
node.cache = capture_and_link(node, fn)
|
||||||
|
|
||||||
return function()
|
return node_get
|
||||||
return get(node)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return derive
|
return derive
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
--------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
-- vide.luau
|
-- vide.luau
|
||||||
-- v0.1.0
|
-- v0.1.0
|
||||||
--------------------------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
if not game then script = (require :: any) "test/wrap-require" end
|
if not game then script = (require :: any) "test/wrap-require" end
|
||||||
|
|
||||||
|
|
@ -12,12 +12,10 @@ local cleanup, clean_garbage = require(script.cleanup)()
|
||||||
local derive = require(script.derive)
|
local derive = require(script.derive)
|
||||||
local map = require(script.map)
|
local map = require(script.map)
|
||||||
local spring, update_springs = require(script.spring)()
|
local spring, update_springs = require(script.spring)()
|
||||||
|
local action = require(script.action)()
|
||||||
|
|
||||||
local flags = require(script.flags)
|
local flags = require(script.flags)
|
||||||
|
|
||||||
type Map<K, V> = { [K]: V }
|
|
||||||
type Setter<T> = ( (new: T, force: true?) -> T ) & ( (update: (old: T) -> T, force: true?) -> T )
|
|
||||||
|
|
||||||
local vide = {
|
local vide = {
|
||||||
-- core
|
-- core
|
||||||
create = create,
|
create = create,
|
||||||
|
|
@ -30,17 +28,13 @@ local vide = {
|
||||||
-- animations
|
-- animations
|
||||||
spring = spring,
|
spring = spring,
|
||||||
|
|
||||||
-- symbols
|
-- actions
|
||||||
-- Event = Event,
|
action = action,
|
||||||
-- Changed = Changed,
|
|
||||||
-- Layout = Layout,
|
|
||||||
-- Children = Children,
|
|
||||||
-- Created = Created,
|
|
||||||
|
|
||||||
-- flags
|
-- flags
|
||||||
strict = (nil :: any) :: boolean,
|
strict = (nil :: any) :: boolean,
|
||||||
|
|
||||||
-- test
|
-- runtime
|
||||||
step = function(dt: number)
|
step = function(dt: number)
|
||||||
update_springs(dt)
|
update_springs(dt)
|
||||||
clean_garbage()
|
clean_garbage()
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ local function map<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K)
|
||||||
return recompute(input())
|
return recompute(input())
|
||||||
end
|
end
|
||||||
|
|
||||||
local output, get_output = create(output_cache)
|
local output, output_get = create(output_cache)
|
||||||
|
|
||||||
local nodes, value = capture(input)
|
local nodes, value = capture(input)
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ local function map<K, VI, VO>(input: () -> Map<K, VI>, transform: (() -> VI, K)
|
||||||
|
|
||||||
output.cache = recompute(value)
|
output.cache = recompute(value)
|
||||||
|
|
||||||
return get_output
|
return output_get
|
||||||
end
|
end
|
||||||
|
|
||||||
return map
|
return map
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ if not game then script = require "test/wrap-require" end
|
||||||
local graph = require(script.Parent.graph)
|
local graph = require(script.Parent.graph)
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
local create = graph.create
|
local create = graph.create
|
||||||
local get = graph.get
|
|
||||||
local set = graph.set
|
local set = graph.set
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
if not game then script = (require :: any) "test/wrap-require" end
|
if not game then script = (require :: any) "test/wrap-require" end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Spring animation library adapted from RDL::spring v1.0
|
|
||||||
|
|
||||||
Supported datatypes:
|
Supported datatypes:
|
||||||
*number
|
*number
|
||||||
!bool
|
!bool
|
||||||
*CFrame
|
*CFrame
|
||||||
?Rect
|
?Rect
|
||||||
*Color3
|
*Color3
|
||||||
*UDim
|
*UDim
|
||||||
*UDim2
|
*UDim2
|
||||||
*Vector2
|
*Vector2
|
||||||
!Vector2int16
|
!Vector2int16
|
||||||
*Vector3
|
*Vector3
|
||||||
!Vector3int16
|
!Vector3int16
|
||||||
!EnumItem
|
!EnumItem
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local throw = require(script.Parent.throw)
|
local throw = require(script.Parent.throw)
|
||||||
|
|
@ -24,6 +24,8 @@ local graph = require(script.Parent.graph)
|
||||||
local create = graph.create
|
local create = graph.create
|
||||||
local get = graph.get
|
local get = graph.get
|
||||||
local set = graph.set
|
local set = graph.set
|
||||||
|
local set_effect = graph.set_effect
|
||||||
|
local capture = graph.capture
|
||||||
|
|
||||||
type Node<T> = graph.Node<T>
|
type Node<T> = graph.Node<T>
|
||||||
|
|
||||||
|
|
@ -38,6 +40,7 @@ type SpringData<T> = {
|
||||||
initial_velocity: number,
|
initial_velocity: number,
|
||||||
initial_position: T,
|
initial_position: T,
|
||||||
target_position: T,
|
target_position: T,
|
||||||
|
target_updated: boolean,
|
||||||
target: () -> T
|
target: () -> T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,8 +93,9 @@ local springs: { [SpringData<any>]: Node<any> } = {}
|
||||||
setmetatable(springs, { __mode = "vs" })
|
setmetatable(springs, { __mode = "vs" })
|
||||||
|
|
||||||
local function spring<T>(target: () -> T, period: number?, damping_ratio: number?): () -> T
|
local function spring<T>(target: () -> T, period: number?, damping_ratio: number?): () -> T
|
||||||
local initial_position = target()
|
local inputs, initial_position = capture(target)
|
||||||
local node = create(initial_position)
|
|
||||||
|
local output, output_get = create(initial_position)
|
||||||
|
|
||||||
local data: SpringData<T> = {
|
local data: SpringData<T> = {
|
||||||
alpha = 0,
|
alpha = 0,
|
||||||
|
|
@ -102,19 +106,28 @@ local function spring<T>(target: () -> T, period: number?, damping_ratio: number
|
||||||
initial_velocity = 0,
|
initial_velocity = 0,
|
||||||
initial_position = initial_position,
|
initial_position = initial_position,
|
||||||
target_position = initial_position,
|
target_position = initial_position,
|
||||||
|
target_updated = false,
|
||||||
target = target
|
target = target
|
||||||
}
|
}
|
||||||
|
|
||||||
springs[data] = node
|
local function input_changed()
|
||||||
|
data.target_updated = true
|
||||||
return function()
|
data.target_position = target()
|
||||||
return get(node)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for _, input in next, inputs do
|
||||||
|
set_effect(input, input_changed, output)
|
||||||
|
end
|
||||||
|
|
||||||
|
springs[data] = output
|
||||||
|
|
||||||
|
return output_get
|
||||||
end
|
end
|
||||||
|
|
||||||
local function update_springs(dt: number)
|
local function update_springs(dt: number)
|
||||||
for data, output in next, springs do
|
for data, output in next, springs do
|
||||||
if data.target() ~= data.target_position then
|
if data.target_updated then
|
||||||
|
data.target_updated = false
|
||||||
data.target = data.target()
|
data.target = data.target()
|
||||||
data.initial_position = get(output)
|
data.initial_position = get(output)
|
||||||
data.alpha = 0
|
data.alpha = 0
|
||||||
|
|
@ -122,9 +135,9 @@ local function update_springs(dt: number)
|
||||||
data.initial_velocity = data.velocity
|
data.initial_velocity = data.velocity
|
||||||
end
|
end
|
||||||
|
|
||||||
local initial_position: Animatable = data.initial_position
|
local initial_position = data.initial_position
|
||||||
local target_position: Animatable = data.target_position
|
local target_position = data.target_position
|
||||||
local target_type: string = typeof(target_position)
|
local target_type = typeof(target_position)
|
||||||
|
|
||||||
if target_type ~= typeof(initial_position) then
|
if target_type ~= typeof(initial_position) then
|
||||||
springs[data] = nil
|
springs[data] = nil
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
local vide = require "src/init"
|
|
||||||
local source = vide.source
|
|
||||||
local derive = vide.derive
|
|
||||||
local map = vide.map
|
|
||||||
local create = vide.create
|
|
||||||
|
|
||||||
type Action<T> = {
|
|
||||||
type: T,
|
|
||||||
priority: number,
|
|
||||||
callback: (Instance) -> ()
|
|
||||||
}
|
|
||||||
|
|
||||||
local function processor(priority: number, fn: (Instance) -> ()): Action<any>
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local function Changed(property: string, callback: () -> ())
|
|
||||||
return processor(1, function(instance)
|
|
||||||
instance:GetPropertyChangedSignal(property):Connect(callback)
|
|
||||||
end) :: Action<"Changed">
|
|
||||||
end
|
|
||||||
|
|
||||||
local function Cleanup(t: {})
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function TextInput(p: {
|
|
||||||
OnInput: Action<"Changed">
|
|
||||||
})
|
|
||||||
create "TextBox" {
|
|
||||||
Text = "test",
|
|
||||||
|
|
||||||
Changed("Text", function()
|
|
||||||
|
|
||||||
end),
|
|
||||||
|
|
||||||
Cleanup {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
create("Frame") {}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
@ -1031,9 +1031,9 @@ TEST("spring()", function()
|
||||||
do CASE "Garbage collection"
|
do CASE "Garbage collection"
|
||||||
do -- `output` should not allow gc of `input`
|
do -- `output` should not allow gc of `input`
|
||||||
local input = source(10)
|
local input = source(10)
|
||||||
local output = spring(input)
|
local _output = spring(input)
|
||||||
|
|
||||||
local wref = { input }
|
local wref = weak { input }
|
||||||
input = nil :: any
|
input = nil :: any
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
|
|
@ -1044,7 +1044,7 @@ TEST("spring()", function()
|
||||||
local input = source(10)
|
local input = source(10)
|
||||||
local output = spring(input)
|
local output = spring(input)
|
||||||
|
|
||||||
local wref = { output }
|
local wref = weak { output }
|
||||||
output = nil :: any
|
output = nil :: any
|
||||||
|
|
||||||
gc()
|
gc()
|
||||||
|
|
@ -1099,76 +1099,38 @@ TEST("Events", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--[[
|
TEST("actions", function()
|
||||||
TEST("Changed", function()
|
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local Changed = vide.Changed
|
local action = vide.action
|
||||||
local source = vide.source
|
|
||||||
|
|
||||||
do CASE "Connects event"
|
do CASE "Run action"
|
||||||
local connected = false
|
|
||||||
local label = create "TextLabel" {
|
|
||||||
[Changed.Text] = function(text)
|
|
||||||
CHECK(text == "hi")
|
|
||||||
connected = true
|
|
||||||
end
|
|
||||||
}
|
|
||||||
CHECK(not connected)
|
|
||||||
label.Text = "hi"
|
|
||||||
CHECK(connected)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Bind connection to state"
|
|
||||||
local countA = 0
|
|
||||||
local countB = 0
|
|
||||||
local listener, set = source(function() countA += 1 end :: () -> ()?)
|
|
||||||
|
|
||||||
|
|
||||||
local label = create "TextLabel" {
|
|
||||||
[Changed.Text] = listener
|
|
||||||
}
|
|
||||||
|
|
||||||
label.Text = "1"
|
|
||||||
label.Text = "2"
|
|
||||||
CHECK(countA == 2)
|
|
||||||
set(function() return function() countB += 1 end end)
|
|
||||||
label.Text = "3"
|
|
||||||
label.Text = "4"
|
|
||||||
CHECK(countA == 2)
|
|
||||||
CHECK(countB == 2)
|
|
||||||
set(nil)
|
|
||||||
label.Text = "5"
|
|
||||||
CHECK(countA == 2)
|
|
||||||
CHECK(countB == 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Always return same object for a given index"
|
|
||||||
CHECK(Changed.Test == Changed.Test)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
]]
|
|
||||||
|
|
||||||
--[[
|
|
||||||
TEST("Created", function()
|
|
||||||
local create = vide.create
|
|
||||||
local Created = vide.Created
|
|
||||||
|
|
||||||
do CASE "Run after instance creation"
|
|
||||||
local ran = false
|
local ran = false
|
||||||
|
|
||||||
create "Frame" {
|
create "Frame" {
|
||||||
A = true,
|
action(function(self)
|
||||||
B = true,
|
|
||||||
C = true,
|
|
||||||
[Created] = function(self)
|
|
||||||
ran = true
|
ran = true
|
||||||
CHECK(self.A and self.B and self.C)
|
end, 1)
|
||||||
end
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CHECK(ran)
|
CHECK(ran)
|
||||||
end
|
end
|
||||||
end)]]
|
|
||||||
|
do CASE "Priorities"
|
||||||
|
local queue = {}
|
||||||
|
|
||||||
|
create "Frame" {
|
||||||
|
action(function(self)
|
||||||
|
table.insert(queue, 2)
|
||||||
|
end, 2),
|
||||||
|
|
||||||
|
action(function(self)
|
||||||
|
table.insert(queue, 1)
|
||||||
|
end, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(testkit.seq(queue, { 1, 2 }))
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
TEST("strict", function()
|
TEST("strict", function()
|
||||||
vide.strict = true
|
vide.strict = true
|
||||||
|
|
|
||||||
52
todo.md
52
todo.md
|
|
@ -1,43 +1,13 @@
|
||||||
```lua
|
# todo
|
||||||
function TextInput(p: {
|
|
||||||
DefaultText: string,
|
|
||||||
Output: (string) -> ()
|
|
||||||
} & Layout)
|
|
||||||
return create "TextBox" {
|
|
||||||
Layout = p.Layout,
|
|
||||||
Children = p.Children
|
|
||||||
|
|
||||||
BackgroundText = DefaultText,
|
- Implement from solid
|
||||||
|
- onCleanup
|
||||||
[{"Changed"}] = function(self)
|
- Index
|
||||||
p.Output(self.Text)
|
- For
|
||||||
end
|
- untrack
|
||||||
}
|
- batch
|
||||||
end
|
- async/loading/suspense
|
||||||
|
- define order with nested properties
|
||||||
function Counter()
|
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
Text = count
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
source
|
|
||||||
derive
|
|
||||||
map
|
|
||||||
|
|
||||||
spring
|
|
||||||
```
|
|
||||||
|
|
||||||
onCleanup
|
|
||||||
Index
|
|
||||||
For
|
|
||||||
untrack
|
|
||||||
batch
|
|
||||||
async/loading/suspense
|
|
||||||
|
|
||||||
define order with nested properties
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
type Action<T> = {
|
type Action<T> = {
|
||||||
|
|
@ -50,7 +20,7 @@ local function action(priority: number, fn: (Instance) -> ()): Action
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function Changed(property: string, callback: () -> ())
|
local function changed(property: string, callback: () -> ())
|
||||||
return action(1, function(instance)
|
return action(1, function(instance)
|
||||||
instance:GetPropertyChangedSignal(property):Connect(callback)
|
instance:GetPropertyChangedSignal(property):Connect(callback)
|
||||||
end) :: Action<"Changed">
|
end) :: Action<"Changed">
|
||||||
|
|
@ -59,7 +29,7 @@ end
|
||||||
create "TextBox" {
|
create "TextBox" {
|
||||||
Text = "test",
|
Text = "test",
|
||||||
|
|
||||||
Changed "Text" < function(self, data)
|
changed "Text" < function(self, data)
|
||||||
|
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue