├── .gitattributes ├── .gitignore ├── GuiLibrary.rbxl ├── LICENSE ├── README.md ├── Test ├── Images │ ├── assorted.gif │ ├── dropdown.gif │ ├── radialmenu.gif │ └── slider.gif └── RadialBackpack.rbxl ├── default.project.json ├── selene.toml └── src └── GuiLib ├── Classes ├── CheckboxLabel.lua ├── Dragger.lua ├── Dropdown.lua ├── RadialMenu │ ├── CONSTANTS.lua │ ├── CreateRadial.lua │ ├── Triangle.lua │ └── init.lua ├── RadioButtonGroup.lua ├── RadioButtonLabel.lua ├── Slider.lua └── TextMask │ ├── Integer.lua │ ├── Number.lua │ ├── String.lua │ ├── Vector2.lua │ ├── Vector3.lua │ └── init.lua ├── Constructors └── List.lua ├── Defaults.rbxmx ├── LazyLoader.lua └── Utilities ├── Maid.lua └── Spring.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.lock 3 | -------------------------------------------------------------------------------- /GuiLibrary.rbxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/GuiLibrary.rbxl -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 EgoMoose 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rbx Gui Library 2 | 3 | Rbx Gui is a library I created to help developers with a few common UI components that are not currently present on the Roblox platform. 4 | 5 | The library is designed around two principles: 6 | 7 | 1. It should attempt to integrate into standard Roblox Lua UI programming. There are no complex frameworks or big setups, this should feel familiar like using built-in UI objects. 8 | 9 | 2. The style of the components should be easy to change so that developers can have functionality without having to sacrafice artistic direction. You can edit the default UI objects under the `Defaults` ScreenGui. 10 | 11 | Documentation for each class/constructor is in the individual module. You can also see the test place for an example of the components in use. 12 | 13 | Currently GuiLib provides support for: 14 | 15 | * Checkbox Labels 16 | * RadioButton Groups 17 | * Text Input Masking 18 | * Sliders 19 | * Dropdown Lists 20 | * Scrolling Lists 21 | * Radial Menu 22 | 23 | ![](Test/Images/assorted.gif) 24 | 25 | ![](Test/Images/dropdown.gif) 26 | 27 | ![](Test/Images/slider.gif) 28 | 29 | ![](Test/Images/radialmenu.gif) -------------------------------------------------------------------------------- /Test/Images/assorted.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/Test/Images/assorted.gif -------------------------------------------------------------------------------- /Test/Images/dropdown.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/Test/Images/dropdown.gif -------------------------------------------------------------------------------- /Test/Images/radialmenu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/Test/Images/radialmenu.gif -------------------------------------------------------------------------------- /Test/Images/slider.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/Test/Images/slider.gif -------------------------------------------------------------------------------- /Test/RadialBackpack.rbxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EgoMoose/Rbx-Gui-Library/37deb77e46be913f99ba2e39b0391c39dc9460e6/Test/RadialBackpack.rbxl -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rbx-Gui-Library", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ReplicatedStorage": { 6 | "$className": "ReplicatedStorage", 7 | "$path": "src" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | [rules] 2 | parenthese_conditions = "allow" 3 | multiple_statements = "allow" -------------------------------------------------------------------------------- /src/GuiLib/Classes/CheckboxLabel.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.CheckboxLabel 3 | 4 | This class creates a single checkbox label 5 | 6 | Constructors: 7 | new(frame [instance]) 8 | > 9 | Create(text) 10 | > Creates a CheckboxLabel from the text provided. 11 | 12 | Properties: 13 | Frame [instance] 14 | > The container frame for the CheckboxLabel. Can be used for positioning and resizing. 15 | Button [instance] 16 | > The button used to track when the user clicks on the checkbox button or not 17 | 18 | Methods: 19 | :GetValue() [boolean] 20 | > Returns whether the checkbox is selected or not. 21 | :SetValue(bool [boolean]) [void] 22 | > Sets if the checkbox is selected or not. 23 | :Destroy() [void] 24 | > Destroys the RadioButtonLabel and all the events, etc that were running it. 25 | 26 | Events: 27 | .Changed:Connect(function(bool [boolean]) 28 | > Fired when the user clicks the checkbox. 29 | --]] 30 | 31 | 32 | -- CONSTANTS 33 | 34 | local GuiLib = script.Parent.Parent 35 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 36 | local Defaults = GuiLib:WaitForChild("Defaults") 37 | 38 | local CHECKBOX_LABEL = Defaults:WaitForChild("CheckboxLabel") 39 | 40 | -- Class 41 | 42 | local CheckboxLabelClass = {} 43 | CheckboxLabelClass.__index = CheckboxLabelClass 44 | CheckboxLabelClass.__type = "CheckboxLabel" 45 | 46 | function CheckboxLabelClass:__tostring() 47 | return CheckboxLabelClass.__type 48 | end 49 | 50 | -- Public Constructors 51 | 52 | function CheckboxLabelClass.new(frame) 53 | local self = setmetatable({}, CheckboxLabelClass) 54 | 55 | self._Maid = Lazy.Utilities.Maid.new() 56 | self._ChangedBind = Instance.new("BindableEvent") 57 | 58 | self.Frame = frame 59 | self.Button = frame.CheckContainer.CheckButton 60 | self.Changed = self._ChangedBind.Event 61 | 62 | init(self) 63 | self:SetValue(false) 64 | 65 | return self 66 | end 67 | 68 | function CheckboxLabelClass.Create(text) 69 | local cbLabel = CHECKBOX_LABEL:Clone() 70 | cbLabel.Label.Text = text 71 | return CheckboxLabelClass.new(cbLabel) 72 | end 73 | 74 | -- Private Methods 75 | 76 | function init(self) 77 | local label = self.Frame.Label 78 | local container = self.Frame.CheckContainer 79 | local checkmark = self.Button.Checkmark 80 | 81 | local function contentSizeUpdate() 82 | local absSize = self.Frame.AbsoluteSize 83 | local ratio = absSize.y / absSize.x 84 | container.Size = UDim2.new(ratio, 0, 1, 0) 85 | label.Size = UDim2.new(1 - ratio, -10, 1, 0) 86 | label.Position = UDim2.new(ratio, 10, 0, 0) 87 | end 88 | 89 | contentSizeUpdate() 90 | self._Maid:Mark(self.Frame:GetPropertyChangedSignal("AbsoluteSize"):Connect(contentSizeUpdate)) 91 | 92 | self._Maid:Mark(self.Button.Activated:Connect(function() 93 | self:SetValue(not checkmark.Visible) 94 | end)) 95 | end 96 | 97 | -- Public Methods 98 | 99 | function CheckboxLabelClass:GetValue() 100 | return self.Button.Checkmark.Visible 101 | end 102 | 103 | function CheckboxLabelClass:SetValue(bool) 104 | bool = not not bool 105 | 106 | local container = self.Frame.CheckContainer 107 | local colorA = container.BackgroundColor3 108 | local colorB = container.BorderColor3 109 | local colorC = container.Outline.BackgroundColor3 110 | 111 | local outlineColor = bool and colorB or colorC 112 | if (bool and container.CheckButton.BackgroundTransparency == 1) then 113 | outlineColor = colorC 114 | end 115 | 116 | for _, child in next, container.Outline:GetChildren() do 117 | child.BackgroundColor3 = outlineColor 118 | end 119 | 120 | container.CheckButton.BackgroundColor3 = bool and colorB or colorA 121 | container.CheckButton.Checkmark.Visible = bool 122 | 123 | self._ChangedBind:Fire(bool) 124 | end 125 | 126 | function CheckboxLabelClass:Destroy() 127 | self._Maid:Sweep() 128 | self.Frame:Destroy() 129 | self.Changed = nil 130 | end 131 | 132 | -- 133 | 134 | return CheckboxLabelClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/Dragger.lua: -------------------------------------------------------------------------------- 1 | -- Meant for internal use, no documentation given 2 | 3 | -- CONSTANTS 4 | 5 | local GuiLib = script.Parent.Parent 6 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 7 | 8 | local UIS = game:GetService("UserInputService") 9 | local RUNSERVICE = game:GetService("RunService") 10 | 11 | local DEADZONE2 = 0.15^2 12 | local FLIP_THUMB = Vector3.new(1, -1, 1) 13 | 14 | local VALID_PRESS = { 15 | [Enum.UserInputType.MouseButton1] = true; 16 | [Enum.UserInputType.Touch] = true; 17 | } 18 | 19 | local VALID_MOVEMENT = { 20 | [Enum.UserInputType.MouseMovement] = true; 21 | [Enum.UserInputType.Touch] = true; 22 | } 23 | 24 | -- Class 25 | 26 | local DraggerClass = {} 27 | DraggerClass.__index = DraggerClass 28 | DraggerClass.__type = "Dragger" 29 | 30 | function DraggerClass:__tostring() 31 | return DraggerClass.__type 32 | end 33 | 34 | -- Public Constructors 35 | 36 | function DraggerClass.new(element) 37 | local self = setmetatable({}, DraggerClass) 38 | 39 | self._Maid = Lazy.Utilities.Maid.new() 40 | self._DragBind = Instance.new("BindableEvent") 41 | self._StartBind = Instance.new("BindableEvent") 42 | self._StopBind = Instance.new("BindableEvent") 43 | 44 | self.Element = element 45 | self.IsDragging = false 46 | self.DragChanged = self._DragBind.Event 47 | self.DragStart = self._StartBind.Event 48 | self.DragStop = self._StopBind.Event 49 | 50 | init(self) 51 | 52 | return self 53 | end 54 | 55 | -- Private Methods 56 | 57 | function init(self) 58 | local element = self.Element 59 | local maid = self._Maid 60 | local dragBind = self._DragBind 61 | local lastMousePosition = Vector3.new() 62 | 63 | maid:Mark(self._DragBind) 64 | maid:Mark(self._StartBind) 65 | maid:Mark(self._StopBind) 66 | 67 | maid:Mark(element.InputBegan:Connect(function(input) 68 | if (VALID_PRESS[input.UserInputType]) then 69 | lastMousePosition = input.Position 70 | self.IsDragging = true 71 | self._StartBind:Fire() 72 | end 73 | end)) 74 | 75 | maid:Mark(UIS.InputEnded:Connect(function(input) 76 | if (VALID_PRESS[input.UserInputType]) then 77 | self.IsDragging = false 78 | self._StopBind:Fire() 79 | end 80 | end)) 81 | 82 | maid:Mark(UIS.InputChanged:Connect(function(input, process) 83 | if (self.IsDragging) then 84 | if (VALID_MOVEMENT[input.UserInputType]) then 85 | local delta = input.Position - lastMousePosition 86 | lastMousePosition = input.Position 87 | dragBind:Fire(element, input, delta) 88 | end 89 | end 90 | end)) 91 | end 92 | 93 | -- Public Methods 94 | 95 | function DraggerClass:Destroy() 96 | self._Maid:Sweep() 97 | self.DragChanged = nil 98 | self.DragStart = nil 99 | self.DragStop = nil 100 | self.Element = nil 101 | end 102 | 103 | -- 104 | 105 | return DraggerClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/Dropdown.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.Dropdown 3 | 4 | This class creates a dropdown that the user can select a list of options from. 5 | 6 | Constructors: 7 | new(frame [instance], listFrame [instance]) 8 | > 9 | Create(list[] [string], max [integer]) 10 | > Creates a dropdown from the list with a max scrolling number. 11 | 12 | Properties: 13 | Frame [instance] 14 | > The container frame for the dropdown. Can be used for positioning and resizing. 15 | ListFrame [instance] 16 | > The contaienr frame for the dropdown list. Parented underneath the main container frame. 17 | 18 | Methods: 19 | :Set(option [instance]) [void] 20 | > option is a child of the ListFrame and you can set it as the selected option of the dropdown with this method 21 | :Get() [instance] 22 | > Returns the selected option frame (which is again, a child of the ListFrame) 23 | :Show(bool [boolean]) 24 | > Set whether the dropdown list is visible or not. 25 | :Destroy() [void] 26 | > Destroys the RadioButtonGroup and all the events, etc that were running it. 27 | 28 | Events: 29 | .Changed:Connect(function(option [instance]) 30 | > Fired when the user selects a new option from the dropdown list. 31 | --]] 32 | 33 | 34 | -- CONSTANTS 35 | 36 | local GuiLib = script.Parent.Parent 37 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 38 | local Defaults = GuiLib:WaitForChild("Defaults") 39 | 40 | local UIS = game:GetService("UserInputService") 41 | 42 | local VALID_PRESS = { 43 | [Enum.UserInputType.MouseButton1] = true; 44 | [Enum.UserInputType.Touch] = true; 45 | } 46 | 47 | local ARROW_UP = "rbxassetid://5154078925" 48 | local ARROW_DOWN = "rbxassetid://5143165549" 49 | 50 | local DROP_BUTTON = Defaults:WaitForChild("DropdownButton") 51 | 52 | -- Class 53 | 54 | local DropdownClass = {} 55 | DropdownClass.__index = DropdownClass 56 | DropdownClass.__type = "Dropdown" 57 | 58 | function DropdownClass:__tostring() 59 | return DropdownClass.__type 60 | end 61 | 62 | -- Public Constructors 63 | 64 | function DropdownClass.new(button, listFrame) 65 | local self = setmetatable({}, DropdownClass) 66 | 67 | self._Maid = Lazy.Utilities.Maid.new() 68 | self._ChangedBind = Instance.new("BindableEvent") 69 | self._Options = {} 70 | self._Selected = nil 71 | 72 | self.Button = button 73 | self.ListFrame = listFrame 74 | self.Changed = self._ChangedBind.Event 75 | 76 | init(self) 77 | self:Set(self._Options[1]) 78 | 79 | return self 80 | end 81 | 82 | function DropdownClass.Create(list, max) 83 | max = max or #list 84 | 85 | local button = DROP_BUTTON:Clone() 86 | local listFrame = Lazy.Constructors.List.Create(list, max) 87 | 88 | listFrame.Position = UDim2.new(0, 0, 1, 0) 89 | listFrame.Size = UDim2.new(1, 0, max, 0) 90 | listFrame.Visible = false 91 | listFrame.Parent = button 92 | 93 | return DropdownClass.new(button, listFrame) 94 | end 95 | 96 | -- Private Methods 97 | 98 | function init(self) 99 | local button = self.Button 100 | local listFrame = self.ListFrame 101 | 102 | local function contentSizeUpdate() 103 | local absSize = button.AbsoluteSize 104 | local ratio = absSize.y / absSize.x 105 | 106 | button.Arrow.Size = UDim2.new(ratio, 0, 1, 0) 107 | button.Option.Size = UDim2.new(1 - ratio, -12, 1, 0) 108 | end 109 | 110 | contentSizeUpdate() 111 | self._Maid:Mark(button:GetPropertyChangedSignal("AbsoluteSize"):Connect(contentSizeUpdate)) 112 | 113 | for i, optionButton in next, listFrame.ScrollFrame:GetChildren() do 114 | self._Options[i] = optionButton 115 | optionButton.Activated:Connect(function() 116 | self:Set(optionButton) 117 | end) 118 | end 119 | 120 | self._Maid:Mark(button.Activated:Connect(function() 121 | self:Show(not listFrame.Visible) 122 | end)) 123 | 124 | self._Maid:Mark(UIS.InputBegan:Connect(function(input) 125 | if (VALID_PRESS[input.UserInputType]) then 126 | local p = input.Position 127 | local p2 = Vector2.new(p.x, p.y) 128 | 129 | if (listFrame.Visible and not (isInFrame(listFrame, p2) or isInFrame(button, p2))) then 130 | self:Show(false) 131 | end 132 | end 133 | end)) 134 | end 135 | 136 | function isInFrame(frame, pos) 137 | local fPos = frame.AbsolutePosition 138 | local fSize = frame.AbsoluteSize 139 | local d = pos - fPos 140 | return (d.x >= 0 and d.x <= fSize.x and d.y >= 0 and d.y <= fSize.y) 141 | end 142 | 143 | -- Public Methods 144 | 145 | function DropdownClass:Set(option) 146 | if (self._Selected ~= option) then 147 | self._Selected = option 148 | self._ChangedBind:Fire(option) 149 | self.Button.Option.Text = option.Label.Text 150 | end 151 | self:Show(false) 152 | end 153 | 154 | function DropdownClass:Get() 155 | return self._Selected 156 | end 157 | 158 | function DropdownClass:Show(bool) 159 | self.Button.Arrow.Image = bool and ARROW_UP or ARROW_DOWN 160 | self.ListFrame.Visible = bool 161 | end 162 | 163 | function DropdownClass:Destroy() 164 | self._Maid:Sweep() 165 | self._Changed = nil 166 | self._Options = nil 167 | self._Selected = nil 168 | self.Button:Destroy() 169 | end 170 | 171 | -- 172 | 173 | return DropdownClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadialMenu/CONSTANTS.lua: -------------------------------------------------------------------------------- 1 | TAU = math.pi*2 2 | 3 | GAP = 2 4 | FOV = 70 5 | VIEW_DIST = 50 6 | PART_PER_UNIT = 6 7 | 8 | CENTER = CFrame.new(0, 0, -VIEW_DIST) 9 | EXTERIOR_RADIUS = VIEW_DIST * math.tan(math.rad(FOV/2)) 10 | G_OFFSET = GAP / (2*EXTERIOR_RADIUS) 11 | EX_OFFSET = -TAU/4 + G_OFFSET 12 | 13 | local module = getfenv() 14 | module.script = nil 15 | return module -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadialMenu/CreateRadial.lua: -------------------------------------------------------------------------------- 1 | -- CONSTANTS 2 | 3 | local CONSTANTS = require(script.Parent:WaitForChild("CONSTANTS")) 4 | local Triangle = require(script.Parent:WaitForChild("Triangle")) 5 | 6 | local PI2 = math.pi/2 7 | local TAU = CONSTANTS.TAU 8 | 9 | local GAP = CONSTANTS.GAP 10 | local PART_PER_UNIT = CONSTANTS.PART_PER_UNIT 11 | 12 | local CENTER = CONSTANTS.CENTER 13 | local EXTERIOR_RADIUS = CONSTANTS.EXTERIOR_RADIUS 14 | local EX_OFFSET = CONSTANTS.EX_OFFSET 15 | local G_OFFSET = CONSTANTS.G_OFFSET 16 | 17 | local VPF = Instance.new("ViewportFrame") 18 | VPF.Ambient = Color3.new(1, 1, 1) 19 | VPF.LightColor = Color3.new(1, 1, 1) 20 | VPF.LightDirection = Vector3.new(0, 0, -1) 21 | VPF.BackgroundTransparency = 1 22 | VPF.Size = UDim2.new(1, 0, 1, 0) 23 | 24 | local CAMERA = Instance.new("Camera") 25 | CAMERA.CameraType = Enum.CameraType.Scriptable 26 | CAMERA.CFrame = CFrame.new() 27 | CAMERA.FieldOfView = CONSTANTS.FOV 28 | 29 | -- 30 | 31 | local function pivotAround(model, pivotCF, newCF) 32 | local invPivotCF = pivotCF:Inverse() 33 | for _, part in next, model:GetDescendants() do 34 | part.CFrame = newCF * (invPivotCF * part.CFrame) 35 | end 36 | end 37 | 38 | local function createSection(subN, interior_radius, exterior_radius, ppu) 39 | local subModel = Instance.new("Model") 40 | 41 | local exCircum = (TAU*exterior_radius)/subN - GAP 42 | local inCircum = (TAU*interior_radius)/subN - GAP 43 | 44 | local exTheta = exCircum / (exterior_radius) 45 | local inTheta = inCircum / (interior_radius) 46 | local diffTheta = exTheta - inTheta 47 | 48 | local exPoints = {} 49 | local inPoints = {} 50 | 51 | local nParts = math.ceil(exCircum/ppu) 52 | 53 | for i = 0, nParts do 54 | exPoints[i + 1] = CENTER * CFrame.fromEulerAnglesXYZ(0, 0, (i/nParts)*exTheta) * Vector3.new(exterior_radius, 0, 0) 55 | inPoints[i + 1] = CENTER * CFrame.fromEulerAnglesXYZ(0, 0, diffTheta/2 + (i/nParts)*inTheta) * Vector3.new(interior_radius, 0, 0) 56 | end 57 | 58 | for i = 1, nParts do 59 | local a = exPoints[i] 60 | local b = inPoints[i] 61 | local c = exPoints[i + 1] 62 | local d = inPoints[i + 1] 63 | 64 | Triangle(subModel, a, b, c) 65 | Triangle(subModel, b, c, d) 66 | end 67 | 68 | return subModel 69 | end 70 | 71 | local function createRadial(subN, tPercent, rotation) 72 | rotation = rotation or 0 73 | 74 | local dialEx = (1 - tPercent)*EXTERIOR_RADIUS - 1 75 | local dialIn = dialEx - 2 76 | 77 | local section = createSection(subN, (1 - tPercent)*EXTERIOR_RADIUS, EXTERIOR_RADIUS, PART_PER_UNIT) 78 | local innerSection = createSection(subN, dialIn, dialEx, PART_PER_UNIT/2) 79 | 80 | local frame = Instance.new("Frame") 81 | local radialFrame = Instance.new("Frame") 82 | local attachFrame = Instance.new("Frame") 83 | radialFrame.BackgroundTransparency = 1 84 | attachFrame.BackgroundTransparency = 1 85 | radialFrame.Size = UDim2.new(1, 0, 1, 0) 86 | attachFrame.Size = UDim2.new(1, 0, 1, 0) 87 | radialFrame.Name = "Radial" 88 | attachFrame.Name = "Attach" 89 | radialFrame.Parent = frame 90 | attachFrame.Parent = frame 91 | 92 | local thickness = tPercent * EXTERIOR_RADIUS 93 | local interior_radius = EXTERIOR_RADIUS - thickness 94 | local inv_tPercent = 1 - tPercent/2 95 | 96 | local exCircum = (TAU*EXTERIOR_RADIUS)/subN 97 | local exTheta = exCircum / EXTERIOR_RADIUS 98 | local inCircum = (TAU*interior_radius)/subN - GAP 99 | local inTheta = inCircum / (interior_radius) 100 | 101 | local edge = Vector2.new(math.cos(inTheta), math.sin(inTheta))*interior_radius - Vector2.new(interior_radius, 0) 102 | local edgeLen = math.min(edge.Magnitude / (EXTERIOR_RADIUS*2), 0.18) 103 | 104 | for i = 0, subN - 1 do 105 | local vpf = VPF:Clone() 106 | local cam = CAMERA:Clone() 107 | vpf.CurrentCamera = cam 108 | vpf.Name = i + 1 109 | 110 | local theta = (i/subN)*TAU + rotation 111 | 112 | local sub = section:Clone() 113 | pivotAround(sub, CENTER, CENTER * CFrame.fromEulerAnglesXYZ(0, 0, theta + EX_OFFSET)) 114 | sub.Parent = vpf 115 | 116 | local t = theta - EX_OFFSET + exTheta/2 + G_OFFSET 117 | local c = -math.cos(t)/2 * inv_tPercent 118 | local s = math.sin(t)/2 * inv_tPercent 119 | 120 | local attach = Instance.new("Frame") 121 | attach.Name = i + 1 122 | attach.BackgroundTransparency = 1 123 | attach.BackgroundColor3 = Color3.new() 124 | attach.BorderSizePixel = 0 125 | attach.AnchorPoint = Vector2.new(0.5, 0.5) 126 | attach.Position = UDim2.new(0.5 + c, 0, 0.5 + s, 0) 127 | attach.Size = UDim2.new(edgeLen, 0, edgeLen, 0) 128 | attach.Parent = attachFrame 129 | 130 | cam.Parent = vpf 131 | vpf.Parent = radialFrame 132 | end 133 | 134 | section:Destroy() 135 | 136 | local vpf = VPF:Clone() 137 | local cam = CAMERA:Clone() 138 | vpf.CurrentCamera = cam 139 | vpf.Name = "RadialDial" 140 | 141 | local g = GAP / (2*dialEx) 142 | local off = -TAU/4 + g 143 | 144 | pivotAround(innerSection, CENTER, CENTER * CFrame.fromEulerAnglesXYZ(0, 0, rotation + off)) 145 | innerSection.Parent = vpf 146 | vpf.Parent = frame 147 | 148 | frame.BackgroundTransparency = 1 149 | frame.SizeConstraint = Enum.SizeConstraint.RelativeYY 150 | frame.Size = UDim2.new(1, 0, 1, 0) 151 | return frame 152 | end 153 | 154 | -- 155 | 156 | return createRadial -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadialMenu/Triangle.lua: -------------------------------------------------------------------------------- 1 | local WEDGE = Instance.new("WedgePart") 2 | WEDGE.Material = Enum.Material.SmoothPlastic 3 | WEDGE.Anchored = true 4 | WEDGE.CanCollide = false 5 | WEDGE.Color = Color3.new(1, 1, 1) 6 | 7 | return function(parent, a, b, c) 8 | local ab, ac, bc = b - a, c - a, c - b 9 | local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc) 10 | 11 | if (abd > acd and abd > bcd) then 12 | c, a = a, c 13 | elseif (acd > bcd and acd > abd) then 14 | a, b = b, a 15 | end 16 | 17 | ab, ac, bc = b - a, c - a, c - b 18 | 19 | local right = ac:Cross(ab).Unit 20 | local up = bc:Cross(right).Unit 21 | local back = bc.Unit 22 | 23 | local height = math.abs(ab:Dot(up)) 24 | local width1 = math.abs(ab:Dot(back)) 25 | local width2 = math.abs(ac:Dot(back)) 26 | 27 | local w1 = WEDGE:Clone() 28 | w1.Size = Vector3.new(0, height, width1) 29 | w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back) 30 | w1.Parent = parent 31 | 32 | local w2 = WEDGE:Clone() 33 | w2.Size = Vector3.new(0, height, width2) 34 | w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back) 35 | w2.Parent = parent 36 | 37 | return w1, w2 38 | end -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadialMenu/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.RadialMenu 3 | 4 | This class creates a radial menu. It is by far the most "raw" module in this library as so much of how you interact with it is developer defined. 5 | 6 | Constructors: 7 | new(subN [integer], tPercent [float], rotation [float]) 8 | > Creates a radial menu divided into subN sections where the ring is tPercent width of the frame radius 9 | > and the ring is rotationally offset by rotation radians 10 | 11 | Properties: 12 | Frame [instance] 13 | > The container frame for the radial menu. Can be used for positioning and resizing. 14 | > Note that this frame should always be square, by default it's set to YY size constraining 15 | Rotation [float] 16 | > The rotation offset that the developer entered as an argument when creating the radial menu. 17 | SubN [integer] 18 | > The number of subsections that the developer entered as an argument when creating the radial menu. 19 | Enabled [boolean] 20 | > Whether or not the radial menu is actively tracking input. 21 | > Defaults to true 22 | DeadZoneIn [float] 23 | > Number represents a percentage from the radius that will be ignored in regards to input 24 | > By default this is 0.5 meaning the center 50% of the radial frame ignores input 25 | DeadZoneOut [float] 26 | > Number represents a percentage from the radius that once passed will be ignored in regards to input. 27 | > By default this is math.huge meaning that as long as your outside of DeadZoneIn your input will not be ignored. 28 | 29 | Methods: 30 | :SetRadialProps(props [dictionary]) [void] 31 | > Sets the properties of all the radial background UI 32 | :SetDialProps(props [dictionary]) [void] 33 | > Sets the properties of the radial dial UI 34 | :GetTheta(userInputType [Enum.UserInputType]) [float] 35 | > Depending on if MouseMovement, Touch, or a Gamepad returns the directional angle that the user is inputting on their device 36 | > If input is not in deadzone range then this method returns nil 37 | > Returns the angle in radians 38 | :PickIndex(theta) [integer] 39 | > Given a directional angle returns the closest element on the radial wheel as an index. 40 | :GetRadial(index) [instance] 41 | > Returns the radial background UI for that index. 42 | :GetAttachment(index) [instance] 43 | > Returns the radial attachment UI for that index. 44 | > This frame is useful for putting text or images in. 45 | :IsVisible() [boolean] 46 | > Returns whether or not the radial menu is visible to the user or not. 47 | :Destroy() [void] 48 | > Destroys the RadioButtonGroup and all the events, etc that were running it. 49 | 50 | Events: 51 | .Clicked:Connect(function(index [integer]) 52 | > Fired when the user selects an element on the radial menu. 53 | .Hover:Connect(function(oldIndex [integer], newIndex [integer]) 54 | > Fired when the user hovers in the direction of a new element on the radial menu 55 | --]] 56 | 57 | 58 | -- CONSTANTS 59 | 60 | local GuiLib = script.Parent.Parent 61 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 62 | local Defaults = GuiLib:WaitForChild("Defaults") 63 | 64 | local UIS = game:GetService("UserInputService") 65 | local RUNSERVICE = game:GetService("RunService") 66 | local CONSTANTS = require(script:WaitForChild("CONSTANTS")) 67 | 68 | local PI = math.pi 69 | local TAU = CONSTANTS.TAU 70 | local EX_OFFSET = CONSTANTS.EX_OFFSET 71 | 72 | local GAMEPAD_CLICK = { 73 | [Enum.KeyCode.ButtonA] = true 74 | } 75 | 76 | local MOUSE_GROUP = { 77 | [Enum.UserInputType.MouseButton1] = true, 78 | [Enum.UserInputType.MouseMovement] = true, 79 | [Enum.UserInputType.Touch] = true 80 | } 81 | 82 | local GAMEPAD_GROUP = { 83 | [Enum.UserInputType.Gamepad1] = true, 84 | [Enum.UserInputType.Gamepad2] = true, 85 | [Enum.UserInputType.Gamepad3] = true, 86 | [Enum.UserInputType.Gamepad4] = true, 87 | [Enum.UserInputType.Gamepad5] = true, 88 | [Enum.UserInputType.Gamepad6] = true, 89 | [Enum.UserInputType.Gamepad7] = true, 90 | [Enum.UserInputType.Gamepad8] = true 91 | } 92 | 93 | local CreateRadial = require(script:WaitForChild("CreateRadial")) 94 | 95 | -- Class 96 | 97 | local RadialMenuClass = {} 98 | RadialMenuClass.__index = RadialMenuClass 99 | RadialMenuClass.__type = "RadialMenu" 100 | 101 | function RadialMenuClass:__tostring() 102 | return RadialMenuClass.__type 103 | end 104 | 105 | -- Public Constructors 106 | 107 | function RadialMenuClass.new(subN, tPercent, rotation) 108 | local self = setmetatable({}, RadialMenuClass) 109 | 110 | self._Maid = Lazy.Utilities.Maid.new() 111 | self._ClickedBind = Instance.new("BindableEvent") 112 | self._HoverBind = Instance.new("BindableEvent") 113 | self._LastHoverIndex = nil 114 | 115 | self.Frame = CreateRadial(subN, tPercent, rotation) 116 | self.Rotation = rotation 117 | self.SubN = subN 118 | 119 | self.Enabled = true 120 | self.DeadZoneIn = 0.5 121 | self.DeadZoneOut = math.huge 122 | 123 | self.Clicked = self._ClickedBind.Event 124 | self.Hover = self._HoverBind.Event 125 | 126 | init(self) 127 | 128 | self:SetDialProps{ImageColor3 = Color3.new(0, 0, 0)} 129 | self:SetRadialProps{ 130 | ImageColor3 = Color3.new(0, 0, 0), 131 | ImageTransparency = 0.7 132 | } 133 | 134 | return self 135 | end 136 | 137 | -- Private Methods 138 | 139 | local function shortestDist(start, stop) 140 | local modDiff = (stop - start) % TAU 141 | local sDist = PI - math.abs(math.abs(modDiff) - PI) 142 | if ((modDiff + TAU) % TAU < PI) then 143 | return sDist 144 | else 145 | return -sDist 146 | end 147 | end 148 | 149 | function init(self) 150 | local subN = self.SubN 151 | local dial = self.Frame.RadialDial 152 | 153 | local inputType = Enum.UserInputType.MouseMovement 154 | 155 | self._Maid:Mark(self._ClickedBind) 156 | self._Maid:Mark(self._HoverBind) 157 | 158 | self._Maid:Mark(UIS.LastInputTypeChanged:Connect(function(iType) 159 | if (MOUSE_GROUP[iType] or GAMEPAD_GROUP[iType]) then 160 | inputType = iType 161 | end 162 | end)) 163 | 164 | local lTheta = 0 165 | 166 | self._Maid:Mark(UIS.InputBegan:Connect(function(input) 167 | if (not self.Enabled) then 168 | return 169 | end 170 | 171 | 172 | if (GAMEPAD_GROUP[input.UserInputType]) then 173 | if (not GAMEPAD_CLICK[input.KeyCode]) then 174 | return 175 | else 176 | self._ClickedBind:Fire(self:PickIndex(lTheta)) 177 | end 178 | end 179 | 180 | local theta = self:GetTheta(input.UserInputType) 181 | if (theta) then 182 | self._ClickedBind:Fire(self:PickIndex(theta)) 183 | end 184 | end)) 185 | 186 | self._Maid:Mark(RUNSERVICE.RenderStepped:Connect(function(dt) 187 | if (not self.Enabled) then 188 | return 189 | end 190 | 191 | local theta = self:GetTheta(inputType) 192 | if (theta and self:IsVisible()) then 193 | lTheta = theta 194 | 195 | local frameRot = math.rad(self.Frame.Rotation) 196 | local toDeg = math.deg(theta - self.Rotation + frameRot + EX_OFFSET + 2*TAU) % 360 197 | local closest = toDeg / (360 / self.SubN) + 0.5 198 | 199 | dial.Rotation = math.deg(self:GetRotation(closest)) 200 | 201 | local index = self:PickIndex(theta) 202 | if (index ~= self._LastHoverIndex) then 203 | self._HoverBind:Fire(self._LastHoverIndex, index) 204 | self._LastHoverIndex = index 205 | end 206 | end 207 | end)) 208 | end 209 | 210 | -- Public Methods 211 | 212 | function RadialMenuClass:SetRadialProps(props) 213 | for _, child in next, self.Frame.Radial:GetChildren() do 214 | for prop, value in next, props do 215 | child[prop] = value 216 | end 217 | end 218 | end 219 | 220 | function RadialMenuClass:SetDialProps(props) 221 | local dial = self.Frame.RadialDial 222 | for prop, value in next, props do 223 | dial[prop] = value 224 | end 225 | end 226 | 227 | function RadialMenuClass:GetTheta(userInputType) 228 | local delta = nil 229 | 230 | if (MOUSE_GROUP[userInputType]) then 231 | local frame = self.Frame 232 | local radius = frame.AbsoluteSize.y/2 233 | local center = frame.AbsolutePosition + frame.AbsoluteSize/2 234 | local mousePos = UIS:GetMouseLocation() + Vector2.new(0, -36) 235 | delta = (mousePos - center) / radius 236 | elseif (GAMEPAD_GROUP[userInputType]) then 237 | local states = UIS:GetGamepadState(userInputType) 238 | for _, state in next, states do 239 | states[state.KeyCode] = state 240 | end 241 | delta = states[Enum.KeyCode.Thumbstick2].Position * Vector3.new(1, -1, 1) 242 | end 243 | 244 | if (delta) then 245 | local m = delta.Magnitude 246 | if (m >= self.DeadZoneIn and m <= self.DeadZoneOut) then 247 | return math.atan2(delta.y, -delta.x) 248 | end 249 | end 250 | end 251 | 252 | function RadialMenuClass:PickIndex(theta) 253 | local frameRot = math.rad(self.Frame.Rotation) 254 | local toDeg = math.deg(theta - self.Rotation + frameRot + EX_OFFSET + 2*TAU) % 360 255 | local closest = math.floor(toDeg / (360 / self.SubN)) 256 | return closest + 1 257 | end 258 | 259 | function RadialMenuClass:GetRotation(index) 260 | return -TAU * ((index - 1) / self.SubN) 261 | end 262 | 263 | function RadialMenuClass:GetRadial(index) 264 | return self.Frame.Radial[index] 265 | end 266 | 267 | function RadialMenuClass:GetAttachment(index) 268 | return self.Frame.Attach[index] 269 | end 270 | 271 | function RadialMenuClass:IsVisible() 272 | local frame = self.Frame 273 | while (frame and frame:IsA("GuiObject") and frame.Visible) do 274 | frame = frame.Parent 275 | if (frame and frame:IsA("ScreenGui") and frame.Enabled) then 276 | return true 277 | end 278 | end 279 | return false 280 | end 281 | 282 | function RadialMenuClass:Destroy() 283 | self._Maid:Sweep() 284 | self.Frame:Destroy() 285 | self.Clicked = nil 286 | self.Hover = nil 287 | self.Frame = nil 288 | end 289 | 290 | -- 291 | 292 | return RadialMenuClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadioButtonGroup.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.RadioButtonGroup 3 | 4 | This class creates a list of radio buttons that the user can select from. 5 | 6 | Constructors: 7 | new(frame [instance], buttons[] [RadioButtonLabel]) 8 | Create(list[] [string], max [integer]) 9 | > Creates a RadioButtonGroup from the list with a max scrolling number. 10 | 11 | Properties: 12 | Frame [instance] 13 | > The container frame for the RadioButtonGroup. Can be used for positioning and resizing. 14 | RadioButtons[] [RadioButtonLabel] 15 | > An array of the RadioButtonLabels that are used in the RadioButtonGroup. 16 | 17 | Methods: 18 | :GetActiveRadio() [RadioButtonLabel] 19 | > Returns the currently selected RadioButtonLabel 20 | :Destroy() [void] 21 | > Destroys the RadioButtonGroup and all the events, etc that were running it. 22 | 23 | Events: 24 | .Changed:Connect(function(old [RadioButtonLabel], new [RadioButtonLabel]) 25 | > When the selected radio button is changed in the radio button group this event fires 26 | --]] 27 | 28 | -- CONSTANTS 29 | 30 | local GuiLib = script.Parent.Parent 31 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 32 | 33 | -- Class 34 | 35 | local RadioButtonGroupClass = {} 36 | RadioButtonGroupClass.__index = RadioButtonGroupClass 37 | RadioButtonGroupClass.__type = "RadioButtonLabel" 38 | 39 | function RadioButtonGroupClass:__tostring() 40 | return RadioButtonGroupClass.__type 41 | end 42 | 43 | -- Public Constructors 44 | 45 | function RadioButtonGroupClass.new(frame, radioButtons) 46 | local self = setmetatable({}, RadioButtonGroupClass) 47 | 48 | self._Maid = Lazy.Utilities.Maid.new() 49 | self._ChangedBind = Instance.new("BindableEvent") 50 | self._ActiveRadio = radioButtons[1] 51 | 52 | self.Frame = frame 53 | self.RadioButtons = radioButtons 54 | self.Changed = self._ChangedBind.Event 55 | 56 | init(self) 57 | 58 | return self 59 | end 60 | 61 | function RadioButtonGroupClass.Create(list, max) 62 | local radios = {} 63 | 64 | local function instanceFunc(index, option) 65 | local radio = Lazy.Classes.RadioButtonLabel.Create(option) 66 | radio.Frame.LayoutOrder = index 67 | radios[index] = radio 68 | return radio.Frame 69 | end 70 | 71 | local frame = Lazy.Constructors.List.Create(list, max or #list, Enum.FillDirection.Vertical, UDim.new(0, 5), instanceFunc) 72 | 73 | return RadioButtonGroupClass.new(frame, radios) 74 | end 75 | 76 | -- Private Methods 77 | 78 | function init(self) 79 | for _, radio in next, self.RadioButtons do 80 | radio:SetValue(false) 81 | self._Maid:Mark(radio.Button.Activated:Connect(function() 82 | local old = self._ActiveRadio 83 | 84 | self._ActiveRadio:SetValue(false) 85 | self._ActiveRadio = radio 86 | self._ActiveRadio:SetValue(true) 87 | 88 | self._ChangedBind:Fire(old, radio) 89 | end)) 90 | end 91 | 92 | self._ActiveRadio:SetValue(true) 93 | end 94 | 95 | -- Public Methods 96 | 97 | function RadioButtonGroupClass:GetActiveRadio() 98 | return self._ActiveRadio 99 | end 100 | 101 | function RadioButtonGroupClass:Destroy() 102 | self._Maid:Sweep() 103 | self.Frame:Destroy() 104 | end 105 | 106 | -- 107 | 108 | return RadioButtonGroupClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/RadioButtonLabel.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.RadioButtonLabel 3 | 4 | This class creates a single radio button label. 5 | 6 | Constructors: 7 | new(frame [instance]) 8 | > 9 | Create(text) 10 | > Creates a RadioButtonLabel from the text provided. 11 | 12 | Properties: 13 | Frame [instance] 14 | > The container frame for the RadioButtonLabel. Can be used for positioning and resizing. 15 | Button [instance] 16 | > The button used to track when the user clicks on the radio button or not 17 | 18 | Methods: 19 | :GetValue() [boolean] 20 | > Returns whether the button is selected or not. 21 | :SetValue(bool [boolean]) [void] 22 | > Sets if the button is selected or not 23 | :Destroy() [void] 24 | > Destroys the RadioButtonLabel and all the events, etc that were running it. 25 | --]] 26 | 27 | -- CONSTANTS 28 | 29 | local GuiLib = script.Parent.Parent 30 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 31 | local Defaults = GuiLib:WaitForChild("Defaults") 32 | 33 | local RADIOBUTTON_LABEL = Defaults:WaitForChild("RadioButtonLabel") 34 | 35 | -- Class 36 | 37 | local RadioButtonLabelClass = {} 38 | RadioButtonLabelClass.__index = RadioButtonLabelClass 39 | RadioButtonLabelClass.__type = "RadioButtonLabel" 40 | 41 | function RadioButtonLabelClass:__tostring() 42 | return RadioButtonLabelClass.__type 43 | end 44 | 45 | -- Public Constructors 46 | 47 | function RadioButtonLabelClass.new(frame) 48 | local self = setmetatable({}, RadioButtonLabelClass) 49 | 50 | self._Maid = Lazy.Utilities.Maid.new() 51 | 52 | self.Frame = frame 53 | self.Button = frame.RadioContainer.RadioButton 54 | 55 | init(self) 56 | self:SetValue(false) 57 | 58 | return self 59 | end 60 | 61 | function RadioButtonLabelClass.Create(text) 62 | local cbLabel = RADIOBUTTON_LABEL:Clone() 63 | cbLabel.Label.Text = text 64 | return RadioButtonLabelClass.new(cbLabel) 65 | end 66 | 67 | -- Private Methods 68 | 69 | function init(self) 70 | local label = self.Frame.Label 71 | local container = self.Frame.RadioContainer 72 | 73 | local function contentSizeUpdate() 74 | local absSize = self.Frame.AbsoluteSize 75 | local ratio = absSize.y / absSize.x 76 | container.Size = UDim2.new(ratio, 0, 1, 0) 77 | label.Size = UDim2.new(1 - ratio, -10, 1, 0) 78 | label.Position = UDim2.new(ratio, 10, 0, 0) 79 | end 80 | 81 | contentSizeUpdate() 82 | self._Maid:Mark(self.Frame:GetPropertyChangedSignal("AbsoluteSize"):Connect(contentSizeUpdate)) 83 | end 84 | 85 | -- Public Methods 86 | 87 | function RadioButtonLabelClass:GetValue() 88 | return self.Button.Circle.Visible 89 | end 90 | 91 | function RadioButtonLabelClass:SetValue(bool) 92 | bool = not not bool 93 | 94 | local container = self.Frame.RadioContainer 95 | local colorA = container.BorderColor3 96 | local colorB = container.BackgroundColor3 97 | 98 | container.Outline.ImageColor3 = bool and colorA or colorB 99 | container.RadioButton.Circle.Visible = bool 100 | end 101 | 102 | function RadioButtonLabelClass:Destroy() 103 | self._Maid:Sweep() 104 | self.Frame:Destroy() 105 | end 106 | 107 | -- 108 | 109 | return RadioButtonLabelClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/Slider.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.Slider 3 | 4 | This class creates a slider object which can be dragged for different values. 5 | 6 | Constructors: 7 | new(frame [instance], axis [string]) 8 | > Slider frames must have the following format: 9 | >> SliderFrame 10 | >>> Dragger 11 | >>> Background 12 | > axis defines if the slider is horizontal "x" or vertical "y" 13 | Create(axis [string]) 14 | > Creates a slider frame from the default, simply define whether it's horizontal "x" or vertical "y". 15 | 16 | Properties: 17 | Frame [instance] 18 | > The container frame for the slider. Can be used for positioning and resizing. 19 | Interval [number] [0, 1] 20 | > Set this to force an interval step on the slider. For example if you only wanted steps of 1/10th then you'd write 21 | > Slider.Interval = 0.1 22 | IsActive [boolean] 23 | > When true the slider can be interacted with by the user, when false its values can only be set by the developer. 24 | TweenClick [boolean] 25 | > If true then when the user clicks on the slider the dragger will tween to that target. If not it will be instant. 26 | Inverted [boolean] 27 | > If true then the value of the slider will be inverted (e.g. when horizontal the right-most position will be zero and left-most 1) 28 | > This is useful for when you have a vertical slider as typically users envision the down-most position to be zero. 29 | 30 | Methods: 31 | :Get() [number] 32 | > Returns the slider position from 0 to 1 33 | :Set(value [number], doTween [boolean]) [void] 34 | > Sets the slider to a specific position or closest possible if interval > 0. If doTween is true then the slider will tween to that position. 35 | :Destroy() [void] 36 | > Destroys the slider frame and all the events, etc that were running it 37 | 38 | Events: 39 | .Changed:Connect(function(value [number]) 40 | > When the slider's position changes this fires the slider's current position 41 | .Clicked:Connect(function(value [number]) 42 | > When the user clicks somewhere on the slider this fires the clicked position 43 | .DragStart:Connect(function() 44 | > Fires when the user starts dragging the slider 45 | .DragStop:Connect(function() 46 | > Fires when the user stops dragging the slider 47 | --]] 48 | 49 | -- CONSTANTS 50 | 51 | local GuiLib = script.Parent.Parent 52 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 53 | local Defaults = GuiLib:WaitForChild("Defaults") 54 | 55 | local UIS = game:GetService("UserInputService") 56 | local RUNSERVICE = game:GetService("RunService") 57 | 58 | local SLIDER_FRAMEX = Defaults:WaitForChild("SliderFrameX") 59 | local SLIDER_FRAMEY = Defaults:WaitForChild("SliderFrameY") 60 | 61 | local XBOX_STEP = 0.01 62 | local DEBOUNCE_TICK = 0.1 63 | local XBOX_DEADZONE = 0.35 64 | local THUMBSTICK = Enum.KeyCode.Thumbstick2 65 | 66 | -- Class 67 | 68 | local SliderClass = {} 69 | SliderClass.__index = SliderClass 70 | SliderClass.__type = "Slider" 71 | 72 | function SliderClass:__tostring() 73 | return SliderClass.__type 74 | end 75 | 76 | -- Public Constructors 77 | 78 | function SliderClass.new(sliderFrame, axis) 79 | local self = setmetatable({}, SliderClass) 80 | 81 | self._Maid = Lazy.Utilities.Maid.new() 82 | self._Spring = Lazy.Utilities.Spring.new(1, 0.1, 1, 0) 83 | self._Axis = axis or "x" 84 | self._ChangedBind = Instance.new("BindableEvent") 85 | self._ClickedBind = Instance.new("BindableEvent") 86 | 87 | self.Interval = 0 88 | self.IsActive = true 89 | self.TweenClick = true 90 | self.Inverted = false 91 | 92 | self.Frame = sliderFrame 93 | self.Changed = self._ChangedBind.Event 94 | self.Clicked = self._ClickedBind.Event 95 | self.DragStart = nil 96 | self.DragStop = nil 97 | 98 | init(self) 99 | self:Set(0.5) 100 | 101 | return self 102 | end 103 | 104 | function SliderClass.Create(axis) 105 | local slider = nil 106 | 107 | if (not axis or axis == "x") then 108 | slider = SliderClass.new(SLIDER_FRAMEX:Clone(), axis) 109 | else 110 | slider = SliderClass.new(SLIDER_FRAMEY:Clone(), axis) 111 | slider.Inverted = true 112 | end 113 | 114 | return slider 115 | end 116 | 117 | -- Private Methods 118 | 119 | function init(self) 120 | local frame = self.Frame 121 | local dragger = frame.Dragger 122 | local background = frame.Background 123 | 124 | local axis = self._Axis 125 | local maid = self._Maid 126 | local spring = self._Spring 127 | local dragTracker = Lazy.Classes.Dragger.new(dragger) 128 | 129 | self.DragStart = dragTracker.DragStart 130 | self.DragStop = dragTracker.DragStop 131 | 132 | maid:Mark(frame) 133 | maid:Mark(self._ChangedBind) 134 | maid:Mark(self._ClickedBind) 135 | maid:Mark(function() dragTracker:Destroy() end) 136 | 137 | -- Get bounds and background size scaled accordingly for calculations 138 | local function setUdim2(a, b) 139 | if (axis == "y") then a, b = b, a end 140 | return UDim2.new(a, 0, b, 0) 141 | end 142 | 143 | local last = -1 144 | local bPos, bSize 145 | local initialBoundsCalculated = false 146 | local function updateBounds() 147 | bPos, bSize = getBounds(self) 148 | background.Size = setUdim2(bSize / frame.AbsoluteSize[axis], 1) 149 | last = -1 150 | end 151 | 152 | maid:Mark(frame:GetPropertyChangedSignal("AbsoluteSize"):Connect(updateBounds)) 153 | maid:Mark(frame:GetPropertyChangedSignal("AbsolutePosition"):Connect(updateBounds)) 154 | maid:Mark(frame:GetPropertyChangedSignal("Parent"):Connect(updateBounds)) 155 | 156 | -- Move the slider when the xbox moves it 157 | local xboxDir = 0 158 | local xboxTick = 0 159 | local xboxSelected = false 160 | 161 | maid:Mark(dragger.SelectionGained:Connect(function() 162 | xboxSelected = true 163 | end)) 164 | 165 | maid:Mark(dragger.SelectionLost:Connect(function() 166 | xboxSelected = false 167 | end)) 168 | 169 | maid:Mark(UIS.InputChanged:Connect(function(input, process) 170 | if (process and input.KeyCode == THUMBSTICK) then 171 | local pos = input.Position 172 | xboxDir = math.abs(pos[axis]) > XBOX_DEADZONE and math.sign(pos[axis]) or 0 173 | end 174 | end)) 175 | 176 | -- Move the slider when we drag it 177 | maid:Mark(dragTracker.DragChanged:Connect(function(element, input, delta) 178 | if (self.IsActive) then 179 | self:Set((input.Position[axis] - bPos) / bSize, false) 180 | end 181 | end)) 182 | 183 | -- Move the slider when we click somewhere on the bar 184 | maid:Mark(frame.InputBegan:Connect(function(input) 185 | if (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) then 186 | local t = (input.Position[axis] - bPos) / bSize 187 | self._ClickedBind:Fire(math.clamp(t, 0, 1)) 188 | if (self.IsActive) then 189 | self:Set(t, self.TweenClick) 190 | end 191 | end 192 | end)) 193 | 194 | -- position the slider 195 | maid:Mark(RUNSERVICE.RenderStepped:Connect(function(dt) 196 | if not initialBoundsCalculated then 197 | -- update on first rendered frame to make sure absolute sizes are correct 198 | updateBounds() 199 | initialBoundsCalculated = true 200 | end 201 | 202 | if (xboxSelected) then 203 | local t = tick() 204 | if (self.Interval <= 0) then 205 | self:Set(self:Get() + xboxDir*XBOX_STEP*dt*60) 206 | elseif (t - xboxTick > DEBOUNCE_TICK) then 207 | xboxTick = t 208 | self:Set(self:Get() + self.Interval*xboxDir) 209 | end 210 | end 211 | 212 | spring:Update(dt) 213 | local x = spring.x 214 | if (x ~= last) then 215 | local aPos = frame.AbsolutePosition[axis] 216 | local aSize = frame.AbsoluteSize[axis] 217 | local scalePos = (bPos + (x * bSize) - aPos) / aSize 218 | dragger.Position = setUdim2(scalePos, 0.5) 219 | self._ChangedBind:Fire(self:Get()) 220 | last = x 221 | end 222 | end)) 223 | end 224 | 225 | function getBounds(self) 226 | local frame = self.Frame 227 | local dragger = frame.Dragger 228 | local axis = self._Axis 229 | 230 | local pos = frame.AbsolutePosition[axis] + dragger.AbsoluteSize[axis]/2 231 | local size = frame.AbsoluteSize[axis] - dragger.AbsoluteSize[axis] 232 | 233 | return pos, size 234 | end 235 | 236 | -- Public Methods 237 | 238 | function SliderClass:Get() 239 | local t = self._Spring.x 240 | if (self.Inverted) then t = 1 - t end 241 | return t 242 | end 243 | 244 | function SliderClass:Set(value, doTween) 245 | local spring = self._Spring 246 | local newT = math.clamp(value, 0, 1) 247 | 248 | if (self.Interval > 0) then 249 | newT = math.floor((newT / self.Interval) + 0.5) * self.Interval 250 | end 251 | 252 | spring.t = newT 253 | spring.instant = not doTween 254 | end 255 | 256 | function SliderClass:Destroy() 257 | self._Maid:Sweep() 258 | self.Frame:Destroy() 259 | self.Changed = nil 260 | self.Clicked = nil 261 | self.StartDrag = nil 262 | self.StopDrag = nil 263 | self.Frame = nil 264 | end 265 | 266 | -- 267 | 268 | return SliderClass -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/Integer.lua: -------------------------------------------------------------------------------- 1 | local NumberMask = require(script.Parent:WaitForChild("Number")) 2 | 3 | local Mask = {} 4 | 5 | Mask.Name = "Integer" 6 | Mask.Default = "0" 7 | 8 | function Mask:Process(text) 9 | local new = NumberMask:Process(text) 10 | local find = new:find("%.") or #new + 1 11 | 12 | return new:sub(1, find - 1) 13 | end 14 | 15 | function Mask:Verify(text) 16 | local valid = NumberMask:Verify(text) 17 | return (valid and not text:find("%.")) 18 | end 19 | 20 | function Mask:ToType(text) 21 | text = self:Verify(text) and text or self.Default 22 | return tonumber(text) 23 | end 24 | 25 | return Mask -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/Number.lua: -------------------------------------------------------------------------------- 1 | local Mask = {} 2 | 3 | Mask.Name = "Number" 4 | Mask.Default = "0" 5 | 6 | function Mask:Process(text) 7 | local new = text:gsub("[^%d.-]", "") 8 | 9 | local neg = new:sub(1, 1) 10 | new = neg .. new:sub(2):gsub("%-", "") 11 | 12 | local found = new:find("%.") 13 | if (found) then 14 | local a = new:sub(1, found) 15 | local b = new:sub(found + 1, #new):gsub("[%.]", "") 16 | return a .. b 17 | end 18 | 19 | return new 20 | end 21 | 22 | function Mask:Verify(text) 23 | local find = text:find("[^%d.-]") -- find any non number, decimal, negative 24 | if (not find) then 25 | if (text:sub(1, 1) == "-") then 26 | find = text:sub(2):find("%-") 27 | if (find or #text == 1) then 28 | return false 29 | end 30 | end 31 | 32 | find = text:find("%.") 33 | if (find) then 34 | local new = text:sub(find + 1, #text):find("%.") -- check for second decimal 35 | return not new 36 | end 37 | 38 | -- Empty string is not a number 39 | return text ~= "" 40 | end 41 | return false 42 | end 43 | 44 | function Mask:ToType(text) 45 | text = self:Verify(text) and text or self.Default 46 | return tonumber(text) 47 | end 48 | 49 | return Mask -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/String.lua: -------------------------------------------------------------------------------- 1 | local Mask = {} 2 | 3 | Mask.Name = "String" 4 | Mask.Default = "" 5 | 6 | function Mask:Process(text) 7 | return text 8 | end 9 | 10 | function Mask:Verify(text) 11 | return true 12 | end 13 | 14 | function Mask:ToType(text) 15 | return text 16 | end 17 | 18 | return Mask -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/Vector2.lua: -------------------------------------------------------------------------------- 1 | local NumberMask = require(script.Parent:WaitForChild("Number")) 2 | 3 | local function matchArray(str, pattern) 4 | local arr = {} 5 | for sub in string.gmatch(str, pattern) do 6 | arr[#arr + 1] = sub 7 | end 8 | return arr 9 | end 10 | 11 | local Mask = {} 12 | 13 | Mask.Name = "Vector2" 14 | Mask.Default = "0, 0" 15 | 16 | function Mask:Process(text) 17 | return text 18 | end 19 | 20 | function Mask:Verify(text) 21 | local find = text:find("[^%d., -]") -- find any non number, decimal, comma, space or negative 22 | if (not find) then 23 | local nCommas = #string.split(text, ",") 24 | if (nCommas == 2) then 25 | local nums = matchArray(text, "%-*%d[%d.]*") 26 | if (#nums == 2) then 27 | for i, num in next, nums do 28 | if (not NumberMask:Verify(num)) then 29 | return false 30 | end 31 | end 32 | return true 33 | end 34 | end 35 | end 36 | return false 37 | end 38 | 39 | function Mask:ToType(text) 40 | text = self:Verify(text) and text or self.Default 41 | 42 | local components = string.split(text, ",") 43 | for i, c in next, components do 44 | components[i] = tonumber(c) 45 | end 46 | 47 | return Vector2.new(unpack(components)) 48 | end 49 | 50 | return Mask -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/Vector3.lua: -------------------------------------------------------------------------------- 1 | local NumberMask = require(script.Parent:WaitForChild("Number")) 2 | 3 | local function matchArray(str, pattern) 4 | local arr = {} 5 | for sub in string.gmatch(str, pattern) do 6 | arr[#arr + 1] = sub 7 | end 8 | return arr 9 | end 10 | 11 | local Mask = {} 12 | 13 | Mask.Name = "Vector3" 14 | Mask.Default = "0, 0, 0" 15 | 16 | function Mask:Process(text) 17 | return text 18 | end 19 | 20 | function Mask:Verify(text) 21 | local find = text:find("[^%d., -]") -- find any non number, decimal, comma, space or negative 22 | if (not find) then 23 | local nCommas = #string.split(text, ",") 24 | if (nCommas == 3) then 25 | local nums = matchArray(text, "%-*%d[%d.]*") 26 | if (#nums == 3) then 27 | for i, num in next, nums do 28 | if (not NumberMask:Verify(num)) then 29 | return false 30 | end 31 | end 32 | return true 33 | end 34 | end 35 | end 36 | return false 37 | end 38 | 39 | function Mask:ToType(text) 40 | text = self:Verify(text) and text or self.Default 41 | 42 | local components = string.split(text, ",") 43 | for i, c in next, components do 44 | components[i] = tonumber(c) 45 | end 46 | 47 | return Vector3.new(unpack(components)) 48 | end 49 | 50 | return Mask -------------------------------------------------------------------------------- /src/GuiLib/Classes/TextMask/init.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Classes.TextMask 3 | 4 | This class creates a text mask object which can be used to limit user input into a text gui element 5 | 6 | Constructors: 7 | new(textFrame [instance]) 8 | > Creates a text mask object for the given text frame. 9 | 10 | Properties: 11 | Frame [instance] 12 | > The text frame that you put as an argument when creating the text mask object. 13 | 14 | Methods: 15 | :GetValue() [variant] 16 | > Returns the text frame's value converted to the mask type. 17 | > For instance if you used a vector3 mask then this would return the value inputted as a vector3 18 | :GetMaskType() [string] 19 | > Returns the name of the mask type you are using 20 | :SetMaxLength(len [integer]) [void] 21 | > Sets the maximum number of characters the user can input into the text field 22 | > By default the max length is 230,000 characters 23 | :SetMaskType(name [string]) [void] 24 | > Sets mask type of the mask object. This name should coincide with a child of this module 25 | > By default the mask type is always set to "String" 26 | :Destroy() [void] 27 | > Destroys the mask object and any events, etc used in the purpose of running it. 28 | 29 | --]] 30 | 31 | -- CONSTANTS 32 | 33 | local GuiLib = script.Parent.Parent 34 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 35 | local Defaults = GuiLib:WaitForChild("Defaults") 36 | 37 | local INCREMENT_BUTTON = Defaults:WaitForChild("IncrementButton") 38 | local DECREMENT_BUTTON = Defaults:WaitForChild("DecrementButton") 39 | 40 | local WARN_MSG = "%s is not a valid mask. Defaulting to 'String' mask." 41 | 42 | local MASKS = {} 43 | for _, module in pairs(script:GetChildren()) do 44 | MASKS[module.Name] = require(module) 45 | end 46 | 47 | -- Class 48 | 49 | local TextMaskClass = {} 50 | TextMaskClass.__index = TextMaskClass 51 | TextMaskClass.__type = "TextMask" 52 | 53 | function TextMaskClass:__tostring() 54 | return TextMaskClass.__type 55 | end 56 | 57 | -- Public Constructors 58 | 59 | function TextMaskClass.new(textFrame) 60 | local self = setmetatable({}, TextMaskClass) 61 | 62 | self._Maid = Lazy.Utilities.Maid.new() 63 | self._MaskType = MASKS.String 64 | self._MaxLength = 230000 65 | 66 | self.Frame = textFrame 67 | 68 | init(self) 69 | 70 | return self 71 | end 72 | 73 | -- Private Methods 74 | 75 | function init(self) 76 | local frame = self.Frame 77 | local maid = self._Maid 78 | 79 | maid:Mark(frame:GetPropertyChangedSignal("Text"):Connect(function() 80 | local mask = self._MaskType 81 | local result = mask:Process(frame.Text):sub(1, self._MaxLength) 82 | frame.Text = result 83 | end)) 84 | 85 | maid:Mark(frame.FocusLost:Connect(function() 86 | local mask = self._MaskType 87 | if (not mask:Verify(frame.Text)) then 88 | frame.Text = mask.Default 89 | end 90 | end)) 91 | end 92 | 93 | -- Public Methods 94 | 95 | function TextMaskClass:GetValue() 96 | return self._MaskType:ToType(self.Frame.Text) 97 | end 98 | 99 | function TextMaskClass:GetMaskType() 100 | return self._MaskType.Name 101 | end 102 | 103 | function TextMaskClass:SetMaxLength(len) 104 | self._MaxLength = len 105 | end 106 | 107 | function TextMaskClass:SetMaskType(name) 108 | local mask = MASKS[name] 109 | if (not mask) then 110 | mask = MASKS.String 111 | warn(WARN_MSG:format(name)) 112 | end 113 | self._MaskType = mask 114 | self.Frame.Text = mask:Process(self.Frame.Text):sub(1, self._MaxLength) 115 | 116 | -- Add increment/decrement buttons for numeric inputs 117 | if name == "Number" then 118 | -- Initialize text to 0, consumer can override 119 | self.Frame.Text = 0 120 | 121 | self.IncrementButton = INCREMENT_BUTTON:Clone() 122 | self.IncrementButton.AnchorPoint = Vector2.new(1, 0) 123 | self.IncrementButton.Position = UDim2.new(1, 0, 0, 0) 124 | self._Maid:Mark(self.IncrementButton.Activated:Connect(function() 125 | self.Frame.Text = tonumber(self.Frame.Text) + 1 126 | end)) 127 | self.IncrementButton.Parent = self.Frame 128 | self._Maid:Mark(self.IncrementButton) 129 | 130 | self.DecrementButton = DECREMENT_BUTTON:Clone() 131 | self.DecrementButton.AnchorPoint = Vector2.new(1, 1) 132 | self.DecrementButton.Position = UDim2.new(1, 0, 1, 0) 133 | self._Maid:Mark(self.DecrementButton.Activated:Connect(function() 134 | self.Frame.Text = tonumber(self.Frame.Text) - 1 135 | end)) 136 | self.DecrementButton.Parent = self.Frame 137 | self._Maid:Mark(self.DecrementButton) 138 | end 139 | end 140 | 141 | function TextMaskClass:Destroy() 142 | self._Maid:Sweep() 143 | end 144 | 145 | -- 146 | 147 | return TextMaskClass 148 | -------------------------------------------------------------------------------- /src/GuiLib/Constructors/List.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Constructors.List 3 | 4 | This module provides a set of functions useful for generating lists that can be scrolled through. 5 | 6 | This module only has one function: 7 | 8 | List.Create( 9 | string[] list, -- an array of strings used to generate instances for the list 10 | integer scrollMax, -- the maximum number of elements visible in the scroll frame at any given time. Defaults to math.huge 11 | enum fillDirection, -- Enum.FillDirection lets you decide if the list is vertical or horizontal. Defaults to verical 12 | udim padding, -- scale and offset for padding between instances. Defaults to no padding 13 | function instanceFunc -- must return a UI object such as a frame. Defaults to creating a textbutton with a child textlabel 14 | ) 15 | 16 | --]] 17 | 18 | -- CONSTANTS 19 | 20 | local GuiLib = script.Parent.Parent 21 | local Lazy = require(GuiLib:WaitForChild("LazyLoader")) 22 | local Defaults = GuiLib:WaitForChild("Defaults") 23 | 24 | local LIST_BUTTON = Defaults:WaitForChild("ListButton") 25 | 26 | local DEFAULT_LAYOUT = { 27 | SortOrder = Enum.SortOrder.LayoutOrder 28 | } 29 | 30 | -- Private Functions 31 | 32 | local function defaultButton(index, option) 33 | local button = LIST_BUTTON:Clone() 34 | button.Name = option .. "_button" 35 | button.Label.Text = option 36 | return button 37 | end 38 | 39 | -- Library 40 | 41 | local List = {} 42 | 43 | function List.Create(list, max, fillDirection, padding, instanceFunc) 44 | max = max or math.huge 45 | padding = padding or UDim.new(0, 0) 46 | fillDirection = fillDirection or Enum.FillDirection.Vertical 47 | instanceFunc = instanceFunc or defaultButton 48 | 49 | local nList = #list 50 | local dList = 1 / nList 51 | 52 | local isVertical = (fillDirection == Enum.FillDirection.Vertical) 53 | 54 | local listFrame = Instance.new("Frame") 55 | listFrame.Name = "ListFrame" 56 | listFrame.BorderSizePixel = 0 57 | 58 | local scrollFrame = Instance.new("ScrollingFrame") 59 | scrollFrame.Name = "ScrollFrame" 60 | scrollFrame.BackgroundTransparency = 1 61 | scrollFrame.BorderSizePixel = 0 62 | scrollFrame.Size = UDim2.new(1, 0, 1, 0) 63 | 64 | local canvasSize = nil 65 | local elementSize = nil 66 | local extraPosition = nil 67 | 68 | local adj = (nList - 1) / nList 69 | local scale = padding.Scale * adj 70 | local offset = padding.Offset * adj 71 | 72 | if (isVertical) then 73 | canvasSize = UDim2.new(0, 0, nList / max, 0) 74 | elementSize = UDim2.new(1, 0, dList - scale, -offset) 75 | else 76 | canvasSize = UDim2.new(nList / max , 0, 0, 0) 77 | elementSize = UDim2.new(dList - scale, -offset, 1, 0) 78 | end 79 | 80 | scrollFrame.CanvasSize = canvasSize 81 | 82 | local items = {} 83 | for i, option in ipairs(list) do 84 | local item = instanceFunc(i, option) 85 | item.LayoutOrder = i 86 | item.Size = elementSize 87 | item.Position = UDim2.new(0, 0, (i-1)*dList, 0) 88 | 89 | if (isVertical) then 90 | item.Position = item.Position + UDim2.new(0, 0, padding.Scale/nList*(i-1), padding.Offset/nList*(i-1)) 91 | else 92 | item.Position = item.Position + UDim2.new(padding.Scale/nList*(i-1), padding.Offset/nList*(i-1), 0, 0) 93 | end 94 | 95 | items[i] = item 96 | item.Parent = scrollFrame 97 | end 98 | 99 | listFrame.BackgroundTransparency = items[1].BackgroundTransparency 100 | listFrame.BackgroundColor3 = items[1].BackgroundColor3 101 | scrollFrame.Parent = listFrame 102 | 103 | return listFrame 104 | end 105 | 106 | -- 107 | 108 | return List -------------------------------------------------------------------------------- /src/GuiLib/Defaults.rbxmx: -------------------------------------------------------------------------------- 1 | 2 | true 3 | null 4 | nil 5 | 6 | 7 | 8 | true 9 | 0 10 | true 11 | false 12 | Defaults 13 | true 14 | null 15 | -1 16 | 17 | 1 18 | 19 | 20 | 21 | true 22 | 23 | 0 24 | 0 25 | 26 | 27 | true 28 | true 29 | 0 30 | 31 | 0.113725491 32 | 0.113725491 33 | 0.117647059 34 | 35 | 0 36 | 37 | 0.105882362 38 | 0.164705887 39 | 0.207843155 40 | 41 | 0 42 | 0 43 | false 44 | false 45 | 17 46 | 0 47 | 1 48 | false 49 | ListButton 50 | null 51 | null 52 | null 53 | null 54 | 55 | 0 56 | 10 57 | 0 58 | 10 59 | 60 | false 61 | null 62 | 0 63 | true 64 | false 65 | null 66 | 67 | 0 68 | 100 69 | 0 70 | 35 71 | 72 | 0 73 | -1 74 | 0 75 | 76 | 77 | 78 | 1 79 | 1 80 | 1 81 | 82 | false 83 | 14 84 | 85 | 0 86 | 0 87 | 0 88 | 89 | 1 90 | 0 91 | 0 92 | false 93 | 2 94 | 1 95 | true 96 | 1 97 | 98 | 99 | 100 | false 101 | 102 | 0.5 103 | 0.5 104 | 105 | 106 | true 107 | 0 108 | 109 | 1 110 | 1 111 | 1 112 | 113 | 1 114 | 115 | 0.105882362 116 | 0.164705887 117 | 0.207843155 118 | 119 | 0 120 | 1 121 | true 122 | false 123 | 17 124 | 0 125 | 1 126 | Label 127 | null 128 | null 129 | null 130 | null 131 | 132 | 0.5 133 | 0 134 | 0.5 135 | 0 136 | 137 | false 138 | null 139 | 0 140 | false 141 | null 142 | 143 | 1 144 | -20 145 | 1 146 | 0 147 | 148 | 0 149 | -1 150 | 151 | Label 152 | 153 | 1 154 | 1 155 | 1 156 | 157 | false 158 | 14 159 | 160 | 0 161 | 0 162 | 0 163 | 164 | 1 165 | 0 166 | 1 167 | false 168 | 0 169 | 1 170 | true 171 | 1 172 | 173 | 174 | 175 | 176 | 177 | false 178 | 179 | 0 180 | 0 181 | 182 | 183 | true 184 | 0 185 | 186 | 0.113725491 187 | 0.113725491 188 | 0.117647059 189 | 190 | 1 191 | 192 | 0.105882362 193 | 0.164705887 194 | 0.207843155 195 | 196 | 0 197 | 0 198 | true 199 | false 200 | 0 201 | CheckboxLabel 202 | null 203 | null 204 | null 205 | null 206 | 207 | 0 208 | 200 209 | 0 210 | 10 211 | 212 | null 213 | 0 214 | false 215 | null 216 | 217 | 0 218 | 300 219 | 0 220 | 24 221 | 222 | 0 223 | -1 224 | 0 225 | 226 | true 227 | 1 228 | 229 | 230 | 231 | false 232 | 233 | 0 234 | 0 235 | 236 | 237 | true 238 | 0 239 | 240 | 1 241 | 1 242 | 1 243 | 244 | 1 245 | 246 | 0.105882362 247 | 0.164705887 248 | 0.207843155 249 | 250 | 0 251 | 1 252 | false 253 | false 254 | 17 255 | 1 256 | 1 257 | Label 258 | null 259 | null 260 | null 261 | null 262 | 263 | 0.0799999982 264 | 10 265 | 0 266 | 0 267 | 268 | false 269 | null 270 | 0 271 | false 272 | null 273 | 274 | 0.920000017 275 | -10 276 | 1 277 | 0 278 | 279 | 0 280 | -1 281 | 282 | Hello world my name is 283 | 284 | 1 285 | 1 286 | 1 287 | 288 | false 289 | 14 290 | 291 | 0 292 | 0 293 | 0 294 | 295 | 1 296 | 0 297 | 1 298 | false 299 | 0 300 | 1 301 | true 302 | 1 303 | 304 | 305 | 306 | 307 | false 308 | 309 | 0 310 | 0 311 | 312 | 313 | true 314 | 0 315 | 316 | 0.184313729 317 | 0.184313729 318 | 0.188235298 319 | 320 | 1 321 | 322 | 0.129411772 323 | 0.372549027 324 | 0.870588243 325 | 326 | 0 327 | 1 328 | false 329 | false 330 | 0 331 | CheckContainer 332 | null 333 | null 334 | null 335 | null 336 | 337 | 0 338 | 0 339 | 0 340 | 0 341 | 342 | null 343 | 0 344 | false 345 | null 346 | 347 | 0.0799999982 348 | 0 349 | 1 350 | 0 351 | 352 | 0 353 | -1 354 | 0 355 | 356 | true 357 | 1 358 | 359 | 360 | 361 | true 362 | 363 | 0.5 364 | 0.5 365 | 366 | 367 | false 368 | true 369 | 0 370 | 371 | 0.184313729 372 | 0.184313729 373 | 0.188235298 374 | 375 | 0 376 | 377 | 1 378 | 1 379 | 1 380 | 381 | 0 382 | 0 383 | false 384 | false 385 | 0 386 | 0 387 | 1 388 | false 389 | CheckButton 390 | null 391 | null 392 | null 393 | null 394 | 395 | 0.5 396 | 0 397 | 0.5 398 | 0 399 | 400 | false 401 | null 402 | 0 403 | false 404 | false 405 | null 406 | 407 | 1 408 | -4 409 | 1 410 | -4 411 | 412 | 0 413 | -1 414 | 0 415 | 416 | 417 | 418 | 0.105882362 419 | 0.164705887 420 | 0.207843155 421 | 422 | false 423 | 8 424 | 425 | 0 426 | 0 427 | 0 428 | 429 | 1 430 | 0 431 | 0 432 | false 433 | 2 434 | 1 435 | true 436 | 1 437 | 438 | 439 | 440 | false 441 | 442 | 0.5 443 | 0.5 444 | 445 | 446 | true 447 | 0 448 | 449 | 0.129411772 450 | 0.372549027 451 | 0.870588243 452 | 453 | 1 454 | 455 | 0.105882362 456 | 0.164705887 457 | 0.207843155 458 | 459 | 0 460 | 0 461 | false 462 | false 463 | rbxassetid://5422477875 464 | 465 | 1 466 | 1 467 | 1 468 | 469 | 470 | 0 471 | 0 472 | 473 | 474 | 0 475 | 0 476 | 477 | 0 478 | 0 479 | Checkmark 480 | null 481 | null 482 | null 483 | null 484 | 485 | 0.5 486 | 0 487 | 0.5 488 | 0 489 | 490 | null 491 | 0 492 | 0 493 | false 494 | null 495 | 496 | 1.29999995 497 | 0 498 | 1.29999995 499 | 0 500 | 501 | 0 502 | 503 | 504 | 0 505 | 0 506 | 507 | 508 | 0 509 | 0 510 | 511 | 512 | 1 513 | -1 514 | 515 | 516 | 1 517 | 0 518 | 1 519 | 0 520 | 521 | false 522 | 1 523 | 524 | 525 | 526 | 527 | 528 | false 529 | 530 | 0 531 | 0 532 | 533 | 534 | true 535 | 0 536 | 537 | 1 538 | 1 539 | 1 540 | 541 | 1 542 | 543 | 1 544 | 1 545 | 1 546 | 547 | 0 548 | 1 549 | false 550 | false 551 | 0 552 | Outline 553 | null 554 | null 555 | null 556 | null 557 | 558 | 0 559 | 0 560 | 0 561 | 0 562 | 563 | null 564 | 0 565 | false 566 | null 567 | 568 | 1 569 | 0 570 | 1 571 | 0 572 | 573 | 0 574 | -1 575 | 0 576 | 577 | true 578 | 1 579 | 580 | 581 | 582 | false 583 | 584 | 0 585 | 0 586 | 587 | 588 | true 589 | 0 590 | 591 | 1 592 | 1 593 | 1 594 | 595 | 0 596 | 597 | 0.105882362 598 | 0.164705887 599 | 0.207843155 600 | 601 | 0 602 | 0 603 | false 604 | false 605 | 0 606 | Frame 607 | null 608 | null 609 | null 610 | null 611 | 612 | 0 613 | 0 614 | 0 615 | 0 616 | 617 | null 618 | 0 619 | false 620 | null 621 | 622 | 1 623 | 0 624 | 0 625 | 2 626 | 627 | 0 628 | -1 629 | 0 630 | 631 | true 632 | 1 633 | 634 | 635 | 636 | 637 | false 638 | 639 | 0 640 | 1 641 | 642 | 643 | true 644 | 0 645 | 646 | 1 647 | 1 648 | 1 649 | 650 | 0 651 | 652 | 0.105882362 653 | 0.164705887 654 | 0.207843155 655 | 656 | 0 657 | 0 658 | false 659 | false 660 | 0 661 | Frame 662 | null 663 | null 664 | null 665 | null 666 | 667 | 0 668 | 0 669 | 1 670 | 0 671 | 672 | null 673 | 0 674 | false 675 | null 676 | 677 | 1 678 | 0 679 | 0 680 | 2 681 | 682 | 0 683 | -1 684 | 0 685 | 686 | true 687 | 1 688 | 689 | 690 | 691 | 692 | false 693 | 694 | 0 695 | 0 696 | 697 | 698 | true 699 | 0 700 | 701 | 1 702 | 1 703 | 1 704 | 705 | 0 706 | 707 | 0.105882362 708 | 0.164705887 709 | 0.207843155 710 | 711 | 0 712 | 0 713 | false 714 | false 715 | 0 716 | Frame 717 | null 718 | null 719 | null 720 | null 721 | 722 | 0 723 | 0 724 | 0 725 | 0 726 | 727 | null 728 | 0 729 | false 730 | null 731 | 732 | 0 733 | 2 734 | 1 735 | 0 736 | 737 | 0 738 | -1 739 | 0 740 | 741 | true 742 | 1 743 | 744 | 745 | 746 | 747 | false 748 | 749 | 1 750 | 0 751 | 752 | 753 | true 754 | 0 755 | 756 | 1 757 | 1 758 | 1 759 | 760 | 0 761 | 762 | 0.105882362 763 | 0.164705887 764 | 0.207843155 765 | 766 | 0 767 | 0 768 | false 769 | false 770 | 0 771 | Frame 772 | null 773 | null 774 | null 775 | null 776 | 777 | 1 778 | 0 779 | 0 780 | 0 781 | 782 | null 783 | 0 784 | false 785 | null 786 | 787 | 0 788 | 2 789 | 1 790 | 0 791 | 792 | 0 793 | -1 794 | 0 795 | 796 | true 797 | 1 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | false 806 | 807 | 0 808 | 0 809 | 810 | 811 | true 812 | 0 813 | 814 | 0.113725491 815 | 0.113725491 816 | 0.117647059 817 | 818 | 1 819 | 820 | 0.129411772 821 | 0.372549027 822 | 0.870588243 823 | 824 | 0 825 | 0 826 | false 827 | false 828 | 0 829 | RadioButtonLabel 830 | null 831 | null 832 | null 833 | null 834 | 835 | 0 836 | 200 837 | 0 838 | 60 839 | 840 | null 841 | 0 842 | false 843 | null 844 | 845 | 0 846 | 300 847 | 0 848 | 24 849 | 850 | 0 851 | -1 852 | 0 853 | 854 | true 855 | 1 856 | 857 | 858 | 859 | false 860 | 861 | 0 862 | 0 863 | 864 | 865 | true 866 | 0 867 | 868 | 1 869 | 1 870 | 1 871 | 872 | 1 873 | 874 | 0.105882362 875 | 0.164705887 876 | 0.207843155 877 | 878 | 0 879 | 1 880 | true 881 | false 882 | 17 883 | 1 884 | 1 885 | Label 886 | null 887 | null 888 | null 889 | null 890 | 891 | 0.0799999982 892 | 10 893 | 0 894 | 0 895 | 896 | false 897 | null 898 | 0 899 | false 900 | null 901 | 902 | 0.920000017 903 | -10 904 | 1 905 | 0 906 | 907 | 0 908 | -1 909 | 910 | Hello world my name is bob 911 | 912 | 1 913 | 1 914 | 1 915 | 916 | false 917 | 14 918 | 919 | 0 920 | 0 921 | 0 922 | 923 | 1 924 | 0 925 | 1 926 | false 927 | 0 928 | 1 929 | true 930 | 1 931 | 932 | 933 | 934 | 935 | false 936 | 937 | 0 938 | 0 939 | 940 | 941 | true 942 | 0 943 | 944 | 1 945 | 1 946 | 1 947 | 948 | 1 949 | 950 | 0.129411772 951 | 0.372549027 952 | 0.870588243 953 | 954 | 0 955 | 1 956 | false 957 | false 958 | 0 959 | RadioContainer 960 | null 961 | null 962 | null 963 | null 964 | 965 | 0 966 | 0 967 | 0 968 | 0 969 | 970 | null 971 | 0 972 | false 973 | null 974 | 975 | 0.0799999982 976 | 0 977 | 1 978 | 0 979 | 980 | 0 981 | -1 982 | 0 983 | 984 | true 985 | 1 986 | 987 | 988 | 989 | false 990 | 991 | 1 992 | 1 993 | 994 | 995 | true 996 | 0 997 | 998 | 1 999 | 1 1000 | 1 1001 | 1002 | 1 1003 | 1004 | 1 1005 | 1 1006 | 1 1007 | 1008 | 0 1009 | 1 1010 | false 1011 | false 1012 | rbxassetid://4504304159 1013 | 1014 | 1 1015 | 1 1016 | 1 1017 | 1018 | 1019 | 0 1020 | 0 1021 | 1022 | 1023 | 0 1024 | 0 1025 | 1026 | 0 1027 | 0 1028 | Outline 1029 | null 1030 | null 1031 | null 1032 | null 1033 | 1034 | 1 1035 | 0 1036 | 1 1037 | 0 1038 | 1039 | null 1040 | 0 1041 | 0 1042 | false 1043 | null 1044 | 1045 | 1 1046 | 0 1047 | 1 1048 | 0 1049 | 1050 | 0 1051 | 1052 | 1053 | 0 1054 | 0 1055 | 1056 | 1057 | 0 1058 | 0 1059 | 1060 | 1061 | 1 1062 | -1 1063 | 1064 | 1065 | 1 1066 | 0 1067 | 1 1068 | 0 1069 | 1070 | true 1071 | 0 1072 | 1073 | 1074 | 1075 | 1076 | true 1077 | 1078 | 0.5 1079 | 0.5 1080 | 1081 | 1082 | false 1083 | true 1084 | 0 1085 | 1086 | 0.184313729 1087 | 0.184313729 1088 | 0.188235298 1089 | 1090 | 1 1091 | 1092 | 1 1093 | 1 1094 | 1 1095 | 1096 | 0 1097 | 0 1098 | false 1099 | false 1100 | 1101 | rbxassetid://4504304159 1102 | 1103 | 0.184313729 1104 | 0.184313729 1105 | 0.188235298 1106 | 1107 | 1108 | 0 1109 | 0 1110 | 1111 | 1112 | 0 1113 | 0 1114 | 1115 | 0 1116 | 0 1117 | false 1118 | RadioButton 1119 | null 1120 | null 1121 | null 1122 | null 1123 | 1124 | 0.5 1125 | 0 1126 | 0.5 1127 | 0 1128 | 1129 | 1130 | null 1131 | 0 1132 | 0 1133 | false 1134 | false 1135 | null 1136 | 1137 | 1 1138 | -4 1139 | 1 1140 | -4 1141 | 1142 | 0 1143 | 1144 | 1145 | 0 1146 | 0 1147 | 1148 | 1149 | 0 1150 | 0 1151 | 1152 | 1153 | 1 1154 | -1 1155 | 0 1156 | 1157 | 1158 | 1 1159 | 0 1160 | 1 1161 | 0 1162 | 1163 | true 1164 | 1 1165 | 1166 | 1167 | 1168 | false 1169 | 1170 | 0.5 1171 | 0.5 1172 | 1173 | 1174 | true 1175 | 0 1176 | 1177 | 0.129411772 1178 | 0.372549027 1179 | 0.870588243 1180 | 1181 | 1 1182 | 1183 | 0.105882362 1184 | 0.164705887 1185 | 0.207843155 1186 | 1187 | 0 1188 | 0 1189 | false 1190 | false 1191 | rbxassetid://4504304159 1192 | 1193 | 0.129411772 1194 | 0.372549027 1195 | 0.870588243 1196 | 1197 | 1198 | 0 1199 | 0 1200 | 1201 | 1202 | 0 1203 | 0 1204 | 1205 | 0 1206 | 0 1207 | Circle 1208 | null 1209 | null 1210 | null 1211 | null 1212 | 1213 | 0.5 1214 | 0 1215 | 0.5 1216 | 0 1217 | 1218 | null 1219 | 0 1220 | 0 1221 | false 1222 | null 1223 | 1224 | 1 1225 | -6 1226 | 1 1227 | -6 1228 | 1229 | 0 1230 | 1231 | 1232 | 0 1233 | 0 1234 | 1235 | 1236 | 0 1237 | 0 1238 | 1239 | 1240 | 1 1241 | -1 1242 | 1243 | 1244 | 1 1245 | 0 1246 | 1 1247 | 0 1248 | 1249 | true 1250 | 1 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | false 1259 | 1260 | 0 1261 | 0 1262 | 1263 | 1264 | true 1265 | 0 1266 | 1267 | 0.113725491 1268 | 0.113725491 1269 | 0.117647059 1270 | 1271 | 1 1272 | 1273 | 0.105882362 1274 | 0.164705887 1275 | 0.207843155 1276 | 1277 | 0 1278 | 0 1279 | false 1280 | false 1281 | 0 1282 | SliderFrameY 1283 | null 1284 | null 1285 | null 1286 | null 1287 | 1288 | 0 1289 | 50 1290 | 0 1291 | 250 1292 | 1293 | null 1294 | 0 1295 | false 1296 | null 1297 | 1298 | 0 1299 | 20 1300 | 0 1301 | 300 1302 | 1303 | 0 1304 | -1 1305 | 0 1306 | 1307 | true 1308 | 1 1309 | 1310 | 1311 | 1312 | false 1313 | 1314 | 0.5 1315 | 0.5 1316 | 1317 | 1318 | true 1319 | 0 1320 | 1321 | 1 1322 | 1 1323 | 1 1324 | 1325 | 1 1326 | 1327 | 0.105882362 1328 | 0.164705887 1329 | 0.207843155 1330 | 1331 | 0 1332 | 1 1333 | false 1334 | false 1335 | 0 1336 | Background 1337 | null 1338 | null 1339 | null 1340 | null 1341 | 1342 | 0.5 1343 | 0 1344 | 0.5 1345 | 0 1346 | 1347 | null 1348 | 0 1349 | false 1350 | null 1351 | 1352 | 1 1353 | 0 1354 | 1 1355 | 0 1356 | 1357 | 0 1358 | -1 1359 | 0 1360 | 1361 | true 1362 | 1 1363 | 1364 | 1365 | 1366 | false 1367 | 1368 | 0.5 1369 | 0.5 1370 | 1371 | 1372 | true 1373 | 0 1374 | 1375 | 0.0666666701 1376 | 0.603921592 1377 | 0.870588303 1378 | 1379 | 0 1380 | 1381 | 0.105882362 1382 | 0.164705887 1383 | 0.207843155 1384 | 1385 | 0 1386 | 0 1387 | false 1388 | false 1389 | 1 1390 | Bar 1391 | null 1392 | null 1393 | null 1394 | null 1395 | 1396 | 0.5 1397 | 0 1398 | 0.5 1399 | 0 1400 | 1401 | null 1402 | 0 1403 | false 1404 | null 1405 | 1406 | 0.125 1407 | 0 1408 | 1 1409 | 0 1410 | 1411 | 0 1412 | -1 1413 | 0 1414 | 1415 | true 1416 | 1 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | true 1423 | 1424 | 0.5 1425 | 0.5 1426 | 1427 | 1428 | true 1429 | 0 1430 | 1431 | 1 1432 | 1 1433 | 1 1434 | 1435 | 1 1436 | 1437 | 0.105882362 1438 | 0.164705887 1439 | 0.207843155 1440 | 1441 | 0 1442 | 1 1443 | false 1444 | false 1445 | rbxassetid://4504304159 1446 | 1447 | 0.129411772 1448 | 0.372549027 1449 | 0.870588243 1450 | 1451 | 1452 | 0 1453 | 0 1454 | 1455 | 1456 | 0 1457 | 0 1458 | 1459 | 0 1460 | 0 1461 | Dragger 1462 | null 1463 | null 1464 | null 1465 | null 1466 | 1467 | 0.5 1468 | 0 1469 | 0.5 1470 | 0 1471 | 1472 | null 1473 | 0 1474 | 0 1475 | true 1476 | null 1477 | 1478 | 0.5 1479 | 0 1480 | 0.5 1481 | 0 1482 | 1483 | 1 1484 | 1485 | 1486 | 0 1487 | 0 1488 | 1489 | 1490 | 0 1491 | 0 1492 | 1493 | 1494 | 1 1495 | -1 1496 | 1497 | 1498 | 1 1499 | 0 1500 | 1 1501 | 0 1502 | 1503 | true 1504 | 4 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | false 1511 | 1512 | 0 1513 | 0 1514 | 1515 | 1516 | true 1517 | 0 1518 | 1519 | 0.113725491 1520 | 0.113725491 1521 | 0.117647059 1522 | 1523 | 1 1524 | 1525 | 0.105882362 1526 | 0.164705887 1527 | 0.207843155 1528 | 1529 | 0 1530 | 0 1531 | false 1532 | false 1533 | 0 1534 | SliderFrameX 1535 | null 1536 | null 1537 | null 1538 | null 1539 | 1540 | 0 1541 | 50 1542 | 0 1543 | 200 1544 | 1545 | null 1546 | 0 1547 | false 1548 | null 1549 | 1550 | 0 1551 | 300 1552 | 0 1553 | 20 1554 | 1555 | 0 1556 | -1 1557 | 0 1558 | 1559 | true 1560 | 1 1561 | 1562 | 1563 | 1564 | false 1565 | 1566 | 0.5 1567 | 0.5 1568 | 1569 | 1570 | true 1571 | 0 1572 | 1573 | 1 1574 | 1 1575 | 1 1576 | 1577 | 1 1578 | 1579 | 0.105882362 1580 | 0.164705887 1581 | 0.207843155 1582 | 1583 | 0 1584 | 1 1585 | false 1586 | false 1587 | 0 1588 | Background 1589 | null 1590 | null 1591 | null 1592 | null 1593 | 1594 | 0.5 1595 | 0 1596 | 0.5 1597 | 0 1598 | 1599 | null 1600 | 0 1601 | false 1602 | null 1603 | 1604 | 1 1605 | 0 1606 | 1 1607 | 0 1608 | 1609 | 0 1610 | -1 1611 | 0 1612 | 1613 | true 1614 | 1 1615 | 1616 | 1617 | 1618 | false 1619 | 1620 | 0.5 1621 | 0.5 1622 | 1623 | 1624 | true 1625 | 0 1626 | 1627 | 0.0666666701 1628 | 0.603921592 1629 | 0.870588303 1630 | 1631 | 0 1632 | 1633 | 0.105882362 1634 | 0.164705887 1635 | 0.207843155 1636 | 1637 | 0 1638 | 0 1639 | false 1640 | false 1641 | 1 1642 | Bar 1643 | null 1644 | null 1645 | null 1646 | null 1647 | 1648 | 0.5 1649 | 0 1650 | 0.5 1651 | 0 1652 | 1653 | null 1654 | 0 1655 | false 1656 | null 1657 | 1658 | 1 1659 | 0 1660 | 0.125 1661 | 0 1662 | 1663 | 0 1664 | -1 1665 | 0 1666 | 1667 | true 1668 | 1 1669 | 1670 | 1671 | 1672 | 1673 | 1674 | true 1675 | 1676 | 0.5 1677 | 0.5 1678 | 1679 | 1680 | true 1681 | 0 1682 | 1683 | 1 1684 | 1 1685 | 1 1686 | 1687 | 1 1688 | 1689 | 0.105882362 1690 | 0.164705887 1691 | 0.207843155 1692 | 1693 | 0 1694 | 1 1695 | false 1696 | false 1697 | rbxassetid://4504304159 1698 | 1699 | 0.129411772 1700 | 0.372549027 1701 | 0.870588243 1702 | 1703 | 1704 | 0 1705 | 0 1706 | 1707 | 1708 | 0 1709 | 0 1710 | 1711 | 0 1712 | 0 1713 | Dragger 1714 | null 1715 | null 1716 | null 1717 | null 1718 | 1719 | 0.5 1720 | 0 1721 | 0.5 1722 | 0 1723 | 1724 | null 1725 | 0 1726 | 0 1727 | true 1728 | null 1729 | 1730 | 0.5 1731 | 0 1732 | 0.5 1733 | 0 1734 | 1735 | 2 1736 | 1737 | 1738 | 0 1739 | 0 1740 | 1741 | 1742 | 0 1743 | 0 1744 | 1745 | 1746 | 1 1747 | -1 1748 | 1749 | 1750 | 1 1751 | 0 1752 | 1 1753 | 0 1754 | 1755 | true 1756 | 4 1757 | 1758 | 1759 | 1760 | 1761 | 1762 | true 1763 | 1764 | 0 1765 | 0 1766 | 1767 | 1768 | true 1769 | true 1770 | 0 1771 | 1772 | 0.113725491 1773 | 0.113725491 1774 | 0.117647059 1775 | 1776 | 0 1777 | 1778 | 0.105882362 1779 | 0.164705887 1780 | 0.207843155 1781 | 1782 | 0 1783 | 0 1784 | false 1785 | false 1786 | 3 1787 | 0 1788 | 1 1789 | false 1790 | DropdownButton 1791 | null 1792 | null 1793 | null 1794 | null 1795 | 1796 | 0 1797 | 10 1798 | 0 1799 | 60 1800 | 1801 | false 1802 | null 1803 | 0 1804 | true 1805 | false 1806 | null 1807 | 1808 | 0 1809 | 100 1810 | 0 1811 | 35 1812 | 1813 | 0 1814 | -1 1815 | 0 1816 | 1817 | 1818 | 1819 | 1 1820 | 1 1821 | 1 1822 | 1823 | false 1824 | 14 1825 | 1826 | 0 1827 | 0 1828 | 0 1829 | 1830 | 1 1831 | 0 1832 | 0 1833 | false 1834 | 2 1835 | 1 1836 | true 1837 | 2 1838 | 1839 | 1840 | 1841 | false 1842 | 1843 | 0 1844 | 0.5 1845 | 1846 | 1847 | true 1848 | 0 1849 | 1850 | 1 1851 | 1 1852 | 1 1853 | 1854 | 1 1855 | 1856 | 0.105882362 1857 | 0.164705887 1858 | 0.207843155 1859 | 1860 | 0 1861 | 1 1862 | true 1863 | false 1864 | 17 1865 | 0 1866 | 1 1867 | Option 1868 | null 1869 | null 1870 | null 1871 | null 1872 | 1873 | 0 1874 | 12 1875 | 0.5 1876 | 0 1877 | 1878 | false 1879 | null 1880 | 0 1881 | false 1882 | null 1883 | 1884 | 0.649999976 1885 | -12 1886 | 1 1887 | 0 1888 | 1889 | 0 1890 | -1 1891 | 1892 | 1 1893 | 1894 | 1 1895 | 1 1896 | 1 1897 | 1898 | false 1899 | 14 1900 | 1901 | 0 1902 | 0 1903 | 0 1904 | 1905 | 1 1906 | 0 1907 | 1 1908 | false 1909 | 0 1910 | 1 1911 | true 1912 | 1 1913 | 1914 | 1915 | 1916 | 1917 | false 1918 | 1919 | 1 1920 | 0.5 1921 | 1922 | 1923 | true 1924 | 0 1925 | 1926 | 1 1927 | 1 1928 | 1 1929 | 1930 | 1 1931 | 1932 | 0.105882362 1933 | 0.164705887 1934 | 0.207843155 1935 | 1936 | 0 1937 | 1 1938 | false 1939 | false 1940 | rbxassetid://5143165549 1941 | 1942 | 1 1943 | 1 1944 | 1 1945 | 1946 | 1947 | 0 1948 | 0 1949 | 1950 | 1951 | 0 1952 | 0 1953 | 1954 | 0 1955 | 0 1956 | Arrow 1957 | null 1958 | null 1959 | null 1960 | null 1961 | 1962 | 1 1963 | 0 1964 | 0.5 1965 | 0 1966 | 1967 | null 1968 | 0 1969 | 0 1970 | false 1971 | null 1972 | 1973 | 0.349999994 1974 | 0 1975 | 1 1976 | 0 1977 | 1978 | 0 1979 | 1980 | 1981 | 0 1982 | 0 1983 | 1984 | 1985 | 0 1986 | 0 1987 | 1988 | 1989 | 1 1990 | -1 1991 | 1992 | 1993 | 1 1994 | 0 1995 | 1 1996 | 0 1997 | 1998 | true 1999 | 1 2000 | 2001 | 2002 | 2003 | 2004 | 2005 | false 2006 | 2007 | 0 2008 | 0 2009 | 2010 | 2011 | true 2012 | 0 2013 | 2014 | 0.184313729 2015 | 0.184313729 2016 | 0.188235298 2017 | 2018 | 0 2019 | 2020 | 0.105882362 2021 | 0.164705887 2022 | 0.207843155 2023 | 2024 | 0 2025 | 1 2026 | false 2027 | false 2028 | 0 2029 | Background 2030 | null 2031 | null 2032 | null 2033 | null 2034 | 2035 | 0 2036 | 0 2037 | 0 2038 | 0 2039 | 2040 | null 2041 | 0 2042 | false 2043 | null 2044 | 2045 | 1 2046 | 0 2047 | 1 2048 | 0 2049 | 2050 | 0 2051 | -1 2052 | 0 2053 | 2054 | true 2055 | 0 2056 | 2057 | 2058 | 2059 | 2060 | true 2061 | 2062 | 0 2063 | 0 2064 | 2065 | 2066 | true 2067 | true 2068 | 0 2069 | 2070 | 0.113725491 2071 | 0.113725491 2072 | 0.117647059 2073 | 2074 | 0 2075 | 2076 | 0.105882362 2077 | 0.164705887 2078 | 0.207843155 2079 | 2080 | 0 2081 | 0 2082 | false 2083 | false 2084 | 3 2085 | 0 2086 | 1 2087 | false 2088 | IncrementButton 2089 | null 2090 | null 2091 | null 2092 | null 2093 | 2094 | 0 2095 | 10 2096 | 0 2097 | 60 2098 | 2099 | false 2100 | null 2101 | 0 2102 | true 2103 | false 2104 | null 2105 | 2106 | 1 2107 | 0 2108 | 0.5 2109 | 0 2110 | 2111 | 0 2112 | -1 2113 | 0 2114 | 2115 | 2116 | 2117 | 1 2118 | 1 2119 | 1 2120 | 2121 | false 2122 | 14 2123 | 2124 | 0 2125 | 0 2126 | 0 2127 | 2128 | 1 2129 | 0 2130 | 0 2131 | false 2132 | 2 2133 | 1 2134 | true 2135 | 2 2136 | 2137 | 2138 | 2139 | false 2140 | 2141 | 0 2142 | 0 2143 | 2144 | 2145 | true 2146 | 0 2147 | 2148 | 1 2149 | 1 2150 | 1 2151 | 2152 | 1 2153 | 2154 | 0.105882362 2155 | 0.164705887 2156 | 0.207843155 2157 | 2158 | 0 2159 | 1 2160 | false 2161 | false 2162 | rbxassetid://5154078925 2163 | 2164 | 1 2165 | 1 2166 | 1 2167 | 2168 | 2169 | 0 2170 | 0 2171 | 2172 | 2173 | 0 2174 | 0 2175 | 2176 | 0 2177 | 0 2178 | Arrow 2179 | null 2180 | null 2181 | null 2182 | null 2183 | 2184 | 0 2185 | 0 2186 | 0 2187 | 0 2188 | 2189 | null 2190 | 0 2191 | 0 2192 | false 2193 | null 2194 | 2195 | 1 2196 | 0 2197 | 1 2198 | 0 2199 | 2200 | 0 2201 | 2202 | 2203 | 0 2204 | 0 2205 | 2206 | 2207 | 0 2208 | 0 2209 | 2210 | 2211 | 1 2212 | -1 2213 | 2214 | 2215 | 1 2216 | 0 2217 | 1 2218 | 0 2219 | 2220 | true 2221 | 1 2222 | 2223 | 2224 | 2225 | 2226 | 1 2227 | 0 2228 | 2229 | 0 2230 | UIAspectRatioConstraint 2231 | -1 2232 | 2233 | 2234 | 2235 | 2236 | 2237 | 2238 | true 2239 | 2240 | 0 2241 | 0 2242 | 2243 | 2244 | true 2245 | true 2246 | 0 2247 | 2248 | 0.113725491 2249 | 0.113725491 2250 | 0.117647059 2251 | 2252 | 0 2253 | 2254 | 0.105882362 2255 | 0.164705887 2256 | 0.207843155 2257 | 2258 | 0 2259 | 0 2260 | false 2261 | false 2262 | 3 2263 | 0 2264 | 1 2265 | false 2266 | DecrementButton 2267 | null 2268 | null 2269 | null 2270 | null 2271 | 2272 | 0 2273 | 10 2274 | 0 2275 | 60 2276 | 2277 | false 2278 | null 2279 | 0 2280 | true 2281 | false 2282 | null 2283 | 2284 | 1 2285 | 0 2286 | 0.5 2287 | 0 2288 | 2289 | 0 2290 | -1 2291 | 0 2292 | 2293 | 2294 | 2295 | 1 2296 | 1 2297 | 1 2298 | 2299 | false 2300 | 14 2301 | 2302 | 0 2303 | 0 2304 | 0 2305 | 2306 | 1 2307 | 0 2308 | 0 2309 | false 2310 | 2 2311 | 1 2312 | true 2313 | 2 2314 | 2315 | 2316 | 2317 | false 2318 | 2319 | 0 2320 | 0 2321 | 2322 | 2323 | true 2324 | 0 2325 | 2326 | 1 2327 | 1 2328 | 1 2329 | 2330 | 1 2331 | 2332 | 0.105882362 2333 | 0.164705887 2334 | 0.207843155 2335 | 2336 | 0 2337 | 1 2338 | false 2339 | false 2340 | rbxassetid://5143165549 2341 | 2342 | 1 2343 | 1 2344 | 1 2345 | 2346 | 2347 | 0 2348 | 0 2349 | 2350 | 2351 | 0 2352 | 0 2353 | 2354 | 0 2355 | 0 2356 | Arrow 2357 | null 2358 | null 2359 | null 2360 | null 2361 | 2362 | 0 2363 | 0 2364 | 0 2365 | 0 2366 | 2367 | null 2368 | 0 2369 | 0 2370 | false 2371 | null 2372 | 2373 | 1 2374 | 0 2375 | 1 2376 | 0 2377 | 2378 | 0 2379 | 2380 | 2381 | 0 2382 | 0 2383 | 2384 | 2385 | 0 2386 | 0 2387 | 2388 | 2389 | 1 2390 | -1 2391 | 2392 | 2393 | 1 2394 | 0 2395 | 1 2396 | 0 2397 | 2398 | true 2399 | 1 2400 | 2401 | 2402 | 2403 | 2404 | 1 2405 | 0 2406 | 2407 | 0 2408 | UIAspectRatioConstraint 2409 | -1 2410 | 2411 | 2412 | 2413 | 2414 | 2415 | -------------------------------------------------------------------------------- /src/GuiLib/LazyLoader.lua: -------------------------------------------------------------------------------- 1 | local GuiLib = script.Parent 2 | local FORMAT_STR = "\"%s\" is not an existing class/folder." 3 | 4 | local VALID = { 5 | ["Folder"] = true; 6 | ["ModuleScript"] = true; 7 | } 8 | 9 | local function getLoaderOf(child) 10 | local Library = {} 11 | local Meta = {} 12 | 13 | function Meta:__index(key) 14 | if (Library[key]) then 15 | return Library[key] 16 | end 17 | 18 | local object = child:FindFirstChild(key) 19 | if (object and object ~= script and VALID[object.ClassName]) then 20 | Library[key] = object:IsA("ModuleScript") and require(object) or getLoaderOf(object) 21 | return Library[key] 22 | end 23 | 24 | error(FORMAT_STR:format(key), 2) 25 | end 26 | 27 | return setmetatable({}, Meta) 28 | end 29 | 30 | return getLoaderOf(GuiLib) -------------------------------------------------------------------------------- /src/GuiLib/Utilities/Maid.lua: -------------------------------------------------------------------------------- 1 | -- CONSTANTS 2 | 3 | local FORMAT_STR = "Maid does not support type \"%s\"" 4 | 5 | local DESTRUCTORS = { 6 | ["function"] = function(item) 7 | item() 8 | end; 9 | ["RBXScriptConnection"] = function(item) 10 | item:Disconnect() 11 | end; 12 | ["Instance"] = function(item) 13 | item:Destroy() 14 | end; 15 | } 16 | 17 | -- Class 18 | 19 | local MaidClass = {} 20 | MaidClass.__index = MaidClass 21 | MaidClass.__type = "Maid" 22 | 23 | function MaidClass:__tostring() 24 | return MaidClass.__type 25 | end 26 | 27 | -- Public Constructors 28 | 29 | function MaidClass.new() 30 | local self = setmetatable({}, MaidClass) 31 | 32 | self.Trash = {} 33 | 34 | return self 35 | end 36 | 37 | -- Public Methods 38 | 39 | function MaidClass:Mark(item) 40 | local tof = typeof(item) 41 | 42 | if (DESTRUCTORS[tof]) then 43 | self.Trash[item] = tof 44 | else 45 | error(FORMAT_STR:format(tof), 2) 46 | end 47 | end 48 | 49 | function MaidClass:Unmark(item) 50 | if (item) then 51 | self.Trash[item] = nil 52 | else 53 | self.Trash = {} 54 | end 55 | end 56 | 57 | function MaidClass:Sweep() 58 | for item, tof in next, self.Trash do 59 | DESTRUCTORS[tof](item) 60 | end 61 | self.Trash = {} 62 | end 63 | 64 | -- 65 | 66 | return MaidClass -------------------------------------------------------------------------------- /src/GuiLib/Utilities/Spring.lua: -------------------------------------------------------------------------------- 1 | -- CONSTANTS 2 | 3 | -- Private Functions 4 | 5 | local function getAbsDist(a, b) 6 | local d = b - a 7 | if (type(d) == "number") then 8 | return math.abs(d) 9 | end 10 | return d.Magnitude 11 | end 12 | 13 | -- Class 14 | 15 | local SpringClass = {} 16 | SpringClass.__index = SpringClass 17 | SpringClass.__type = "Spring" 18 | 19 | function SpringClass:__tostring() 20 | return SpringClass.__type 21 | end 22 | 23 | -- Public Constructors 24 | 25 | function SpringClass.new(stiffness, dampingCoeff, dampingRatio, initialPos) 26 | local self = setmetatable({}, SpringClass) 27 | 28 | self.instant = false 29 | self.marginOfError = 1E-6 30 | 31 | dampingRatio = dampingRatio or 1 32 | local m = dampingCoeff*dampingCoeff/(4*stiffness*dampingRatio*dampingRatio) 33 | self.k = stiffness/m 34 | self.d = -dampingCoeff/m 35 | self.x = initialPos 36 | self.t = initialPos 37 | self.v = initialPos*0 38 | 39 | return self 40 | end 41 | 42 | -- Public Methods 43 | 44 | function SpringClass:Update(dt) 45 | if (not self.instant) then 46 | local t, k, d, x0, v0 = self.t, self.k, self.d, self.x, self.v 47 | local a0 = k*(t - x0) + v0*d 48 | local v1 = v0 + a0*(dt/2) 49 | local a1 = k*(t - (x0 + v0*(dt/2))) + v1*d 50 | local v2 = v0 + a1*(dt/2) 51 | local a2 = k*(t - (x0 + v1*(dt/2))) + v2*d 52 | local v3 = v0 + a2*dt 53 | local x4 = x0 + (v0 + 2*(v1 + v2) + v3)*(dt/6) 54 | self.x, self.v = x4, v0 + (a0 + 2*(a1 + a2) + k*(t - (x0 + v2*dt)) + v3*d)*(dt/6) 55 | 56 | if (getAbsDist(x4, self.t) > self.marginOfError) then 57 | return x4 58 | end 59 | end 60 | 61 | self.x, self.v = self.t, self.v*0 62 | return self.x 63 | end 64 | 65 | -- 66 | 67 | return SpringClass --------------------------------------------------------------------------------