diff --git a/.gitattributes b/.gitattributes
index 54736db..2606156 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,2 +1,2 @@
-* text=auto
+* text=auto eol=crlf
*.lua text eol=crlf linguist-language=Luau
diff --git a/.gitignore b/.gitignore
index 435d041..2111a02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
bin/
build/
+node_modules/
.vscode/
*.rbxl
@@ -7,5 +8,6 @@ build/
*.rbxm
*.rbxmx
*.lock
+package*
sourcemap.json
diff --git a/assets/IrisHelpfulChart.png b/assets/IrisHelpfulChart.png
deleted file mode 100644
index a72f1c4..0000000
Binary files a/assets/IrisHelpfulChart.png and /dev/null differ
diff --git a/assets/simple-example1.png b/assets/simple-example1.png
new file mode 100644
index 0000000..998f69b
Binary files /dev/null and b/assets/simple-example1.png differ
diff --git a/assets/simple-example2.png b/assets/simple-example2.png
new file mode 100644
index 0000000..27cd7f7
Binary files /dev/null and b/assets/simple-example2.png differ
diff --git a/docs/about/_category_.json b/docs/about/_category_.json
new file mode 100644
index 0000000..49d26ed
--- /dev/null
+++ b/docs/about/_category_.json
@@ -0,0 +1,4 @@
+{
+ "label": "About",
+ "position": 6
+}
diff --git a/docs/about/cycle.md b/docs/about/cycle.md
new file mode 100644
index 0000000..8da5345
--- /dev/null
+++ b/docs/about/cycle.md
@@ -0,0 +1,155 @@
+---
+sidebar_position: 1
+---
+
+# Understanding the Lifecycle
+
+## General Game Lifecycle
+
+Iris is designed for games with a core 'game loop' which is the structure that controls what
+part of the game process happens when. A typical game loop make look very similar to this:
+
+```cpp
+while(not_closed) {
+ poll_input();
+ update_game_state();
+ step_physics();
+ render_content();
+ wait(); // for a 60 fps limit
+}
+```
+Here we start firstly with polling for any input changes, since these affect the game state
+for that frame. We then update the game state which generally includes the majority of a
+game engine, since it would control any user updates, world changes, UI updates and others.
+We may also then choose to step our physics engine, assuming we are using a constant frame
+rate. Finally we render out everything to our GPU and wait until the appropriate time to
+start processing the next frame.
+
+Roblox takes most of this away from developers, and instead chooses to rely on an event-driven
+loop, where we hook onto a part of the engine allowing something else to happen. This makes it
+more difficult to use Iris, since not every place we want it will run every frame. However,
+Roblox provides access to RunService events, allowing us to execute code every frame, which is
+seen below:
+
+```lua
+while not_closed do
+ update(UserInputService)
+ update(ContextActionService)
+
+ event(RunService.BindToRenderStepped)
+ event(RunService.RenderStepped)
+
+ render()
+
+ event(wait)
+ event(RunService.Stepped)
+ update(PhysicsService)
+
+ event(RunService.Heartbeat)
+ update(ReplicationService)
+
+ delay() -- for a 60 fps limit
+end
+```
+
+This is taken from the [Task Scheduler Documentation](https://create.roblox.com/docs/studio/microprofiler/task-scheduler)
+which goes into more detail about this.
+
+## Iris Lifecycle
+
+Iris needs to run every frame, called the cycle, in order to update global variables and to
+clean any unused widgets. This is equivalent to calling `ImGui::EndFrame()` for Dear ImGui,
+which would then process the frame buffers ready for use. This order is important for Iris,
+which by default uses the `RunService.Heartbeat` event to process this all on. Therefore, for
+each frame, any Iris code must run before this event. It is possible to change the event Iris
+runs on when initialising, but for most cases, `RunService.Heartbeat` is ideal.
+
+Understanding this is the key to most effectively using Iris. The library provides a handy
+`Iris:Connect()` function which will run any code in the function every frame before the
+cycle. This makes it the most convenient. However, any functions provided here will also run
+on the initialised event, `RunService.Heartbeat` here, so will run after physics and animations
+are calculated. Thankfully, Iris does not constrain you to use only `Iris:Connect()`. You are
+able to run Iris code anywhere, in any event, at any time. As long as it is consistent on
+every frame, and before the cycle event, it will work properly. Therefore, it is very possible
+to put Iris directly into your core game loops.
+
+## Demonstration
+
+Say you have a weapon class which is used by every weapon and then also a weapon handler/serivce/system/controller
+for handling all weapons on the client. Integrating Iris may look something similar to this:
+```lua
+------------------------------------------------------------------------
+--- game.ReplicatedStorage.Modules.Client.Weaopns.WeaponsService.lua
+------------------------------------------------------------------------
+local WeaponsService = {
+ maxWeapons = 10,
+ activeWeapon = nil,
+ weapons = {}
+}
+
+function WeaponsService.init()
+end
+
+-- called every frame to update all weapons
+function WeaponsService.update(deltaTime: number)
+ Iris.Window({ "Weapons Service" })
+
+ WeaponsService.doSomething()
+ Iris.CollapsingHeader({ "Global Variables" })
+ Iris.DragNum({ "Max Weapons", 1, 0 }, { number = Iris.TableState(WeaponsService.maxWeapons) })
+ Iris.End()
+
+ Iris.CollapsingHeader({ "Weapons" })
+ Iris.Tree({ `Active Weapon: {WeaponsService.activeWeapon.name}` })
+ WeaponsService.activeWeapon:update()
+ Iris.End()
+
+ Iris.SeparatorText({ "All Weapons" })
+ for _, weapon: weapon in WeaponsService.weapons do
+ Iris.Tree({ weapon.name })
+ weapon:update()
+ Iris.End()
+ end
+ Iris.End()
+
+ WeaponsService.doSomethingElse()
+ Iris.End()
+end
+
+function WeaponsService.terminate()
+end
+
+return WeaponsService
+
+------------------------------------------------------------------------
+--- game.ReplicatedStorage.Modules.Client.Weaopns.Weapon.lua
+------------------------------------------------------------------------
+local Weapon = {}
+Weapon.__index = Weapon
+
+function Weapon.new(...)
+end
+
+function Weapon.update(self, deltaTime: number)
+ Iris.Text({ `ID: {self.id}` })
+ Iris.Text({ `Bullets: {self.bullets}/{self.capacity}" })
+ Iris.Checkbox({ "No reload" }, { isChecked = Iris.TableState(self.noreload) })
+ ...
+ self:updateInputs()
+ self:updateTransforms()
+ ...
+end
+
+function Weapon.destroy(self)
+end
+
+```
+
+Although this is very bare bones code, we are not using any `Iris:Connect()` methods
+and instead place our Iris code directly in our update events which we know will run
+every frame. Another practice this shows is starting a window somewhere and keeping it
+open through all weapons before closing it and the end of the update. Therefore, we
+can place lots of different widgets in one window and keep everything organised.
+
+The showcase by [@Boogle](https://x.com/LeBoogle/status/1772384187426709879) shows
+off Iris used exactly like this, but with an actual working system.
diff --git a/docs/events.md b/docs/about/events.md
similarity index 70%
rename from docs/events.md
rename to docs/about/events.md
index 605d1d2..cf9d82b 100644
--- a/docs/events.md
+++ b/docs/about/events.md
@@ -1,9 +1,14 @@
-# Events
+---
+sidebar_position: 4
+---
+
+# Understanding Events
Each widget has a number of events connected to it. You can see these events on the [API page](/API/Iris).
-Certain events will happen once, such as a window being collapsed or a button being clicked. Other events can be continuous, such as a widget being hovered.
-Each event is a function which returns a boolean value for whether the event has happened that frame or not.
+Certain events will happen once, such as a window being collapsed or a button being clicked. Other events can be
+continuous, such as a widget being hovered. Each event is a function which returns a boolean value for whether the
+event has happened that frame or not.
To listen to an event, use the following:
```lua
@@ -13,8 +18,8 @@ if button.clicked() then
end
```
-Events will fire the frame after the initial action happened. This is so that any changes caused by that event can propogate visually.
-For example on a checkbox:
+Events will fire the frame after the initial action happened. This is so that any changes caused by that event can
+propogate visually. For example on a checkbox:
- [Frames 1 - 60]
The mouse is elsewhere.
diff --git a/docs/about/states.md b/docs/about/states.md
new file mode 100644
index 0000000..df7dcf3
--- /dev/null
+++ b/docs/about/states.md
@@ -0,0 +1,71 @@
+---
+sidebar_position: 3
+---
+
+# Understanding State
+
+An Iris State object is simply a table containg a value, and an array of connected widgets. It provides functions to
+get or set the value, the latter of which will update any widgets UI that are dependent on that state. Functions can
+also be connected which will be fired whenever the value changes.
+
+A state object ultimately attempts to copy the behaviour that a pointer would do in other languages, but is not
+possible in native Luau. A Luau table is the best option, because it is passed by reference.
+
+## Types of State
+
+Iris provides multiple different types of State objects, suited for different needs.
+
+### State
+
+The base and most common state type, which implements the basically functionality of any state object.
+
+### WeakState
+
+A WeakState is very similar to State, except that every time it is called by ID, using `Iris.WeakState()`, all
+connected widgets and functions are removed, whilst keeping the value. This is useful if you need to disconnect any
+widgets from a state, so that they no longer update, whilst also keeping the existing value.
+
+### VariableState
+
+A VariableState takes both a value, and a function which gives the new value of the state whenever it is changed. This
+is designed for when you have a variable within a file, and want to link it to a state object. By default, when the
+function is called, if the variable and state are different, it will choose the local variable value. But if the state
+is changed, it will use the callback which is designed to update the local variable.
+
+This is best shown with an example:
+```lua
+local myNumber = 5
+
+local state = Iris.VariableState(myNumber, function(value)
+ myNumber = value
+end)
+Iris.DragNum({ "My number" }, { number = state })
+```
+
+Here we create a state for a DragNum. If we update the value of `myNumber` within the code earlier, it will update the
+state value. And if we drag the widget, and update the state, it will call our callback, where we update the value of
+`myNumber`.
+
+### TableState
+
+A TableState acts like VariableState, but takes a table and index so that whenever the table value changes, the state
+changes and vice versa. Because tables are shared, we do not need to provide a function to update the table value, and
+is instead handled internally.
+
+We can see this with an example:
+```lua
+local data = {
+ myNumber = 5
+}
+
+local state = Iris.TableState(data, "myNumber")
+Iris.DragNum({ "My number" }, { number = state })
+```
+
+A third argment provides extra functionality, allowing us to call a function before updating the table value, which can
+be used when we need to change some other values, for example when enabling or disabling a class.
+
+### ComputedState
+
+ComputedState takes an existing state and a function which will convert the value of one state to a new one. We can use
+this to ensure that a state always stays dependent on another state.
diff --git a/docs/about/widgets.md b/docs/about/widgets.md
new file mode 100644
index 0000000..21ddd9e
--- /dev/null
+++ b/docs/about/widgets.md
@@ -0,0 +1,342 @@
+---
+sidebar_position: 2
+---
+
+# Understanding Widgets
+
+An Iris widget is simply a table (or struct) of data. At its most basic, a widget contains only its
+ID, type, tick, events, arguments, zindex, parent widget and gui instance. None of the functionality
+of a widget is contained in the widget object. Instead, Iris uses a factory pattern for a widget
+class whereby the changes are applied onto any widget passed to the function.
+
+## Creating a Widget Class
+
+To declare or construct a widget we create a 'widget class', that is, the functions which take in a
+widget and make changes to it. This widget class is where all the functionality of a a widget is
+added and therefore needs to be declared or constucted in advance. The class widget is itself a table
+of data and functions but conforming to a specification which includes the necessary functions for
+operation.
+
+To create a widget class, use the `WidgetConstructor: (type: string, widgetClass: WidgetClass) -> ()`
+function in `Iris.Internal`. This takes two arguments, a type for the widget, such as Text, InputVector2
+or SameLine, and a widget class table, containing the functions. This WidgetClass is defined as:
+```lua
+export type WidgetClass = {
+ -- Required
+ Generate: (thisWidget: Widget) -> GuiObject,
+ Discard: (thisWidget: Widget) -> (),
+ Update: (thisWidget: Widget, ...any) -> (),
+
+ Args: { [string]: number },
+ Events: Events,
+ hasChildren: boolean,
+ hasState: boolean,
+
+ -- Generated on construction
+ ArgNames: { [number]: string },
+
+ -- Required for widgets with state
+ GenerateState: (thisWidget: Widget) -> (),
+ UpdateState: (thisWidget: Widget) -> (),
+
+ -- Required for widgets with children
+ ChildAdded: (thisWidget: Widget, thisChild: Widget) -> GuiObject,
+ -- Optional for widgets with children
+ ChildDiscarded: (thisWidget: Widget, thisChild: Widget) -> (),
+}
+```
+
+Here, some of the functions are required for all widgets, which define what to do when the widget is
+first created, when it is destroyed or discarded because it is no longer called and when any arguments
+provided to it are updated. And others are only needed if the widget has state or has children.
+
+## Understanding the Widget Lifecycle
+
+When a widget is called, Iris will try to find the widget from the preivous frame using a VDOM table
+and an ID dependent on the line it was called from. If the widget was called for the first time, then
+Iris must generate a new widget. It does this by firstly generating the widget structure, being a table
+with a few properties. It then generates the UI instances, by calling the `Generate` function of the
+widget class. Iris will then use the parent of the widget and call the `ChildAdded` function of the
+parent widget class which tells Iris where to parent the UI instances to. At this point, the new widget
+looks the same as a pre-existing one.
+
+Iris then checks the arguments provided to determine if they have changed. If they have, then Iris will
+call 'Update' from the widget class which handles any UI changes needed. At this point, the widget is
+ready, with a functioning UI and can be placed correctly. And that is the end of the call.
+
+At the end of the frame, if Iris finds any widgets which were not called in that frame, it will call
+the `Discard` function which is essentially designed to destroy the UI instances.
+
+## Required
+
+Every widget class must have a `Generate`, `Discard` and `Update` function, an `Args` and `Events`
+table and `hasChildren` and `hasState` value.
+
+Generally we define whether the widget will have children or have state first since this affects any
+other functions needed for the class.
+
+### Args
+
+Args is a string-indexed table where each possible argument for a widget is given an index, which
+corresponds to the index when calling the widget. Therefore, we specify every argument, but do not give
+a type, or default value.
+
+An example for Window and Button are shown below:
+
+
+
+```lua
+-- Button arguments
+Args = {
+ ["Text"] = 1,
+ ["Size"] = 2,
+}
+
+
+
+
+
+
+
+
+```
+
+
+
+```lua
+-- Window arguments
+Args = {
+ ["Title"] = 1,
+ ["NoTitleBar"] = 2,
+ ["NoBackground"] = 3,
+ ["NoCollapse"] = 4,
+ ["NoClose"] = 5,
+ ["NoMove"] = 6,
+ ["NoScrollbar"] = 7,
+ ["NoResize"] = 8,
+ ["NoNav"] = 9,
+ ["NoMenu"] = 10,
+}
+```
+
+
+
+### Events
+
+Events are used to query the current state of a widget. For a button, it might be the whether it has been
+clicked. For a checkbox, whether it is active or for a window whether it is open. All of these are
+defined to be custom in Iris and called like regular functions on a widget. To do this, we specify a table
+containing all of the possible events.
+
+Each event is a string index for a table conaining to functions: an `Init` function, to setup any
+prerequisites; and a `Get` function, which returns the value when the event is called. Because some events
+are so common, such as `hovered()` and `clicked()`, Iris provides shorthands for these, making them easier
+to add to any widget.
+
+If we look at the example Window widget events, we'll see the two common ways:
+```lua
+Events = {
+ ["closed"] = {
+ ["Init"] = function(_thisWidget: Types.Window) end,
+ ["Get"] = function(thisWidget: Types.Window)
+ return thisWidget.lastClosedTick == Iris._cycleTick
+ end,
+ },
+ ["opened"] = {
+ ["Init"] = function(_thisWidget: Types.Window) end,
+ ["Get"] = function(thisWidget: Types.Window)
+ return thisWidget.lastOpenedTick == Iris._cycleTick
+ end,
+ },
+ ["collapsed"] = {
+ ["Init"] = function(_thisWidget: Types.Window) end,
+ ["Get"] = function(thisWidget: Types.Window)
+ return thisWidget.lastCollapsedTick == Iris._cycleTick
+ end,
+ },
+ ["uncollapsed"] = {
+ ["Init"] = function(_thisWidget: Types.Window) end,
+ ["Get"] = function(thisWidget: Types.Window)
+ return thisWidget.lastUncollapsedTick == Iris._cycleTick
+ end,
+ },
+ ["hovered"] = widgets.EVENTS.hover(function(thisWidget: Types.Widget)
+ local Window = thisWidget.Instance :: Frame
+ return Window.WindowButton
+ end),
+}
+```
+
+The hovered event here used a macro utility, which takes a function that returns the Instance to check for
+hovering on. It then sets up the `MouseHover` events for us, and returns the two functions so that th event
+is setup correctly. This is the easiest way, since we only need to provide the UI Instance.
+
+The other events are added manually. They each work by checking whether an event has fired on this tick.
+Elsewhere in the code for the Window widget, these tick variables are set when an event happens. For example,
+in the code which checks for a click on the window collapse button, it will update the tick variable to the
+next cycle, ensuring that the event fires when all of the UI changes.
+
+Most of the time, you can the existing examples from other widgets.
+
+### hasChildren
+
+If your widget is going to be a parent and therefore have other widgets placed within or under it, like
+a Window, Tree or SameLine widget, this must be true. If not, it must be specified as false.
+
+### hasState
+
+If your widget will take in valuse and possibliy modify them in the widget and return them back, then
+the widget will use state objects and therefore must be set to true. Otherwise it must be sepcified as
+false.
+
+### Generate
+
+The `Generate` function is called whenever a widget is first called and is responsible for creating the
+actual UI instances that make up the window. It handles all of the styling but does not use the
+widget arguments (this is hande=led in the `Update` function). Any widget interactivity such as events
+for clicks or hovers, which may also change the style, are setup in here. There area few rules which
+the generated UI instances follow:
+1. The root instance should be named "Iris_[WIDGET_TYPE]".
+2. The ZIndex and LayoutOrder of the root element are taken from the ZIndex property of the widget.
+3. Returns the root instance.
+4. Widgets are generally sized using AutomaticSize or the config over hard-coded numbers, and therefore
+ scale better
+5. The arguments are never used to modify any instances because if the arguments change then the widget
+ should be able to handle the changes on existing UI rather than creating a new design.
+
+The code of a Button best demonstrates this:
+```lua
+Generate = function(thisWidget: Types.Button)
+ -- a TextButton is the best option here because it has the correct events
+ local Button: TextButton = Instance.new("TextButton")
+ -- we rely on auomatic size
+ Button.Size = UDim2.fromOfset(0, 0)
+ -- using the config values
+ Button.BackgroundColor3 = Iris._config.ButtonColor
+ Button.BackgroundTransparency = Iris._config.ButtonTransparency
+ Button.AutoButtonColor = false
+ Button.AutomaticSize = Enum.AutomaticSize.XY
+
+ -- utility functions exist such as this one which correctly sets the text
+ -- style for the widget
+ widgets.applyTextStyle(Button)
+ Button.TextXAlignment = Enum.TextXAlignment.Center
+
+ -- another utility function which adds any borders or padding dependent
+ -- on the config
+ widgets.applyFrameStyle(Button)
+
+ -- an utility event which uses clicks and hovers to colour the button
+ -- when the mouse interacts with it: normal, hovered and clicked
+ widgets.applyInteractionHighlights("Background", Button, Button, {
+ Color = Iris._config.ButtonColor,
+ Transparency = Iris._config.ButtonTransparency,
+ HoveredColor = Iris._config.ButtonHoveredColor,
+ HoveredTransparency = Iris._config.ButtonHoveredTransparency,
+ ActiveColor = Iris._config.ButtonActiveColor,
+ ActiveTransparency = Iris._config.ButtonActiveTransparency,
+ })
+
+ -- set the correct layout order and zindex to ensure it stays in the
+ -- correct order. Iris relies heavily on UIListLayouts to automatically
+ -- position the UI, and therefore relies on the LayoutOrder property.
+ Button.ZIndex = thisWidget.ZIndex
+ Button.LayoutOrder = thisWidget.ZIndex
+
+ -- we finally return the instance, which is correctly parented and
+ -- Iris sets the widget.instance property to this root element
+ return Button
+end,
+```
+
+### Discard
+
+Discard is called whenever the widget is about to be destroyed because it has not been called this frame.
+It hsould remove any instances used by the widget. Most of the time, it is possible to just destroy the
+root Instance of the widget, which will close any connections and remove all child instances. If a widget
+has any state objects, you will also need to call the `discardState()` function in the widget utility
+library, which removes any connected states from the widget, allowing the widget to be correctly cleaned
+up.
+
+### Update
+
+Update is used to alter the widget dependent on the provided arguments. Since the arguments can change
+every frame, Iris will call `Update` whenever these arguments change, and when the widget is first
+created. Within Generate, all possible instances that are used by the widget are created. `Update` is
+used to determine which ones are visible and the style of them.
+
+For example, the Text argument of a widget can be updated dynamically, by simply changing the Text value
+for the UI Instance.
+
+## State
+
+These functions are both required for any widget which has a state.
+
+### GenerateState
+
+GenerateState will create all of the state objects used by that widget, if they are not provided by
+the user. It is called only once, when the widget is first created. Creating a new state is not just
+creating the object, but also linking it to the widget, so that when the state changes, it updates the
+widget. We can use an example to demonstrate the macro function that Iris provides to make this easier,
+as shown in the Checkbox widget:
+
+```lua
+GenerateState = function(thisWidget: Types.Checkbox)
+ if thisWidget.state.isChecked == nil then
+ thisWidget.state.isChecked = Iris._widgetState(thisWidget, "checked", false)
+ end
+end
+```
+
+We first check whether the state already exists. If it doesn't we can use the `Iris._widgetState()`
+function which will construct a new state for this widget with a given name and default value. We can
+therefore give the states their default values here. Within `Iris._widgetState()`, we create a new
+state object and add this widget to its internal table of all connected widgets, and then return the
+new state.
+
+Since `GenerateState` is called only once, we can also use it to connect any widget states together
+using the `:onChange()` connection.
+
+### UpdateState
+
+UpdateState is the equivalent to `Update`, but for state. If any state object connected to this widget
+is updated, then this will be called, which handles all of the UI changes. This is also called once at
+widget creation, to properly design the UI before it is first shown.
+
+:::note
+Any changes to UI due to state should be handled here, and not in the code which updates the state.
+
+For example, if you have a click event within `GenerateState`, such as for a checkbox, which changes
+the state, the code within `UpdateState` should change the UI, such as show a tick, rather than handling
+it in `GenerateState`.
+:::
+
+## Children
+
+These functions are used if a widget has children. Only `ChildAdded` is required.
+
+### ChildAdded
+
+ChildAdded returns the UI instance which the child widget should be parented to. For most functions,
+it just returns this Instance. However, it is also possible to validate that the child widget is a certain
+type, such as only Menus under a MenuBar. You can also update any UI behaviour which may depend on the
+number of children, such as a container height.
+
+### ChildDiscarded
+
+ChildDiscarded is optional, and is only necessary when the removal of a child needs to update the parent
+widget in some way, such as changing the size.
+
+## Calling a Widget
+
+We have constructed our widget class, but need to know how to call it. We use the `_Insert` API under
+`Iris.Internal`: `_Insert: (widgetType: string, arguments: WidgetArguments?, states: WidgetStates?) -> Widget`.
+We provide the widgetType, as specified in the constructor, and then arguments and states.
+
+For example, we can create a Text widget by calling:
+```lua
+Iris.Internal._Insert("Text", { "Text label" })
+```
+
+We create an alias for these functions under `Iris` directly, which is why the user does not call
+this function directly.
diff --git a/docs/common_issues.md b/docs/common_issues.md
new file mode 100644
index 0000000..e700518
--- /dev/null
+++ b/docs/common_issues.md
@@ -0,0 +1,129 @@
+---
+sidebar_position: 9
+---
+
+# Common Issues
+
+When using Iris you may run into different issues. The most common ones are explained
+below and explain why the issue arises and how to fix it.
+
+## Iris.Init() can only be called once.
+:::danger[Error]
+`Iris.Init() can only be called once.`
+:::
+
+Iris can only be initialised once per client. The best way to initialise Iris then is
+to place it at the start of one of your first running script. For example you may have:
+```lua
+----------------------------------
+--- ReplicatedFirst/client.lua or StarterPlayer/StarterPlayerScripts/client.lua
+----------------------------------
+ 1| -- code in ReplicatedFirst will execute before other code, so it is best practice
+ 2| -- to initialise Iris here even if you are not going to use it.
+ 3| reqire(game.ReplicatedStorage.Iris).Init()
+ 4|
+ 5| ...
+
+----------------------------------------
+--- StarterPlayer/StarterPlayerScripts/raycast.lua
+----------------------------------------
+ 1| -- therefore, when you require it any scripts elsewhere it is already initialised
+ 2| -- and ready to go and you do not need to worry about where to init
+ 3| local Iris = require(game.ReplicatedStorage.Iris)
+ 4|
+ 5| -- wrong, you have initialised it twice here
+ 6| local Iris = require(game.ReplicatedStorage.Iris).Init()
+ 7|
+ 8| ...
+
+```
+
+This becomes more difficult if you have many local scripts which could all execute at
+the same time. This is why most games will only use a few local scripts and rely on
+modules for the rest, which ensures that code runs in an expected and deterministic
+order and therefore any client-wide initialisation can happen before anything that
+relies on it does.
+
+## Iris:Connect() was called before calling Iris.Init(); always initialise Iris first.
+:::caution[Warn]
+`Iris:Connect() was called before calling Iris.Init(); always initialise Iris first.`
+:::
+
+Iris should always be initialised before attempting to use 'Connect()'. This is just a
+warning to make sure that you are initialising Iris in the first place. If you connect
+and then initialise, your code will still run normally and Iris functions fine. However,
+as mentioned in the `Iris.Init() can only be called once.` issue, it is better practice to
+initalise Iris before any other Iris code runs and therefore you can ensure consistent
+ordering.
+
+## Iris cycleCoroutine took to long to yield. Connected functions should not yield.
+:::danger[Error]
+`Iris cycleCoroutine took to long to yield. Connected functions should not yield.`
+:::
+
+Iris does not support yielding statements becaues it needs to run and finish every frame.
+Therefore if you have code which needs to yield and wait, you should either handle it
+outside of an Iris widget, or spawn a new thread. The example below demonstrates the issue:
+
+```lua
+-----------------------
+--- bad_example.lua
+-----------------------
+ 4| Iris.Window({"Async Window"})
+ 5| -- this code yeilds which will prevent Iris from finishing before the next frame
+ 6| local response = httpService:GetAsync(...)
+ 7| Iris.Text(response)
+ 8| Iris.End()
+
+------------------------
+--- good_example.lua
+------------------------
+ 4| local response = "NONE"
+ 5|
+ 6| Iris.Window({"Async Window"})
+ 7| -- we use another thread to ensure the thread Iris is in will finish before the next frame
+ 8| task.spawn(function()
+ 9| response = httpService:GetAsync(...)
+10| end)
+11| Iris.Text(response)
+12| Iris.End()
+```
+
+These examples are fairly simple, but when you are integrating Iris directly into your codebase
+it should become much clearer.
+
+## Too few calls to Iris.End()., Too many calls to Iris.End().
+:::danger[Error]
+`Too few calls to Iris.End().`, `Too many calls to Iris.End().`
+:::
+
+These issues are caused respectively by have too few or too many calls to `Iris.End()`. Every
+widget that has children, from Windows and Trees to MenuBars and Combos to SameLine and Indent
+must have an `Iris.End()` statement to say that you are done appending to that parent. To ensure
+this does not happen, it is best to use do-end blocks to indent out parent widgets from their
+children and make it clearer to see where an `Iris.End()` statement must go. For example:
+
+```lua
+ 4| Iris.Window({ "Do-End Block" })
+ 5| do
+ 6| Iris.Text({ "Text goes here." })
+ 7| end
+ 8| Iris.End()
+```
+This makes it clear that an `Iris.End()` statement should always go after an `end` block.
+
+This issue may also arise if some of your code either yields or errors and therefore not all the
+`Iris.End()` calls happen. For example:
+
+```lua
+ 4| Iris.Window({ "Valid Code with Error" })
+ 5| error("Something has gone wrong. :(") -- errors within Iris
+ 6| Iris.End()
+
+ 7| Iris.Window({ "Asynchronous Code" })
+ 8| task.wait(1) -- yeilds within Iris
+ 9| Iris.End()
+```
+
+Although all the `Iris.End()` statements are there and in the right space, the error has prevented
+it from running and therefore we will get this error.
diff --git a/docs/creatingCustomWidgets.md b/docs/creatingCustomWidgets.md
deleted file mode 100644
index cf3316f..0000000
--- a/docs/creatingCustomWidgets.md
+++ /dev/null
@@ -1,185 +0,0 @@
-# Creating Custom Widgets
-
-:::note
-This page needs to be rewritten and is lacking in detail.
-:::
-
-# Overview
-
-Iris has a widget constructor method to create widgets with. Once a widget has been constructed, you can than use it like any other widget. Every widget follows a set of guidelines it must follow when constructed.
-
-To construct a new widget, you can call `Iris.WidgetConstructor()` with the widget name and widget class. To then use the widget you can call `Iris.Internal._Insert()` with the widget name and then optional argument and state tables.
-
-# Documentation
-
-## Widget Construction
-
-For Instance, this is the call to `Iris.WidgetConstructor` for the `Iris.Text` widget:
-```lua
-Iris.WidgetConstructor("Text", {
- hasState = false,
- hasChildren = false,
- Args = {
- ["Text"] = 1
- },
- Events {
- ["hovered"] = {
- ...
- }
- }
- Generate = function(thisWidget)
- local Text = Instance.new("TextLabel")
-
- ...
-
- return Text
- end,
- Update = function(thisWidget)
- ...
- end,
- Discard = function(thisWidget)
- thisWidget.Instance:Destroy()
- end
-})
-```
-
-
-The first argument, `type: string`, specifies a name for the widget.
-
-
-The second argument is the widget class. The methods which a widget class has depends on the value of `hasState` and `hasChildren`. Every widget class should specify if it `hasState` and `hasChildren`. The example widget, a text label, has no state, and it does not contain other widgets, so both are false. Every widget must have the following functions:
-
-| All Widgets | Widgets with State | Widgets with Children |
-| ----------- | ------------------ | ------------------------- |
-| Generate | GenerateState | ChildAdded |
-| Update | UpdateState | ChildDiscarded (optional) |
-| Discard | | |
-| Args | | |
-| Events | | |
-
-### Generate
-Generate is called when a widget is first instantiated. It should create all the instances and properly adjust them to fit the config properties.
-Generate is also called when style properties change.
-
-Generate should return the instance which acts as the root of the widget. (what should be parented to the parents designated Instance)
-
-### Update
-Update is called only after instantiation and when widget arguments have changed.
-For instance, in `Iris.Text`
-```lua
-Update = function(thisWidget)
- local Text = thisWidget.Instance
- if thisWidget.arguments.Text == nil then
- error("Iris.Text Text Argument is required", 5)
- end
- Text.Text = thisWidget.arguments.Text
-end
-```
-
-### Discard
-Discard is called when the widget stops being displayed. In most cases the function body should resemble this:
-```lua
-Discard = function(thisWidget)
- thisWidget.Instance:Destroy()
-end
-```
-
-### Events
-Events is a table, not a method. It contains all of the possible events which a widget can have. Lets look at the hovered event as an example.
-```lua
-["hovered"] = {
- ["Init"] = function(thisWidget)
- local hoveredGuiObject = thisWidget.Instance
- thisWidget.isHoveredEvent = false
-
- hoveredGuiObject.MouseEnter:Connect(function()
- thisWidget.isHoveredEvent = true
- end)
- hoveredGuiObject.MouseLeave:Connect(function()
- thisWidget.isHoveredEvent = false
- end)
- end,
- ["Get"] = function(thisWidget)
- return thisWidget.isHoveredEvent
- end
-}
-```
-Every event has 2 methods, `Init` and `Get`.
-`Init` is called when a widget first polls the value of an event.
-Because of this, you can instantiate events and variables for an event to only widgets which need it.
-`Get` is the actual function which is called by the call to an event (like `Button.hovered()`), it should return the event value.
-
-### Args
-Args is a table, not a method. It enumerates all of the possible arguments which may be passed as arguments into the widget.
-The order of the tables indicies indicate which position the Argument will be interpreted as. For instance, in `Iris.Text`:
-```lua
-Args = {
- ["Text"] = 1
-}
-```
-when a Text widget is generated, the first index of the Arguments table will be interpreted as the 'Text' parameter
-```lua
-Iris.Text({[1] = "Hello"})
--- same result
-Iris.Text({"Hello"})
-```
-the `Update` function can retrieve arguments from `thisWidget.arguments`, such as `thisWidget.arguments.Text`
-
-### GenerateState
-GenerateState is called when the widget is first Instantiated, It should generate any state objects which weren't passed as a state by the user.
-For Instance, in `Iris.Checkbox`:
-```lua
-GenerateState = function(thisWidget)
- if thisWidget.state.isChecked == nil then
- thisWidget.state.isChecked = Iris._widgetState(thisWidget, "checked", false)
- end
-end
-```
-
-### UpdateState
-UpdateState is called whenever ANY state objects are updated, using its :set() method.
-For instance, in `Iris.Checkbox`:
-```lua
-UpdateState = function(thisWidget)
- local Checkbox = thisWidget.Instance.CheckboxBox
- if thisWidget.state.isChecked.value then
- Checkbox.Text = ICONS.CHECK_MARK
- thisWidget.events.checked = true
- else
- Checkbox.Text = ""
- thisWidget.events.unchecked = true
- end
-end
-```
-:::caution
-calling :set() to any of a widget's own state objects inside of UpdateState may cause an infinite loop of state updates.
-UpdateState should avoid calling :set().
-:::
-
-### ChildAdded
-ChildAdded is called when a widget is first Initiated and is a child of the widget. ChildAdded should return the Instance which the Child will be parented to.
-
-### ChildDiscarded
-ChildDiscarded is called when a widget is Discarded and is a child of the widget. ChildDiscarded is optional.
-
-## Widget Usage
-
-To use this widget once it has been constructed, you can use:
-```lua
-Iris.Internal._Insert("Text", {"Sampele text"}, nil) -- "Text" argument and no state
-```
-This is the same as calling any other widget but requires the widget name as passed to `Iris.WidgetConstructor()` as the first argument.
-
-***
-
-## When does a widget need to have state?
-State should only be used by widgets when there are properties which are able to be set by BOTH the widget, and by the user's code.
-
-For Instance, `Iris.Window` has a state, `size`. This field can be changed by the user's code, to adjust or initiate the size, and the widget also changes the size when it is resized.
-
-If the window was never able to change the size property, such as if there were no resize feature, then instead it should be an argument.
-
-This table demonstrates the relation between User / Widget permissions, and where the field should belong inside the widget class.
-
-
-
diff --git a/docs/getting_started.md b/docs/getting_started.md
new file mode 100644
index 0000000..cae2f0b
--- /dev/null
+++ b/docs/getting_started.md
@@ -0,0 +1,186 @@
+---
+sidebar_position: 2
+---
+
+# Getting Started
+
+## Installing Iris
+
+Iris is available to download using Wally, use the release from GitHub, or build yourself. It is best to
+place Iris somewhere on the client, such as under `StarterPlayerScripts` or `ReplicatedStorage`. Once
+Iris is installed, you can `require(path.to.Iris)` the module from any client script. Iris also has a
+public types system, which you can access from `require(path.to.Iris.PubTypes)`. To start Iris, you will
+need to run `Iris.Init()` before using Iris anywhere else. This can be difficult when you have multiple
+scripts running at the same time, so it is best to organise your code with a single entry point to
+initialise Iris from.
+
+# Checking Iris Works
+
+We can first test Iris works properly by using the DemoWindow, to display all the widgets in Iris.
+First we'll create a client script under `StarterPlayer.StarterPlayerScipts`, and put this into it:
+```lua
+local Iris = require(path.to.Iris)
+
+Iris.Init()
+Iris:Connect(Iris.ShowDemoWindow)
+```
+If we then run the game, we should see the Iris Demo Window appear on the screen. This shows that Iris
+is working properly and we can start writing our own code. Check [here](intro.md) for some example code,
+read through the [`demoWindow.lua`](https://github.com/SirMallard/Iris/blob/main/lib/demoWindow.lua)
+file to see how the demo window works, or check the rest of the documentation for each widget.
+
+## Understanding the API
+
+The Iris API is fairly unique and can be difficult to understand initially. However, once understood, it
+becomes much clearer and is consistent between all widgets.
+
+We will use a Window as an example because it best demonstrates the API and is used in every Iris project.
+
+The API documentation for a window is as follows and contains all the information we need:
+```lua
+hasChildren = true
+hasState = true
+Arguments = {
+ Title: string,
+ NoTitleBar: boolean? = false,
+ NoBackground: boolean? = false, -- the background behind the widget container.
+ NoCollapse: boolean? = false,
+ NoClose: boolean? = false,
+ NoMove: boolean? = false,
+ NoScrollbar: boolean? = false, -- the scrollbar if the window is too short for all widgets.
+ NoResize: boolean? = false,
+ NoNav: boolean? = false, -- unimplemented.
+ NoMenu: boolean? = false -- whether the menubar will show if created.
+}
+Events = {
+ opened: () -> boolean, -- once when opened.
+ closed: () -> boolean, -- once when closed.
+ collapsed: () -> boolean, -- once when collapsed.
+ uncollapsed: () -> boolean, -- once when uncollapsed.
+ hovered: () -> boolean -- fires when the mouse hovers over any of the window.
+}
+States = {
+ size = State? = Vector2.new(400, 300),
+ position = State?,
+ isUncollapsed = State? = true,
+ isOpened = State? = true,
+ scrollDistance = State? -- vertical scroll distance, if too short.
+}
+```
+
+The first documentation says that a Window has children, and therefore, we know that calling `Iris.Windw()`
+must always be followed eventually by `Iris.End()` to exit out of the window. We are then told that a window
+has state, and the different states, their types and default values are shown in the State table. We are also
+told that they are all optional, and will be created if not provided.
+
+### Using Arguments
+
+The next information is the Arguments table. This contains the ordered list of all arguments, the type and
+default value if optional. For a Window, the Title is a required string, whereas the other arguments are all
+optional booleans defaulting to false. We will thus need to provide a string as the first argument for any window.
+
+:::info
+THe arguments provided to a widget are sent as an array with index 1 as the first argument, index 2 as the
+second and so on. This means it is possible to provide the arguments in a different order, such as
+`{ [1] = "Title", [6] = true}` which provides the title and also sets `NoMove` to true. We therefore do not
+have to provide
+:::
+
+We will ignore the Events table for now, since they are not required for calling a widget.
+
+The window API prototype looks like this: `(arguments: { any }, states: { [string]: State }?) -> Window`.
+Each widget is a function which takes two parameters, an array of arguments and a string dictionary of States.
+Notice how the arguments array is required but the state dictionary is optional, because none of the states
+is optional. If the arguments were all optional, then the arguments array would itself also be optional.
+
+Using this, we can now assemble our API call for a window. The arguments for this will be the `TItle`, `NoClose`
+and `NoResize`. We will not provide any states, instead Iris will generate them for us. Our final function looks
+like this:
+
+```lua
+local Iris = require(Iris)
+
+-- These are all equivalent:
+Iris.Window({"Title", nil, nil, nil, true, nil, nil, true})
+Iris.Window({ [1] = "Title", [5] = true, [8] = true })
+Iris.Window({ [Iris.Args.Window.Title] = "Title", [Iris.Args.Window.NoClose] = true, [Iris.Args.Window.NoResize] = true })
+```
+For the last two, the order no longer matters and the arguments can be placed in any order. The last one uses
+`Iris.Args.[WIDGET].[ARGUMENT]` which contains the index or number for each argument position. It makes it clearer
+which arguments you are using, but at the cost of longer function calls. This is generally only used for widgets
+with rarely used arguments.
+:::info
+These are what the values actually are.
+`Iris.Args.Window.Title` = 1
+`Iris.Args.Window.NoClose` = 5
+`Iris.Args.Window.NoResize` = 8
+
+These are just shorthands, making it easier for you, if you choose to use them.
+
+Providing `{Title = "Title"}` or any variation of this with a string index will not work and will error.
+:::
+
+Iris is designed to mainly use the first example, because it is very similar to Dear ImGui and acts the same way
+as if providing the arguments directly to a function, where the order matters. However, because widgets have both
+arguments and state, the separation into two tables is required and we cannot use a regular function.
+
+### Using State
+
+If we decided that we wanted to provide a state to the widget, we can use the state table to determine the correct
+name and type for each widget. The state is what controls any properties which the user can both send and receive
+data from a widget, which may be updated by either the user or by an interaction with the widget. For example,
+moving a window around will change the position state. And if the user sets the position state somewhere in the
+code, the window will be moved to that position.
+
+:::info
+States in Iris take the place of pointers in C++ that Dear ImGui uses. If we have a number and then provide it as
+an parameter to a function, the value will be copied over in memory for the function and therefore updating the
+number in the function would not update it outside the function. If Lua had pointers, this would work, but instead
+we use states which are tables to store all the changes.
+:::
+
+Providing a state in Iris is very easy, we first create it and then provide it with the string name to the widget:
+```lua
+local positionState = Iris.State(Vector2.new(100, 100))
+
+Iris.Window({ "Positioned Window" }, { position = positionState })
+```
+
+We now have access to the window position state which we can set or read from anywhere else in our code. When first
+created, the window will be positioned at (100, 100) on the screen, but can still be moved around. Notice how we
+provide the state number rather than an index for the state table.
+
+We do not need to provide the state to use the widget, we can just grab it from the created widget:
+```lua
+local window = Iris.Window({ "Positioned Window" })
+
+local positionState = window.state.position
+```
+
+### Using Events
+
+We've covered children, arguments and state but not yet events. Events are what make widgets interactive and
+allow us run code when we use a widget. Each widget has a set of predefined events which we can check for
+every frame.
+
+To listen to any event, we can just call the function on the widget like this:
+
+```lua
+local window = Iris.Window({"Window"})
+-- the window has opened and uncollapsed events, which return booleans
+if window.opened() and window.uncollapsed() then
+ -- run the window code only if the window is actually open and uncollapsed,
+ -- which is more efficient.
+
+ -- the button has a clicked event, returning true when it is pressed
+ if Iris.Button({"Click me"}).clicked() then
+ -- run code if we click the button
+ end
+end
+Iris.End()
+```
+
+Here, we are listening to events which are just functions that return a boolean if the condition is true.
+We can refer to the API to find all the events, and they should be fairly self-explanatory in what they do.
+Some events will only happen once when the user interacts with the widget, others will depend on the state of
+the widget, such as if it is open.
diff --git a/docs/intro.md b/docs/intro.md
index 404a5bb..cb2cc02 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -1,9 +1,114 @@
+---
+sidebar_position: 1
+---
+
# Iris
-Iris is an Immediate mode GUI Library for Roblox, based on Dear ImGui. It is designed to make building debug, visualisation or content-creation tool UI easier, simpler and quicker.
+Iris is an Immediate mode GUI Library for Roblox, based on Dear ImGui, designed for building the UI for debug / visualisation and content-creation tools.
+
+Iris does this by requiring the developer to only describe what they want and not how they want it to be done, and Iris will handle all of the 'how'. This allows quick iterations from simple and minimal code to produce the tools you need anywhere in your game. Iris makes it faster and more convenient for developers to use a wide range of powerful widgets to build their UI systems. Iris is aimed at the developers who need to quickly check and alter code in development rather than an end UI solution, but how you use it is up to you.
+
+Iris uses an immediate mode UI paradigm, which is different from other conventional UI libraries designed for Roblox. Instead of keeping a reference to every UI element, with Iris you declare the UI you want to appear every frame and it will show you what you asked for. Iris also manages the layout and arrangment of systems whilst also giving control to you making it simple to construct a fully suite of UI debugging tools without worrying about where the UI is going to be positioned.
+
+## Demonstration
+
+### Simple Example
+
+With just 8 lines of code, you can create a basic window and widgets, with instant functionality:
+
+
+
+
+```lua
+local StarterPlayerScripts = game.StarterPlayer.StarterPlayerScripts
+local Iris = require(StarterPlayerScripts.Client.Iris).Init()
+
+Iris:Connect(function()
+ Iris.Window({"My First Window!"})
+ Iris.Text({"Hello, World"})
+ Iris.Button({"Save"})
+ Iris.InputNum({"Input"})
+ Iris.End()
+end)
+```
+
+
+
+
+
+
+We can break this code down to explain Iris better:
+```lua
+local StarterPlayerScripts = game.StarterPlayer.StarterPlayerScripts
+-- We first need to initialise Iris once before it is used anywhere. `Init()` will
+-- begin the main loop and set up the root widgets. Init can only be called once per
+-- client and returns Iris when called.
+local Iris = require(StarterPlayerScripts.Client.Iris).Init()
+
+-- 'Connect()' will run the provided function every frame. Iris code will need to run
+-- every frame to appear but you can use any other event and place your code anywhere
+Iris:Connect(function()
+ -- We create a window and give it a title of 'My First Window!'. All widgets will
+ -- be descended from a window which can be moved and scaled around the screen.
+ Iris.Window({"My First Window!"})
+ -- A text widget can show any text we want, including support for RichText.
+ Iris.Text({"Hello, World"})
+ -- A button has a clicked event which we can use to detech when the user
+ -- activates it and handle that any way we want.
+ Iris.Button({"Save"})
+ -- Iris has input, slider and drag widgets for each of the core datatype
+ -- with support for min, max and increments.
+ Iris.InputNum({"Input"})
+ -- Any widget which has children must end with an 'End()'. This includes
+ -- windows, trees, tables and a few others. To make it easier to see, we can use
+ -- a do-end loop wrapped around every parent widget.
+ Iris.End()
+end)
+```
+
+### More Complex Example
+
+We can also then make a more complicated example:
+
+
+
+
+```lua
+local StarterPlayerScripts = game.StarterPlayer.StarterPlayerScripts
+local Iris = require(StarterPlayerScripts.Client.Iris).Init()
+
+Iris:Connect(function()
+ local windowSize = Iris.State(Vector2.new(300, 400))
+
+ Iris.Window({"My Second Window"}, {size = windowSize})
+ Iris.Text({"The current time is: " .. time()})
+
+ Iris.InputText({"Enter Text"})
+
+ if Iris.Button({"Click me"}).clicked() then
+ print("button was clicked")
+ end
+
+ Iris.InputColor4()
+
+ Iris.Tree()
+ for i = 1,8 do
+ Iris.Text({"Text in a loop: " .. i})
+ end
+ Iris.End()
+ Iris.End()
+end)
+```
+
+
+
+
+
+
+This example has introduced the state object which allows us to control the state or value of Iris widgets and use these values in actual code. This is the bridge between your variables and being able to modify them in Iris. We also demonstrate the tree node which is useful for helping organise your UI tools.
+
+## Adding to your Game
-Iris does this by requiring the developer to only describe what they want and not how they want it, whilst Iris handles all of the 'how'. This makes it quicker for developers to build a usable and powerful system with minimal code from them. Iris offers a range of versatile widgets which are all designed to be minimal and yet powerful and fit together perfectly.
+So far we've seen how Iris works in a simple environment, but Iris is most helpful when you are using it alongside your main code. In order to showcase this, we have taken 'The Mystery of Duval Drive' and added Iris which allows us to test and modify the game state when testing the game.
-:::note
-This page is not complete yet and will updated.
-:::
+For more examples of Iris being used in actual games as either debug and visualisation tools or for content creation tooling, checkout the [Showcases](./showcase.md) page.
diff --git a/docs/showcase.md b/docs/showcase.md
index b2ef688..a0188c7 100644
--- a/docs/showcase.md
+++ b/docs/showcase.md
@@ -1,3 +1,7 @@
+---
+sidebar_position: 8
+---
+
# Showcases
Iris is already being used across Roblox. A few examples of what people have publicly posted and what they are saying about Iris are shown here. Over time, this page will be updated. If you are using Iris or see Iris being used somewhere, reach out to us so we can add it here.
diff --git a/lib/API.lua b/lib/API.lua
index 6313cac..33001d4 100644
--- a/lib/API.lua
+++ b/lib/API.lua
@@ -61,7 +61,7 @@ return function(Iris: Types.Iris)
NoScrollbar: boolean? = false, -- the scrollbar if the window is too short for all widgets.
NoResize: boolean? = false,
NoNav: boolean? = false, -- unimplemented.
- NoMenu: boolean? -- whether the menubar will show if created.
+ NoMenu: boolean? = false -- whether the menubar will show if created.
}
Events = {
opened: () -> boolean, -- once when opened.
@@ -156,7 +156,7 @@ return function(Iris: Types.Iris)
:::info
There are widgets which are designed for being parented to a menu whilst other happens to work. There is nothing
- preventing you from adding any widget as a child, but the behaviour is unexplained and not intended, despite allowed.
+ preventing you from adding any widget as a child, but the behaviour is unexplained and not intended.
:::
```lua
@@ -464,7 +464,7 @@ return function(Iris: Types.Iris)
hasState = false
Arguments = {
Text: string,
- Size: UDim2? = 0,
+ Size: UDim2? = UDim2.fromOffset(0, 0),
}
Events = {
clicked: () -> boolean,
@@ -1744,7 +1744,7 @@ return function(Iris: Types.Iris)
]=]
Iris.NextColumn = function()
local parentWidget = Iris.Internal._GetParentWidget() :: Types.Table
- assert(parentWidget.type == "Table", "Iris.NextColumn can only be called within a table.")
+ assert(parentWidget.type == "Table", "Iris.NextColumn() can only be called within a table.")
parentWidget.RowColumnIndex += 1
end
@@ -1757,8 +1757,8 @@ return function(Iris: Types.Iris)
]=]
Iris.SetColumnIndex = function(columnIndex: number)
local parentWidget = Iris.Internal._GetParentWidget() :: Types.Table
- assert(parentWidget.type == "Table", "Iris.SetColumnIndex can only be called within a table.")
- assert(columnIndex >= parentWidget.InitialNumColumns, "Iris.SetColumnIndex Argument must be in column range")
+ assert(parentWidget.type == "Table", "Iris.SetColumnIndex() can only be called within a table.")
+ assert(columnIndex >= parentWidget.InitialNumColumns, "Iris.SetColumnIndex() argument must be in column range.")
parentWidget.RowColumnIndex = math.floor(parentWidget.RowColumnIndex / parentWidget.InitialNumColumns) + (columnIndex - 1)
end
@@ -1772,7 +1772,7 @@ return function(Iris: Types.Iris)
Iris.NextRow = function()
-- sets column Index back to 0, increments Row
local parentWidget = Iris.Internal._GetParentWidget() :: Types.Table
- assert(parentWidget.type == "Table", "Iris.NextColumn can only be called within a table.")
+ assert(parentWidget.type == "Table", "Iris.NextColumn() can only be called within a table.")
local InitialNumColumns: number = parentWidget.InitialNumColumns
local nextRow: number = math.floor((parentWidget.RowColumnIndex + 1) / InitialNumColumns) * InitialNumColumns
parentWidget.RowColumnIndex = nextRow
diff --git a/lib/Internal.lua b/lib/Internal.lua
index b10c2f1..9ad38d0 100644
--- a/lib/Internal.lua
+++ b/lib/Internal.lua
@@ -242,7 +242,7 @@ return function(Iris: Types.Iris): Types.Internal
-- end
local compatibleParent: boolean = (Internal.parentInstance:IsA("GuiBase2d") or Internal.parentInstance:IsA("CoreGui") or Internal.parentInstance:IsA("PluginGui") or Internal.parentInstance:IsA("PlayerGui"))
if compatibleParent == false then
- error("Iris Parent Instance cant contain GUI")
+ error("The Iris parent instance will not display any GUIs.")
end
-- if we are running in Studio, we want full error tracebacks, so we don't have
@@ -277,7 +277,7 @@ return function(Iris: Types.Iris): Types.Internal
if Internal._stackIndex ~= 1 then
-- has to be larger than 1 because of the check that it isnt below 1 in Iris.End
Internal._stackIndex = 1
- error("Callback has too few calls to Iris.End()", 0)
+ error("Too few calls to Iris.End().", 0)
end
--debug.profileend()
diff --git a/lib/init.lua b/lib/init.lua
index 888d7e0..3137290 100644
--- a/lib/init.lua
+++ b/lib/init.lua
@@ -70,8 +70,8 @@ Iris.Events = {}
If the `eventConnection` is `false` then Iris will not create a cycle loop and the user will need to call [Internal._cycle] every frame.
]=]
function Iris.Init(parentInstance: Instance?, eventConnection: (RBXScriptSignal | () -> () | false)?): Types.Iris
- assert(Internal._started == false, "Iris.Init can only be called once.")
- assert(Internal._shutdown == false, "Iris.Init cannot be called once shutdown.")
+ assert(Internal._started == false, "Iris.Init() can only be called once.")
+ assert(Internal._shutdown == false, "Iris.Init() cannot be called once shutdown.")
if parentInstance == nil then
-- coalesce to playerGui
@@ -153,7 +153,7 @@ end
]=]
function Iris:Connect(callback: () -> ()): () -> () -- this uses method syntax for no reason.
if Internal._started == false then
- warn("Iris:Connect() was called before calling Iris.Init(), the connected function will never run")
+ warn("Iris:Connect() was called before calling Iris.Init(); always initialise Iris first.")
end
local connectionIndex: number = #Internal._connectedFunctions + 1
Internal._connectedFunctions[connectionIndex] = callback
@@ -212,7 +212,7 @@ end
]=]
function Iris.End()
if Internal._stackIndex == 1 then
- error("Callback has too many calls to Iris.End()", 2)
+ error("Too many calls to Iris.End().", 2)
end
Internal._IDStack[Internal._stackIndex] = nil
@@ -339,7 +339,7 @@ Internal._globalRefreshRequested = false -- UpdatingGlobalConfig changes this to
Sets the id discriminator for the next widgets. Use [Iris.PopId] to remove it.
]=]
function Iris.PushId(ID: Types.ID)
- assert(typeof(ID) == "string", "Iris expected Iris.PushId id to PushId to be a string.")
+ assert(typeof(ID) == "string", "The ID argument to Iris.PushId() to be a string.")
Internal._pushedId = tostring(ID)
end
@@ -551,7 +551,7 @@ end
Here the `data._started` should never be updated directly, only through the `toggle` function. However, we still want to monitor the value and be able to change it.
Therefore, we use the callback to toggle the function for us and prevent Iris from updating the table value by returning false.
```lua
- local data ={
+ local data = {
_started = false
}
diff --git a/lib/widgets/Combo.lua b/lib/widgets/Combo.lua
index b8e02a7..496ff77 100644
--- a/lib/widgets/Combo.lua
+++ b/lib/widgets/Combo.lua
@@ -107,7 +107,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
GenerateState = function(thisWidget: Types.Selectable)
if thisWidget.state.index == nil then
if thisWidget.arguments.Index ~= nil then
- error("a shared state index is required for Selectables with an Index argument", 5)
+ error("A shared state index is required for Iris.Selectables() with an Index argument.", 5)
end
thisWidget.state.index = Iris._widgetState(thisWidget, "index", false)
end
diff --git a/lib/widgets/Input.lua b/lib/widgets/Input.lua
index 60761ec..9f11c19 100644
--- a/lib/widgets/Input.lua
+++ b/lib/widgets/Input.lua
@@ -68,7 +68,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
return v[index]
end
- error(`Incorrect datatype or value: {value} {typeof(value)} {index}`)
+ error(`Incorrect datatype or value: {value} {typeof(value)} {index}.`)
end
local function updateValueByIndex(value: T, index: number, newValue: number, arguments: Types.Arguments): T
@@ -134,7 +134,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
end
end
- error(`Incorrect datatype or value {value} {typeof(value)} {index}`)
+ error(`Incorrect datatype or value {value} {typeof(value)} {index}.`)
end
local defaultIncrements: { [InputDataTypes]: { number } } = {
diff --git a/lib/widgets/Table.lua b/lib/widgets/Table.lua
index 1fe661b..99fe938 100644
--- a/lib/widgets/Table.lua
+++ b/lib/widgets/Table.lua
@@ -62,7 +62,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
if thisWidget.InitialNumColumns == -1 then
if thisWidget.arguments.NumColumns == nil then
- error("Iris.Table NumColumns argument is required", 5)
+ error("NumColumns argument is required for Iris.Table().", 5)
end
thisWidget.InitialNumColumns = thisWidget.arguments.NumColumns
@@ -87,7 +87,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
elseif thisWidget.arguments.NumColumns ~= thisWidget.InitialNumColumns then
-- its possible to make it so that the NumColumns can increase,
-- but decreasing it would interfere with child widget instances
- error("Iris.Table NumColumns Argument must be static")
+ error("NumColumns Argument must be static for Iris.Table().")
end
if thisWidget.arguments.RowBg == false then
diff --git a/lib/widgets/Text.lua b/lib/widgets/Text.lua
index f1840bb..de173c5 100644
--- a/lib/widgets/Text.lua
+++ b/lib/widgets/Text.lua
@@ -33,7 +33,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
Update = function(thisWidget: Types.Text)
local Text = thisWidget.Instance :: TextLabel
if thisWidget.arguments.Text == nil then
- error("Iris.Text Text Argument is required", 5)
+ error("Text argument is required for Iris.Text().", 5)
end
if thisWidget.arguments.Wrapped ~= nil then
Text.TextWrapped = thisWidget.arguments.Wrapped
@@ -123,7 +123,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
local SeparatorText = thisWidget.Instance :: Frame
local TextLabel: TextLabel = SeparatorText.TextLabel
if thisWidget.arguments.Text == nil then
- error("Iris.Text Text Argument is required", 5)
+ error("Text argument is required for Iris.SeparatorText().", 5)
end
TextLabel.Text = thisWidget.arguments.Text
end,
diff --git a/lib/widgets/Window.lua b/lib/widgets/Window.lua
index 2a2d26c..182a6ed 100644
--- a/lib/widgets/Window.lua
+++ b/lib/widgets/Window.lua
@@ -63,7 +63,7 @@ return function(Iris: Types.Internal, widgets: Types.WidgetUtility)
local Tooltip = thisWidget.Instance :: Frame
local TooltipText: TextLabel = Tooltip.TooltipText
if thisWidget.arguments.Text == nil then
- error("Iris.Text Text Argument is required", 5)
+ error("Text argument is required for Iris.Tooltip().", 5)
end
TooltipText.Text = thisWidget.arguments.Text
relocateTooltips()
diff --git a/moonwave.toml b/moonwave.toml
index 3f7d7a3..356a4f7 100644
--- a/moonwave.toml
+++ b/moonwave.toml
@@ -35,9 +35,10 @@ classes = ["Internal"]
[docusaurus]
onBrokenMarkdownLinks = "warn"
+trailingSlash = false
[docusarus.themeConfig.prism]
additionalLangues = ["lua", "luau", "cpp"]
[footer]
style = "dark"
-copyright = "Copyright © 2024 Michael_48"
+copyright = "©2024 Michael_48 & SirMallard"