Skip to content

Commit

Permalink
Better Types
Browse files Browse the repository at this point in the history
# Better Types
Redesigned the types to create a unique one for each widget containing only the necessary arguments and properties. Added generic type support to State widgets which should make it clearer to use.

This may be inconvenient to have a type for every widget, but it should make Iris easier to use with intellisense which is one of the biggest issues I here of.

## Additions
- State objects and Input widgets use generic types rather than any.
- PubTypes file for public types which most users only need to be exposed to.
- WidgetTypes file containing a unique type for every widget.

## Changes
- Removed unnecessary thisWidget arguments to certain utility functions.
- Fixed child widget naming inconsistencies.

## Fixes
- Fixed Style Window reset not chaning combo boxes.
  • Loading branch information
SirMallard committed Sep 2, 2024
1 parent 7be1b09 commit 211d296
Show file tree
Hide file tree
Showing 22 changed files with 1,199 additions and 732 deletions.
54 changes: 43 additions & 11 deletions lib/API.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ local Types = require(script.Parent.Types)

return function(Iris: Types.Iris)
-- basic wrapper for nearly every widget, saves space.
local function wrapper(name: string): (arguments: Types.WidgetArguments?, states: Types.WidgetStates?) -> Types.Widget
local function wrapper(name: string)
return function(arguments: Types.WidgetArguments?, states: Types.WidgetStates?): Types.Widget
return Iris.Internal._Insert(name, arguments, states)
end
Expand Down Expand Up @@ -84,7 +84,7 @@ return function(Iris: Types.Iris)
--[=[
@function SetFocusedWindow
@within Iris
@param window Types.Widget -- the window to focus.
@param window Types.Window -- the window to focus.
Sets the focused window to the window provided, which brings it to the front and makes it active.
]=]
Expand Down Expand Up @@ -362,9 +362,9 @@ return function(Iris: Types.Iris)
}
```
]=]
Iris.TextWrapped = function(arguments: Types.WidgetArguments): Types.Widget
Iris.TextWrapped = function(arguments: Types.WidgetArguments): Types.Text
arguments[2] = true
return Iris.Internal._Insert("Text", arguments)
return Iris.Internal._Insert("Text", arguments) :: Types.Text
end

--[=[
Expand All @@ -387,10 +387,10 @@ return function(Iris: Types.Iris)
}
```
]=]
Iris.TextColored = function(arguments: Types.WidgetArguments): Types.Widget
Iris.TextColored = function(arguments: Types.WidgetArguments): Types.Text
arguments[3] = arguments[2]
arguments[2] = nil
return Iris.Internal._Insert("Text", arguments)
return Iris.Internal._Insert("Text", arguments) :: Types.Text
end

