mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
This commit is contained in:
parent
5057b7772b
commit
8e31946dda
9 changed files with 234 additions and 775 deletions
|
|
@ -1,56 +1,39 @@
|
||||||
# Animation API
|
# Animation API
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## spring()
|
## spring()
|
||||||
|
|
||||||
Returns a new state with an animated value of the original.
|
Returns a new state with a dynamically animated value of the source.
|
||||||
|
|
||||||
### Type
|
- ### Type
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function spring<T>(state: State<T>, period: number, dampingRatio: number = 1): State<T>
|
function spring<T>(
|
||||||
```
|
source: () -> T & Animatable,
|
||||||
|
period: number = 1,
|
||||||
|
damping_ratio: number = 1
|
||||||
|
): () -> T
|
||||||
|
|
||||||
### Details
|
type Animatable = number | CFrame | Color3 | UDim | UDim2 | Vector2 | Vector3
|
||||||
|
```
|
||||||
|
|
||||||
The output state's value is updated every frame based on the current input state's value.
|
- ### Details
|
||||||
|
|
||||||
The change is physically simulated according to a [spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
|
The output state value is updated every frame based on the source state
|
||||||
|
value.
|
||||||
|
|
||||||
`period` is the amount of time in seconds it takes for the spring to complete one full cycle
|
The change is physically simulated according to a
|
||||||
|
[spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
|
||||||
|
|
||||||
`dampingRatio` is relared to the amount of resistant force applied to the spring.
|
`period` is the amount of time in seconds it takes for the spring to
|
||||||
|
complete one full oscillation.
|
||||||
|
|
||||||
- \>1 = Overdamped (Not currently supported)
|
`damping_ratio` is the amount of resistance applied to the spring.
|
||||||
- 1 = Critically damped
|
|
||||||
- <1 = Underdamped
|
|
||||||
- 0 = Undamped
|
|
||||||
|
|
||||||
Velocity is conserved between input state updates for smooth animation.
|
- \>1 = Overdamped (not currently supported).
|
||||||
|
- 1 = Critically damped - reaches target without any overshoot.
|
||||||
|
- <1 = Underdamped - reaches target with some overshoot.
|
||||||
|
- 0 = Undamped - never stabilizes, oscillates forever.
|
||||||
|
|
||||||
### Example
|
Velocity is conserved between source state updates for smooth animation.
|
||||||
|
|
||||||
```lua
|
--------------------------------------------------------------------------------
|
||||||
local state = wrap(1)
|
|
||||||
|
|
||||||
local springed = spring(state, 1, 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
<details><summary>Example of an animated counter</summary>
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local count = wrap(1000)
|
|
||||||
|
|
||||||
local function Counter(props)
|
|
||||||
local tweenedCount = spring(count, 0.5, 1)
|
|
||||||
|
|
||||||
return create("TextLabel") {
|
|
||||||
Text = "Count: " .. tweenedCount
|
|
||||||
}
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
@ -6,320 +6,76 @@
|
||||||
|
|
||||||
Creates a new UI element, applying any given properties.
|
Creates a new UI element, applying any given properties.
|
||||||
|
|
||||||
### Type
|
- ### Type
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function create(classNameOrInstance: string | Instance): (properties: Map<string, any>) -> Instance
|
function create(class: string): (Properties) -> Instance
|
||||||
```
|
function create(instance: Instace): (Properties) -> Instance
|
||||||
|
|
||||||
### Details
|
type Properties = Map<string|number, any>
|
||||||
|
```
|
||||||
|
|
||||||
The function can take either a `string` or an `Instance` as its first argument.
|
- ### Details
|
||||||
|
|
||||||
- If given a `string`, a new instance with the string class name will be created with default properties already applied.
|
The function can take either a `string` or an `Instance` as its first argument.
|
||||||
- If given an `Instance`, a new instance that is a clone of the given instance will be created.
|
|
||||||
|
|
||||||
This returns another function that is used to apply any properties to the new instance.
|
- If given a `string`, a new instance with the same class name will be created.
|
||||||
|
- If given an `Instance`, a new instance that is a clone of the given instance
|
||||||
|
will be created.
|
||||||
|
|
||||||
### Example
|
This returns another function that is used to apply any properties to the new
|
||||||
|
instance.
|
||||||
|
|
||||||
```lua
|
- ### Property setting rules
|
||||||
local frame = create("Frame") {
|
|
||||||
Name = "NewFrame",
|
|
||||||
Position = UDim2.fromScale(1, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
-- creates a clone of `frame` with new properties applied.
|
- If a table value is another table, that nested table is processed so that
|
||||||
local frame2 = create(frame) {
|
any properties inside that table are also applied to the instance just
|
||||||
Size = UDim2.fromOffset(50, 100)
|
like the outer table.
|
||||||
}
|
- If a table index is a string:
|
||||||
```
|
- If its value is a function then it will either bind that property to
|
||||||
|
a state or connect it if the property type is a `RBXScriptSignal`.
|
||||||
|
- If the value is not a function then the property will be set to that
|
||||||
|
value.
|
||||||
|
- If a table index is a number:
|
||||||
|
- If its value is a function then it will parent any instances returned by
|
||||||
|
that function as children.
|
||||||
|
- If its value is an instance then it will be parented to the instance.
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
- ### Example
|
||||||
|
|
||||||
<br/>
|
Basic element creation.
|
||||||
|
|
||||||
## apply()
|
```lua
|
||||||
|
local frame = create "Frame" {
|
||||||
Applies any given properties to a given instance.
|
Name = "NewFrame",
|
||||||
|
Position = UDim2.fromScale(1, 0)
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
function apply(instance: Instance): (properties: Map<string, any>) -> Instance
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
Applies properties in the same manner as `create` for already existing existances.
|
|
||||||
|
|
||||||
Can use symbols and bind state just like `create`.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local frame = Instance.new("Frame")
|
|
||||||
|
|
||||||
apply(frame) {
|
|
||||||
Position = UDim2.fromScale(1, 0)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Layout
|
|
||||||
|
|
||||||
Symbol used to pass layout properties to elements.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Layout = Symbol
|
|
||||||
|
|
||||||
type LayoutProps = {
|
|
||||||
[Symbol] = {
|
|
||||||
-- These are all properties considered to be "layout properties"
|
|
||||||
AnchorPoint: Prop<Vector2>?;
|
|
||||||
LayoutOrder: Prop<number>?;
|
|
||||||
Position: Prop<UDim2>?;
|
|
||||||
Rotation: Prop<number>?;
|
|
||||||
Size: Prop<UDim2>?;
|
|
||||||
SizeConstraint: Prop<Enum.SizeConstraint>?;
|
|
||||||
Visible: Prop<boolean>?;
|
|
||||||
ZIndex: Prop<number>?;
|
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
|
|
||||||
type Prop<T> = T | State<T>
|
A component using property nesting/grouping.
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
```lua
|
||||||
|
type Layout = {
|
||||||
The primary purpose of this symbol is to enable easy passthrough of layout properties
|
Layout = {
|
||||||
through user-defined component hierarchies.
|
Position: UDim2?,
|
||||||
|
Size: UDim2?,
|
||||||
It is recommended to only set layout properties using the `Layout` symbol when using
|
AnchorPoint: Vector2?
|
||||||
your own components.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local function BlackFrame(props)
|
|
||||||
return create("Frame") {
|
|
||||||
BackgroundColor3 = Color3.new(0, 0, 0),
|
|
||||||
[Layout] = props[Layout]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
BlackFrame {
|
|
||||||
[Layout] = {
|
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
|
||||||
Position = UDim2.fromScale(0.5, 0.5),
|
|
||||||
Size = UDim2.fromOffset(100, 50)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Children
|
|
||||||
|
|
||||||
Symbol used to pass child instances to elements.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Children = Symbol
|
|
||||||
|
|
||||||
type ChildrenProps = {
|
|
||||||
[Symbol] = ChildrenProp
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChildrenProp = Prop<Instance> | Array<ChildrenProp>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
This symbol is flexible in the way that children can be passed in the form of nested arrays.
|
|
||||||
|
|
||||||
Children can also be assigned using a state binding.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
create("Frame") {
|
|
||||||
-- all of the below are valid methods of assigning children
|
|
||||||
[Children] = create("TextLabel") {},
|
|
||||||
|
|
||||||
[Children] = {
|
|
||||||
create("TextLabel") {},
|
|
||||||
create("TextLabel") {},
|
|
||||||
},
|
|
||||||
|
|
||||||
[Children] = {
|
|
||||||
create("TextLabel") {},
|
|
||||||
{
|
|
||||||
create("TextLabel") {},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
type Children = {
|
||||||
|
Children = Array<Instance>
|
||||||
|
}
|
||||||
|
|
||||||
<br/>
|
function Background(props: Layout & Children & {
|
||||||
|
Color: Color3
|
||||||
## Event
|
})
|
||||||
|
return create "Frame" {
|
||||||
Symbol used to connect callbacks to instance events.
|
BackgroundColor3 = Color,
|
||||||
|
props.Layout,
|
||||||
### Type
|
props.Children
|
||||||
|
}
|
||||||
```lua
|
|
||||||
type Event = Map<string, Symbol>
|
|
||||||
|
|
||||||
type EventProps = {
|
|
||||||
[Symbol] = Prop<(...unknown) -> ()>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
The `Event` symbol can be indexed to get symbols to connect to specific events.
|
|
||||||
|
|
||||||
Event parameters are passed into the callback.
|
|
||||||
|
|
||||||
When callbacks are connected by binding to a state,
|
|
||||||
connections are automatically disconnected when the state changes.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
create("TextButton") {
|
|
||||||
[Event.Activated] = function()
|
|
||||||
print("Clicked")
|
|
||||||
end
|
end
|
||||||
}
|
```
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Changed
|
|
||||||
|
|
||||||
Symbol used to connect callbacks to instance property changed events.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Changed = Map<string, Symbol>
|
|
||||||
|
|
||||||
type ChangedProps = {
|
|
||||||
[Symbol] = Prop<(...unknown) -> ()>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
The `Changed` symbol can be indexed to get symbols to connect to specific events just like `Event`.
|
|
||||||
|
|
||||||
Event parameters are passed into the callback.
|
|
||||||
|
|
||||||
When callbacks are connected by binding to a state,
|
|
||||||
connections are automatically disconnected when the state changes.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
create("TextBox") {
|
|
||||||
[Changed.Text] = function()
|
|
||||||
print("New text entered")
|
|
||||||
end
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Bind
|
|
||||||
|
|
||||||
Symbol used to bind states to instance properties.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Bind = Map<string, Symbol>
|
|
||||||
|
|
||||||
type BindProps = {
|
|
||||||
[Symbol] = State<any>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
The `Bind` symbol can be indexed to bind specific properties just like `Event`.
|
|
||||||
|
|
||||||
Sets the given state value to the instance property value immediately after instance creation.
|
|
||||||
|
|
||||||
When an instance property is changed, the value of the given state will automatically
|
|
||||||
be set to the new property. Effectively a shorthand for connecting a property changed event
|
|
||||||
to set state values.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local text = wrap()
|
|
||||||
|
|
||||||
local box = create("TextBox") {
|
|
||||||
[Bind.Text] = text
|
|
||||||
}
|
|
||||||
|
|
||||||
box.Text = "New text"
|
|
||||||
print(text.Value) -- "New text"
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Created
|
|
||||||
|
|
||||||
Symbol used to run a callback once immediately after instance creation.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Created = Symbol
|
|
||||||
|
|
||||||
type CreatedProps = {
|
|
||||||
[Symbol] = (Instance) -> ()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
The instance being defined with the `Created` symbol is passed as the first
|
|
||||||
argument to the callback.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local frame
|
|
||||||
|
|
||||||
create("Frame") {
|
|
||||||
Name = "Background",
|
|
||||||
[Created] = function(instance)
|
|
||||||
frame = instance
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
||||||
print(frame.Name) -- "Background"
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
@ -2,248 +2,167 @@
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
## wrap()
|
## source()
|
||||||
|
|
||||||
Wraps and returns any given values with reactive state objects.
|
Creates a new source state with the given value.
|
||||||
|
|
||||||
### Type
|
- ### Type
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function wrap<T>(value: T): State<T>
|
function source<T>(value: T): (T?) -> T
|
||||||
function wrap(value: ...unknown): ...State<any>
|
```
|
||||||
|
|
||||||
type State<T> = {
|
- ### Details
|
||||||
Value: T,
|
|
||||||
value: T
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
Calling the returned state with no arguments will return its stored value,
|
||||||
|
calling with arguments will set a new value.
|
||||||
|
|
||||||
The state object has a single mutable field `.Value`.
|
Reading from the state from within any reactive scope will cause changes
|
||||||
|
to that state to be tracked and anything depending on it to update.
|
||||||
|
|
||||||
Read operations to `.Value` are tracked and write operations can trigger
|
- ### Example
|
||||||
dependency updates and watchers.
|
|
||||||
|
|
||||||
### Example
|
```lua
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
```lua
|
count() -- 0
|
||||||
local count = wrap(0)
|
|
||||||
|
|
||||||
print(count.Value) -- 0
|
count(count() + 1) -- 1
|
||||||
|
```
|
||||||
|
|
||||||
count.Value += 1
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
print(count.Value) -- 1
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## derive()
|
## derive()
|
||||||
|
|
||||||
Derives a new reactive state object from an existing state object.
|
Derives a new state from existing states.
|
||||||
|
|
||||||
### Type
|
- ### Type
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function derive<T>(
|
function derive<T>(source: () -> T): () -> T
|
||||||
(from) -> T,
|
```
|
||||||
cleanup: (value: T) -> ()?
|
|
||||||
): State<T>
|
|
||||||
|
|
||||||
function from<T>(T | State<T>): T
|
- ### Details
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
The derived state will have its value recalculated when any source state it
|
||||||
|
derives from is updated.
|
||||||
|
|
||||||
The derived state will have its value recalculated when any state it derives from is updated.
|
Anytime its value is recalculated it is also cached, subsequent calls will
|
||||||
|
retun this cached value until it recalculates again.
|
||||||
|
|
||||||
Takes a callback that is immediately run to determine what states are being referenced. Only states referenced in the immediate function scope can trigger updates.
|
Takes a callback that is immediately run to determine what states are being
|
||||||
|
referenced.
|
||||||
|
|
||||||
The state object returned by this function is readonly.
|
> ⚠️ Non-yielding.
|
||||||
|
|
||||||
Has an optional cleanup parameter which takes a function that is called with the old value any
|
- ### Example
|
||||||
time the derived state recalculates a value.
|
|
||||||
|
|
||||||
An optional utility function is passed as the first argument to the callback, if given a state, the value of the state will be returned (changes to this state still triggers updates unlike `unwrap`), if given a value the value is returned.
|
```lua
|
||||||
|
local count = wrap(0)
|
||||||
|
local text = derive(function() return `count: {count()}` end)
|
||||||
|
|
||||||
> ⚠️ The callback cannot yield.
|
text() -- "count: 0"
|
||||||
|
|
||||||
### Example
|
count(1)
|
||||||
|
|
||||||
```lua
|
text() -- "count: 1"
|
||||||
local count = wrap(0)
|
```
|
||||||
local text = derive(function() return "Count: "..count.Value end)
|
|
||||||
|
|
||||||
print(text.Value) -- "Count: 0"
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
count.Value += 1
|
## map()
|
||||||
|
|
||||||
print(text.Value) -- "Count: 1"
|
Maps each value in a table state to a new table state.
|
||||||
```
|
|
||||||
|
|
||||||
```lua
|
- ### Type
|
||||||
local count = wrap(0)
|
|
||||||
local text = derive(function(from)
|
|
||||||
return "Count: "..from(count)
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
A shorthand method for deriving states also exists, following example is equivalent to the above:
|
```lua
|
||||||
|
function map<KI, VI, VO>(
|
||||||
|
source: () -> Map<KI, VI>,
|
||||||
|
transform: (value: () -> VI, index: KI) -> VO
|
||||||
|
): Map<KI, VO>
|
||||||
|
|
||||||
```lua
|
- ### Details
|
||||||
local count = wrap(0)
|
|
||||||
local text = "Count: "..count -- all binary operators are supported
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
The transform function is called only ever *once* for each index in the
|
||||||
|
source table. The first argument is a state containing the index's value and
|
||||||
|
the second argument is just the index.
|
||||||
|
|
||||||
<br/>
|
Anytime a new index is added, the transform function will be called again for
|
||||||
|
that new index.
|
||||||
|
|
||||||
## foreach()
|
Anytime an existing index changes, the transform function is not rerun,
|
||||||
|
instead the passed state for that index will update, causing anything
|
||||||
|
depending on it to update too.
|
||||||
|
|
||||||
Derives a new state object from an existing state object.
|
Returns a state containing the mapped key-value pairs.
|
||||||
Designed to work specifically with table states.
|
|
||||||
|
|
||||||
Also works with non state tables.
|
> ⚠️ Non-yielding.
|
||||||
|
|
||||||
### Type
|
- ### Example
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function foreach<KO, VO>( -- number as first arg
|
type Item = {
|
||||||
i: number,
|
name: string,
|
||||||
transform: (key: number) -> (KO, VO),
|
icon: number
|
||||||
cleanup: (KO, VO) -> ()?
|
}
|
||||||
): Map<KO, VO>
|
|
||||||
|
|
||||||
function foreach<KI, KO, VI, VO>( -- table as first arg
|
local items = source {} :: () -> Array<Item>
|
||||||
table: Map<KI, VI>,
|
|
||||||
transform: (key: KI, value: VI) -> (KO, VO),
|
|
||||||
cleanup: (KO, VO) -> ()?
|
|
||||||
): Map<KO, VO>
|
|
||||||
|
|
||||||
function foreach<KI, KO, VI, VO>( -- state as first arg
|
local displays = map(numbers, function(item, i)
|
||||||
state: State<Map<KI, VI>>,
|
return ItemDisplay {
|
||||||
transform: (key: KI, value: VI) -> (KO, VO),
|
Name = function()
|
||||||
cleanup: (KO, VO) -> ()?
|
return item().name
|
||||||
): State<Map<KO, VO>>
|
end,
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
Image = function()
|
||||||
|
return "rbxassetid://" .. item().icon
|
||||||
|
end,
|
||||||
|
|
||||||
When the state being derived from is updated, the derived state will
|
LayoutOrder = i
|
||||||
recompute by applying its transform function to each key-value pair.
|
}
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
Will only be recomputed if the corresponding key differs between updates.
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
Has an optional cleanup function to cleanup the old key and value.
|
|
||||||
|
|
||||||
> ⚠️ The transform function cannot yield.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local numbers = wrap { 1, 2, 3 }
|
|
||||||
local plusOne = foreach(numbers, function(i, v)
|
|
||||||
return i, v + 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
print(plusOne.Value) -- { [1]: 2, [2]: 3, [3]: 4 }
|
|
||||||
|
|
||||||
-- note that assignment must take place to trigger reactive updates.
|
|
||||||
-- modifying the value without assignment `numbers.Value[2] = 5` will not trigger updates.
|
|
||||||
numbers.Value = { 1, 5, 3 }
|
|
||||||
|
|
||||||
print(plusOne.Value) -- { [1]: 2, [2]: 6, [3]: 4 }
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## match()
|
|
||||||
|
|
||||||
Derives a new state object from an existing state object.
|
|
||||||
Similar to switch statements in other languages.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
function match<K, V>(value: K): (transform: Map<K, V>) -> V
|
|
||||||
function Match<K, V>(state: State<K>): (transform: Map<K, V>) -> State<V>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
When the state being derived from is updated, the derived state will
|
|
||||||
recompute by using the input value as a key to map to an output value.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local state = wrap(true)
|
|
||||||
local matched = match(state) {
|
|
||||||
[true] = 1,
|
|
||||||
[false] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
print(matched.Value) -- 1
|
|
||||||
|
|
||||||
state.Value = false
|
|
||||||
|
|
||||||
print(matched.Value) -- 0
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## watch()
|
## watch()
|
||||||
|
|
||||||
Runs a callback on state change.
|
Runs a callback on state change.
|
||||||
|
|
||||||
### Type
|
- ### Type
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function watch(callback: () -> Cleanup?): Unwatch
|
function watch(callback: () -> ()): Unwatch
|
||||||
|
|
||||||
type Cleanup = () -> ()
|
type Unwatch = () -> ()
|
||||||
type Unwatch = () -> ()
|
```
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
- ### Details
|
||||||
|
|
||||||
The callback is ran immediately to determine what states to watch.
|
The callback is ran immediately to determine what states are referenced.
|
||||||
|
|
||||||
Any time a state read in the watch callback is changed, the watcher callback will be deferred
|
Any time a state referenced in the callback is changed, the callback will be
|
||||||
to the end of the resumption cycle and ran.
|
reran.
|
||||||
|
|
||||||
Only states in the immediate function scope can trigger the watch callback.
|
Also returns a function that when called, stops the watcher immediately.
|
||||||
|
|
||||||
Watchers are run *before* UI properties are updated.
|
> ⚠️ Non yielding.
|
||||||
|
|
||||||
The callback can return an optional cleanup function that is run each time the watcher is rerun.
|
- ### Example
|
||||||
|
|
||||||
Also returns a function that when called, stops the watcher immediately (also runs cleanup if any was given).
|
```lua
|
||||||
|
local state = wrap(1)
|
||||||
|
|
||||||
> ⚠️ The callback cannot yield.
|
watch(function()
|
||||||
|
print(state.Value)
|
||||||
|
end)
|
||||||
|
|
||||||
### Example
|
-- prints 1
|
||||||
|
|
||||||
```lua
|
state.Value += 1
|
||||||
local state = wrap(1)
|
|
||||||
|
|
||||||
watch(function()
|
-- prints 2
|
||||||
print(state.Value)
|
```
|
||||||
end)
|
|
||||||
|
|
||||||
-- prints 1
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
state.Value += 1
|
|
||||||
|
|
||||||
-- prints 2
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
@ -1,152 +1,27 @@
|
||||||
# Reactivity API: Utility
|
# Reactivity API: Utility
|
||||||
|
|
||||||
<br/>
|
## cleanup()
|
||||||
|
|
||||||
## isState()
|
Runs a callback anytime a reactive scope is re-ran.
|
||||||
|
|
||||||
Determines if a given value is a state object or not.
|
- ### Type
|
||||||
|
|
||||||
### Type
|
```lua
|
||||||
|
function cleanup(callback: () -> ())
|
||||||
|
```
|
||||||
|
|
||||||
```lua
|
- ### Example
|
||||||
function isState(value: unknown): boolean
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
```lua
|
||||||
|
local data = source(1)
|
||||||
|
|
||||||
```lua
|
watch(function()
|
||||||
local value = wrap()
|
local label = create "TextLabel" { Text = data }
|
||||||
|
|
||||||
print(isState(value)) -- true
|
cleanup(function()
|
||||||
|
label:Destroy()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
value = 0
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
print(isState(value)) -- false
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## unwrap()
|
|
||||||
|
|
||||||
Unwraps a state and returns its stored value.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
function unwrap<T>(value: T | State<T>): T
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
If given a state, the state's stored value will be returned.
|
|
||||||
|
|
||||||
Unwrapping a state within a derived callback will not trigger updates.
|
|
||||||
|
|
||||||
Can be given a non-state value, in which case the same value will just be returned.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local state = wrap(1)
|
|
||||||
|
|
||||||
print(unwrap(state)) -- 1
|
|
||||||
|
|
||||||
print(unwrap(1)) -- 1
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## readonly()
|
|
||||||
|
|
||||||
Creates a new derived state with the same value as the state being derived from.
|
|
||||||
|
|
||||||
Used to create readonly states.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
function readonly<T>(state: State<T>): State<T>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local count = wrap(1)
|
|
||||||
local read = readonly(count)
|
|
||||||
|
|
||||||
print(read.Value) -- 1
|
|
||||||
|
|
||||||
count.Value += 1
|
|
||||||
|
|
||||||
print(read.Value) -- 2
|
|
||||||
|
|
||||||
read.Value += 1 -- error
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## mutate()
|
|
||||||
|
|
||||||
Mutates a given state's value and updated any derived states.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
function mutate<T>(value: T | State<T>): T
|
|
||||||
```
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
Since states only update derived states if a new value is set (tables are compared by reference),
|
|
||||||
this function serves as a way to trigger derived state updates if a state's value is not changed but
|
|
||||||
instead mutated.
|
|
||||||
|
|
||||||
Can also take non-state as an argument.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local state = wrap { Count = 1 }
|
|
||||||
|
|
||||||
local derived = derive(function()
|
|
||||||
return state.Value.Count
|
|
||||||
end)
|
|
||||||
|
|
||||||
mutate(state, function(value)
|
|
||||||
value.Count += 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
print(derived.Value) -- 2
|
|
||||||
```
|
|
||||||
|
|
||||||
<details><summary>Motivation for this function</summary>
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local state = wrap { Count = 1 }
|
|
||||||
|
|
||||||
local derived = derive(function()
|
|
||||||
return state.Value.Count
|
|
||||||
end)
|
|
||||||
|
|
||||||
state.Value.Count += 1
|
|
||||||
|
|
||||||
print(derived.Value) -- still 1 because `state.Value` was never set with a new value so change wasn't detected
|
|
||||||
|
|
||||||
local value = state.Value
|
|
||||||
value.Count += 1
|
|
||||||
state.Value = value
|
|
||||||
|
|
||||||
print(derived.Value) -- still 1 because although `state.Value` was set, when the new value set was compared,
|
|
||||||
-- it was still the same as the previous (tables are compared by reference not their contents)
|
|
||||||
-- and so no update was made
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,27 @@
|
||||||
# Strict Mode
|
# Strict Mode
|
||||||
|
|
||||||
<br/>
|
Vide has a special mode called "strict mode" which is used for debugging.
|
||||||
|
|
||||||
## strict
|
The purpose of strict mode is to help ensure stateful code is *pure*
|
||||||
|
(deterministic and free from side effects) or if there are side-effects, that
|
||||||
|
they are cleaned up correctly.
|
||||||
|
|
||||||
A flag that users can set to enable or disable strict mode (disabled by default).
|
Vide is set to strict by doing:
|
||||||
|
|
||||||
### Type
|
```lua
|
||||||
|
local vide = require(path_to_vide)
|
||||||
```ts
|
vide.strict = true
|
||||||
boolean strict = false
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
The purpose of strict mode is to help ensure stateful code is *pure* (deterministic and free from side effects).
|
|
||||||
|
|
||||||
Setting this flag is global for all scripts requiring the same instance of the Vide module.
|
|
||||||
|
|
||||||
What strict mode will do:
|
What strict mode will do:
|
||||||
|
|
||||||
1. Run derived callbacks twice when calculating state value.
|
1. Run derived callbacks twice when re-evaluating.
|
||||||
2. Run watcher callbacks twice each time state changes.
|
2. Run watcher callbacks twice when a state changes.
|
||||||
3. Throw an error if a derived callback yields.
|
3. Throw an error if yields occur where they are not allowed.
|
||||||
4. Throw an error if a watcher callback yields.
|
4. Checks for `map()` returning primitive values.
|
||||||
|
5. Better error reporting and stack traces.
|
||||||
|
|
||||||
It is recommend to develop UI with strict mode set to `true`
|
It is recommend to develop UI with strict mode and to disable it when pushing to
|
||||||
and to set it back to false when pushing to production.
|
production.
|
||||||
|
|
||||||
Using strict mode will help identify potential non-deterministic code and side-effects by running code
|
--------------------------------------------------------------------------------
|
||||||
multiple times in places where it would only run once.
|
|
||||||
|
|
||||||
Strict mode will also ensure that watcher side effects are self contained in the sense that they clean themselves up
|
|
||||||
properly when ran multiple times in quick succession, in case any asynchronous operation is performed.
|
|
||||||
|
|
||||||
Yielding within derived or watcher callbacks can cause undefined behavior as the reactive graph is not designed
|
|
||||||
to work with asynchronous code. Strict mode can identify and throw an error when asynchronous code is detected.
|
|
||||||
This isn't done during runtime as these checks are computationally expensive.
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
vide.strict = true -- this only needs to be done once, preferably in the first module to require Vide
|
|
||||||
|
|
||||||
local state = wrap()
|
|
||||||
|
|
||||||
watch(function()
|
|
||||||
local cleanup = doAsyncOperation(state.Value)
|
|
||||||
|
|
||||||
return function()
|
|
||||||
cleanup()
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
state.Value = 1 -- this will cause the watcher to be ran twice, identifying if cleanup occurs properly
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# Types API
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## State\<T>
|
|
||||||
|
|
||||||
A type representing a Vide state object.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type State<T> = {
|
|
||||||
Value: T,
|
|
||||||
value: T
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local state: State<number> = wrap(1)
|
|
||||||
|
|
||||||
local derived: State<string> = "Count: " .. state
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## Prop\<T>
|
|
||||||
|
|
||||||
A utility type representing a union of a value and a state.
|
|
||||||
|
|
||||||
### Type
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type Prop<T> = T | State<T>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
```lua
|
|
||||||
type BackgroundProps = {
|
|
||||||
Position: Prop<UDim2>,
|
|
||||||
Size: Prop<UDim2>
|
|
||||||
}
|
|
||||||
local function Background(props: { Position: Prop<UDim2> })
|
|
||||||
return create("Frame") {
|
|
||||||
Position = props.Position
|
|
||||||
Size = props.Size
|
|
||||||
}
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
-------------------------------------------------------------------
|
|
||||||
|
|
@ -36,8 +36,14 @@ local vide = {
|
||||||
|
|
||||||
-- runtime
|
-- runtime
|
||||||
step = function(dt: number)
|
step = function(dt: number)
|
||||||
|
-- debug.profilebegin("VIDE STEP")
|
||||||
|
-- debug.profilebegin("VIDE SPRING")
|
||||||
update_springs(dt)
|
update_springs(dt)
|
||||||
|
-- debug.profileend()
|
||||||
|
-- debug.profilebegin("VIDE GARBAGE CLEANUP")
|
||||||
clean_garbage()
|
clean_garbage()
|
||||||
|
-- debug.profileend()
|
||||||
|
-- debug.profileend()
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,20 @@ if not game then script = (require :: any) "test/wrap-require" end
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
Supported datatypes:
|
Supported datatypes:
|
||||||
*number
|
- number
|
||||||
!bool
|
- CFrame
|
||||||
*CFrame
|
- Color3
|
||||||
?Rect
|
- UDim
|
||||||
*Color3
|
- UDim2
|
||||||
*UDim
|
- Vector2
|
||||||
*UDim2
|
- Vector3
|
||||||
*Vector2
|
|
||||||
!Vector2int16
|
Unsupported datatypes:
|
||||||
*Vector3
|
- bool
|
||||||
!Vector3int16
|
- Rect
|
||||||
!EnumItem
|
- Vector2int16
|
||||||
|
- Vector3int16
|
||||||
|
- EnumItem
|
||||||
|
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
|
|
||||||
3
todo.md
3
todo.md
|
|
@ -1,5 +1,8 @@
|
||||||
# todo
|
# todo
|
||||||
|
|
||||||
|
- strict mode
|
||||||
|
- warn when map returns primitive
|
||||||
|
|
||||||
- Implement from solid
|
- Implement from solid
|
||||||
- onCleanup
|
- onCleanup
|
||||||
- Index
|
- Index
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue