diff --git a/default.project.json b/default.project.json index dca8bb7..b882aa6 100644 --- a/default.project.json +++ b/default.project.json @@ -1,60 +1,6 @@ { "name": "iris", "tree": { - "$className": "DataModel", - - "StarterPlayer": { - "StarterPlayerScripts": { - "Client": { - "$path": "src/client" - } - } - }, - - "Workspace": { - "$properties": { - "FilteringEnabled": true - }, - "Baseplate": { - "$className": "Part", - "$properties": { - "Anchored": true, - "Color": [ - 0.38823, - 0.37254, - 0.38823 - ], - "Locked": true, - "Position": [ - 0, - -10, - 0 - ], - "Size": [ - 512, - 20, - 512 - ] - } - } - }, - "Lighting": { - "$properties": { - "Ambient": [ - 0, - 0, - 0 - ], - "Brightness": 2, - "GlobalShadows": true, - "Outlines": false, - "Technology": "Voxel" - } - }, - "SoundService": { - "$properties": { - "RespectFilteringEnabled": true - } - } + "$path": "lib" } } \ No newline at end of file diff --git a/dev.project.json b/dev.project.json new file mode 100644 index 0000000..7faa8bf --- /dev/null +++ b/dev.project.json @@ -0,0 +1,18 @@ +{ + "name": "iris", + "tree": { + "$className": "DataModel", + + "ReplicatedStorage": { + "Iris": { + "$path": "default.project.json" + } + }, + + "StarterPlayer": { + "StarterPlayerScripts": { + "$path": "src/client" + } + } + } +} \ No newline at end of file diff --git a/src/client/Iris/API.lua b/lib/API.lua similarity index 100% rename from src/client/Iris/API.lua rename to lib/API.lua diff --git a/src/client/Iris/Internal.lua b/lib/Internal.lua similarity index 100% rename from src/client/Iris/Internal.lua rename to lib/Internal.lua diff --git a/src/client/Iris/Types.lua b/lib/Types.lua similarity index 100% rename from src/client/Iris/Types.lua rename to lib/Types.lua diff --git a/src/client/Iris/config.lua b/lib/config.lua similarity index 97% rename from src/client/Iris/config.lua rename to lib/config.lua index a72eeda..52ef839 100644 --- a/src/client/Iris/config.lua +++ b/lib/config.lua @@ -1,242 +1,242 @@ -local TemplateConfig = { - colorDark = { -- Dear, ImGui default dark - TextColor = Color3.fromRGB(255, 255, 255), - TextTransparency = 0, - TextDisabledColor = Color3.fromRGB(128, 128, 128), - TextDisabledTransparency = 0, - - BorderColor = Color3.fromRGB(110, 110, 125), - -- Dear ImGui uses 110, 110, 125 - -- The Roblox window selection highlight is 67, 191, 254 - BorderActiveColor = Color3.fromRGB(160, 160, 175), -- does not exist in Dear ImGui - - BorderTransparency = 0, - BorderActiveTransparency = 0, - -- BorderTransparency will be problematic for non UIStroke border implimentations - -- is not implimented because of this - - WindowBgColor = Color3.fromRGB(15, 15, 15), - WindowBgTransparency = 0.072, - - ScrollbarGrabColor = Color3.fromRGB(128, 128, 128), - ScrollbarGrabTransparency = 0, - - TitleBgColor = Color3.fromRGB(10, 10, 10), - TitleBgTransparency = 0, - TitleBgActiveColor = Color3.fromRGB(41, 74, 122), - TitleBgActiveTransparency = 0, - TitleBgCollapsedColor = Color3.fromRGB(0, 0, 0), - TitleBgCollapsedTransparency = 0.5, - - MenubarBgColor = Color3.fromRGB(36, 36, 36), - MenubarBgTransparency = 0, - - FrameBgColor = Color3.fromRGB(41, 74, 122), - FrameBgTransparency = 0.46, - FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), - FrameBgHoveredTransparency = 0.46, - FrameBgActiveColor = Color3.fromRGB(66, 150, 250), - FrameBgActiveTransparency = 0.33, - - ButtonColor = Color3.fromRGB(66, 150, 250), - ButtonTransparency = 0.6, - ButtonHoveredColor = Color3.fromRGB(66, 150, 250), - ButtonHoveredTransparency = 0, - ButtonActiveColor = Color3.fromRGB(15, 135, 250), - ButtonActiveTransparency = 0, - - SliderGrabColor = Color3.fromRGB(66, 150, 250), - SliderGrabTransparency = 0, - SliderGrabActiveColor = Color3.fromRGB(117, 138, 204), - SliderGrabActiveTransparency = 0, - - HeaderColor = Color3.fromRGB(66, 150, 250), - HeaderTransparency = 0.69, - HeaderHoveredColor = Color3.fromRGB(66, 150, 250), - HeaderHoveredTransparency = 0.2, - HeaderActiveColor = Color3.fromRGB(66, 150, 250), - HeaderActiveTransparency = 0, - - SelectionImageObjectColor = Color3.fromRGB(255, 255, 255), - SelectionImageObjectTransparency = 0.8, - SelectionImageObjectBorderColor = Color3.fromRGB(255, 255, 255), - SelectionImageObjectBorderTransparency = 0, - - TableBorderStrongColor = Color3.fromRGB(79, 79, 89), - TableBorderStrongTransparency = 0, - TableBorderLightColor = Color3.fromRGB(59, 59, 64), - TableBorderLightTransparency = 0, - TableRowBgColor = Color3.fromRGB(0, 0, 0), - TableRowBgTransparency = 1, - TableRowBgAltColor = Color3.fromRGB(255, 255, 255), - TableRowBgAltTransparency = 0.94, - - NavWindowingHighlightColor = Color3.fromRGB(255, 255, 255), - NavWindowingHighlightTransparency = 0.3, - NavWindowingDimBgColor = Color3.fromRGB(204, 204, 204), - NavWindowingDimBgTransparency = 0.65, - - SeparatorColor = Color3.fromRGB(110, 110, 128), - SeparatorTransparency = 0.5, - - CheckMarkColor = Color3.fromRGB(66, 150, 250), - CheckMarkTransparency = 0, - }, - colorLight = { -- Dear, ImGui default light - TextColor = Color3.fromRGB(0, 0, 0), - TextTransparency = 0, - TextDisabledColor = Color3.fromRGB(153, 153, 153), - TextDisabledTransparency = 0, - - BorderColor = Color3.fromRGB(64, 64, 64), - -- Dear ImGui uses 0, 0, 0, 77 - -- The Roblox window selection highlight is 67, 191, 254 - BorderActiveColor = Color3.fromRGB(64, 64, 64), -- does not exist in Dear ImGui - - -- BorderTransparency = 0.5, - -- BorderTransparency will be problematic for non UIStroke border implimentations - -- will not be implimented because of this - - WindowBgColor = Color3.fromRGB(240, 240, 240), - WindowBgTransparency = 0, - - TitleBgColor = Color3.fromRGB(245, 245, 245), - TitleBgTransparency = 0, - TitleBgActiveColor = Color3.fromRGB(209, 209, 209), - TitleBgActiveTransparency = 0, - TitleBgCollapsedColor = Color3.fromRGB(255, 255, 255), - TitleBgCollapsedTransparency = 0.5, - - MenubarBgColor = Color3.fromRGB(219, 219, 219), - MenubarBgTransparency = 0, - - ScrollbarGrabColor = Color3.fromRGB(96, 96, 96), - ScrollbarGrabTransparency = 0, - - FrameBgColor = Color3.fromRGB(255, 255, 255), - FrameBgTransparency = 0.6, - FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), - FrameBgHoveredTransparency = 0.6, - FrameBgActiveColor = Color3.fromRGB(66, 150, 250), - FrameBgActiveTransparency = 0.33, - - ButtonColor = Color3.fromRGB(66, 150, 250), - ButtonTransparency = 0.6, - ButtonHoveredColor = Color3.fromRGB(66, 150, 250), - ButtonHoveredTransparency = 0, - ButtonActiveColor = Color3.fromRGB(15, 135, 250), - ButtonActiveTransparency = 0, - - HeaderColor = Color3.fromRGB(66, 150, 250), - HeaderTransparency = 0.31, - HeaderHoveredColor = Color3.fromRGB(66, 150, 250), - HeaderHoveredTransparency = 0.2, - HeaderActiveColor = Color3.fromRGB(66, 150, 250), - HeaderActiveTransparency = 0, - - SliderGrabColor = Color3.fromRGB(61, 133, 224), - SliderGrabTransparency = 0, - SliderGrabActiveColor = Color3.fromRGB(66, 150, 250), - SliderGrabActiveTransparency = 0, - - SelectionImageObjectColor = Color3.fromRGB(0, 0, 0), - SelectionImageObjectTransparency = 0.8, - SelectionImageObjectBorderColor = Color3.fromRGB(0, 0, 0), - SelectionImageObjectBorderTransparency = 0, - - TableBorderStrongColor = Color3.fromRGB(145, 145, 163), - TableBorderStrongTransparency = 0, - TableBorderLightColor = Color3.fromRGB(173, 173, 189), - TableBorderLightTransparency = 0, - TableRowBgColor = Color3.fromRGB(0, 0, 0), - TableRowBgTransparency = 1, - TableRowBgAltColor = Color3.fromRGB(77, 77, 77), - TableRowBgAltTransparency = 0.91, - - NavWindowingHighlightColor = Color3.fromRGB(179, 179, 179), - NavWindowingHighlightTransparency = 0.3, - NavWindowingDimBgColor = Color3.fromRGB(51, 51, 51), - NavWindowingDimBgTransparency = 0.8, - - SeparatorColor = Color3.fromRGB(99, 99, 99), - SeparatorTransparency = 0.38, - - CheckMarkColor = Color3.fromRGB(66, 150, 250), - CheckMarkTransparency = 0, - }, - - sizeDefault = { -- Dear, ImGui default - ItemWidth = UDim.new(1, 0), - ContentWidth = UDim.new(0.65, 0), - - WindowPadding = Vector2.new(8, 8), - WindowResizePadding = Vector2.new(6, 6), - FramePadding = Vector2.new(4, 3), - ItemSpacing = Vector2.new(8, 4), - ItemInnerSpacing = Vector2.new(4, 4), - CellPadding = Vector2.new(4, 2), - DisplaySafeAreaPadding = Vector2.new(0, 0), - SeparatorTextPadding = Vector2.new(20, 3), - IndentSpacing = 21, - - TextFont = Font.fromEnum(Enum.Font.Code), - TextSize = 13, - FrameBorderSize = 0, - FrameRounding = 0, - GrabRounding = 0, - WindowRounding = 0, -- these don't actually work but it's nice to have them. - WindowBorderSize = 1, - WindowTitleAlign = Enum.LeftRight.Left, - PopupBorderSize = 1, - PopupRounding = 0, - ScrollbarSize = 7, - GrabMinSize = 10, - SeparatorTextBorderSize = 3, - }, - sizeClear = { -- easier to read and manuveure - ItemWidth = UDim.new(1, 0), - ContentWidth = UDim.new(0.65, 0), - - WindowPadding = Vector2.new(12, 8), - WindowResizePadding = Vector2.new(8, 8), - FramePadding = Vector2.new(6, 4), - ItemSpacing = Vector2.new(8, 8), - ItemInnerSpacing = Vector2.new(8, 8), - CellPadding = Vector2.new(4, 4), - DisplaySafeAreaPadding = Vector2.new(8, 8), - SeparatorTextPadding = Vector2.new(24, 6), - IndentSpacing = 25, - - TextFont = Font.fromEnum(Enum.Font.Ubuntu), - TextSize = 15, - FrameBorderSize = 1, - FrameRounding = 4, - GrabRounding = 4, - WindowRounding = 4, - WindowBorderSize = 1, - WindowTitleAlign = Enum.LeftRight.Center, - PopupBorderSize = 1, - PopupRounding = 4, - ScrollbarSize = 9, - GrabMinSize = 14, - SeparatorTextBorderSize = 4, - }, - - utilityDefault = { - UseScreenGUIs = true, - IgnoreGuiInset = false, - Parent = nil, - RichText = false, - DisableWidget = false, - DisplayOrderOffset = 127, - ZIndexOffset = 0, - - MouseDoubleClickTime = 0.30, -- Time for a double-click, in seconds. - MouseDoubleClickMaxDist = 6.0, -- Distance threshold to stay in to validate a double-click, in pixels. - - HoverColor = Color3.fromRGB(255, 255, 0), - HoverTransparency = 0.1, - }, -} - -return TemplateConfig +local TemplateConfig = { + colorDark = { -- Dear, ImGui default dark + TextColor = Color3.fromRGB(255, 255, 255), + TextTransparency = 0, + TextDisabledColor = Color3.fromRGB(128, 128, 128), + TextDisabledTransparency = 0, + + BorderColor = Color3.fromRGB(110, 110, 125), + -- Dear ImGui uses 110, 110, 125 + -- The Roblox window selection highlight is 67, 191, 254 + BorderActiveColor = Color3.fromRGB(160, 160, 175), -- does not exist in Dear ImGui + + BorderTransparency = 0, + BorderActiveTransparency = 0, + -- BorderTransparency will be problematic for non UIStroke border implimentations + -- is not implimented because of this + + WindowBgColor = Color3.fromRGB(15, 15, 15), + WindowBgTransparency = 0.072, + + ScrollbarGrabColor = Color3.fromRGB(128, 128, 128), + ScrollbarGrabTransparency = 0, + + TitleBgColor = Color3.fromRGB(10, 10, 10), + TitleBgTransparency = 0, + TitleBgActiveColor = Color3.fromRGB(41, 74, 122), + TitleBgActiveTransparency = 0, + TitleBgCollapsedColor = Color3.fromRGB(0, 0, 0), + TitleBgCollapsedTransparency = 0.5, + + MenubarBgColor = Color3.fromRGB(36, 36, 36), + MenubarBgTransparency = 0, + + FrameBgColor = Color3.fromRGB(41, 74, 122), + FrameBgTransparency = 0.46, + FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), + FrameBgHoveredTransparency = 0.46, + FrameBgActiveColor = Color3.fromRGB(66, 150, 250), + FrameBgActiveTransparency = 0.33, + + ButtonColor = Color3.fromRGB(66, 150, 250), + ButtonTransparency = 0.6, + ButtonHoveredColor = Color3.fromRGB(66, 150, 250), + ButtonHoveredTransparency = 0, + ButtonActiveColor = Color3.fromRGB(15, 135, 250), + ButtonActiveTransparency = 0, + + SliderGrabColor = Color3.fromRGB(66, 150, 250), + SliderGrabTransparency = 0, + SliderGrabActiveColor = Color3.fromRGB(117, 138, 204), + SliderGrabActiveTransparency = 0, + + HeaderColor = Color3.fromRGB(66, 150, 250), + HeaderTransparency = 0.69, + HeaderHoveredColor = Color3.fromRGB(66, 150, 250), + HeaderHoveredTransparency = 0.2, + HeaderActiveColor = Color3.fromRGB(66, 150, 250), + HeaderActiveTransparency = 0, + + SelectionImageObjectColor = Color3.fromRGB(255, 255, 255), + SelectionImageObjectTransparency = 0.8, + SelectionImageObjectBorderColor = Color3.fromRGB(255, 255, 255), + SelectionImageObjectBorderTransparency = 0, + + TableBorderStrongColor = Color3.fromRGB(79, 79, 89), + TableBorderStrongTransparency = 0, + TableBorderLightColor = Color3.fromRGB(59, 59, 64), + TableBorderLightTransparency = 0, + TableRowBgColor = Color3.fromRGB(0, 0, 0), + TableRowBgTransparency = 1, + TableRowBgAltColor = Color3.fromRGB(255, 255, 255), + TableRowBgAltTransparency = 0.94, + + NavWindowingHighlightColor = Color3.fromRGB(255, 255, 255), + NavWindowingHighlightTransparency = 0.3, + NavWindowingDimBgColor = Color3.fromRGB(204, 204, 204), + NavWindowingDimBgTransparency = 0.65, + + SeparatorColor = Color3.fromRGB(110, 110, 128), + SeparatorTransparency = 0.5, + + CheckMarkColor = Color3.fromRGB(66, 150, 250), + CheckMarkTransparency = 0, + }, + colorLight = { -- Dear, ImGui default light + TextColor = Color3.fromRGB(0, 0, 0), + TextTransparency = 0, + TextDisabledColor = Color3.fromRGB(153, 153, 153), + TextDisabledTransparency = 0, + + BorderColor = Color3.fromRGB(64, 64, 64), + -- Dear ImGui uses 0, 0, 0, 77 + -- The Roblox window selection highlight is 67, 191, 254 + BorderActiveColor = Color3.fromRGB(64, 64, 64), -- does not exist in Dear ImGui + + -- BorderTransparency = 0.5, + -- BorderTransparency will be problematic for non UIStroke border implimentations + -- will not be implimented because of this + + WindowBgColor = Color3.fromRGB(240, 240, 240), + WindowBgTransparency = 0, + + TitleBgColor = Color3.fromRGB(245, 245, 245), + TitleBgTransparency = 0, + TitleBgActiveColor = Color3.fromRGB(209, 209, 209), + TitleBgActiveTransparency = 0, + TitleBgCollapsedColor = Color3.fromRGB(255, 255, 255), + TitleBgCollapsedTransparency = 0.5, + + MenubarBgColor = Color3.fromRGB(219, 219, 219), + MenubarBgTransparency = 0, + + ScrollbarGrabColor = Color3.fromRGB(96, 96, 96), + ScrollbarGrabTransparency = 0, + + FrameBgColor = Color3.fromRGB(255, 255, 255), + FrameBgTransparency = 0.6, + FrameBgHoveredColor = Color3.fromRGB(66, 150, 250), + FrameBgHoveredTransparency = 0.6, + FrameBgActiveColor = Color3.fromRGB(66, 150, 250), + FrameBgActiveTransparency = 0.33, + + ButtonColor = Color3.fromRGB(66, 150, 250), + ButtonTransparency = 0.6, + ButtonHoveredColor = Color3.fromRGB(66, 150, 250), + ButtonHoveredTransparency = 0, + ButtonActiveColor = Color3.fromRGB(15, 135, 250), + ButtonActiveTransparency = 0, + + HeaderColor = Color3.fromRGB(66, 150, 250), + HeaderTransparency = 0.31, + HeaderHoveredColor = Color3.fromRGB(66, 150, 250), + HeaderHoveredTransparency = 0.2, + HeaderActiveColor = Color3.fromRGB(66, 150, 250), + HeaderActiveTransparency = 0, + + SliderGrabColor = Color3.fromRGB(61, 133, 224), + SliderGrabTransparency = 0, + SliderGrabActiveColor = Color3.fromRGB(66, 150, 250), + SliderGrabActiveTransparency = 0, + + SelectionImageObjectColor = Color3.fromRGB(0, 0, 0), + SelectionImageObjectTransparency = 0.8, + SelectionImageObjectBorderColor = Color3.fromRGB(0, 0, 0), + SelectionImageObjectBorderTransparency = 0, + + TableBorderStrongColor = Color3.fromRGB(145, 145, 163), + TableBorderStrongTransparency = 0, + TableBorderLightColor = Color3.fromRGB(173, 173, 189), + TableBorderLightTransparency = 0, + TableRowBgColor = Color3.fromRGB(0, 0, 0), + TableRowBgTransparency = 1, + TableRowBgAltColor = Color3.fromRGB(77, 77, 77), + TableRowBgAltTransparency = 0.91, + + NavWindowingHighlightColor = Color3.fromRGB(179, 179, 179), + NavWindowingHighlightTransparency = 0.3, + NavWindowingDimBgColor = Color3.fromRGB(51, 51, 51), + NavWindowingDimBgTransparency = 0.8, + + SeparatorColor = Color3.fromRGB(99, 99, 99), + SeparatorTransparency = 0.38, + + CheckMarkColor = Color3.fromRGB(66, 150, 250), + CheckMarkTransparency = 0, + }, + + sizeDefault = { -- Dear, ImGui default + ItemWidth = UDim.new(1, 0), + ContentWidth = UDim.new(0.65, 0), + + WindowPadding = Vector2.new(8, 8), + WindowResizePadding = Vector2.new(6, 6), + FramePadding = Vector2.new(4, 3), + ItemSpacing = Vector2.new(8, 4), + ItemInnerSpacing = Vector2.new(4, 4), + CellPadding = Vector2.new(4, 2), + DisplaySafeAreaPadding = Vector2.new(0, 0), + SeparatorTextPadding = Vector2.new(20, 3), + IndentSpacing = 21, + + TextFont = Font.fromEnum(Enum.Font.Code), + TextSize = 13, + FrameBorderSize = 0, + FrameRounding = 0, + GrabRounding = 0, + WindowRounding = 0, -- these don't actually work but it's nice to have them. + WindowBorderSize = 1, + WindowTitleAlign = Enum.LeftRight.Left, + PopupBorderSize = 1, + PopupRounding = 0, + ScrollbarSize = 7, + GrabMinSize = 10, + SeparatorTextBorderSize = 3, + }, + sizeClear = { -- easier to read and manuveure + ItemWidth = UDim.new(1, 0), + ContentWidth = UDim.new(0.65, 0), + + WindowPadding = Vector2.new(12, 8), + WindowResizePadding = Vector2.new(8, 8), + FramePadding = Vector2.new(6, 4), + ItemSpacing = Vector2.new(8, 8), + ItemInnerSpacing = Vector2.new(8, 8), + CellPadding = Vector2.new(4, 4), + DisplaySafeAreaPadding = Vector2.new(8, 8), + SeparatorTextPadding = Vector2.new(24, 6), + IndentSpacing = 25, + + TextFont = Font.fromEnum(Enum.Font.Ubuntu), + TextSize = 15, + FrameBorderSize = 1, + FrameRounding = 4, + GrabRounding = 4, + WindowRounding = 4, + WindowBorderSize = 1, + WindowTitleAlign = Enum.LeftRight.Center, + PopupBorderSize = 1, + PopupRounding = 4, + ScrollbarSize = 9, + GrabMinSize = 14, + SeparatorTextBorderSize = 4, + }, + + utilityDefault = { + UseScreenGUIs = true, + IgnoreGuiInset = false, + Parent = nil, + RichText = false, + DisableWidget = false, + DisplayOrderOffset = 127, + ZIndexOffset = 0, + + MouseDoubleClickTime = 0.30, -- Time for a double-click, in seconds. + MouseDoubleClickMaxDist = 6.0, -- Distance threshold to stay in to validate a double-click, in pixels. + + HoverColor = Color3.fromRGB(255, 255, 0), + HoverTransparency = 0.1, + }, +} + +return TemplateConfig diff --git a/src/client/Iris/demoWindow.lua b/lib/demoWindow.lua similarity index 97% rename from src/client/Iris/demoWindow.lua rename to lib/demoWindow.lua index 89653ad..80b3815 100644 --- a/src/client/Iris/demoWindow.lua +++ b/lib/demoWindow.lua @@ -1,1048 +1,1048 @@ -local Types = require(script.Parent.Types) - -return function(Iris: Types.Iris) - local showMainWindow = Iris.State(true) - local showRecursiveWindow = Iris.State(false) - local showRuntimeInfo = Iris.State(false) - local showStyleEditor = Iris.State(false) - local showWindowlessDemo = Iris.State(false) - local showMainMenuBarWindow = Iris.State(false) - - -- stylua: ignore start - local function helpMarker(helpText) - Iris.PushConfig({ TextColor = Iris._config.TextDisabledColor }) - local text = Iris.Text({ "(?)" }) - Iris.PopConfig() - - Iris.PushConfig({ ContentWidth = UDim.new(0, 350) }) - if text.hovered() then - Iris.Tooltip({ helpText }) - end - Iris.PopConfig() - end - - -- shows each widgets functionality - local widgetDemos = { - Basic = function() - Iris.Tree({ "Basic" }) - Iris.SeparatorText({ "Basic" }) - - local radioButtonState = Iris.State(1) - Iris.Button({ "Button" }) - Iris.SmallButton({ "SmallButton" }) - Iris.Text({ "Text" }) - Iris.TextWrapped({ string.rep("Text Wrapped ", 5) }) - Iris.TextColored({ "Colored Text", Color3.fromRGB(255, 128, 0) }) - Iris.Text({ `Rich Text: bold text italic text underline text strikethrough text red text bigger text`, true, nil, true }) - Iris.SameLine() - Iris.RadioButton({ "Index '1'", 1 }, { index = radioButtonState }) - Iris.RadioButton({ "Index 'two'", "two" }, { index = radioButtonState }) - if Iris.RadioButton({ "Index 'false'", false }, { index = radioButtonState }).active() == false then - if Iris.SmallButton({ "Select last" }).clicked() then - radioButtonState:set(false) - end - end - Iris.End() - Iris.Text({ "The Index is: " .. tostring(radioButtonState.value) }) - - Iris.SeparatorText({ "Inputs" }) - - Iris.InputNum({}) - Iris.DragNum({}) - Iris.SliderNum({}) - - Iris.End() - end, - - Tree = function() - Iris.Tree({ "Trees" }) - Iris.Tree({ "Tree using SpanAvailWidth", [Iris.Args.Tree.SpanAvailWidth] = true }) - helpMarker("SpanAvailWidth determines if the Tree is selectable from its entire with, or only the text area") - Iris.End() - - local tree1 = Iris.Tree({ "Tree with Children" }) - Iris.Text({ "Im inside the first tree!" }) - Iris.Button({ "Im a button inside the first tree!" }) - Iris.Tree({ "Im a tree inside the first tree!" }) - Iris.Text({ "I am the innermost text!" }) - Iris.End() - Iris.End() - - Iris.Checkbox({ "Toggle above tree" }, { isChecked = tree1.state.isUncollapsed }) - - Iris.End() - end, - - CollapsingHeader = function() - Iris.Tree({ "Collapsing Headers" }) - Iris.CollapsingHeader({ "A header" }) - Iris.Text({ "This is under the first header!" }) - Iris.End() - - local secondHeader = Iris.State(true) - Iris.CollapsingHeader({ "Another header" }, { isUncollapsed = secondHeader }) - if Iris.Button({ "Shhh... secret button!" }).clicked() then - secondHeader:set(true) - end - Iris.End() - Iris.End() - end, - - Group = function() - Iris.Tree({ "Groups" }) - Iris.SameLine() - Iris.Group() - Iris.Text({ "I am in group A" }) - Iris.Button({ "Im also in A" }) - Iris.End() - - Iris.Separator() - - Iris.Group() - Iris.Text({ "I am in group B" }) - Iris.Button({ "Im also in B" }) - Iris.Button({ "Also group B" }) - Iris.End() - Iris.End() - Iris.End() - end, - - Indent = function() - Iris.Tree({ "Indents" }) - Iris.Text({ "Not Indented" }) - Iris.Indent() - Iris.Text({ "Indented" }) - Iris.Indent({ 7 }) - Iris.Text({ "Indented by 7 more pixels" }) - Iris.End() - - Iris.Indent({ -7 }) - Iris.Text({ "Indented by 7 less pixels" }) - Iris.End() - Iris.End() - Iris.End() - end, - - Input = function() - Iris.Tree({ "Input" }) - local NoField, NoButtons, Min, Max, Increment, Format = Iris.State(false), Iris.State(false), Iris.State(0), Iris.State(100), Iris.State(1), Iris.State("%d") - - Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) - local InputNum = Iris.InputNum({ - "Input Number", - -- [Iris.Args.InputNum.NoField] = NoField.value, - [Iris.Args.InputNum.NoButtons] = NoButtons.value, - [Iris.Args.InputNum.Min] = Min.value, - [Iris.Args.InputNum.Max] = Max.value, - [Iris.Args.InputNum.Increment] = Increment.value, - [Iris.Args.InputNum.Format] = { Format.value }, - }) - Iris.PopConfig() - Iris.Text({ "The Value is: " .. InputNum.number.value }) - if Iris.Button({ "Randomize Number" }).clicked() then - InputNum.number:set(math.random(1, 99)) - end - local NoFieldCheckbox = Iris.Checkbox({ "NoField" }, { isChecked = NoField }) - local NoButtonsCheckbox = Iris.Checkbox({ "NoButtons" }, { isChecked = NoButtons }) - if NoFieldCheckbox.checked() and NoButtonsCheckbox.isChecked.value == true then - NoButtonsCheckbox.isChecked:set(false) - end - if NoButtonsCheckbox.checked() and NoFieldCheckbox.isChecked.value == true then - NoFieldCheckbox.isChecked:set(false) - end - - Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) - Iris.InputVector2({ "InputVector2" }) - Iris.InputVector3({ "InputVector3" }) - Iris.InputUDim({ "InputUDim" }) - Iris.InputUDim2({ "InputUDim2" }) - local UseFloats = Iris.State(false) - local UseHSV = Iris.State(false) - local sharedColor = Iris.State(Color3.new()) - local transparency = Iris.State(0) - Iris.SliderNum({ "Transparency", 0.01, 0, 1 }, { number = transparency }) - Iris.InputColor3({ "InputColor3", UseFloats:get(), UseHSV:get() }, { color = sharedColor }) - Iris.InputColor4({ "InputColor4", UseFloats:get(), UseHSV:get() }, { color = sharedColor, transparency = transparency }) - Iris.SameLine() - Iris.Text({ sharedColor:get():ToHex() }) - Iris.Checkbox({ "Use Floats" }, { isChecked = UseFloats }) - Iris.Checkbox({ "Use HSV" }, { isChecked = UseHSV }) - Iris.End() - - Iris.PopConfig() - - Iris.Separator() - - Iris.SameLine() - Iris.Text({ "Slider Numbers" }) - helpMarker("ctrl + click slider number widgets to input a number") - Iris.End() - Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) - Iris.SliderNum({ "Slide Int", 1, 1, 8 }) - Iris.SliderNum({ "Slide Float", 0.01, 0, 100 }) - Iris.SliderNum({ "Small Numbers", 0.001, -2, 1, "%f radians" }) - Iris.SliderNum({ "Odd Ranges", 0.001, -math.pi, math.pi, "%f radians" }) - Iris.SliderNum({ "Big Numbers", 1e4, 1e5, 1e7 }) - Iris.SliderNum({ "Few Numbers", 1, 0, 3 }) - Iris.PopConfig() - - Iris.Separator() - - Iris.SameLine() - Iris.Text({ "Drag Numbers" }) - helpMarker("ctrl + click or double click drag number widgets to input a number, hold shift/alt while dragging to increase/decrease speed") - Iris.End() - Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) - Iris.DragNum({ "Drag Int" }) - Iris.DragNum({ "Slide Float", 0.001, -10, 10 }) - Iris.DragNum({ "Percentage", 1, 0, 100, "%d %%" }) - Iris.PopConfig() - Iris.End() - end, - - InputText = function() - Iris.Tree({ "Input Text" }) - Iris.PushConfig({ ContentWidth = UDim.new(0, 250) }) - local InputText = Iris.InputText({ "Input Text Test", [Iris.Args.InputText.TextHint] = "Input Text here" }) - Iris.PopConfig() - Iris.Text({ "The text is: " .. InputText.text.value }) - Iris.End() - end, - - MultiInput = function() - Iris.Tree({"Multi-Component Input"}) - - local sharedVector2 = Iris.State(Vector2.new()) - local sharedVector3 = Iris.State(Vector3.new()) - local sharedUDim = Iris.State(UDim.new()) - local sharedUDim2 = Iris.State(UDim2.new()) - local sharedColor3 = Iris.State(Color3.new()) - local SharedRect = Iris.State(Rect.new(0, 0, 0, 0)) - - Iris.SeparatorText({"Input"}) - - Iris.InputVector2({}, {number = sharedVector2}) - Iris.InputVector3({}, {number = sharedVector3}) - Iris.InputUDim({}, {number = sharedUDim}) - Iris.InputUDim2({}, {number = sharedUDim2}) - Iris.InputRect({}, {number = SharedRect}) - - Iris.SeparatorText({"Drag"}) - - Iris.DragVector2({}, {number = sharedVector2}) - Iris.DragVector3({}, {number = sharedVector3}) - Iris.DragUDim({}, {number = sharedUDim}) - Iris.DragUDim2({}, {number = sharedUDim2}) - Iris.DragRect({}, {number = SharedRect}) - - Iris.SeparatorText({"Slider"}) - - Iris.SliderVector2({}, {number = sharedVector2}) - Iris.SliderVector3({}, {number = sharedVector3}) - Iris.SliderUDim({}, {number = sharedUDim}) - Iris.SliderUDim2({}, {number = sharedUDim2}) - Iris.SliderRect({}, {number = SharedRect}) - - Iris.SeparatorText({"Color"}) - - Iris.InputColor3({}, {color = sharedColor3}) - Iris.InputColor4({}, {color = sharedColor3}) - - Iris.End() - end, - - Tooltip = function() - Iris.PushConfig({ ContentWidth = UDim.new(0, 250) }) - Iris.Tree({ "Tooltip" }) - if Iris.Text({ "Hover over me to reveal a tooltip" }).hovered() then - Iris.Tooltip({ "I am some helpful tooltip text" }) - end - local dynamicText = Iris.State("Hello ") - local numRepeat = Iris.State(1) - if Iris.InputNum({ "# of repeat", 1, 1, 50 }, { number = numRepeat }).numberChanged() then - dynamicText:set(string.rep("Hello ", numRepeat:get())) - end - if Iris.Checkbox({ "Show dynamic text tooltip" }).isChecked.value then - Iris.Tooltip({ dynamicText:get() }) - end - Iris.End() - Iris.PopConfig() - end, - - Selectable = function() - Iris.Tree({ "Selectable" }) - local sharedIndex = Iris.State(2) - Iris.Selectable({ "Selectable #1", 1 }, { index = sharedIndex }) - Iris.Selectable({ "Selectable #2", 2 }, { index = sharedIndex }) - if Iris.Selectable({ "Double click Selectable", 3, true }, { index = sharedIndex }).doubleClicked() then - sharedIndex:set(3) - end - Iris.Selectable({ "Impossible to select", 4, true }, { index = sharedIndex }) - if Iris.Button({ "Select last" }).clicked() then - sharedIndex:set(4) - end - Iris.Selectable({ "Independent Selectable" }) - Iris.End() - end, - - Combo = function() - Iris.Tree({ "Combo" }) - Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) - local sharedComboIndex = Iris.State("No Selection") - Iris.SameLine() - local NoPreview = Iris.Checkbox({ "No Preview" }) - local NoButton = Iris.Checkbox({ "No Button" }) - if NoPreview.checked() and NoButton.isChecked.value == true then - NoButton.isChecked:set(false) - end - if NoButton.checked() and NoPreview.isChecked.value == true then - NoPreview.isChecked:set(false) - end - Iris.End() - Iris.Combo({ "Basic Usage", NoButton.isChecked:get(), NoPreview.isChecked:get() }, { index = sharedComboIndex }) - Iris.Selectable({ "Select 1", "One" }, { index = sharedComboIndex }) - Iris.Selectable({ "Select 2", "Two" }, { index = sharedComboIndex }) - Iris.Selectable({ "Select 3", "Three" }, { index = sharedComboIndex }) - Iris.End() - - Iris.ComboArray({ "Using ComboArray" }, { index = "No Selection" }, { "Red", "Green", "Blue" }) - - local sharedComboIndex2 = Iris.State("7 AM") - Iris.Combo({ "Combo with Inner widgets" }, { index = sharedComboIndex2 }) - Iris.Tree({ "Morning Shifts" }) - Iris.Selectable({ "Shift at 7 AM", "7 AM" }, { index = sharedComboIndex2 }) - Iris.Selectable({ "Shift at 11 AM", "11 AM" }, { index = sharedComboIndex2 }) - Iris.Selectable({ "Shist at 3 PM", "3 PM" }, { index = sharedComboIndex2 }) - Iris.End() - Iris.Tree({ "Night Shifts" }) - Iris.Selectable({ "Shift at 6 PM", "6 PM" }, { index = sharedComboIndex2 }) - Iris.Selectable({ "Shift at 9 PM", "9 PM" }, { index = sharedComboIndex2 }) - Iris.End() - Iris.End() - - local ComboEnum = Iris.ComboEnum({ "Using ComboEnum" }, { index = Enum.UserInputState.Begin }, Enum.UserInputState) - Iris.Text({ "Selected: " .. ComboEnum.index:get().Name }) - Iris.PopConfig() - Iris.End() - end, - } - local widgetDemosOrder = { "Basic", "Tree", "CollapsingHeader", "Group", "Indent", "Input", "MultiInput", "InputText", "Tooltip", "Selectable", "Combo" } - - local function recursiveTree() - local theTree = Iris.Tree({ "Recursive Tree" }) - if theTree.state.isUncollapsed.value then - recursiveTree() - end - Iris.End() - end - - local function recursiveWindow(parentCheckboxState) - Iris.Window({ "Recursive Window" }, { size = Iris.State(Vector2.new(175, 100)), isOpened = parentCheckboxState }) - local theCheckbox = Iris.Checkbox({ "Recurse Again" }) - Iris.End() - if theCheckbox.isChecked.value then - recursiveWindow(theCheckbox.isChecked) - end - end - - -- shows list of runtime widgets and states, including IDs. shows other info about runtime and can show widgets/state info in depth. - local function runtimeInfo() - local runtimeInfoWindow = Iris.Window({ "Runtime Info" }, { isOpened = showRuntimeInfo }) - local lastVDOM = Iris.Internal._lastVDOM - local states = Iris.Internal._states - - local numSecondsDisabled = Iris.State(3) - local rollingDT = Iris.State(0) - local lastT = Iris.State(os.clock()) - - Iris.SameLine() - Iris.InputNum({ "", [Iris.Args.InputNum.Format] = "%d Seconds", [Iris.Args.InputNum.Max] = 10 }, { number = numSecondsDisabled }) - if Iris.Button({ "Disable" }).clicked() then - Iris.Disabled = true - task.delay(numSecondsDisabled:get(), function() - Iris.Disabled = false - end) - end - Iris.End() - - local t = os.clock() - local dt = t - lastT.value - rollingDT.value += (dt - rollingDT.value) * 0.2 - lastT.value = t - Iris.Text({ string.format("Average %.3f ms/frame (%.1f FPS)", rollingDT.value * 1000, 1 / rollingDT.value) }) - - Iris.Text({ - string.format("Window Position: (%d, %d), Window Size: (%d, %d)", runtimeInfoWindow.position.value.X, runtimeInfoWindow.position.value.Y, runtimeInfoWindow.size.value.X, runtimeInfoWindow.size.value.Y), - }) - - Iris.SameLine() - Iris.Text({ "Enter an ID to learn more about it." }) - helpMarker("every widget and state has an ID which Iris tracks to remember which widget is which. below lists all widgets and states, with their respective IDs") - Iris.End() - - Iris.PushConfig({ ItemWidth = UDim.new(1, -150) }) - local enteredText = Iris.InputText({ "ID field" }, { text = Iris.State(runtimeInfoWindow.ID) }).text.value - Iris.PopConfig() - - Iris.Indent() - local enteredWidget = lastVDOM[enteredText] - local enteredState = states[enteredText] - if enteredWidget then - Iris.Table({ 1, [Iris.Args.Table.RowBg] = false }) - Iris.Text({ string.format('The ID, "%s", is a widget', enteredText) }) - Iris.NextRow() - - Iris.Text({ string.format("Widget is type: %s", enteredWidget.type) }) - Iris.NextRow() - - Iris.Tree({ "Widget has Args:" }, { isUncollapsed = Iris.State(true) }) - for i, v in enteredWidget.arguments do - Iris.Text({ i .. " - " .. tostring(v) }) - end - Iris.End() - Iris.NextRow() - - if enteredWidget.state then - Iris.Tree({ "Widget has State:" }, { isUncollapsed = Iris.State(true) }) - for i, v in enteredWidget.state do - Iris.Text({ i .. " - " .. tostring(v.value) }) - end - Iris.End() - end - Iris.End() - elseif enteredState then - Iris.Table({ 1, [Iris.Args.Table.RowBg] = false }) - Iris.Text({ string.format('The ID, "%s", is a state', enteredText) }) - Iris.NextRow() - - Iris.Text({ string.format("Value is type: %s, Value = %s", typeof(enteredState.value), tostring(enteredState.value)) }) - Iris.NextRow() - - Iris.Tree({ "state has connected widgets:" }, { isUncollapsed = Iris.State(true) }) - for i, v in enteredState.ConnectedWidgets do - Iris.Text({ i .. " - " .. v.type }) - end - Iris.End() - Iris.NextRow() - - Iris.Text({ string.format("state has: %d connected functions", #enteredState.ConnectedFunctions) }) - Iris.End() - else - Iris.Text({ string.format('The ID, "%s", is not a state or widget', enteredText) }) - end - Iris.End() - - if Iris.Tree({ "Widgets" }).isUncollapsed.value then - local widgetCount = 0 - local widgetStr = "" - for _, v in lastVDOM do - widgetCount += 1 - widgetStr ..= "\n" .. v.ID .. " - " .. v.type - end - - Iris.Text({ "Number of Widgets: " .. widgetCount }) - - Iris.Text({ widgetStr }) - end - Iris.End() - if Iris.Tree({ "States" }).isUncollapsed.value then - local stateCount = 0 - local stateStr = "" - for i, v in states do - stateCount += 1 - stateStr ..= "\n" .. i .. " - " .. tostring(v.value) - end - - Iris.Text({ "Number of States: " .. stateCount }) - - Iris.Text({ stateStr }) - end - Iris.End() - Iris.End() - end - - local function recursiveMenu() - -- stylua: ignore start - if Iris.Menu({ "Recursive" }).state.isOpened.value then - Iris.MenuItem({ "New", Enum.KeyCode.N, Enum.ModifierKey.Ctrl }) - Iris.MenuItem({ "Open", Enum.KeyCode.O, Enum.ModifierKey.Ctrl }) - Iris.MenuItem({ "Save", Enum.KeyCode.S, Enum.ModifierKey.Ctrl }) - Iris.Separator() - Iris.MenuToggle({ "Autosave" }) - Iris.MenuToggle({ "Checked" }) - Iris.Separator() - Iris.Menu({ "Options" }) - Iris.MenuItem({ "Red" }) - Iris.MenuItem({ "Yellow" }) - Iris.MenuItem({ "Green" }) - Iris.MenuItem({ "Blue" }) - Iris.Separator() - recursiveMenu() - Iris.End() - end - Iris.End() - -- stylua: ignore end - - end - - local function mainMenuBar() - Iris.MenuBar() - Iris.Menu({ "File" }) - Iris.MenuItem({ "New", Enum.KeyCode.N, Enum.ModifierKey.Ctrl }) - Iris.MenuItem({ "Open", Enum.KeyCode.O, Enum.ModifierKey.Ctrl }) - Iris.MenuItem({ "Save", Enum.KeyCode.S, Enum.ModifierKey.Ctrl }) - recursiveMenu() - Iris.MenuItem({ "Quit", Enum.KeyCode.Q, Enum.ModifierKey.Alt }) - Iris.End() - - Iris.Menu({ "Examples" }) - Iris.MenuToggle({ "Recursive Window" }, { isChecked = showRecursiveWindow }) - Iris.MenuToggle({ "Windowless" }, { isChecked = showWindowlessDemo }) - Iris.MenuToggle({ "Main Menu Bar" }, { isChecked = showMainMenuBarWindow }) - Iris.End() - - Iris.Menu({ "Tools" }) - Iris.MenuToggle({ "Runtime Info" }, { isChecked = showRuntimeInfo }) - Iris.MenuToggle({ "Style Editor" }, { isChecked = showStyleEditor }) - Iris.End() - Iris.End() - end - - local function mainMenuBarExample() - -- local screenSize = Iris.Internal._rootWidget.Instance.PseudoWindowScreenGui.AbsoluteSize - -- Iris.Window( - -- {[Iris.Args.Window.NoBackground] = true, [Iris.Args.Window.NoTitleBar] = true, [Iris.Args.Window.NoMove] = true, [Iris.Args.Window.NoResize] = true}, - -- {size = Iris.State(screenSize), position = Iris.State(Vector2.new(0, 0))} - -- ) - - mainMenuBar() - - --Iris.End() - end - - -- allows users to edit state - local styleEditor - do - styleEditor = function() - local selectedPanel = Iris.State(1) - - local styleList = { - { - "Sizing", - function() - local UpdatedConfig = Iris.State({}) - - if Iris.Button({ "Update Config" }).clicked() then - Iris.UpdateGlobalConfig(UpdatedConfig:get()) - UpdatedConfig:set({}) - end - - local UDims = { - { "ItemWidth", nil, UDim.new(), UDim.new(1, 200) }, - { "ContentWidth", nil, UDim.new(), UDim.new(1, 200) } - } - for _, vUDim in UDims do - local Input = Iris.SliderUDim({ table.unpack(vUDim) }, { number = Iris.WeakState(Iris._config[vUDim[1]]) }) - if Input.numberChanged() then - UpdatedConfig:get()[vUDim[1]] = Input.number:get() - end - end - - local Vector2s = { - { "WindowPadding", nil, Vector2.zero, Vector2.one * 20 }, - { "WindowResizePadding", nil, Vector2.zero, Vector2.one * 20 }, - { "FramePadding", nil, Vector2.zero, Vector2.one * 20 }, - { "ItemSpacing", nil, Vector2.zero, Vector2.one * 20 }, - { "ItemInnerSpacing", nil, Vector2.zero, Vector2.one * 20 }, - { "CellPadding", nil, Vector2.zero, Vector2.one * 20 }, - { "DisplaySafeAreaPadding", nil, Vector2.zero, Vector2.one * 20 }, - } - for _, vVector2 in Vector2s do - local Input = Iris.SliderVector2({ table.unpack(vVector2) }, { number = Iris.WeakState(Iris._config[vVector2[1]]) }) - if Input.numberChanged() then - UpdatedConfig:get()[vVector2[1]] = Input.number:get() - end - end - - local Numbers = { - { "TextSize", 1, 4, 20 }, - { "FrameBorderSize", 0.1, 0, 1 }, - { "FrameRounding", 1, 0, 12 }, - { "GrabRounding", 1, 0, 12 }, - { "WindowBorderSize", 0.1, 0, 1 }, - { "PopupBorderSize", 0.1, 0, 1 }, - { "PopupRounding", 1, 0, 12 }, - { "ScrollbarSize", 1, 0, 20 }, - { "GrabMinSize", 1, 0, 20 }, - } - for _, vNumber in Numbers do - local Input = Iris.SliderNum({ table.unpack(vNumber) }, { number = Iris.WeakState(Iris._config[vNumber[1]]) }) - if Input.numberChanged() then - UpdatedConfig:get()[vNumber[1]] = Input.number:get() - end - end - - local Enums = { - "WindowTitleAlign", - -- "TextFont" - } - for _, vEnum in Enums do - local Input = Iris.ComboEnum({ vEnum }, { index = Iris.WeakState(Iris._config[vEnum]) }, Iris._config[vEnum].EnumType) - if Input.closed() then - Iris.UpdateGlobalConfig({ [vEnum] = Input.index:get() }) - end - end - end, - }, - { - "Colors", - function() - local UpdatedConfig = Iris.State({}) - - if Iris.Button({ "Update Config" }).clicked() then - Iris.UpdateGlobalConfig(UpdatedConfig:get()) - UpdatedConfig:set({}) - end - - local color3s = { "BorderColor", "BorderActiveColor" } - - for _, vColor in color3s do - local Input = Iris.InputColor3({ vColor }, { color = Iris.WeakState(Iris._config[vColor]) }) - if Input.numberChanged() then - Iris.UpdateGlobalConfig({ [vColor] = Input.color:get() }) - end - end - - local color4s = { - "Text", - "TextDisabled", - "WindowBg", - "ScrollbarGrab", - "TitleBg", - "TitleBgActive", - "TitleBgCollapsed", - "MenubarBg", - "FrameBg", - "FrameBgHovered", - "FrameBgActive", - "Button", - "ButtonHovered", - "ButtonActive", - "SliderGrab", - "SliderGrabActive", - "Header", - "HeaderHovered", - "HeaderActive", - "SelectionImageObject", - "SelectionImageObjectBorder", - "TableBorderStrong", - "TableBorderLight", - "TableRowBg", - "TableRowBgAlt", - "NavWindowingHighlight", - "NavWindowingDimBg", - "Separator", - "CheckMark", - } - - for _, vColor in color4s do - local Input = Iris.InputColor4({ vColor }, { - color = Iris.WeakState(Iris._config[vColor .. "Color"]), - transparency = Iris.WeakState(Iris._config[vColor .. "Transparency"]), - }) - if Input.numberChanged() then - UpdatedConfig:get()[vColor .. "Color"] = Input.color:get() - UpdatedConfig:get()[vColor .. "Transparency"] = Input.transparency:get() - end - end - end, - }, - } - - local window = Iris.Window({ "Style Editor" }, { isOpened = showStyleEditor }) - Iris.Text({ `Clicked close: {window.closed()}` }) - Iris.Text({ "Customize the look of Iris in realtime." }) - Iris.SameLine() - if Iris.SmallButton({ "Light Theme" }).clicked() then - Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorLight) - end - if Iris.SmallButton({ "Dark Theme" }).clicked() then - Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) - end - Iris.End() - - Iris.SameLine() - if Iris.SmallButton({ "Classic Size" }).clicked() then - Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) - end - if Iris.SmallButton({ "Larger Size" }).clicked() then - Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeClear) - end - Iris.End() - - if Iris.SmallButton({ "Reset Everything" }).clicked() then - Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) - Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) - end - Iris.Separator() - - Iris.SameLine() - for i, v in ipairs(styleList) do - Iris.RadioButton({ v[1], i }, { index = selectedPanel }) - end - Iris.End() - - styleList[selectedPanel:get()][2]() - Iris.End() - end - end - - local function widgetEventInteractivity() - Iris.CollapsingHeader({ "Widget Event Interactivity" }) - local clickCount = Iris.State(0) - if Iris.Button({ "Click to increase Number" }).clicked() then - clickCount:set(clickCount:get() + 1) - end - Iris.Text({ "The Number is: " .. clickCount:get() }) - - Iris.Separator() - - local showEventText = Iris.State(false) - local selectedEvent = Iris.State("clicked") - Iris.SameLine() - Iris.RadioButton({ "clicked", "clicked" }, { index = selectedEvent }) - Iris.RadioButton({ "rightClicked", "rightClicked" }, { index = selectedEvent }) - Iris.RadioButton({ "doubleClicked", "doubleClicked" }, { index = selectedEvent }) - Iris.RadioButton({ "ctrlClicked", "ctrlClicked" }, { index = selectedEvent }) - Iris.End() - Iris.SameLine() - - if Iris.Button({ selectedEvent:get() .. " to reveal text" })[selectedEvent:get()]() then - showEventText:set(not showEventText:get()) - end - if showEventText:get() then - Iris.Text({ "Here i am!" }) - end - - Iris.End() - - Iris.Separator() - - local showTextTimer = Iris.State(0) - Iris.SameLine() - if Iris.Button({ "Click to show text for 20 frames" }).clicked() then - showTextTimer:set(20) - end - if showTextTimer:get() > 0 then - Iris.Text({ "Here i am!" }) - end - Iris.End() - showTextTimer:set(math.max(0, showTextTimer:get() - 1)) - Iris.Text({ "Text Timer: " .. showTextTimer:get() }) - - local checkbox0 = Iris.Checkbox({ "Event-tracked checkbox" }) - Iris.Indent() - Iris.Text({ "unchecked: " .. tostring(checkbox0.unchecked()) }) - Iris.Text({ "checked: " .. tostring(checkbox0.checked()) }) - Iris.End() - Iris.SameLine() - if Iris.Button({ "Hover over me" }).hovered() then - Iris.Text({ "The button is hovered" }) - end - Iris.End() - Iris.End() - end - - local function widgetStateInteractivity() - Iris.CollapsingHeader({ "Widget State Interactivity" }) - local checkbox0 = Iris.Checkbox({ "Widget-Generated State" }) - Iris.Text({ `isChecked: {checkbox0.state.isChecked.value}\n` }) - - local checkboxState0 = Iris.State(false) - local checkbox1 = Iris.Checkbox({ "User-Generated State" }, { isChecked = checkboxState0 }) - Iris.Text({ `isChecked: {checkbox1.state.isChecked.value}\n` }) - - local checkbox2 = Iris.Checkbox({ "Widget Coupled State" }) - local checkbox3 = Iris.Checkbox({ "Coupled to above Checkbox" }, { isChecked = checkbox2.state.isChecked }) - Iris.Text({ `isChecked: {checkbox3.state.isChecked.value}\n` }) - - local checkboxState1 = Iris.State(false) - local _checkbox4 = Iris.Checkbox({ "Widget and Code Coupled State" }, { isChecked = checkboxState1 }) - local Button0 = Iris.Button({ "Click to toggle above checkbox" }) - if Button0.clicked() then - checkboxState1:set(not checkboxState1:get()) - end - Iris.Text({ `isChecked: {checkboxState1.value}\n` }) - - local checkboxState2 = Iris.State(true) - local checkboxState3 = Iris.ComputedState(checkboxState2, function(newValue) - return not newValue - end) - local _checkbox5 = Iris.Checkbox({ "ComputedState (dynamic coupling)" }, { isChecked = checkboxState2 }) - local _checkbox5 = Iris.Checkbox({ "Inverted of above checkbox" }, { isChecked = checkboxState3 }) - Iris.Text({ `isChecked: {checkboxState3.value}\n` }) - - Iris.End() - end - - local function dynamicStyle() - Iris.CollapsingHeader({ "Dynamic Styles" }) - local colorH = Iris.State(0) - Iris.SameLine() - if Iris.Button({ "Change Color" }).clicked() then - colorH:set(math.random()) - end - Iris.Text({ "Hue: " .. math.floor(colorH:get() * 255) }) - helpMarker("Using PushConfig with a changing value, this can be done with any config field") - Iris.End() - Iris.PushConfig({ TextColor = Color3.fromHSV(colorH:get(), 1, 1) }) - Iris.Text({ "Text with a unique and changable color" }) - Iris.PopConfig() - Iris.End() - end - - local function tablesDemo() - local showTablesTree = Iris.State(false) - - Iris.CollapsingHeader({ "Tables & Columns" }, { isUncollapsed = showTablesTree }) - if showTablesTree.value == false then - -- optimization to skip code which draws GUI which wont be seen. - -- its a trade off because when the tree becomes opened widgets will all have to be generated again. - -- Dear ImGui utilizes the same trick, but its less useful here because the Retained mode Backend - Iris.End() - else - Iris.SameLine() - Iris.Text({ "Table using NextRow and NextColumn syntax:" }) - helpMarker("calling Iris.NextRow() in the outer loop, and Iris.NextColumn()in the inner loop") - Iris.End() - Iris.Table({ 3 }) - for i = 1, 4 do - Iris.NextRow() - for i2 = 1, 3 do - Iris.NextColumn() - Iris.Text({ `Row: {i}, Column: {i2}` }) - end - end - Iris.End() - - Iris.Text({ "" }) - - Iris.SameLine() - Iris.Text({ "Table using NextColumn only syntax:" }) - helpMarker("only calling Iris.NextColumn() in the inner loop, the result is identical") - Iris.End() - - Iris.Table({ 2 }) - for i = 1, 4 do - for i2 = 1, 2 do - Iris.NextColumn() - Iris.Text({ `Row: {i}, Column: {i2}` }) - end - end - Iris.End() - - Iris.Separator() - - local TableRowBg = Iris.State(false) - local TableBordersOuter = Iris.State(false) - local TableBordersInner = Iris.State(true) - local TableUseButtons = Iris.State(true) - local TableNumRows = Iris.State(3) - - Iris.Text({ "Table with Customizable Arguments" }) - Iris.Table({ - 4, - [Iris.Args.Table.RowBg] = TableRowBg.value, - [Iris.Args.Table.BordersOuter] = TableBordersOuter.value, - [Iris.Args.Table.BordersInner] = TableBordersInner.value, - }) - for i = 1, TableNumRows:get() do - for i2 = 1, 4 do - Iris.NextColumn() - if TableUseButtons.value then - Iris.Button({ `Month: {i}, Week: {i2}` }) - else - Iris.Text({ `Month: {i}, Week: {i2}` }) - end - end - end - Iris.End() - - Iris.Checkbox({ "RowBg" }, { isChecked = TableRowBg }) - Iris.Checkbox({ "BordersOuter" }, { isChecked = TableBordersOuter }) - Iris.Checkbox({ "BordersInner" }, { isChecked = TableBordersInner }) - Iris.SameLine() - Iris.RadioButton({ "Buttons", true }, { index = TableUseButtons }) - Iris.RadioButton({ "Text", false }, { index = TableUseButtons }) - Iris.End() - Iris.InputNum({ - "Number of rows", - [Iris.Args.InputNum.Min] = 0, - [Iris.Args.InputNum.Max] = 100, - [Iris.Args.InputNum.Format] = "%d", - }, { number = TableNumRows }) - - Iris.End() - end - end - - local function layoutDemo() - Iris.CollapsingHeader({ "Widget Layout" }) - Iris.Tree({ "Content Width" }) - local value = Iris.State(50) - local index = Iris.State(Enum.Axis.X) - - Iris.Text({ "The Content Width is a size property which determines the width of input fields." }) - Iris.SameLine() - Iris.Text({ "By default the value is UDim.new(0.65, 0)" }) - helpMarker("This is the default value from Dear ImGui.\nIt is 65% of the window width.") - Iris.End() - Iris.Text({ "This works well, but sometimes we know how wide elements are going to be and want to maximise the space." }) - Iris.Text({ "Therefore, we can use Iris.PushConfig() to change the width" }) - - Iris.Separator() - - Iris.SameLine() - Iris.Text({ "Content Width = 150 pixels" }) - helpMarker("UDim.new(0, 150)") - Iris.End() - Iris.PushConfig({ ContentWidth = UDim.new(0, 150) }) - Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) - Iris.ComboEnum({ "axis" }, { index = index }, Enum.Axis) - Iris.PopConfig() - - Iris.SameLine() - Iris.Text({ "Content Width = 50% window width" }) - helpMarker("UDim.new(0.5, 0)") - Iris.End() - Iris.PushConfig({ ContentWidth = UDim.new(0.5, 0) }) - Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) - Iris.ComboEnum({ "axis" }, { index = index }, Enum.Axis) - Iris.PopConfig() - - Iris.SameLine() - Iris.Text({ "Content Width = -150 pixels from the right side" }) - helpMarker("UDim.new(1, -150)") - Iris.End() - Iris.PushConfig({ ContentWidth = UDim.new(1, -150) }) - Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) - Iris.InputEnum({ "axis" }, { index = index }, Enum.Axis) - Iris.PopConfig() - Iris.End() - Iris.End() - end - - -- showcases how widgets placed outside of a window are placed inside root - local function windowlessDemo() - Iris.PushConfig({ ItemWidth = UDim.new(0, 150) }) - Iris.SameLine() - Iris.TextWrapped({ "Windowless widgets" }) - helpMarker("Widgets which are placed outside of a window will appear on the top left side of the screen.") - Iris.End() - Iris.Button({}) - Iris.Tree({}) - Iris.InputText({}) - Iris.End() - Iris.PopConfig() - end - - -- main demo window - return function() - local NoTitleBar = Iris.State(false) - local NoBackground = Iris.State(false) - local NoCollapse = Iris.State(false) - local NoClose = Iris.State(true) - local NoMove = Iris.State(false) - local NoScrollbar = Iris.State(false) - local NoResize = Iris.State(false) - local NoNav = Iris.State(false) - local NoMenu = Iris.State(false) - - if showMainWindow.value == false then - Iris.Checkbox({ "Open main window" }, { isChecked = showMainWindow }) - return - end - - Iris.Window({ - "Iris Demo Window", - [Iris.Args.Window.NoTitleBar] = NoTitleBar.value, - [Iris.Args.Window.NoBackground] = NoBackground.value, - [Iris.Args.Window.NoCollapse] = NoCollapse.value, - [Iris.Args.Window.NoClose] = NoClose.value, - [Iris.Args.Window.NoMove] = NoMove.value, - [Iris.Args.Window.NoScrollbar] = NoScrollbar.value, - [Iris.Args.Window.NoResize] = NoResize.value, - [Iris.Args.Window.NoNav] = NoNav.value, - [Iris.Args.Window.NoMenu] = NoMenu.value, - }, { size = Iris.State(Vector2.new(600, 550)), position = Iris.State(Vector2.new(100, 25)), isOpened = showMainWindow }) - - mainMenuBar() - - Iris.Text({ "Iris says hello. (" .. Iris.Internal._version .. ")" }) - - Iris.CollapsingHeader({ "Window Options" }) - Iris.Table({ 3, false, false, false }) - Iris.NextColumn() - Iris.Checkbox({ "NoTitleBar" }, { isChecked = NoTitleBar }) - Iris.NextColumn() - Iris.Checkbox({ "NoBackground" }, { isChecked = NoBackground }) - Iris.NextColumn() - Iris.Checkbox({ "NoCollapse" }, { isChecked = NoCollapse }) - Iris.NextColumn() - Iris.Checkbox({ "NoClose" }, { isChecked = NoClose }) - Iris.NextColumn() - Iris.Checkbox({ "NoMove" }, { isChecked = NoMove }) - Iris.NextColumn() - Iris.Checkbox({ "NoScrollbar" }, { isChecked = NoScrollbar }) - Iris.NextColumn() - Iris.Checkbox({ "NoResize" }, { isChecked = NoResize }) - Iris.NextColumn() - Iris.Checkbox({ "NoNav" }, { isChecked = NoNav }) - Iris.NextColumn() - Iris.Checkbox({ "NoMenu" }, { isChecked = NoMenu }) - Iris.End() - Iris.End() - - -- stylua: ignore end - - widgetEventInteractivity() - - widgetStateInteractivity() - - Iris.CollapsingHeader({ "Recursive Tree" }) - recursiveTree() - Iris.End() - - dynamicStyle() - - Iris.Separator() - - Iris.CollapsingHeader({ "Widgets" }) - for _, name in widgetDemosOrder do - widgetDemos[name]() - end - Iris.End() - - tablesDemo() - - layoutDemo() - Iris.End() - - if showRecursiveWindow.value then - recursiveWindow(showRecursiveWindow) - end - if showRuntimeInfo.value then - runtimeInfo() - end - if showStyleEditor.value then - styleEditor() - end - if showWindowlessDemo.value then - windowlessDemo() - end - - if showMainMenuBarWindow.value then - mainMenuBarExample() - end - end -end +local Types = require(script.Parent.Types) + +return function(Iris: Types.Iris) + local showMainWindow = Iris.State(true) + local showRecursiveWindow = Iris.State(false) + local showRuntimeInfo = Iris.State(false) + local showStyleEditor = Iris.State(false) + local showWindowlessDemo = Iris.State(false) + local showMainMenuBarWindow = Iris.State(false) + + -- stylua: ignore start + local function helpMarker(helpText) + Iris.PushConfig({ TextColor = Iris._config.TextDisabledColor }) + local text = Iris.Text({ "(?)" }) + Iris.PopConfig() + + Iris.PushConfig({ ContentWidth = UDim.new(0, 350) }) + if text.hovered() then + Iris.Tooltip({ helpText }) + end + Iris.PopConfig() + end + + -- shows each widgets functionality + local widgetDemos = { + Basic = function() + Iris.Tree({ "Basic" }) + Iris.SeparatorText({ "Basic" }) + + local radioButtonState = Iris.State(1) + Iris.Button({ "Button" }) + Iris.SmallButton({ "SmallButton" }) + Iris.Text({ "Text" }) + Iris.TextWrapped({ string.rep("Text Wrapped ", 5) }) + Iris.TextColored({ "Colored Text", Color3.fromRGB(255, 128, 0) }) + Iris.Text({ `Rich Text: bold text italic text underline text strikethrough text red text bigger text`, true, nil, true }) + Iris.SameLine() + Iris.RadioButton({ "Index '1'", 1 }, { index = radioButtonState }) + Iris.RadioButton({ "Index 'two'", "two" }, { index = radioButtonState }) + if Iris.RadioButton({ "Index 'false'", false }, { index = radioButtonState }).active() == false then + if Iris.SmallButton({ "Select last" }).clicked() then + radioButtonState:set(false) + end + end + Iris.End() + Iris.Text({ "The Index is: " .. tostring(radioButtonState.value) }) + + Iris.SeparatorText({ "Inputs" }) + + Iris.InputNum({}) + Iris.DragNum({}) + Iris.SliderNum({}) + + Iris.End() + end, + + Tree = function() + Iris.Tree({ "Trees" }) + Iris.Tree({ "Tree using SpanAvailWidth", [Iris.Args.Tree.SpanAvailWidth] = true }) + helpMarker("SpanAvailWidth determines if the Tree is selectable from its entire with, or only the text area") + Iris.End() + + local tree1 = Iris.Tree({ "Tree with Children" }) + Iris.Text({ "Im inside the first tree!" }) + Iris.Button({ "Im a button inside the first tree!" }) + Iris.Tree({ "Im a tree inside the first tree!" }) + Iris.Text({ "I am the innermost text!" }) + Iris.End() + Iris.End() + + Iris.Checkbox({ "Toggle above tree" }, { isChecked = tree1.state.isUncollapsed }) + + Iris.End() + end, + + CollapsingHeader = function() + Iris.Tree({ "Collapsing Headers" }) + Iris.CollapsingHeader({ "A header" }) + Iris.Text({ "This is under the first header!" }) + Iris.End() + + local secondHeader = Iris.State(true) + Iris.CollapsingHeader({ "Another header" }, { isUncollapsed = secondHeader }) + if Iris.Button({ "Shhh... secret button!" }).clicked() then + secondHeader:set(true) + end + Iris.End() + Iris.End() + end, + + Group = function() + Iris.Tree({ "Groups" }) + Iris.SameLine() + Iris.Group() + Iris.Text({ "I am in group A" }) + Iris.Button({ "Im also in A" }) + Iris.End() + + Iris.Separator() + + Iris.Group() + Iris.Text({ "I am in group B" }) + Iris.Button({ "Im also in B" }) + Iris.Button({ "Also group B" }) + Iris.End() + Iris.End() + Iris.End() + end, + + Indent = function() + Iris.Tree({ "Indents" }) + Iris.Text({ "Not Indented" }) + Iris.Indent() + Iris.Text({ "Indented" }) + Iris.Indent({ 7 }) + Iris.Text({ "Indented by 7 more pixels" }) + Iris.End() + + Iris.Indent({ -7 }) + Iris.Text({ "Indented by 7 less pixels" }) + Iris.End() + Iris.End() + Iris.End() + end, + + Input = function() + Iris.Tree({ "Input" }) + local NoField, NoButtons, Min, Max, Increment, Format = Iris.State(false), Iris.State(false), Iris.State(0), Iris.State(100), Iris.State(1), Iris.State("%d") + + Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) + local InputNum = Iris.InputNum({ + "Input Number", + -- [Iris.Args.InputNum.NoField] = NoField.value, + [Iris.Args.InputNum.NoButtons] = NoButtons.value, + [Iris.Args.InputNum.Min] = Min.value, + [Iris.Args.InputNum.Max] = Max.value, + [Iris.Args.InputNum.Increment] = Increment.value, + [Iris.Args.InputNum.Format] = { Format.value }, + }) + Iris.PopConfig() + Iris.Text({ "The Value is: " .. InputNum.number.value }) + if Iris.Button({ "Randomize Number" }).clicked() then + InputNum.number:set(math.random(1, 99)) + end + local NoFieldCheckbox = Iris.Checkbox({ "NoField" }, { isChecked = NoField }) + local NoButtonsCheckbox = Iris.Checkbox({ "NoButtons" }, { isChecked = NoButtons }) + if NoFieldCheckbox.checked() and NoButtonsCheckbox.isChecked.value == true then + NoButtonsCheckbox.isChecked:set(false) + end + if NoButtonsCheckbox.checked() and NoFieldCheckbox.isChecked.value == true then + NoFieldCheckbox.isChecked:set(false) + end + + Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) + Iris.InputVector2({ "InputVector2" }) + Iris.InputVector3({ "InputVector3" }) + Iris.InputUDim({ "InputUDim" }) + Iris.InputUDim2({ "InputUDim2" }) + local UseFloats = Iris.State(false) + local UseHSV = Iris.State(false) + local sharedColor = Iris.State(Color3.new()) + local transparency = Iris.State(0) + Iris.SliderNum({ "Transparency", 0.01, 0, 1 }, { number = transparency }) + Iris.InputColor3({ "InputColor3", UseFloats:get(), UseHSV:get() }, { color = sharedColor }) + Iris.InputColor4({ "InputColor4", UseFloats:get(), UseHSV:get() }, { color = sharedColor, transparency = transparency }) + Iris.SameLine() + Iris.Text({ sharedColor:get():ToHex() }) + Iris.Checkbox({ "Use Floats" }, { isChecked = UseFloats }) + Iris.Checkbox({ "Use HSV" }, { isChecked = UseHSV }) + Iris.End() + + Iris.PopConfig() + + Iris.Separator() + + Iris.SameLine() + Iris.Text({ "Slider Numbers" }) + helpMarker("ctrl + click slider number widgets to input a number") + Iris.End() + Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) + Iris.SliderNum({ "Slide Int", 1, 1, 8 }) + Iris.SliderNum({ "Slide Float", 0.01, 0, 100 }) + Iris.SliderNum({ "Small Numbers", 0.001, -2, 1, "%f radians" }) + Iris.SliderNum({ "Odd Ranges", 0.001, -math.pi, math.pi, "%f radians" }) + Iris.SliderNum({ "Big Numbers", 1e4, 1e5, 1e7 }) + Iris.SliderNum({ "Few Numbers", 1, 0, 3 }) + Iris.PopConfig() + + Iris.Separator() + + Iris.SameLine() + Iris.Text({ "Drag Numbers" }) + helpMarker("ctrl + click or double click drag number widgets to input a number, hold shift/alt while dragging to increase/decrease speed") + Iris.End() + Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) + Iris.DragNum({ "Drag Int" }) + Iris.DragNum({ "Slide Float", 0.001, -10, 10 }) + Iris.DragNum({ "Percentage", 1, 0, 100, "%d %%" }) + Iris.PopConfig() + Iris.End() + end, + + InputText = function() + Iris.Tree({ "Input Text" }) + Iris.PushConfig({ ContentWidth = UDim.new(0, 250) }) + local InputText = Iris.InputText({ "Input Text Test", [Iris.Args.InputText.TextHint] = "Input Text here" }) + Iris.PopConfig() + Iris.Text({ "The text is: " .. InputText.text.value }) + Iris.End() + end, + + MultiInput = function() + Iris.Tree({"Multi-Component Input"}) + + local sharedVector2 = Iris.State(Vector2.new()) + local sharedVector3 = Iris.State(Vector3.new()) + local sharedUDim = Iris.State(UDim.new()) + local sharedUDim2 = Iris.State(UDim2.new()) + local sharedColor3 = Iris.State(Color3.new()) + local SharedRect = Iris.State(Rect.new(0, 0, 0, 0)) + + Iris.SeparatorText({"Input"}) + + Iris.InputVector2({}, {number = sharedVector2}) + Iris.InputVector3({}, {number = sharedVector3}) + Iris.InputUDim({}, {number = sharedUDim}) + Iris.InputUDim2({}, {number = sharedUDim2}) + Iris.InputRect({}, {number = SharedRect}) + + Iris.SeparatorText({"Drag"}) + + Iris.DragVector2({}, {number = sharedVector2}) + Iris.DragVector3({}, {number = sharedVector3}) + Iris.DragUDim({}, {number = sharedUDim}) + Iris.DragUDim2({}, {number = sharedUDim2}) + Iris.DragRect({}, {number = SharedRect}) + + Iris.SeparatorText({"Slider"}) + + Iris.SliderVector2({}, {number = sharedVector2}) + Iris.SliderVector3({}, {number = sharedVector3}) + Iris.SliderUDim({}, {number = sharedUDim}) + Iris.SliderUDim2({}, {number = sharedUDim2}) + Iris.SliderRect({}, {number = SharedRect}) + + Iris.SeparatorText({"Color"}) + + Iris.InputColor3({}, {color = sharedColor3}) + Iris.InputColor4({}, {color = sharedColor3}) + + Iris.End() + end, + + Tooltip = function() + Iris.PushConfig({ ContentWidth = UDim.new(0, 250) }) + Iris.Tree({ "Tooltip" }) + if Iris.Text({ "Hover over me to reveal a tooltip" }).hovered() then + Iris.Tooltip({ "I am some helpful tooltip text" }) + end + local dynamicText = Iris.State("Hello ") + local numRepeat = Iris.State(1) + if Iris.InputNum({ "# of repeat", 1, 1, 50 }, { number = numRepeat }).numberChanged() then + dynamicText:set(string.rep("Hello ", numRepeat:get())) + end + if Iris.Checkbox({ "Show dynamic text tooltip" }).isChecked.value then + Iris.Tooltip({ dynamicText:get() }) + end + Iris.End() + Iris.PopConfig() + end, + + Selectable = function() + Iris.Tree({ "Selectable" }) + local sharedIndex = Iris.State(2) + Iris.Selectable({ "Selectable #1", 1 }, { index = sharedIndex }) + Iris.Selectable({ "Selectable #2", 2 }, { index = sharedIndex }) + if Iris.Selectable({ "Double click Selectable", 3, true }, { index = sharedIndex }).doubleClicked() then + sharedIndex:set(3) + end + Iris.Selectable({ "Impossible to select", 4, true }, { index = sharedIndex }) + if Iris.Button({ "Select last" }).clicked() then + sharedIndex:set(4) + end + Iris.Selectable({ "Independent Selectable" }) + Iris.End() + end, + + Combo = function() + Iris.Tree({ "Combo" }) + Iris.PushConfig({ ContentWidth = UDim.new(1, -120) }) + local sharedComboIndex = Iris.State("No Selection") + Iris.SameLine() + local NoPreview = Iris.Checkbox({ "No Preview" }) + local NoButton = Iris.Checkbox({ "No Button" }) + if NoPreview.checked() and NoButton.isChecked.value == true then + NoButton.isChecked:set(false) + end + if NoButton.checked() and NoPreview.isChecked.value == true then + NoPreview.isChecked:set(false) + end + Iris.End() + Iris.Combo({ "Basic Usage", NoButton.isChecked:get(), NoPreview.isChecked:get() }, { index = sharedComboIndex }) + Iris.Selectable({ "Select 1", "One" }, { index = sharedComboIndex }) + Iris.Selectable({ "Select 2", "Two" }, { index = sharedComboIndex }) + Iris.Selectable({ "Select 3", "Three" }, { index = sharedComboIndex }) + Iris.End() + + Iris.ComboArray({ "Using ComboArray" }, { index = "No Selection" }, { "Red", "Green", "Blue" }) + + local sharedComboIndex2 = Iris.State("7 AM") + Iris.Combo({ "Combo with Inner widgets" }, { index = sharedComboIndex2 }) + Iris.Tree({ "Morning Shifts" }) + Iris.Selectable({ "Shift at 7 AM", "7 AM" }, { index = sharedComboIndex2 }) + Iris.Selectable({ "Shift at 11 AM", "11 AM" }, { index = sharedComboIndex2 }) + Iris.Selectable({ "Shist at 3 PM", "3 PM" }, { index = sharedComboIndex2 }) + Iris.End() + Iris.Tree({ "Night Shifts" }) + Iris.Selectable({ "Shift at 6 PM", "6 PM" }, { index = sharedComboIndex2 }) + Iris.Selectable({ "Shift at 9 PM", "9 PM" }, { index = sharedComboIndex2 }) + Iris.End() + Iris.End() + + local ComboEnum = Iris.ComboEnum({ "Using ComboEnum" }, { index = Enum.UserInputState.Begin }, Enum.UserInputState) + Iris.Text({ "Selected: " .. ComboEnum.index:get().Name }) + Iris.PopConfig() + Iris.End() + end, + } + local widgetDemosOrder = { "Basic", "Tree", "CollapsingHeader", "Group", "Indent", "Input", "MultiInput", "InputText", "Tooltip", "Selectable", "Combo" } + + local function recursiveTree() + local theTree = Iris.Tree({ "Recursive Tree" }) + if theTree.state.isUncollapsed.value then + recursiveTree() + end + Iris.End() + end + + local function recursiveWindow(parentCheckboxState) + Iris.Window({ "Recursive Window" }, { size = Iris.State(Vector2.new(175, 100)), isOpened = parentCheckboxState }) + local theCheckbox = Iris.Checkbox({ "Recurse Again" }) + Iris.End() + if theCheckbox.isChecked.value then + recursiveWindow(theCheckbox.isChecked) + end + end + + -- shows list of runtime widgets and states, including IDs. shows other info about runtime and can show widgets/state info in depth. + local function runtimeInfo() + local runtimeInfoWindow = Iris.Window({ "Runtime Info" }, { isOpened = showRuntimeInfo }) + local lastVDOM = Iris.Internal._lastVDOM + local states = Iris.Internal._states + + local numSecondsDisabled = Iris.State(3) + local rollingDT = Iris.State(0) + local lastT = Iris.State(os.clock()) + + Iris.SameLine() + Iris.InputNum({ "", [Iris.Args.InputNum.Format] = "%d Seconds", [Iris.Args.InputNum.Max] = 10 }, { number = numSecondsDisabled }) + if Iris.Button({ "Disable" }).clicked() then + Iris.Disabled = true + task.delay(numSecondsDisabled:get(), function() + Iris.Disabled = false + end) + end + Iris.End() + + local t = os.clock() + local dt = t - lastT.value + rollingDT.value += (dt - rollingDT.value) * 0.2 + lastT.value = t + Iris.Text({ string.format("Average %.3f ms/frame (%.1f FPS)", rollingDT.value * 1000, 1 / rollingDT.value) }) + + Iris.Text({ + string.format("Window Position: (%d, %d), Window Size: (%d, %d)", runtimeInfoWindow.position.value.X, runtimeInfoWindow.position.value.Y, runtimeInfoWindow.size.value.X, runtimeInfoWindow.size.value.Y), + }) + + Iris.SameLine() + Iris.Text({ "Enter an ID to learn more about it." }) + helpMarker("every widget and state has an ID which Iris tracks to remember which widget is which. below lists all widgets and states, with their respective IDs") + Iris.End() + + Iris.PushConfig({ ItemWidth = UDim.new(1, -150) }) + local enteredText = Iris.InputText({ "ID field" }, { text = Iris.State(runtimeInfoWindow.ID) }).text.value + Iris.PopConfig() + + Iris.Indent() + local enteredWidget = lastVDOM[enteredText] + local enteredState = states[enteredText] + if enteredWidget then + Iris.Table({ 1, [Iris.Args.Table.RowBg] = false }) + Iris.Text({ string.format('The ID, "%s", is a widget', enteredText) }) + Iris.NextRow() + + Iris.Text({ string.format("Widget is type: %s", enteredWidget.type) }) + Iris.NextRow() + + Iris.Tree({ "Widget has Args:" }, { isUncollapsed = Iris.State(true) }) + for i, v in enteredWidget.arguments do + Iris.Text({ i .. " - " .. tostring(v) }) + end + Iris.End() + Iris.NextRow() + + if enteredWidget.state then + Iris.Tree({ "Widget has State:" }, { isUncollapsed = Iris.State(true) }) + for i, v in enteredWidget.state do + Iris.Text({ i .. " - " .. tostring(v.value) }) + end + Iris.End() + end + Iris.End() + elseif enteredState then + Iris.Table({ 1, [Iris.Args.Table.RowBg] = false }) + Iris.Text({ string.format('The ID, "%s", is a state', enteredText) }) + Iris.NextRow() + + Iris.Text({ string.format("Value is type: %s, Value = %s", typeof(enteredState.value), tostring(enteredState.value)) }) + Iris.NextRow() + + Iris.Tree({ "state has connected widgets:" }, { isUncollapsed = Iris.State(true) }) + for i, v in enteredState.ConnectedWidgets do + Iris.Text({ i .. " - " .. v.type }) + end + Iris.End() + Iris.NextRow() + + Iris.Text({ string.format("state has: %d connected functions", #enteredState.ConnectedFunctions) }) + Iris.End() + else + Iris.Text({ string.format('The ID, "%s", is not a state or widget', enteredText) }) + end + Iris.End() + + if Iris.Tree({ "Widgets" }).isUncollapsed.value then + local widgetCount = 0 + local widgetStr = "" + for _, v in lastVDOM do + widgetCount += 1 + widgetStr ..= "\n" .. v.ID .. " - " .. v.type + end + + Iris.Text({ "Number of Widgets: " .. widgetCount }) + + Iris.Text({ widgetStr }) + end + Iris.End() + if Iris.Tree({ "States" }).isUncollapsed.value then + local stateCount = 0 + local stateStr = "" + for i, v in states do + stateCount += 1 + stateStr ..= "\n" .. i .. " - " .. tostring(v.value) + end + + Iris.Text({ "Number of States: " .. stateCount }) + + Iris.Text({ stateStr }) + end + Iris.End() + Iris.End() + end + + local function recursiveMenu() + -- stylua: ignore start + if Iris.Menu({ "Recursive" }).state.isOpened.value then + Iris.MenuItem({ "New", Enum.KeyCode.N, Enum.ModifierKey.Ctrl }) + Iris.MenuItem({ "Open", Enum.KeyCode.O, Enum.ModifierKey.Ctrl }) + Iris.MenuItem({ "Save", Enum.KeyCode.S, Enum.ModifierKey.Ctrl }) + Iris.Separator() + Iris.MenuToggle({ "Autosave" }) + Iris.MenuToggle({ "Checked" }) + Iris.Separator() + Iris.Menu({ "Options" }) + Iris.MenuItem({ "Red" }) + Iris.MenuItem({ "Yellow" }) + Iris.MenuItem({ "Green" }) + Iris.MenuItem({ "Blue" }) + Iris.Separator() + recursiveMenu() + Iris.End() + end + Iris.End() + -- stylua: ignore end + + end + + local function mainMenuBar() + Iris.MenuBar() + Iris.Menu({ "File" }) + Iris.MenuItem({ "New", Enum.KeyCode.N, Enum.ModifierKey.Ctrl }) + Iris.MenuItem({ "Open", Enum.KeyCode.O, Enum.ModifierKey.Ctrl }) + Iris.MenuItem({ "Save", Enum.KeyCode.S, Enum.ModifierKey.Ctrl }) + recursiveMenu() + Iris.MenuItem({ "Quit", Enum.KeyCode.Q, Enum.ModifierKey.Alt }) + Iris.End() + + Iris.Menu({ "Examples" }) + Iris.MenuToggle({ "Recursive Window" }, { isChecked = showRecursiveWindow }) + Iris.MenuToggle({ "Windowless" }, { isChecked = showWindowlessDemo }) + Iris.MenuToggle({ "Main Menu Bar" }, { isChecked = showMainMenuBarWindow }) + Iris.End() + + Iris.Menu({ "Tools" }) + Iris.MenuToggle({ "Runtime Info" }, { isChecked = showRuntimeInfo }) + Iris.MenuToggle({ "Style Editor" }, { isChecked = showStyleEditor }) + Iris.End() + Iris.End() + end + + local function mainMenuBarExample() + -- local screenSize = Iris.Internal._rootWidget.Instance.PseudoWindowScreenGui.AbsoluteSize + -- Iris.Window( + -- {[Iris.Args.Window.NoBackground] = true, [Iris.Args.Window.NoTitleBar] = true, [Iris.Args.Window.NoMove] = true, [Iris.Args.Window.NoResize] = true}, + -- {size = Iris.State(screenSize), position = Iris.State(Vector2.new(0, 0))} + -- ) + + mainMenuBar() + + --Iris.End() + end + + -- allows users to edit state + local styleEditor + do + styleEditor = function() + local selectedPanel = Iris.State(1) + + local styleList = { + { + "Sizing", + function() + local UpdatedConfig = Iris.State({}) + + if Iris.Button({ "Update Config" }).clicked() then + Iris.UpdateGlobalConfig(UpdatedConfig:get()) + UpdatedConfig:set({}) + end + + local UDims = { + { "ItemWidth", nil, UDim.new(), UDim.new(1, 200) }, + { "ContentWidth", nil, UDim.new(), UDim.new(1, 200) } + } + for _, vUDim in UDims do + local Input = Iris.SliderUDim({ table.unpack(vUDim) }, { number = Iris.WeakState(Iris._config[vUDim[1]]) }) + if Input.numberChanged() then + UpdatedConfig:get()[vUDim[1]] = Input.number:get() + end + end + + local Vector2s = { + { "WindowPadding", nil, Vector2.zero, Vector2.one * 20 }, + { "WindowResizePadding", nil, Vector2.zero, Vector2.one * 20 }, + { "FramePadding", nil, Vector2.zero, Vector2.one * 20 }, + { "ItemSpacing", nil, Vector2.zero, Vector2.one * 20 }, + { "ItemInnerSpacing", nil, Vector2.zero, Vector2.one * 20 }, + { "CellPadding", nil, Vector2.zero, Vector2.one * 20 }, + { "DisplaySafeAreaPadding", nil, Vector2.zero, Vector2.one * 20 }, + } + for _, vVector2 in Vector2s do + local Input = Iris.SliderVector2({ table.unpack(vVector2) }, { number = Iris.WeakState(Iris._config[vVector2[1]]) }) + if Input.numberChanged() then + UpdatedConfig:get()[vVector2[1]] = Input.number:get() + end + end + + local Numbers = { + { "TextSize", 1, 4, 20 }, + { "FrameBorderSize", 0.1, 0, 1 }, + { "FrameRounding", 1, 0, 12 }, + { "GrabRounding", 1, 0, 12 }, + { "WindowBorderSize", 0.1, 0, 1 }, + { "PopupBorderSize", 0.1, 0, 1 }, + { "PopupRounding", 1, 0, 12 }, + { "ScrollbarSize", 1, 0, 20 }, + { "GrabMinSize", 1, 0, 20 }, + } + for _, vNumber in Numbers do + local Input = Iris.SliderNum({ table.unpack(vNumber) }, { number = Iris.WeakState(Iris._config[vNumber[1]]) }) + if Input.numberChanged() then + UpdatedConfig:get()[vNumber[1]] = Input.number:get() + end + end + + local Enums = { + "WindowTitleAlign", + -- "TextFont" + } + for _, vEnum in Enums do + local Input = Iris.ComboEnum({ vEnum }, { index = Iris.WeakState(Iris._config[vEnum]) }, Iris._config[vEnum].EnumType) + if Input.closed() then + Iris.UpdateGlobalConfig({ [vEnum] = Input.index:get() }) + end + end + end, + }, + { + "Colors", + function() + local UpdatedConfig = Iris.State({}) + + if Iris.Button({ "Update Config" }).clicked() then + Iris.UpdateGlobalConfig(UpdatedConfig:get()) + UpdatedConfig:set({}) + end + + local color3s = { "BorderColor", "BorderActiveColor" } + + for _, vColor in color3s do + local Input = Iris.InputColor3({ vColor }, { color = Iris.WeakState(Iris._config[vColor]) }) + if Input.numberChanged() then + Iris.UpdateGlobalConfig({ [vColor] = Input.color:get() }) + end + end + + local color4s = { + "Text", + "TextDisabled", + "WindowBg", + "ScrollbarGrab", + "TitleBg", + "TitleBgActive", + "TitleBgCollapsed", + "MenubarBg", + "FrameBg", + "FrameBgHovered", + "FrameBgActive", + "Button", + "ButtonHovered", + "ButtonActive", + "SliderGrab", + "SliderGrabActive", + "Header", + "HeaderHovered", + "HeaderActive", + "SelectionImageObject", + "SelectionImageObjectBorder", + "TableBorderStrong", + "TableBorderLight", + "TableRowBg", + "TableRowBgAlt", + "NavWindowingHighlight", + "NavWindowingDimBg", + "Separator", + "CheckMark", + } + + for _, vColor in color4s do + local Input = Iris.InputColor4({ vColor }, { + color = Iris.WeakState(Iris._config[vColor .. "Color"]), + transparency = Iris.WeakState(Iris._config[vColor .. "Transparency"]), + }) + if Input.numberChanged() then + UpdatedConfig:get()[vColor .. "Color"] = Input.color:get() + UpdatedConfig:get()[vColor .. "Transparency"] = Input.transparency:get() + end + end + end, + }, + } + + local window = Iris.Window({ "Style Editor" }, { isOpened = showStyleEditor }) + Iris.Text({ `Clicked close: {window.closed()}` }) + Iris.Text({ "Customize the look of Iris in realtime." }) + Iris.SameLine() + if Iris.SmallButton({ "Light Theme" }).clicked() then + Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorLight) + end + if Iris.SmallButton({ "Dark Theme" }).clicked() then + Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) + end + Iris.End() + + Iris.SameLine() + if Iris.SmallButton({ "Classic Size" }).clicked() then + Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) + end + if Iris.SmallButton({ "Larger Size" }).clicked() then + Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeClear) + end + Iris.End() + + if Iris.SmallButton({ "Reset Everything" }).clicked() then + Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) + Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) + end + Iris.Separator() + + Iris.SameLine() + for i, v in ipairs(styleList) do + Iris.RadioButton({ v[1], i }, { index = selectedPanel }) + end + Iris.End() + + styleList[selectedPanel:get()][2]() + Iris.End() + end + end + + local function widgetEventInteractivity() + Iris.CollapsingHeader({ "Widget Event Interactivity" }) + local clickCount = Iris.State(0) + if Iris.Button({ "Click to increase Number" }).clicked() then + clickCount:set(clickCount:get() + 1) + end + Iris.Text({ "The Number is: " .. clickCount:get() }) + + Iris.Separator() + + local showEventText = Iris.State(false) + local selectedEvent = Iris.State("clicked") + Iris.SameLine() + Iris.RadioButton({ "clicked", "clicked" }, { index = selectedEvent }) + Iris.RadioButton({ "rightClicked", "rightClicked" }, { index = selectedEvent }) + Iris.RadioButton({ "doubleClicked", "doubleClicked" }, { index = selectedEvent }) + Iris.RadioButton({ "ctrlClicked", "ctrlClicked" }, { index = selectedEvent }) + Iris.End() + Iris.SameLine() + + if Iris.Button({ selectedEvent:get() .. " to reveal text" })[selectedEvent:get()]() then + showEventText:set(not showEventText:get()) + end + if showEventText:get() then + Iris.Text({ "Here i am!" }) + end + + Iris.End() + + Iris.Separator() + + local showTextTimer = Iris.State(0) + Iris.SameLine() + if Iris.Button({ "Click to show text for 20 frames" }).clicked() then + showTextTimer:set(20) + end + if showTextTimer:get() > 0 then + Iris.Text({ "Here i am!" }) + end + Iris.End() + showTextTimer:set(math.max(0, showTextTimer:get() - 1)) + Iris.Text({ "Text Timer: " .. showTextTimer:get() }) + + local checkbox0 = Iris.Checkbox({ "Event-tracked checkbox" }) + Iris.Indent() + Iris.Text({ "unchecked: " .. tostring(checkbox0.unchecked()) }) + Iris.Text({ "checked: " .. tostring(checkbox0.checked()) }) + Iris.End() + Iris.SameLine() + if Iris.Button({ "Hover over me" }).hovered() then + Iris.Text({ "The button is hovered" }) + end + Iris.End() + Iris.End() + end + + local function widgetStateInteractivity() + Iris.CollapsingHeader({ "Widget State Interactivity" }) + local checkbox0 = Iris.Checkbox({ "Widget-Generated State" }) + Iris.Text({ `isChecked: {checkbox0.state.isChecked.value}\n` }) + + local checkboxState0 = Iris.State(false) + local checkbox1 = Iris.Checkbox({ "User-Generated State" }, { isChecked = checkboxState0 }) + Iris.Text({ `isChecked: {checkbox1.state.isChecked.value}\n` }) + + local checkbox2 = Iris.Checkbox({ "Widget Coupled State" }) + local checkbox3 = Iris.Checkbox({ "Coupled to above Checkbox" }, { isChecked = checkbox2.state.isChecked }) + Iris.Text({ `isChecked: {checkbox3.state.isChecked.value}\n` }) + + local checkboxState1 = Iris.State(false) + local _checkbox4 = Iris.Checkbox({ "Widget and Code Coupled State" }, { isChecked = checkboxState1 }) + local Button0 = Iris.Button({ "Click to toggle above checkbox" }) + if Button0.clicked() then + checkboxState1:set(not checkboxState1:get()) + end + Iris.Text({ `isChecked: {checkboxState1.value}\n` }) + + local checkboxState2 = Iris.State(true) + local checkboxState3 = Iris.ComputedState(checkboxState2, function(newValue) + return not newValue + end) + local _checkbox5 = Iris.Checkbox({ "ComputedState (dynamic coupling)" }, { isChecked = checkboxState2 }) + local _checkbox5 = Iris.Checkbox({ "Inverted of above checkbox" }, { isChecked = checkboxState3 }) + Iris.Text({ `isChecked: {checkboxState3.value}\n` }) + + Iris.End() + end + + local function dynamicStyle() + Iris.CollapsingHeader({ "Dynamic Styles" }) + local colorH = Iris.State(0) + Iris.SameLine() + if Iris.Button({ "Change Color" }).clicked() then + colorH:set(math.random()) + end + Iris.Text({ "Hue: " .. math.floor(colorH:get() * 255) }) + helpMarker("Using PushConfig with a changing value, this can be done with any config field") + Iris.End() + Iris.PushConfig({ TextColor = Color3.fromHSV(colorH:get(), 1, 1) }) + Iris.Text({ "Text with a unique and changable color" }) + Iris.PopConfig() + Iris.End() + end + + local function tablesDemo() + local showTablesTree = Iris.State(false) + + Iris.CollapsingHeader({ "Tables & Columns" }, { isUncollapsed = showTablesTree }) + if showTablesTree.value == false then + -- optimization to skip code which draws GUI which wont be seen. + -- its a trade off because when the tree becomes opened widgets will all have to be generated again. + -- Dear ImGui utilizes the same trick, but its less useful here because the Retained mode Backend + Iris.End() + else + Iris.SameLine() + Iris.Text({ "Table using NextRow and NextColumn syntax:" }) + helpMarker("calling Iris.NextRow() in the outer loop, and Iris.NextColumn()in the inner loop") + Iris.End() + Iris.Table({ 3 }) + for i = 1, 4 do + Iris.NextRow() + for i2 = 1, 3 do + Iris.NextColumn() + Iris.Text({ `Row: {i}, Column: {i2}` }) + end + end + Iris.End() + + Iris.Text({ "" }) + + Iris.SameLine() + Iris.Text({ "Table using NextColumn only syntax:" }) + helpMarker("only calling Iris.NextColumn() in the inner loop, the result is identical") + Iris.End() + + Iris.Table({ 2 }) + for i = 1, 4 do + for i2 = 1, 2 do + Iris.NextColumn() + Iris.Text({ `Row: {i}, Column: {i2}` }) + end + end + Iris.End() + + Iris.Separator() + + local TableRowBg = Iris.State(false) + local TableBordersOuter = Iris.State(false) + local TableBordersInner = Iris.State(true) + local TableUseButtons = Iris.State(true) + local TableNumRows = Iris.State(3) + + Iris.Text({ "Table with Customizable Arguments" }) + Iris.Table({ + 4, + [Iris.Args.Table.RowBg] = TableRowBg.value, + [Iris.Args.Table.BordersOuter] = TableBordersOuter.value, + [Iris.Args.Table.BordersInner] = TableBordersInner.value, + }) + for i = 1, TableNumRows:get() do + for i2 = 1, 4 do + Iris.NextColumn() + if TableUseButtons.value then + Iris.Button({ `Month: {i}, Week: {i2}` }) + else + Iris.Text({ `Month: {i}, Week: {i2}` }) + end + end + end + Iris.End() + + Iris.Checkbox({ "RowBg" }, { isChecked = TableRowBg }) + Iris.Checkbox({ "BordersOuter" }, { isChecked = TableBordersOuter }) + Iris.Checkbox({ "BordersInner" }, { isChecked = TableBordersInner }) + Iris.SameLine() + Iris.RadioButton({ "Buttons", true }, { index = TableUseButtons }) + Iris.RadioButton({ "Text", false }, { index = TableUseButtons }) + Iris.End() + Iris.InputNum({ + "Number of rows", + [Iris.Args.InputNum.Min] = 0, + [Iris.Args.InputNum.Max] = 100, + [Iris.Args.InputNum.Format] = "%d", + }, { number = TableNumRows }) + + Iris.End() + end + end + + local function layoutDemo() + Iris.CollapsingHeader({ "Widget Layout" }) + Iris.Tree({ "Content Width" }) + local value = Iris.State(50) + local index = Iris.State(Enum.Axis.X) + + Iris.Text({ "The Content Width is a size property which determines the width of input fields." }) + Iris.SameLine() + Iris.Text({ "By default the value is UDim.new(0.65, 0)" }) + helpMarker("This is the default value from Dear ImGui.\nIt is 65% of the window width.") + Iris.End() + Iris.Text({ "This works well, but sometimes we know how wide elements are going to be and want to maximise the space." }) + Iris.Text({ "Therefore, we can use Iris.PushConfig() to change the width" }) + + Iris.Separator() + + Iris.SameLine() + Iris.Text({ "Content Width = 150 pixels" }) + helpMarker("UDim.new(0, 150)") + Iris.End() + Iris.PushConfig({ ContentWidth = UDim.new(0, 150) }) + Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) + Iris.ComboEnum({ "axis" }, { index = index }, Enum.Axis) + Iris.PopConfig() + + Iris.SameLine() + Iris.Text({ "Content Width = 50% window width" }) + helpMarker("UDim.new(0.5, 0)") + Iris.End() + Iris.PushConfig({ ContentWidth = UDim.new(0.5, 0) }) + Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) + Iris.ComboEnum({ "axis" }, { index = index }, Enum.Axis) + Iris.PopConfig() + + Iris.SameLine() + Iris.Text({ "Content Width = -150 pixels from the right side" }) + helpMarker("UDim.new(1, -150)") + Iris.End() + Iris.PushConfig({ ContentWidth = UDim.new(1, -150) }) + Iris.DragNum({ "number", 1, 0, 100 }, { number = value }) + Iris.InputEnum({ "axis" }, { index = index }, Enum.Axis) + Iris.PopConfig() + Iris.End() + Iris.End() + end + + -- showcases how widgets placed outside of a window are placed inside root + local function windowlessDemo() + Iris.PushConfig({ ItemWidth = UDim.new(0, 150) }) + Iris.SameLine() + Iris.TextWrapped({ "Windowless widgets" }) + helpMarker("Widgets which are placed outside of a window will appear on the top left side of the screen.") + Iris.End() + Iris.Button({}) + Iris.Tree({}) + Iris.InputText({}) + Iris.End() + Iris.PopConfig() + end + + -- main demo window + return function() + local NoTitleBar = Iris.State(false) + local NoBackground = Iris.State(false) + local NoCollapse = Iris.State(false) + local NoClose = Iris.State(true) + local NoMove = Iris.State(false) + local NoScrollbar = Iris.State(false) + local NoResize = Iris.State(false) + local NoNav = Iris.State(false) + local NoMenu = Iris.State(false) + + if showMainWindow.value == false then + Iris.Checkbox({ "Open main window" }, { isChecked = showMainWindow }) + return + end + + Iris.Window({ + "Iris Demo Window", + [Iris.Args.Window.NoTitleBar] = NoTitleBar.value, + [Iris.Args.Window.NoBackground] = NoBackground.value, + [Iris.Args.Window.NoCollapse] = NoCollapse.value, + [Iris.Args.Window.NoClose] = NoClose.value, + [Iris.Args.Window.NoMove] = NoMove.value, + [Iris.Args.Window.NoScrollbar] = NoScrollbar.value, + [Iris.Args.Window.NoResize] = NoResize.value, + [Iris.Args.Window.NoNav] = NoNav.value, + [Iris.Args.Window.NoMenu] = NoMenu.value, + }, { size = Iris.State(Vector2.new(600, 550)), position = Iris.State(Vector2.new(100, 25)), isOpened = showMainWindow }) + + mainMenuBar() + + Iris.Text({ "Iris says hello. (" .. Iris.Internal._version .. ")" }) + + Iris.CollapsingHeader({ "Window Options" }) + Iris.Table({ 3, false, false, false }) + Iris.NextColumn() + Iris.Checkbox({ "NoTitleBar" }, { isChecked = NoTitleBar }) + Iris.NextColumn() + Iris.Checkbox({ "NoBackground" }, { isChecked = NoBackground }) + Iris.NextColumn() + Iris.Checkbox({ "NoCollapse" }, { isChecked = NoCollapse }) + Iris.NextColumn() + Iris.Checkbox({ "NoClose" }, { isChecked = NoClose }) + Iris.NextColumn() + Iris.Checkbox({ "NoMove" }, { isChecked = NoMove }) + Iris.NextColumn() + Iris.Checkbox({ "NoScrollbar" }, { isChecked = NoScrollbar }) + Iris.NextColumn() + Iris.Checkbox({ "NoResize" }, { isChecked = NoResize }) + Iris.NextColumn() + Iris.Checkbox({ "NoNav" }, { isChecked = NoNav }) + Iris.NextColumn() + Iris.Checkbox({ "NoMenu" }, { isChecked = NoMenu }) + Iris.End() + Iris.End() + + -- stylua: ignore end + + widgetEventInteractivity() + + widgetStateInteractivity() + + Iris.CollapsingHeader({ "Recursive Tree" }) + recursiveTree() + Iris.End() + + dynamicStyle() + + Iris.Separator() + + Iris.CollapsingHeader({ "Widgets" }) + for _, name in widgetDemosOrder do + widgetDemos[name]() + end + Iris.End() + + tablesDemo() + + layoutDemo() + Iris.End() + + if showRecursiveWindow.value then + recursiveWindow(showRecursiveWindow) + end + if showRuntimeInfo.value then + runtimeInfo() + end + if showStyleEditor.value then + styleEditor() + end + if showWindowlessDemo.value then + windowlessDemo() + end + + if showMainMenuBarWindow.value then + mainMenuBarExample() + end + end +end diff --git a/src/client/Iris/init.lua b/lib/init.lua similarity index 96% rename from src/client/Iris/init.lua rename to lib/init.lua index c8fcc4f..b038376 100644 --- a/src/client/Iris/init.lua +++ b/lib/init.lua @@ -1,421 +1,421 @@ ---!optimize 2 -local Types = require(script.Types) - ---[=[ - @class Iris - - Iris; contains the all user-facing functions and properties. - A set of internal functions can be found in `Iris.Internal` (only use unless you understand). -]=] -local Iris = {} :: Types.Iris -local Internal: Types.Internal = require(script.Internal)(Iris) - ---[=[ - @prop Disabled boolean - @within Iris - - While Iris.Disabled is true, execution of Iris and connected functions will be paused. - The widgets are not destroyed, they are just frozen so no changes will happen to them. -]=] -Iris.Disabled = false - ---[=[ - @prop Args table - @within Iris - - Provides a list of every possible Argument for each type of widget to it's index. - For instance, `Iris.Args.Window.NoResize`. - The Args table is useful for using widget Arguments without remembering their order. - ```lua - Iris.Window({"My Window", [Iris.Args.Window.NoResize] = true}) - ``` -]=] -Iris.Args = {} - ---[=[ - @ignore - @prop Events table - @within Iris - - -todo: work out what this is used for. -]=] -Iris.Events = {} - ---[=[ - @function Init - @within Iris - @param parentInstance Instance | nil -- instance which Iris will place UI in. defaults to [PlayerGui] if unspecified - @param eventConnection RBXScriptSignal | () -> {} | nil - @return Iris - - Initializes Iris and begins rendering. May only be called once. - By default, Iris will create its widgets under the PlayerGui and use the Heartbeat event. -]=] -function Iris.Init(parentInstance: BasePlayerGui?, eventConnection: (RBXScriptSignal | () -> ())?): Types.Iris - if parentInstance == nil then - -- coalesce to playerGui - parentInstance = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui") - end - if eventConnection == nil then - -- coalesce to Heartbeat - eventConnection = game:GetService("RunService").Heartbeat - end - Internal.parentInstance = parentInstance :: BasePlayerGui - assert(Internal._started == false, "Iris.Init can only be called once.") - Internal._started = true - - Internal._generateRootInstance() - Internal._generateSelectionImageObject() - - -- spawns the connection to call `Internal._cycle()` within. - task.spawn(function() - if typeof(eventConnection) == "function" then - while true do - eventConnection() - Internal._cycle() - end - elseif eventConnection ~= nil then - eventConnection:Connect(function() - Internal._cycle() - end) - end - end) - - return Iris -end - ---[=[ - @within Iris - @method Connect - @param callback function -- the callback containg the Iris code. - - Allows users to connect a function which will execute every Iris cycle, (cycle is determined by the callback or event passed to Iris.Init or default to Heartbeat). - Multiple callbacks can be added to Iris from many different scripts or modules. -]=] -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") - end - table.insert(Internal._connectedFunctions, callback) -end - ---[=[ - @function Append - @within Iris - - Allows the caller to insert any Roblox Instance into Iris. The parent can either be determined by the `_config.Parent` - property or by the current parent widget from the stack. -]=] -function Iris.Append(userInstance: GuiObject) - local parentWidget: Types.Widget = Internal._GetParentWidget() - local widgetInstanceParent: GuiObject - if Internal._config.Parent then - widgetInstanceParent = Internal._config.Parent :: any - else - widgetInstanceParent = Internal._widgets[parentWidget.type].ChildAdded(parentWidget, { type = "userInstance" } :: Types.Widget) - end - userInstance.Parent = widgetInstanceParent -end - ---[=[ - @function End - @within Iris - - This function marks the end of any widgets which contain children. For example: - ```lua - -- Widgets placed here **will not** be inside the tree - Iris.Tree({"My First Tree"}) - -- Widgets placed here **will** be inside the tree - Iris.End() - -- Widgets placed here **will not** be inside the tree - ``` - :::caution Caution: Error - Seeing the error `Callback has too few calls to Iris.End()` or `Callback has too many calls to Iris.End()`? - Using the wrong amount of `Iris.End()` calls in your code will lead to an error. Each widget called which might have children should be paired with a call to `Iris.End()`, **Even if the Widget doesnt currently have any children**. - ::: -]=] -function Iris.End() - if Internal._stackIndex == 1 then - error("Callback has too many calls to Iris.End()", 2) - end - Internal._IDStack[Internal._stackIndex] = nil - Internal._stackIndex -= 1 -end - ---[[ - ------------------------ - [SECTION] Config - ------------------------ -]] - ---[=[ - @function ForceRefresh - @within Iris - - Destroys and regenerates all instances used by Iris. Useful if you want to propogate state changes. - :::caution Caution: Performance - Because this function Deletes and Initializes many instances, it may cause **performance issues** when used with many widgets. - In **no** case should it be called every frame. - ::: -]=] -function Iris.ForceRefresh() - Internal._globalRefreshRequested = true -end - ---[=[ - @function UpdateGlobalConfig - @within Iris - @param deltaStyle table -- a table containing the changes in style ex: `{ItemWidth = UDim.new(0, 100)}` - - Allows callers to customize the config which **every** widget will inherit from. - It can be used along with Iris.TemplateConfig to easily swap styles, ex: ```Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorLight) -- use light theme``` - :::caution Caution: Performance - this function internally calls [Iris.ForceRefresh] so that style changes are propogated, it may cause **performance issues** when used with many widgets. - In **no** case should it be called every frame. - ::: -]=] -function Iris.UpdateGlobalConfig(deltaStyle: { [string]: any }) - for index, style in deltaStyle do - Internal._rootConfig[index] = style - end - Iris.ForceRefresh() -end - ---[=[ - @function PushConfig - @within Iris - @param deltaStyle table -- a table containing the changes in style ex: `{ItemWidth = UDim.new(0, 100)}` - - Allows callers to cascade a style, meaning that styles may be locally and hierarchically applied. - Each call to Iris.PushConfig must be paired with a call to [Iris.PopConfig]. - For example: - ```lua - Iris.PushConfig({TextColor = Color3.fromRGB(128, 0, 256)}) - Iris.Text({"Colored Text!"}) - Iris.PopConfig() - ``` -]=] -function Iris.PushConfig(deltaStyle: { [string]: any }) - local ID = Iris.State(-1) - if ID.value == -1 then - ID:set(deltaStyle) - else - -- compare tables - if Internal._deepCompare(ID:get(), deltaStyle) == false then - -- refresh local - Internal._localRefreshActive = true - ID:set(deltaStyle) - end - end - - Internal._config = setmetatable(deltaStyle, { - __index = Internal._config, - }) :: any -end - ---[=[ - @function PopConfig - @within Iris - - Ends a PushConfig style. - Each call to [Iris.PushConfig] must be paired with a call to Iris.PopConfig. -]=] -function Iris.PopConfig() - Internal._localRefreshActive = false - Internal._config = getmetatable(Internal._config :: any).__index -end - ---[=[ - @prop TemplateConfig table - @within Iris - - TemplateConfig provides a table of default styles and configurations which you may apply to your UI. -]=] -Iris.TemplateConfig = require(script.config) -Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) -- use colorDark and sizeDefault themes by default -Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) -Iris.UpdateGlobalConfig(Iris.TemplateConfig.utilityDefault) -Internal._globalRefreshRequested = false -- UpdatingGlobalConfig changes this to true, leads to Root being generated twice. - ---[[ - -------------------- - [SECTION] ID - -------------------- -]] - ---[=[ - @function PushId - @within Iris - @param id Types.ID -- custom id. - - 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.") - - Internal._pushedId = tostring(id) -end - ---[=[ - @function PopId - @within Iris - - Removes the id discriminator set by [Iris.PushId]. -]=] -function Iris.PopId() - Internal._pushedId = nil -end - ---[=[ - @function SetNextWidgetId - @within Iris - @param id Types.ID -- custom id. - - Sets the id for the next widget. Useful for using [Iris.Append] on the same widget. - ```lua - Iris.SetNextWidgetId("demo_window") - Iris.Window({ "Window" }) - Iris.Text({ "Text one placed here." }) - Iris.End() - - -- later in the code - - Iris.SetNextWidgetId("demo_window") - Iris.Window() - Iris.Text({ "Text two placed here." }) - Iris.End() - - -- both text widgets will be placed under the same window despite being called separately. - ``` -]=] -function Iris.SetNextWidgetID(id: Types.ID) - Internal._nextWidgetId = id -end - ---[[ - ----------------------- - [SECTION] State - ----------------------- -]] - ---[=[ - @function State - @within Iris - @param initialValue any -- The initial value for the state - - Constructs a new state object, subsequent ID calls will return the same object - :::info - Iris.State allows you to create "references" to the same value while inside your UI drawing loop. - For example: - ```lua - Iris:Connect(function() - local myNumber = 5; - myNumber = myNumber + 1 - Iris.Text({"The number is: " .. myNumber}) - end) - ``` - This is problematic. Each time the function is called, a new myNumber is initialized, instead of retrieving the old one. - The above code will always display 6. - *** - Iris.State solves this problem: - ```lua - Iris:Connect(function() - local myNumber = Iris.State(5) - myNumber:set(myNumber:get() + 1) - Iris.Text({"The number is: " .. myNumber}) - end) - ``` - In this example, the code will work properly, and increment every frame. - ::: -]=] -function Iris.State(initialValue: any): Types.State - local ID: Types.ID = Internal._getID(2) - if Internal._states[ID] then - return Internal._states[ID] - end - Internal._states[ID] = { - value = initialValue, - ConnectedWidgets = {}, - ConnectedFunctions = {}, - } :: any - setmetatable(Internal._states[ID], Internal.StateClass) - return Internal._states[ID] -end - ---[=[ - @function State - @within Iris - @param initialValue any -- The initial value for the state - - Constructs a new state object, subsequent ID calls will return the same object, except all widgets connected to the state are discarded, the state reverts to the passed initialValue -]=] -function Iris.WeakState(initialValue: any): Types.State - local ID: Types.ID = Internal._getID(2) - if Internal._states[ID] then - if #Internal._states[ID].ConnectedWidgets == 0 then - Internal._states[ID] = nil - else - return Internal._states[ID] - end - end - Internal._states[ID] = { - value = initialValue, - ConnectedWidgets = {}, - ConnectedFunctions = {}, - } :: any - setmetatable(Internal._states[ID], Internal.StateClass) - return Internal._states[ID] -end - ---[=[ - @function ComputedState - @within Iris - @param firstState State -- State to bind to. - @param onChangeCallback function -- callback which should return a value transformed from the firstState value - - Constructs a new State object, but binds its value to the value of another State. - :::info - A common use case for this constructor is when a boolean State needs to be inverted: - ```lua - Iris.ComputedState(otherState, function(newValue) - return not newValue - end) - ``` - ::: -]=] -function Iris.ComputedState(firstState: Types.State, onChangeCallback: (firstState: any) -> any): Types.State - local ID: Types.ID = Internal._getID(2) - - if Internal._states[ID] then - return Internal._states[ID] - else - Internal._states[ID] = { - value = onChangeCallback(firstState.value), - ConnectedWidgets = {}, - ConnectedFunctions = {}, - } :: any - firstState:onChange(function(newValue: any) - Internal._states[ID]:set(onChangeCallback(newValue)) - end) - setmetatable(Internal._states[ID], Internal.StateClass) - return Internal._states[ID] - end -end - ---[=[ - @function ShowDemoWindow - @within Iris - - ShowDemoWindow is a function which creates a Demonstration window. this window contains many useful utilities for coders, - and serves as a refrence for using each part of the library. Ideally, the DemoWindow should always be available in your UI. - It is the same as any other callback you would connect to Iris using [Iris.Connect] - ```lua - Iris:Connect(Iris.ShowDemoWindow) - ``` -]=] -Iris.ShowDemoWindow = require(script.demoWindow)(Iris) - -require(script.widgets)(Internal) -require(script.API)(Iris) - -return Iris +--!optimize 2 +local Types = require(script.Types) + +--[=[ + @class Iris + + Iris; contains the all user-facing functions and properties. + A set of internal functions can be found in `Iris.Internal` (only use unless you understand). +]=] +local Iris = {} :: Types.Iris +local Internal: Types.Internal = require(script.Internal)(Iris) + +--[=[ + @prop Disabled boolean + @within Iris + + While Iris.Disabled is true, execution of Iris and connected functions will be paused. + The widgets are not destroyed, they are just frozen so no changes will happen to them. +]=] +Iris.Disabled = false + +--[=[ + @prop Args table + @within Iris + + Provides a list of every possible Argument for each type of widget to it's index. + For instance, `Iris.Args.Window.NoResize`. + The Args table is useful for using widget Arguments without remembering their order. + ```lua + Iris.Window({"My Window", [Iris.Args.Window.NoResize] = true}) + ``` +]=] +Iris.Args = {} + +--[=[ + @ignore + @prop Events table + @within Iris + + -todo: work out what this is used for. +]=] +Iris.Events = {} + +--[=[ + @function Init + @within Iris + @param parentInstance Instance | nil -- instance which Iris will place UI in. defaults to [PlayerGui] if unspecified + @param eventConnection RBXScriptSignal | () -> {} | nil + @return Iris + + Initializes Iris and begins rendering. May only be called once. + By default, Iris will create its widgets under the PlayerGui and use the Heartbeat event. +]=] +function Iris.Init(parentInstance: BasePlayerGui?, eventConnection: (RBXScriptSignal | () -> ())?): Types.Iris + if parentInstance == nil then + -- coalesce to playerGui + parentInstance = game:GetService("Players").LocalPlayer:WaitForChild("PlayerGui") + end + if eventConnection == nil then + -- coalesce to Heartbeat + eventConnection = game:GetService("RunService").Heartbeat + end + Internal.parentInstance = parentInstance :: BasePlayerGui + assert(Internal._started == false, "Iris.Init can only be called once.") + Internal._started = true + + Internal._generateRootInstance() + Internal._generateSelectionImageObject() + + -- spawns the connection to call `Internal._cycle()` within. + task.spawn(function() + if typeof(eventConnection) == "function" then + while true do + eventConnection() + Internal._cycle() + end + elseif eventConnection ~= nil then + eventConnection:Connect(function() + Internal._cycle() + end) + end + end) + + return Iris +end + +--[=[ + @within Iris + @method Connect + @param callback function -- the callback containg the Iris code. + + Allows users to connect a function which will execute every Iris cycle, (cycle is determined by the callback or event passed to Iris.Init or default to Heartbeat). + Multiple callbacks can be added to Iris from many different scripts or modules. +]=] +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") + end + table.insert(Internal._connectedFunctions, callback) +end + +--[=[ + @function Append + @within Iris + + Allows the caller to insert any Roblox Instance into Iris. The parent can either be determined by the `_config.Parent` + property or by the current parent widget from the stack. +]=] +function Iris.Append(userInstance: GuiObject) + local parentWidget: Types.Widget = Internal._GetParentWidget() + local widgetInstanceParent: GuiObject + if Internal._config.Parent then + widgetInstanceParent = Internal._config.Parent :: any + else + widgetInstanceParent = Internal._widgets[parentWidget.type].ChildAdded(parentWidget, { type = "userInstance" } :: Types.Widget) + end + userInstance.Parent = widgetInstanceParent +end + +--[=[ + @function End + @within Iris + + This function marks the end of any widgets which contain children. For example: + ```lua + -- Widgets placed here **will not** be inside the tree + Iris.Tree({"My First Tree"}) + -- Widgets placed here **will** be inside the tree + Iris.End() + -- Widgets placed here **will not** be inside the tree + ``` + :::caution Caution: Error + Seeing the error `Callback has too few calls to Iris.End()` or `Callback has too many calls to Iris.End()`? + Using the wrong amount of `Iris.End()` calls in your code will lead to an error. Each widget called which might have children should be paired with a call to `Iris.End()`, **Even if the Widget doesnt currently have any children**. + ::: +]=] +function Iris.End() + if Internal._stackIndex == 1 then + error("Callback has too many calls to Iris.End()", 2) + end + Internal._IDStack[Internal._stackIndex] = nil + Internal._stackIndex -= 1 +end + +--[[ + ------------------------ + [SECTION] Config + ------------------------ +]] + +--[=[ + @function ForceRefresh + @within Iris + + Destroys and regenerates all instances used by Iris. Useful if you want to propogate state changes. + :::caution Caution: Performance + Because this function Deletes and Initializes many instances, it may cause **performance issues** when used with many widgets. + In **no** case should it be called every frame. + ::: +]=] +function Iris.ForceRefresh() + Internal._globalRefreshRequested = true +end + +--[=[ + @function UpdateGlobalConfig + @within Iris + @param deltaStyle table -- a table containing the changes in style ex: `{ItemWidth = UDim.new(0, 100)}` + + Allows callers to customize the config which **every** widget will inherit from. + It can be used along with Iris.TemplateConfig to easily swap styles, ex: ```Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorLight) -- use light theme``` + :::caution Caution: Performance + this function internally calls [Iris.ForceRefresh] so that style changes are propogated, it may cause **performance issues** when used with many widgets. + In **no** case should it be called every frame. + ::: +]=] +function Iris.UpdateGlobalConfig(deltaStyle: { [string]: any }) + for index, style in deltaStyle do + Internal._rootConfig[index] = style + end + Iris.ForceRefresh() +end + +--[=[ + @function PushConfig + @within Iris + @param deltaStyle table -- a table containing the changes in style ex: `{ItemWidth = UDim.new(0, 100)}` + + Allows callers to cascade a style, meaning that styles may be locally and hierarchically applied. + Each call to Iris.PushConfig must be paired with a call to [Iris.PopConfig]. + For example: + ```lua + Iris.PushConfig({TextColor = Color3.fromRGB(128, 0, 256)}) + Iris.Text({"Colored Text!"}) + Iris.PopConfig() + ``` +]=] +function Iris.PushConfig(deltaStyle: { [string]: any }) + local ID = Iris.State(-1) + if ID.value == -1 then + ID:set(deltaStyle) + else + -- compare tables + if Internal._deepCompare(ID:get(), deltaStyle) == false then + -- refresh local + Internal._localRefreshActive = true + ID:set(deltaStyle) + end + end + + Internal._config = setmetatable(deltaStyle, { + __index = Internal._config, + }) :: any +end + +--[=[ + @function PopConfig + @within Iris + + Ends a PushConfig style. + Each call to [Iris.PushConfig] must be paired with a call to Iris.PopConfig. +]=] +function Iris.PopConfig() + Internal._localRefreshActive = false + Internal._config = getmetatable(Internal._config :: any).__index +end + +--[=[ + @prop TemplateConfig table + @within Iris + + TemplateConfig provides a table of default styles and configurations which you may apply to your UI. +]=] +Iris.TemplateConfig = require(script.config) +Iris.UpdateGlobalConfig(Iris.TemplateConfig.colorDark) -- use colorDark and sizeDefault themes by default +Iris.UpdateGlobalConfig(Iris.TemplateConfig.sizeDefault) +Iris.UpdateGlobalConfig(Iris.TemplateConfig.utilityDefault) +Internal._globalRefreshRequested = false -- UpdatingGlobalConfig changes this to true, leads to Root being generated twice. + +--[[ + -------------------- + [SECTION] ID + -------------------- +]] + +--[=[ + @function PushId + @within Iris + @param id Types.ID -- custom id. + + 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.") + + Internal._pushedId = tostring(id) +end + +--[=[ + @function PopId + @within Iris + + Removes the id discriminator set by [Iris.PushId]. +]=] +function Iris.PopId() + Internal._pushedId = nil +end + +--[=[ + @function SetNextWidgetId + @within Iris + @param id Types.ID -- custom id. + + Sets the id for the next widget. Useful for using [Iris.Append] on the same widget. + ```lua + Iris.SetNextWidgetId("demo_window") + Iris.Window({ "Window" }) + Iris.Text({ "Text one placed here." }) + Iris.End() + + -- later in the code + + Iris.SetNextWidgetId("demo_window") + Iris.Window() + Iris.Text({ "Text two placed here." }) + Iris.End() + + -- both text widgets will be placed under the same window despite being called separately. + ``` +]=] +function Iris.SetNextWidgetID(id: Types.ID) + Internal._nextWidgetId = id +end + +--[[ + ----------------------- + [SECTION] State + ----------------------- +]] + +--[=[ + @function State + @within Iris + @param initialValue any -- The initial value for the state + + Constructs a new state object, subsequent ID calls will return the same object + :::info + Iris.State allows you to create "references" to the same value while inside your UI drawing loop. + For example: + ```lua + Iris:Connect(function() + local myNumber = 5; + myNumber = myNumber + 1 + Iris.Text({"The number is: " .. myNumber}) + end) + ``` + This is problematic. Each time the function is called, a new myNumber is initialized, instead of retrieving the old one. + The above code will always display 6. + *** + Iris.State solves this problem: + ```lua + Iris:Connect(function() + local myNumber = Iris.State(5) + myNumber:set(myNumber:get() + 1) + Iris.Text({"The number is: " .. myNumber}) + end) + ``` + In this example, the code will work properly, and increment every frame. + ::: +]=] +function Iris.State(initialValue: any): Types.State + local ID: Types.ID = Internal._getID(2) + if Internal._states[ID] then + return Internal._states[ID] + end + Internal._states[ID] = { + value = initialValue, + ConnectedWidgets = {}, + ConnectedFunctions = {}, + } :: any + setmetatable(Internal._states[ID], Internal.StateClass) + return Internal._states[ID] +end + +--[=[ + @function State + @within Iris + @param initialValue any -- The initial value for the state + + Constructs a new state object, subsequent ID calls will return the same object, except all widgets connected to the state are discarded, the state reverts to the passed initialValue +]=] +function Iris.WeakState(initialValue: any): Types.State + local ID: Types.ID = Internal._getID(2) + if Internal._states[ID] then + if #Internal._states[ID].ConnectedWidgets == 0 then + Internal._states[ID] = nil + else + return Internal._states[ID] + end + end + Internal._states[ID] = { + value = initialValue, + ConnectedWidgets = {}, + ConnectedFunctions = {}, + } :: any + setmetatable(Internal._states[ID], Internal.StateClass) + return Internal._states[ID] +end + +--[=[ + @function ComputedState + @within Iris + @param firstState State -- State to bind to. + @param onChangeCallback function -- callback which should return a value transformed from the firstState value + + Constructs a new State object, but binds its value to the value of another State. + :::info + A common use case for this constructor is when a boolean State needs to be inverted: + ```lua + Iris.ComputedState(otherState, function(newValue) + return not newValue + end) + ``` + ::: +]=] +function Iris.ComputedState(firstState: Types.State, onChangeCallback: (firstState: any) -> any): Types.State + local ID: Types.ID = Internal._getID(2) + + if Internal._states[ID] then + return Internal._states[ID] + else + Internal._states[ID] = { + value = onChangeCallback(firstState.value), + ConnectedWidgets = {}, + ConnectedFunctions = {}, + } :: any + firstState:onChange(function(newValue: any) + Internal._states[ID]:set(onChangeCallback(newValue)) + end) + setmetatable(Internal._states[ID], Internal.StateClass) + return Internal._states[ID] + end +end + +--[=[ + @function ShowDemoWindow + @within Iris + + ShowDemoWindow is a function which creates a Demonstration window. this window contains many useful utilities for coders, + and serves as a refrence for using each part of the library. Ideally, the DemoWindow should always be available in your UI. + It is the same as any other callback you would connect to Iris using [Iris.Connect] + ```lua + Iris:Connect(Iris.ShowDemoWindow) + ``` +]=] +Iris.ShowDemoWindow = require(script.demoWindow)(Iris) + +require(script.widgets)(Internal) +require(script.API)(Iris) + +return Iris diff --git a/src/client/Iris/widgets/Button.lua b/lib/widgets/Button.lua similarity index 100% rename from src/client/Iris/widgets/Button.lua rename to lib/widgets/Button.lua diff --git a/src/client/Iris/widgets/Checkbox.lua b/lib/widgets/Checkbox.lua similarity index 100% rename from src/client/Iris/widgets/Checkbox.lua rename to lib/widgets/Checkbox.lua diff --git a/src/client/Iris/widgets/Combo.lua b/lib/widgets/Combo.lua similarity index 100% rename from src/client/Iris/widgets/Combo.lua rename to lib/widgets/Combo.lua diff --git a/src/client/Iris/widgets/Format.lua b/lib/widgets/Format.lua similarity index 100% rename from src/client/Iris/widgets/Format.lua rename to lib/widgets/Format.lua diff --git a/src/client/Iris/widgets/Input.lua b/lib/widgets/Input.lua similarity index 100% rename from src/client/Iris/widgets/Input.lua rename to lib/widgets/Input.lua diff --git a/src/client/Iris/widgets/Menu.lua b/lib/widgets/Menu.lua similarity index 100% rename from src/client/Iris/widgets/Menu.lua rename to lib/widgets/Menu.lua diff --git a/src/client/Iris/widgets/RadioButton.lua b/lib/widgets/RadioButton.lua similarity index 100% rename from src/client/Iris/widgets/RadioButton.lua rename to lib/widgets/RadioButton.lua diff --git a/src/client/Iris/widgets/Root.lua b/lib/widgets/Root.lua similarity index 100% rename from src/client/Iris/widgets/Root.lua rename to lib/widgets/Root.lua diff --git a/src/client/Iris/widgets/Table.lua b/lib/widgets/Table.lua similarity index 100% rename from src/client/Iris/widgets/Table.lua rename to lib/widgets/Table.lua diff --git a/src/client/Iris/widgets/Text.lua b/lib/widgets/Text.lua similarity index 100% rename from src/client/Iris/widgets/Text.lua rename to lib/widgets/Text.lua diff --git a/src/client/Iris/widgets/Tree.lua b/lib/widgets/Tree.lua similarity index 100% rename from src/client/Iris/widgets/Tree.lua rename to lib/widgets/Tree.lua diff --git a/src/client/Iris/widgets/Window.lua b/lib/widgets/Window.lua similarity index 100% rename from src/client/Iris/widgets/Window.lua rename to lib/widgets/Window.lua diff --git a/src/client/Iris/widgets/init.lua b/lib/widgets/init.lua similarity index 100% rename from src/client/Iris/widgets/init.lua rename to lib/widgets/init.lua diff --git a/model.project.json b/model.project.json deleted file mode 100644 index b90f981..0000000 --- a/model.project.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Iris", - "tree": { - "$path": "src/client/Iris" - } -} \ No newline at end of file diff --git a/src/client/helloIris.client.lua b/src/client/helloIris.client.lua index f424c38..d66c6f1 100644 --- a/src/client/helloIris.client.lua +++ b/src/client/helloIris.client.lua @@ -1,4 +1,4 @@ -local StarterPlayerScripts = game.StarterPlayer.StarterPlayerScripts -local Iris = require(StarterPlayerScripts.Client.Iris).Init() +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local Iris = require(ReplicatedStorage.Iris).Init() Iris:Connect(Iris.ShowDemoWindow) diff --git a/wally.toml b/wally.toml index fc886c5..ae5b64b 100644 --- a/wally.toml +++ b/wally.toml @@ -10,11 +10,5 @@ realm = "shared" registry = "https://github.com/upliftgames/wally-index" exclude = ["**"] -include = ["src/client/Iris", "src/client/Iris/*", "model.project.json", "wally.toml"] -private = false - -[dependencies] - -[server-dependencies] - -[dev-dependencies] +include = ["default.project.json", "lib", "lib/**", "LICENSE.txt", "wally.toml"] +private = false \ No newline at end of file