This commit is contained in:
Aaron Smith 2023-08-03 18:07:36 +01:00
parent 8e31946dda
commit 13965868ce
13 changed files with 421 additions and 448 deletions

View file

@ -1,54 +1,14 @@
# Notice
The library is currently in a very early development stage, with breaking changes being made often, out of date documentation and no versioning. Not recommended for use.
<br>
The library is currently in a very early development stage, with breaking
changes being made often, out of date documentation and no versioning. Not
recommended for use.
# Vide
Vide is a declarative, reactive Luau library for building user interfaces on Roblox.
Vide is a declarative, reactive Luau library for building user interfaces on
Roblox.
## API Reference
- ### [API Reference](docs/api/index.md)
### [Reactivity: Core](docs/api/reactivity-core)
- [wrap()](docs/api/reactivity-core#wrap)
- [derive()](docs/api/reactivity-core#derive)
- [foreach()](docs/api/reactivity-core#wrap)
- [match()](docs/api/reactivity-core#match)
- [watch()](docs/api/reactivity-core#watch)
### [Reactivity: Utility](docs/api/reactivity-utility)
- [isState()](docs/api/reactivity-utility#isState)
- [unwrap()](docs/api/reactivity-utility#unwrap)
- [readonly()](docs/api/reactivity-utility#readonly)
- [mutate()](docs/api/reactivity-utility#mutate)
### [Element Creation](docs/api/creation)
- [create()](docs/api/creation#create)
- [apply()](docs/api/creation#apply)
- [Layout](docs/api/creation#Layout)
- [Children](docs/api/creation#Children)
- [Event](docs/api/creation#Event)
- [Changed](docs/api/creation#Changed)
- [Bind](docs/api/creation#Bind)
- [Created](docs/api/creation#Created)
### [Animation](docs/api/animation)
- [spring()](docs/api/animation#spring)
### [Types](docs/api/types)
- [State<T>](docs/api/types#State)
- [Prop<T>](docs/api/types#Prop)
<br/>
## Tutorials
### [Crash Course](docs/tutorials/crash-course)
### [Reactive Graph](docs/tutorials/reactive-graph)
- ### [Crash Course](docs/tut/crash-course/1-introduction.md)

22
docs/api/index.md Normal file
View file

@ -0,0 +1,22 @@
# API Reference
### [Reactivity: Core](../api/reactivity-core.md)
- [source()](../api/reactivity-core.md#source)
- [derive()](../api/reactivity-core.md#derive)
- [map()](../api/reactivity-core.md#map)
- [watch()](../api/reactivity-core.md#watch)
### [Reactivity: Utility](../api/reactivity-utility.md)
- [cleanup()](../api/reactivity-utility.md#cleanup)
### [Element Creation](../api/creation.md)
- [create()](../api/creation.md#create)
### [Animation](../api/animation.md)
- [spring()](../api/animation.md#spring)
### [Strict Mode](../api/strict.md)

View file

@ -1,351 +0,0 @@
# Vide Crash Course
This is a brief tutorial designed to give you a quick run through the usage of
Vide.
Vide is largely inspired by Solid and Fusion.
<br>
## Why Vide?
Creating UI is a slow and tedious process. The purpose of Vide is to make UI
declarative and concise, making it faster to create and more importantly easier
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
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
Instances are created using [`create()`](../api/creation#create).
```lua
local vide = require(vide)
local create = vide.create
```
```lua
local frame = create "Frame" {
Name = "Background",
Position = UDim2.fromScale(0.5, 0.5)
}
```
`create()` returns a constructor for a given class which then takes a table of
properties to assign when creating a new instance for that class.
String keys are assumed to be properties, integer keys are assumed to be
children.
```lua
create "ScreenGui" {
Parent = game.StarterGui,
create("Frame") {
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.fromScale(0.4, 0.7),
create("TextLabel") {
Text = "hi"
},
create("TextLabel") {
Text = "bye"
}
}
}
```
To connect to an event, just set the event property name to a function.
```lua
create "TextButton" {
Activated = function()
print "clicked!"
end
}
```
All event arguments are passed into the function.
<br>
## State
State in Vide are special objects that store data.
A state object in Vide can be created using
[`source()`](../api/reactivity-core#source).
```lua
local source = vide.source
```
```lua
local visible = source(false)
local image = create("ImageLabel") {
Visible = visible -- bind property to state
}
visible(false) -- image label is hidden
visible(true) -- image label is shown
```
`source()` creates a new data source which can be set by calling it with the new
value to set.
Any time the value is set, anything depending on it will automatically be
updated using the new value.
Vide detects when you assign a state object as a property value. This is known
as *binding* and doing so will cause the property to *automatically* update
whenever that state object's value is changed.
You as the programmer do not have to worry about manually updating variables or
UI instances, you can just focus on defining how the data maps to UI and
everything will update when changes occur.
<br>
## Derived State
You can create new state from other states. This is known as *deriving state*.
```lua
local count = source(0)
local function text()
return "count: " .. count()
end
create "TextLabel" {
Text = text
}
```
To read from a state, you call without any arguments which returns its stored
value.
Assigning a non-event property a function will bind that property to that
function, anytime a state being read from inside that function is changed, the
function will be re-ran and the property value updated.
Sometimes when using expensive computations to derive state, you only want to
recalculate it when a source state has changed.
```lua
local derive = vide.derive
```
```lua
local count = source(0)
local factorial = derive(function()
local n = 1
for i = 2, count() do
n *= i
end
return n
end)
```
`derive()` will cache and return the same value until a source state has
changed, where it will recompute and cache a new value.
```lua
create "TextLabel" {
Text = function()
return "factorial: " .. factorial()
end
}
count(3) -- displays "factorial: 6"
count(4) -- displays "factorial: 24"
```
<br>
## Components
Components are custom-made reusable pieces of UI made from other pieces of UI.
```lua
local function Background(args)
return create("Frame") {
BackgroundColor3 = Color3.new(0, 0, 0),
Position = args.Position,
Size = args.Size
}
end
local background = Background {
Position = UDim2.new(),
Size = UDim2.new()
}
```
Above is a simple example of a frame component with its background color set to
black.
A single parameter `args` is used to pass properties to the component.
Components allow you to *encapsulate* behavior. You can only modify the
component in ways that you allow in the component.
This also promotes code reusability. Anytime you want a black frame all you do
is call `Background {}` instead of creating a new frame and settings it color
each time.
<br>
## Stateful components
Often, you need components that maintain their own internal state, such as a
toggle-able button or a counter.
Below you can see how a simple counter component can be implemented.
```lua
local function Counter()
local count = source(0)
return create "TextButton" {
Text = function()
return "count: " .. count()
end
Activated = function()
count(count() + 1)
end,
}
end
```
Each time you call `Counter {}`, it will create a new counter component which
each maintains their own count state.
Clicking on the UI element will automatically increment and display its count.
## Nested Properties and Typechecking
When a key is assigned a table, Vide does not attempt to assign it to a
property, instead, the table is iterated and processed just like the nesting
table.
Below is an example of how you can use this to pass groups of similar properties
together such as position and size, while also using typechecking.
```lua
type Layout = {
Layout = {
Position: UDim2?,
Size: UDim2?,
AnchorPoint: Vector2?
}
}
local function Button(args: Layout & {
Text: string,
Callback: () -> ()
})
local count = source(0)
return create "TextButton" {
Text = args.Text
Activated = args.Callback,
Layout = args.Layout
}
end
Button {
Text = "Click me!",
Callback = function()
print "clicked me!"
end,
Layout = {
Position = UDim2.new(),
Size = UDim2.new()
}
}
```
Here the button component is assigned a position and size as if you passed those
properties directly.
The same can be done for properties such as children.
```lua
type Children = {
Children = Array<Instance>
}
local function List(args: Children & Layout)
return create "Frame" {
Layout = args.Layout,
Children = args.Children,
create "UIListLayout" {}
}
end
List {
Layout = {
Position = UDim2.new()
},
Children = {
create "TextLabel" { Text = "1" }
}
}
```
## Tables of data
Vide has functions for dealing with table states.
Below is an example using the above `List` class.
```lua
type Item = {
Name: string,
Icon: number
}
local items = source({} :: Array<Item>)
List {
Children = map(items, function(item, i)
return create "ImageLabel" {
Image = function()
return "rbxassetid://" .. item().Icon
end,
LayoutOrder = i
}
end)
}
```
Here we map each element in `items` to a value returned by a callback.
The callback is called only *once* per key. The first argument given to the
callback is a state that has the value of the table key's value.
Anytime the value of the corresponding table key changes, the state value
changes too. This saves us from having to recreate a UI element any time a
table index changes.
## WIP

View file

@ -0,0 +1,27 @@
# [Introduction](./index.md)
This is a brief tutorial designed to give you a quick run through the usage of
Vide.
Vide is largely inspired by other UI libraries such as Solid and Fusion.
## Why Vide?
Creating UI is a slow and tedious process. The purpose of Vide is to make UI
declarative and concise, making it faster to create and more importantly easier
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
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 and allowing users
to use their own patterns.
-------------------------------------------------------------------------------
### [Element Creation &rarr;](./2-creation.md)

View file

@ -0,0 +1,67 @@
# [Creating UI Elements](./index.md)
Instances are created using [`create()`](../../api/creation.md#create).
```lua
local vide = require(path_to_vide)
local create = vide.create
```
`create()` returns a constructor for a given class which then takes a table of
properties to assign when creating a new instance for that class.
```lua
local frame = create "Frame" {
Name = "Background",
Position = UDim2.fromScale(0.5, 0.5)
}
```
String keys are assigned as properties and integer keys are assigned as child
instances.
```lua
create "ScreenGui" {
Parent = game.StarterGui,
create "Frame" {
AnchorPoint = Vector2.new(0.5, 0.5),
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.fromScale(0.4, 0.7),
create "TextLabel" {
Text = "hi"
},
create"TextLabel" {
Text = "bye"
}
}
}
```
To connect to an event, just set the event property name to a function.
All event arguments are passed into the function.
```lua
create "TextButton" {
Activated = function()
print "clicked!"
end
}
```
## Summary
In short:
- String keys = properties
- Function values = events
- Non-function values = property values
- Numeric keys = children
--------------------------------------------------------------------------------
### [&larr; Introduction](./1-introduction.md) | [Components &rarr;](./3-components.md)

View file

@ -0,0 +1,52 @@
# [Components](./index.md)
Components are custom-made reusable pieces of UI made from other pieces of UI.
Using components you make your application more modular and better organized.
Components leverage functions to create self-contained UI that can even have
its own state and behavior.
```lua
local function Button(props: {
Position: UDim2,
Text: string,
Callback: () -> ()
})
return create "TextButton" {
BackgroundColor3 = Color3.fromRGB(50, 50, 50),
Size = UDim2.fromOffset(400, 250),
Position = props.Position,
Text = props.Text,
Callback = props.Callback
}
end
local button = Button {
Position = UDim2.new(),
Text = "Click me!",
Callback = function()
print "clicked"
end
}
```
Above is a simple example of a button component with its background color set to
a dark grey and with a fixed size.
A single parameter `props` is used to pass properties to the component.
Components allow you to *encapsulate* behavior. You can only modify the
component in ways that you allow in the component.
This also promotes code reusability. Anytime you want a new button all you do
is call `Button {}` instead of creating and setting every property each time.
This can be extended to much more complicated UI.
--------------------------------------------------------------------------------
### [&larr; Element Creation](./2-creation.md) | [State &rarr;](./4-state.md)

View file

@ -0,0 +1,57 @@
# [State](./index.md)
State in Vide are special objects that store data.
A state object in Vide can be created using
[`source()`](../../api/reactivity-core.md#source).
```lua
local source = vide.source
```
```lua
-- create a new source
local count = source(0)
-- set source value
count(10)
-- get source value
print(count()) -- "10"
```
Below is an example of a counter component that has state.
```lua
local function Counter()
local count = source(0)
return create "TextButton" {
Text = count
Activated = function()
count(count() + 1)
end,
}
end
```
Any time the source value is set, anything depending on it will automatically be
updated using the new value.
Vide detects when you assign a function to a property. This is known
as *binding* and doing so will cause the property to *automatically* update
whenever a state in that function is updated, by rerunning the function and
assigning its return value. You can only bind non-event
properties, otherwise the function is connected as the event callback.
You as the programmer do not have to worry about manually updating variables or
UI instances, you can just focus on defining how the data maps to UI and
everything will update when changes occur.
Each call of `Counter {}` will create a new counter element, each with their own
independent count state.
--------------------------------------------------------------------------------
### [&larr; Components](./3-components.md) | [Derived State &rarr;](./5-derived-state.md)

View file

@ -0,0 +1,57 @@
# [Derived State](./index.md)
You can create new state from existing states. This is known as *deriving
state*.
```lua
local count = source(0)
local function text()
return "count: " .. count()
end
create "TextLabel" {
Text = text
}
```
Assigning a non-event property a function will bind that property to that
function, anytime a state being read from inside that function is changed, the
function will be re-ran and the property value updated.
Sometimes when using expensive computations to derive state, you only want to
recalculate it when a source state has changed.
```lua
local derive = vide.derive
```
```lua
local count = source(0)
local factorial = derive(function()
local n = 1
for i = 2, count() do
n *= i
end
return n
end)
```
`derive()` will cache and return the same value until a source state has
changed, where it will recompute and cache a new value.
```lua
create "TextLabel" {
Text = function()
return "factorial: " .. factorial()
end
}
count(3) -- displays "factorial: 6"
count(4) -- displays "factorial: 24"
```
--------------------------------------------------------------------------------
### [&larr; State](./4-State.md) | [Table State &rarr;](./6-table-state.md)

View file

@ -0,0 +1,39 @@
# [Table State](./index.md)
Vide has functions for dealing with table states.
Below is an example using the above `List` class.
```lua
type Item = {
Name: string,
Icon: number
}
local items = source({} :: Array<Item>)
List {
Children = map(items, function(item, i)
return create "ImageLabel" {
Image = function()
return "rbxassetid://" .. item().Icon
end,
LayoutOrder = i
}
end)
}
```
Here we map each element in `items` to a value returned by a callback.
The callback is called only *once* per key. The first argument given to the
callback is a state that has the value of the table key's value.
Anytime the value of the corresponding table key changes, the state value
changes too. This saves us from having to recreate a UI element any time a
table index changes.
--------------------------------------------------------------------------------
### [&larr; Derived State](./5-derived-state.md) | [Property Groups &rarr;](./7-property-groups.md)

View file

@ -0,0 +1,78 @@
# [Property Groups](./index.md)
When a key is assigned a table, Vide does not attempt to assign it to a
property, instead, the table is iterated and processed just like the nesting
table.
Below is an example of how you can use this to pass groups of similar properties
together such as position and size, while also using typechecking.
```lua
type Layout = {
Layout = {
Position: UDim2?,
Size: UDim2?,
AnchorPoint: Vector2?
}
}
local function Button(args: Layout & {
Text: string,
Callback: () -> ()
})
local count = source(0)
return create "TextButton" {
Text = args.Text
Activated = args.Callback,
Layout = args.Layout
}
end
Button {
Text = "Click me!",
Callback = function()
print "clicked me!"
end,
Layout = {
Position = UDim2.new(),
Size = UDim2.new()
}
}
```
Here the button component is assigned a position and size as if you passed those
properties directly.
The same can be done for properties such as children.
```lua
type Children = {
Children = Array<Instance>
}
local function List(args: Children & Layout)
return create "Frame" {
Layout = args.Layout,
Children = args.Children,
create "UIListLayout" {}
}
end
List {
Layout = {
Position = UDim2.new()
},
Children = {
create "TextLabel" { Text = "1" }
}
}
```
--------------------------------------------------------------------------------
### [&larr; Table State](./6-table-state.md)

View file

@ -0,0 +1,15 @@
# Crash Course
1. ### [Introduction](./1-introduction.md)
2. ### [Element Creation](./2-creation.md)
3. ### [Components](./3-introduction.md)
4. ### [State](./4-introduction.md)
5. ### [Derived State](./5-introduction.md)
6. ### [Table State](./6-introduction.md)
7. ### [Property Groups](./7-introduction.md)

View file

@ -486,7 +486,6 @@ end)
TEST("cleanup()", function()
local source = vide.source
local derive = vide.derive
local watch = vide.watch
local cleanup = vide.cleanup

49
todo.md
View file

@ -11,52 +11,3 @@
- batch
- async/loading/suspense
- define order with nested properties
```lua
type Action<T> = {
type: T,
priority: number,
callback: (Instance) -> ()
}
local function action(priority: number, fn: (Instance) -> ()): Action
end
local function changed(property: string, callback: () -> ())
return action(1, function(instance)
instance:GetPropertyChangedSignal(property):Connect(callback)
end) :: Action<"Changed">
end
create "TextBox" {
Text = "test",
changed "Text" < function(self, data)
end
}
```
## version 1
```lua
map(items, function(item, i)
return Item {
Item = item, -- primitive
LayoutOrder = i
}
end)
```
## version 2
```lua
each(items, function(item, i)
return Item {
Item = item, -- state
LayoutOrder = i
}
end)
```