--[=[
Expand Down Expand Up @@ -1495,15 +1495,15 @@ return function(Iris: Types.Iris)
}
```
]=]
Iris.ComboArray = function(arguments: Types.WidgetArguments, states: Types.WidgetStates?, selectionArray: { any })
Iris.ComboArray = function<T>(arguments: Types.WidgetArguments, states: Types.WidgetStates?, selectionArray: { T })
local defaultState
if states == nil then
defaultState = Iris.State(selectionArray[1])
else
defaultState = states
end
local thisWidget = Iris.Internal._Insert("Combo", arguments, defaultState)
local sharedIndex: Types.State = thisWidget.state.index
local sharedIndex: Types.State<T> = thisWidget.state.index
for _, Selection in selectionArray do
Iris.Internal._Insert("Selectable", { Selection, Selection }, { index = sharedIndex } :: Types.States)
end
Expand Down Expand Up @@ -1560,6 +1560,38 @@ return function(Iris: Types.Iris)

return thisWidget
end

--[=[
@private
@prop InputEnum Iris.InputEnum
@within Slider
@tag Widget
@tag HasState
A field which allows the user to slide a grip to enter a number within a range.
You can ctrl + click to directly input a number, like InputNum.
```lua
hasChildren = false
hasState = true
Arguments = {
Text: string? = "InputEnum",
Increment: number? = 1,
Min: number? = 0,
Max: number? = 100,
Format: string? | { string }? = [DYNAMIC] -- Iris will dynamically generate an approriate format.
}
Events = {
numberChanged: () -> boolean,
hovered: () -> boolean
}
States = {
number: State<number>?,
editingText: State<boolean>?,
enumItem: EnumItem
}
```
]=]
Iris.InputEnum = Iris.ComboEnum

--[[
Expand Down Expand Up @@ -1640,7 +1672,7 @@ return function(Iris: Types.Iris)
then the next cell will be the first column of the next row.
]=]
Iris.NextColumn = function()
local parentWidget: Types.Widget = Iris.Internal._GetParentWidget()
local parentWidget = Iris.Internal._GetParentWidget() :: Types.Table
assert(parentWidget.type == "Table", "Iris.NextColumn can only be called within a table.")
parentWidget.RowColumnIndex += 1
end
Expand All @@ -1653,7 +1685,7 @@ return function(Iris: Types.Iris)
In a table, directly sets the index of the column.
]=]
Iris.SetColumnIndex = function(columnIndex: number)
local parentWidget: Types.Widget = Iris.Internal._GetParentWidget()
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")
parentWidget.RowColumnIndex = math.floor(parentWidget.RowColumnIndex / parentWidget.InitialNumColumns) + (columnIndex - 1)
Expand All @@ -1668,7 +1700,7 @@ return function(Iris: Types.Iris)
]=]
Iris.NextRow = function()
-- sets column Index back to 0, increments Row
local parentWidget: Types.Widget = Iris.Internal._GetParentWidget()
local parentWidget = Iris.Internal._GetParentWidget() :: Types.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
Expand Down
44 changes: 23 additions & 21 deletions lib/Internal.lua
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ return function(Iris: Types.Iris): Types.Internal
Returns the states current value.
]=]
function StateClass:get(): any -- you can also simply use .value
function StateClass:get<T>(): T -- you can also simply use .value
return self.value
end

Expand All @@ -137,7 +137,7 @@ return function(Iris: Types.Iris): Types.Internal
Allows the caller to assign the state object a new value, and returns the new value.
]=]
function StateClass:set(newValue: any): any
function StateClass:set<T>(newValue: T): T
if newValue == self.value then
-- no need to update on no change.
return self.value
Expand All @@ -158,7 +158,7 @@ return function(Iris: Types.Iris): Types.Internal
Allows the caller to connect a callback which is called when the states value is changed.
]=]
function StateClass:onChange(callback: (newValue: any) -> ()): () -> ()
function StateClass:onChange<T>(callback: (newValue: T) -> ()): () -> ()
local connectionIndex: number = #self.ConnectedFunctions + 1
self.ConnectedFunctions[connectionIndex] = callback
return function()
Expand Down Expand Up @@ -443,7 +443,7 @@ return function(Iris: Types.Iris): Types.Internal
end
local thisWidget: Types.Widget = if lastWidget == nil then Internal._GenNewWidget(widgetType, arguments, states, ID) else lastWidget

local parentWidget: Types.Widget = thisWidget.parentWidget
local parentWidget: Types.ParentWidget = thisWidget.parentWidget

if thisWidget.type ~= "Window" and thisWidget.type ~= "Tooltip" then
if thisWidget.ZIndex ~= parentWidget.ZOffset then
Expand Down Expand Up @@ -472,9 +472,10 @@ return function(Iris: Types.Iris): Types.Internal
parentWidget.ZOffset += 1

if thisWidgetClass.hasChildren then
local thisParent = thisWidget :: Types.ParentWidget
-- a parent widget, so we increase our depth.
thisWidget.ZOffset = 0
thisWidget.ZUpdate = false
thisParent.ZOffset = 0
thisParent.ZUpdate = false
Internal._stackIndex += 1
Internal._IDStack[Internal._stackIndex] = thisWidget.ID
end
Expand All @@ -501,7 +502,7 @@ return function(Iris: Types.Iris): Types.Internal
]=]
function Internal._GenNewWidget(widgetType: string, arguments: Types.Arguments, states: Types.WidgetStates?, ID: Types.ID): Types.Widget
local parentId: Types.ID = Internal._IDStack[Internal._stackIndex]
local parentWidget: Types.Widget = Internal._VDOM[parentId]
local parentWidget: Types.ParentWidget = Internal._VDOM[parentId]
local thisWidgetClass: Types.WidgetClass = Internal._widgets[widgetType]

-- widgets are just tables with properties.
Expand Down Expand Up @@ -534,31 +535,32 @@ return function(Iris: Types.Iris): Types.Internal

local eventMTParent
if thisWidgetClass.hasState then
local stateWidget = thisWidget :: Types.StateWidget
if states then
for index: string, state: Types.State in states do
for index: string, state: Types.State<any> in states do
if not (type(state) == "table" and getmetatable(state :: any) == Internal.StateClass) then
-- generate a new state.
states[index] = Internal._widgetState(thisWidget, index, state)
states[index] = Internal._widgetState(stateWidget, index, state)
end
end

thisWidget.state = states
for _, state: Types.State in states do
state.ConnectedWidgets[thisWidget.ID] = thisWidget
stateWidget.state = states
for _, state: Types.State<any> in states do
state.ConnectedWidgets[stateWidget.ID] = stateWidget
end
else
thisWidget.state = {}
stateWidget.state = {}
end

thisWidgetClass.GenerateState(thisWidget)
thisWidgetClass.UpdateState(thisWidget)
thisWidgetClass.GenerateState(stateWidget)
thisWidgetClass.UpdateState(stateWidget)

-- the state MT can't be itself because state has to explicitly only contain stateClass objects
thisWidget.stateMT = {}
setmetatable(thisWidget.state, thisWidget.stateMT)
stateWidget.stateMT = {}
setmetatable(stateWidget.state, stateWidget.stateMT)

thisWidget.__index = thisWidget.state
eventMTParent = thisWidget.stateMT
stateWidget.__index = stateWidget.state
eventMTParent = stateWidget.stateMT
else
eventMTParent = thisWidget
end
Expand Down Expand Up @@ -630,7 +632,7 @@ return function(Iris: Types.Iris): Types.Internal
Connects the state to the widget. If no state exists then a new one is created. Called for every state in every
widget if the user does not provide a state.
]=]
function Internal._widgetState(thisWidget: Types.Widget, stateName: string, initialValue: any): Types.State
function Internal._widgetState(thisWidget: Types.StateWidget, stateName: string, initialValue: any): Types.State<any>
local ID: Types.ID = thisWidget.ID .. stateName
if Internal._states[ID] then
Internal._states[ID].ConnectedWidgets[thisWidget.ID] = thisWidget
Expand Down Expand Up @@ -675,7 +677,7 @@ return function(Iris: Types.Iris): Types.Internal
Returns the parent widget of the currently active widget, based on the stack depth.
]=]
function Internal._GetParentWidget(): Types.Widget
function Internal._GetParentWidget(): Types.ParentWidget
return Internal._VDOM[Internal._IDStack[Internal._stackIndex]]
end

Expand Down
39 changes: 39 additions & 0 deletions lib/PubTypes.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
local Types = require(script.Parent.Types)

export type ID = Types.ID
export type State<T> = Types.State<T>

export type Widget = Types.Widget
export type Root = Types.Root
export type Window = Types.Window
export type Tooltip = Types.Tooltip
export type MenuBar = Types.MenuBar
export type Menu = Types.Menu
export type MenuItem = Types.MenuItem
export type MenuToggle = Types.MenuToggle
export type Separator = Types.Separator
export type Indent = Types.Indent
export type SameLine = Types.SameLine
export type Group = Types.Group
export type Text = Types.Text
export type SeparatorText = Types.SeparatorText
export type Button = Types.Button
export type Checkbox = Types.Checkbox
export type RadioButton = Types.RadioButton
export type Image = Types.Image
export type ImageButton = Types.ImageButton
export type Tree = Types.Tree
export type CollapsingHeader = Types.CollapsingHeader
export type Input<T> = Types.Input<T>
export type InputColor3 = Types.InputColor3
export type InputColor4 = Types.InputColor4
export type InputEnum = Types.InputEnum
export type InputText = Types.InputText
export type Selectable = Types.Selectable
export type Combo = Types.Combo
export type ProgressBar = Types.ProgressBar
export type Table = Types.Table

export type Iris = Types.Iris

return {}
Loading

0 comments on commit 211d296

Please sign in to comment.