From 13965868ceda42dbe29a1b3e51ff222b05c7b26b Mon Sep 17 00:00:00 2001 From: Aaron Smith <83140718+centau@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:07:36 +0100 Subject: [PATCH] --- README.md | 54 +--- docs/api/index.md | 22 ++ docs/tut/crash-course.md | 351 --------------------- docs/tut/crash-course/1-introduction.md | 27 ++ docs/tut/crash-course/2-creation.md | 67 ++++ docs/tut/crash-course/3-components.md | 52 +++ docs/tut/crash-course/4-state.md | 57 ++++ docs/tut/crash-course/5-derived-state.md | 57 ++++ docs/tut/crash-course/6-table-state.md | 39 +++ docs/tut/crash-course/7-property-groups.md | 78 +++++ docs/tut/crash-course/index.md | 15 + test/tests.luau | 1 - todo.md | 49 --- 13 files changed, 421 insertions(+), 448 deletions(-) create mode 100644 docs/api/index.md delete mode 100644 docs/tut/crash-course.md create mode 100644 docs/tut/crash-course/1-introduction.md create mode 100644 docs/tut/crash-course/2-creation.md create mode 100644 docs/tut/crash-course/3-components.md create mode 100644 docs/tut/crash-course/4-state.md create mode 100644 docs/tut/crash-course/5-derived-state.md create mode 100644 docs/tut/crash-course/6-table-state.md create mode 100644 docs/tut/crash-course/7-property-groups.md create mode 100644 docs/tut/crash-course/index.md diff --git a/README.md b/README.md index f9d14c3..09bad6a 100644 --- a/README.md +++ b/README.md @@ -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. - -
+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](docs/api/types#State) -- [Prop](docs/api/types#Prop) - -
- -## Tutorials - -### [Crash Course](docs/tutorials/crash-course) - -### [Reactive Graph](docs/tutorials/reactive-graph) +- ### [Crash Course](docs/tut/crash-course/1-introduction.md) diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..6846cef --- /dev/null +++ b/docs/api/index.md @@ -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) diff --git a/docs/tut/crash-course.md b/docs/tut/crash-course.md deleted file mode 100644 index 2f0c3a0..0000000 --- a/docs/tut/crash-course.md +++ /dev/null @@ -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. - -
- -## 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. - -
- -## 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. - -
- -## 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" -``` - -
- -## 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. - -
- -## 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 -} - -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) - -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 diff --git a/docs/tut/crash-course/1-introduction.md b/docs/tut/crash-course/1-introduction.md new file mode 100644 index 0000000..20cc3b4 --- /dev/null +++ b/docs/tut/crash-course/1-introduction.md @@ -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 →](./2-creation.md) diff --git a/docs/tut/crash-course/2-creation.md b/docs/tut/crash-course/2-creation.md new file mode 100644 index 0000000..71a231a --- /dev/null +++ b/docs/tut/crash-course/2-creation.md @@ -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 + +-------------------------------------------------------------------------------- + +### [← Introduction](./1-introduction.md) | [Components →](./3-components.md) diff --git a/docs/tut/crash-course/3-components.md b/docs/tut/crash-course/3-components.md new file mode 100644 index 0000000..96109ea --- /dev/null +++ b/docs/tut/crash-course/3-components.md @@ -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. + +-------------------------------------------------------------------------------- + +### [← Element Creation](./2-creation.md) | [State →](./4-state.md) + diff --git a/docs/tut/crash-course/4-state.md b/docs/tut/crash-course/4-state.md new file mode 100644 index 0000000..e66f417 --- /dev/null +++ b/docs/tut/crash-course/4-state.md @@ -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. + +-------------------------------------------------------------------------------- + +### [← Components](./3-components.md) | [Derived State →](./5-derived-state.md) diff --git a/docs/tut/crash-course/5-derived-state.md b/docs/tut/crash-course/5-derived-state.md new file mode 100644 index 0000000..c649758 --- /dev/null +++ b/docs/tut/crash-course/5-derived-state.md @@ -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" +``` + +-------------------------------------------------------------------------------- + +### [← State](./4-State.md) | [Table State →](./6-table-state.md) diff --git a/docs/tut/crash-course/6-table-state.md b/docs/tut/crash-course/6-table-state.md new file mode 100644 index 0000000..865acd4 --- /dev/null +++ b/docs/tut/crash-course/6-table-state.md @@ -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) + +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. + +-------------------------------------------------------------------------------- + +### [← Derived State](./5-derived-state.md) | [Property Groups →](./7-property-groups.md) diff --git a/docs/tut/crash-course/7-property-groups.md b/docs/tut/crash-course/7-property-groups.md new file mode 100644 index 0000000..65ee8c6 --- /dev/null +++ b/docs/tut/crash-course/7-property-groups.md @@ -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 +} + +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" } + } +} +``` + +-------------------------------------------------------------------------------- + +### [← Table State](./6-table-state.md) diff --git a/docs/tut/crash-course/index.md b/docs/tut/crash-course/index.md new file mode 100644 index 0000000..6f88c82 --- /dev/null +++ b/docs/tut/crash-course/index.md @@ -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) diff --git a/test/tests.luau b/test/tests.luau index b8cd10e..d49e85c 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -486,7 +486,6 @@ end) TEST("cleanup()", function() local source = vide.source - local derive = vide.derive local watch = vide.watch local cleanup = vide.cleanup diff --git a/todo.md b/todo.md index d20ec85..5d020d9 100644 --- a/todo.md +++ b/todo.md @@ -11,52 +11,3 @@ - batch - async/loading/suspense - define order with nested properties - -```lua -type Action = { - 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) -``` -