├── README.md ├── LICENSE ├── RadioGroup.lua ├── RoStrapPriorityUI.lua ├── [DEPRECATED] Snackbar.md ├── SelectionController.lua ├── AsymmetricTransformation.lua ├── Radio.lua ├── Shadow.lua ├── ConfirmationDialog.lua ├── Snackbar.lua ├── ChoiceDialog.lua ├── Color.lua ├── Rippler.lua ├── RippleButton.lua └── Checkbox.lua /README.md: -------------------------------------------------------------------------------- 1 | # RoStrapUI 2 | Material Design User Interface Modules for Roblox. 3 | 4 | Click the following links for documentation: 5 | - [AsymmetricTransformation](https://rostrap.github.io/Libraries/RoStrapUI/AsymmetricTransformation) 6 | - [Checkbox](https://rostrap.github.io/Libraries/RoStrapUI/Checkbox) 7 | - [ChoiceDialog](https://rostrap.github.io/Libraries/RoStrapUI/ChoiceDialog) 8 | - [Color](https://rostrap.github.io/Libraries/RoStrapUI/Color) 9 | - [Radio](https://rostrap.github.io/Libraries/RoStrapUI/Radio) 10 | - [RadioGroup](https://rostrap.github.io/Libraries/RoStrapUI/RadioGroup) 11 | - [RippleButton](https://rostrap.github.io/Libraries/RoStrapUI/RippleButton) 12 | - [Rippler](https://rostrap.github.io/Libraries/RoStrapUI/Rippler) 13 | - [SelectionController](https://rostrap.github.io/Libraries/RoStrapUI/SelectionController) 14 | - [Shadow](https://rostrap.github.io/Libraries/RoStrapUI/Shadow) 15 | - [Snackbar](https://rostrap.github.io/Libraries/RoStrapUI/Snackbar) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nevermore 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 | -------------------------------------------------------------------------------- /RadioGroup.lua: -------------------------------------------------------------------------------- 1 | -- Simple PseudoInstance wrapper to manage Radio buttons 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/RadioGroup/ 3 | -- @author Validark 4 | 5 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 6 | 7 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 8 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 9 | 10 | return PseudoInstance:Register("RadioGroup", { 11 | Internals = {"Radios", "Selection"}; 12 | Events = {"SelectionChanged"}; 13 | 14 | Methods = { 15 | Add = function(self, Item, Option) 16 | local Radios = self.Radios 17 | Radios[#Radios + 1] = Item 18 | 19 | self.Janitor:Add(Item.OnChecked:Connect(function(Checked) 20 | if Checked then 21 | for i = 1, #Radios do 22 | local Radio = Radios[i] 23 | if Radio ~= Item then 24 | Radio:SetChecked(false) 25 | end 26 | end 27 | 28 | self.Selection = Option 29 | self.SelectionChanged:Fire(Option) 30 | end 31 | end), "Disconnect") 32 | end; 33 | 34 | GetSelection = function(self) -- `Selection` is not directly accessible because you can neither clone a RadioGroup nor set a Selection 35 | return self.Selection or nil 36 | end; 37 | }; 38 | 39 | Init = function(self) 40 | self.Radios = {} 41 | self:superinit() 42 | end; 43 | }) 44 | -------------------------------------------------------------------------------- /RoStrapPriorityUI.lua: -------------------------------------------------------------------------------- 1 | -- Abstract UI ReplicatedPseudoInstance 2 | -- @author Validark 3 | 4 | local Players = game:GetService("Players") 5 | local Lighting = game:GetService("Lighting") 6 | local RunService = game:GetService("RunService") 7 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 8 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 9 | 10 | local Color = Resources:LoadLibrary("Color") 11 | local Debug = Resources:LoadLibrary("Debug") 12 | local Tween = Resources:LoadLibrary("Tween") 13 | local Typer = Resources:LoadLibrary("Typer") 14 | local Enumeration = Resources:LoadLibrary("Enumeration") 15 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 16 | local ReplicatedPseudoInstance = Resources:LoadLibrary("ReplicatedPseudoInstance") 17 | 18 | local InBack = Enumeration.EasingFunction.InBack.Value 19 | local OutBack = Enumeration.EasingFunction.OutBack.Value 20 | 21 | local LocalPlayer, PlayerGui do 22 | if RunService:IsClient() then 23 | if RunService:IsServer() then 24 | PlayerGui = game:GetService("CoreGui") 25 | else 26 | repeat LocalPlayer = Players.LocalPlayer until LocalPlayer or not wait() 27 | repeat PlayerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui") until PlayerGui or not wait() 28 | end 29 | end 30 | end 31 | 32 | local Screen = Instance.new("ScreenGui", PlayerGui) 33 | Screen.Name = "RoStrapPriorityUIs" 34 | Screen.DisplayOrder = 2^31 - 2 35 | 36 | local DialogBlur = Instance.new("BlurEffect") 37 | DialogBlur.Size = 0 38 | DialogBlur.Name = "RoStrapBlur" 39 | 40 | local function SetDialogBlurParentToNil() 41 | DialogBlur.Parent = nil 42 | end 43 | 44 | -- NOTE: Enter()s automatically when Parented 45 | return PseudoInstance:Register("RoStrapPriorityUI", { 46 | Storage = {}; 47 | 48 | Internals = { 49 | Blur = function(self) 50 | DialogBlur.Parent = Lighting 51 | Tween(DialogBlur, "Size", 56, OutBack, self.ENTER_TIME, true) 52 | end; 53 | 54 | Unblur = function(self) 55 | Tween(DialogBlur, "Size", 0, InBack, self.ENTER_TIME, true, SetDialogBlurParentToNil) 56 | end; 57 | 58 | DISMISS_TIME = 75 / 1000 * 2; 59 | ENTER_TIME = 150 / 1000 * 2; 60 | SCREEN = Screen; 61 | }; 62 | 63 | Events = { }; 64 | 65 | Methods = { 66 | Enter = 0; 67 | Dismiss = 0; 68 | 69 | Destroy = function(self) 70 | self:Dismiss() 71 | self:super("Destroy") 72 | end; 73 | }; 74 | 75 | Properties = { 76 | Parent = function(self, Parent) 77 | if Parent and PlayerGui then 78 | self:Enter() 79 | if self.SHOULD_BLUR then 80 | self:Blur() 81 | end 82 | end 83 | 84 | self:rawset("Parent", Parent) 85 | end; 86 | 87 | Dismissed = Typer.Boolean; 88 | }; 89 | 90 | Init = function(self, ...) 91 | self:superinit(...) 92 | end; 93 | }, ReplicatedPseudoInstance) 94 | -------------------------------------------------------------------------------- /[DEPRECATED] Snackbar.md: -------------------------------------------------------------------------------- 1 | ```lua 2 | -- Simple Snackbar Generator 3 | -- @readme https://github.com/RoStrap/UI/blob/master/README.md 4 | -- @author Validark 5 | 6 | -- Snackbar.new(string Text, ScreenGui Screen) 7 | -- Generates a SnackbarFrame with message Text 8 | -- Expect more parameters in the future 9 | -- @spec https://material.io/guidelines/components/snackbars-toasts.html 10 | 11 | local HEIGHT = 48 12 | local ENTER_TIME = 0.275 13 | local DISPLAY_TIME = 2 14 | local SMALLEST_WIDTH = 294 15 | 16 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 17 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 18 | 19 | local Tween = Resources:LoadLibrary("Tween") 20 | local Janitor = Resources:LoadLibrary("Janitor") 21 | 22 | local TweenCompleted = Enum.TweenStatus.Completed 23 | 24 | HEIGHT = HEIGHT + 6 25 | local OpenSnackbar, OpenTween 26 | local ExitPosition = UDim2.new(0.5, 0, 1, 0) 27 | local EnterPosition = UDim2.new(0.5, 0, 1, -HEIGHT) 28 | 29 | local DefaultSnackbar = Instance.new("ImageLabel") 30 | DefaultSnackbar.AnchorPoint = Vector2.new(0.5, 0) 31 | DefaultSnackbar.BackgroundTransparency = 1 32 | DefaultSnackbar.Image = "rbxasset://textures/ui/btn_newWhite.png" 33 | DefaultSnackbar.ImageColor3 = Color3.fromRGB(50, 50, 50) 34 | DefaultSnackbar.Position = UDim2.new(0.5, 0, 1, 0) 35 | DefaultSnackbar.ScaleType = Enum.ScaleType.Slice 36 | DefaultSnackbar.Size = UDim2.new(0, 288 + 6, 0, HEIGHT) 37 | DefaultSnackbar.SliceCenter = Rect.new(7, 7, 13, 13) 38 | DefaultSnackbar.ZIndex = 4 39 | 40 | local SnackbarText = Instance.new("TextLabel", DefaultSnackbar) 41 | SnackbarText.AnchorPoint = Vector2.new(0, 0.5) 42 | SnackbarText.Font = Enum.Font.SourceSans 43 | SnackbarText.Name = "Label" 44 | SnackbarText.Position = UDim2.new(0, 27, 0.5, 0) 45 | SnackbarText.TextSize = 20 46 | SnackbarText.TextColor3 = Color3.fromRGB(255, 255, 255) 47 | SnackbarText.TextXAlignment = Enum.TextXAlignment.Left 48 | SnackbarText.ZIndex = 5 49 | 50 | local Snackbar = {} 51 | 52 | function Snackbar.new(Text, Screen) 53 | -- @param string Text the message you want to appear 54 | -- @param ScreenGui Screen the Parent of the Snackbar 55 | 56 | if OpenSnackbar then 57 | local PreviousSnackbar = OpenSnackbar 58 | local PreviousLabel = PreviousSnackbar:FindFirstChild("Label") 59 | 60 | if PreviousLabel then 61 | if PreviousLabel.Text == Text then 62 | return 63 | end 64 | PreviousLabel.ZIndex = 3 65 | end 66 | 67 | OpenTween:Stop() 68 | PreviousSnackbar.ZIndex = 2 69 | 70 | Tween(PreviousSnackbar, "Position", ExitPosition, "Acceleration", ENTER_TIME * 0.7, false, function(Completed) 71 | if Completed == TweenCompleted then 72 | PreviousSnackbar:Destroy() 73 | if OpenSnackbar == PreviousSnackbar then 74 | OpenSnackbar = nil 75 | end 76 | end 77 | end) 78 | end 79 | 80 | local SnackbarJanitor = Janitor.new() 81 | local SnackbarFrame = DefaultSnackbar:Clone() 82 | OpenSnackbar = SnackbarFrame 83 | local Label = SnackbarFrame:FindFirstChild("Label") 84 | 85 | if Label then 86 | Label.Text = Text 87 | SnackbarJanitor:LinkToInstance(SnackbarFrame) 88 | SnackbarJanitor:Add(SnackbarFrame.Label:GetPropertyChangedSignal("TextBounds"):Connect(function() 89 | SnackbarFrame.Size = UDim2.new(0, Label.TextBounds.X + HEIGHT > SMALLEST_WIDTH and Label.TextBounds.X + HEIGHT or SMALLEST_WIDTH, 0, HEIGHT) 90 | end), "Disconnect") 91 | end 92 | 93 | SnackbarFrame.Parent = Screen 94 | 95 | OpenTween = Tween(SnackbarFrame, "Position", EnterPosition, "Deceleration", ENTER_TIME, false, function(Completed) 96 | if Completed == TweenCompleted and wait(DISPLAY_TIME - ENTER_TIME) then 97 | Tween(SnackbarFrame, "Position", ExitPosition, "Acceleration", ENTER_TIME, false, function(Completed) 98 | if Completed == TweenCompleted then 99 | SnackbarFrame:Destroy() 100 | if OpenSnackbar == SnackbarFrame then 101 | OpenSnackbar = nil 102 | end 103 | end 104 | end) 105 | end 106 | end) 107 | 108 | return setmetatable({}, Snackbar) 109 | end 110 | 111 | return Snackbar 112 | ``` 113 | -------------------------------------------------------------------------------- /SelectionController.lua: -------------------------------------------------------------------------------- 1 | -- SelectionController Class from which Checkbox and Radio inherit 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/SelectionController/ 3 | -- @author Validark 4 | 5 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 6 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 7 | 8 | local Color = Resources:LoadLibrary("Color") 9 | local Debug = Resources:LoadLibrary("Debug") 10 | local Typer = Resources:LoadLibrary("Typer") 11 | local Enumeration = Resources:LoadLibrary("Enumeration") 12 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 13 | 14 | local Rippler = Resources:LoadLibrary("Rippler") 15 | 16 | Enumeration.MaterialTheme = {"Light", "Dark"} 17 | 18 | local Touch = Enum.UserInputType.Touch 19 | local MouseButton1 = Enum.UserInputType.MouseButton1 20 | local MouseMovement = Enum.UserInputType.MouseMovement 21 | 22 | local DEFAULT_COLOR3 = Color.Teal[500] 23 | 24 | return PseudoInstance:Register("SelectionController", { 25 | Internals = { 26 | "Button", "Template", "ClickRippler", "HoverRippler"; 27 | 28 | Themes = { 29 | [Enumeration.MaterialTheme.Light.Value] = { 30 | ImageColor3 = Color.Black; 31 | ImageTransparency = 0.46; 32 | DisabledTransparency = 0.74; 33 | }; 34 | 35 | [Enumeration.MaterialTheme.Dark.Value] = { 36 | ImageColor3 = Color.White; 37 | ImageTransparency = 0.3; 38 | DisabledTransparency = 0.7; 39 | }; 40 | }; 41 | }; 42 | 43 | Events = {"OnChecked"}; 44 | 45 | Properties = { 46 | Checked = Typer.Boolean; 47 | Disabled = Typer.Boolean; 48 | Indeterminate = Typer.Boolean; 49 | 50 | PrimaryColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, Value) 51 | if typeof(Value) ~= "Color3" then Debug.Error("PrimaryColor3 must be a Color3 value") end 52 | 53 | if (self.Checked or self.Indeterminate) and self.PrimaryColor3 ~= Value then 54 | self:SetColorAndTransparency(Value, 0) 55 | end 56 | 57 | self:rawset("PrimaryColor3", Value) 58 | end); 59 | 60 | Theme = Typer.AssignSignature(2, Typer.EnumerationOfTypeMaterialTheme, function(self, Theme) 61 | if not self.Checked then 62 | local Data = self.Themes[Theme.Value] 63 | self:SetColorAndTransparency(Data.ImageColor3, Data.ImageTransparency) 64 | end 65 | 66 | self:rawset("Theme", Theme) 67 | end); 68 | }; 69 | 70 | Methods = { 71 | SetChecked = 0; 72 | }; 73 | 74 | Init = function(self) 75 | local Button = self.Button 76 | 77 | local ClickRippler = PseudoInstance.new("Rippler", Button) 78 | ClickRippler.RippleExpandDuration = 0.45 79 | ClickRippler.Style = Enumeration.RipplerStyle.Icon 80 | 81 | local HoverRippler = PseudoInstance.new("Rippler", Button) 82 | HoverRippler.RippleExpandDuration = 0.1 83 | HoverRippler.RippleFadeDuration = 0.1 84 | HoverRippler.Style = Enumeration.RipplerStyle.Icon 85 | 86 | self.Janitor:Add(ClickRippler, "Destroy") 87 | self.Janitor:Add(HoverRippler, "Destroy") 88 | 89 | local CheckboxIsDown 90 | 91 | self.Janitor:Add(Button.InputBegan:Connect(function(InputObject) 92 | if InputObject.UserInputType == MouseButton1 or InputObject.UserInputType == Touch then 93 | CheckboxIsDown = true 94 | ClickRippler:Down() 95 | elseif InputObject.UserInputType == MouseMovement then 96 | HoverRippler:Down() 97 | end 98 | end), "Disconnect") 99 | 100 | self.Janitor:Add(Button.InputEnded:Connect(function(InputObject) 101 | ClickRippler:Up() 102 | local UserInputType = InputObject.UserInputType 103 | if CheckboxIsDown and UserInputType == MouseButton1 or UserInputType == Touch then 104 | self:SetChecked() 105 | elseif UserInputType == MouseMovement then 106 | CheckboxIsDown = false 107 | HoverRippler:Up() 108 | end 109 | end), "Disconnect") 110 | 111 | self.Button = Button 112 | self.ClickRippler = ClickRippler 113 | self.HoverRippler = HoverRippler 114 | 115 | self.Disabled = false 116 | self.Theme = 0 117 | self.PrimaryColor3 = DEFAULT_COLOR3 118 | self.Checked = false 119 | self:superinit() 120 | end; 121 | }) 122 | -------------------------------------------------------------------------------- /AsymmetricTransformation.lua: -------------------------------------------------------------------------------- 1 | -- Transform function for Paper 2 | -- @author Validark 3 | 4 | -- AsymmetricTransformation(GuiObject Button, UDim2 EndSize) 5 | -- @specs https://material.io/guidelines/motion/transforming-material.html# 6 | 7 | local RunService = game:GetService("RunService") 8 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 9 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 10 | 11 | local Bezier = Resources:LoadLibrary("Bezier") 12 | local Standard = Bezier.new(0.4, 0.0, 0.2, 1) 13 | 14 | local Heartbeat = RunService.Heartbeat 15 | local ceil = math.ceil 16 | 17 | local function AsymmetricTransformation(Button, EndSize) 18 | local StartX = Button.Size.X 19 | local StartY = Button.Size.Y 20 | local EndX = EndSize.X 21 | local EndY = EndSize.Y 22 | 23 | local XStartScale = StartX.Scale 24 | local XStartOffset = StartX.Offset 25 | local YStartScale = StartY.Scale 26 | local YStartOffset = StartY.Offset 27 | 28 | local XScaleChange = EndX.Scale - XStartScale 29 | local XOffsetChange = EndX.Offset - XStartOffset 30 | local YScaleChange = EndY.Scale - YStartScale 31 | local YOffsetChange = EndY.Offset - YStartOffset 32 | 33 | local ElapsedTime, Connection = 0 34 | 35 | local Clone = Button:Clone() 36 | Clone.Name = "" 37 | Clone.Size = EndSize 38 | Clone.Visible = false 39 | Clone.Parent = Button.Parent 40 | 41 | if Button.AbsoluteSize.X * Button.AbsoluteSize.Y < Clone.AbsoluteSize.X * Clone.AbsoluteSize.Y then 42 | -- Expanding 43 | Clone:Destroy() 44 | local Duration = 0.375 45 | local HeightStart = Duration*0.1 46 | local WidthDuration = Duration*0.75 47 | 48 | Connection = Heartbeat:Connect(function(Step) 49 | ElapsedTime = ElapsedTime + Step 50 | if Duration > ElapsedTime then 51 | local XScale, XOffset, YScale, YOffset 52 | 53 | if WidthDuration > ElapsedTime then 54 | local WidthAlpha = Standard(ElapsedTime, 0, 1, WidthDuration) 55 | XScale = XStartScale + WidthAlpha*XScaleChange 56 | XOffset = StartX.Offset + WidthAlpha*XOffsetChange 57 | else 58 | XScale = Button.Size.X.Scale 59 | XOffset = Button.Size.X.Offset 60 | end 61 | 62 | if ElapsedTime > HeightStart then 63 | local HeightAlpha = Standard(ElapsedTime - HeightStart, 0, 1, Duration) 64 | YScale = YStartScale + HeightAlpha*YScaleChange 65 | YOffset = YStartOffset + HeightAlpha*YOffsetChange 66 | else 67 | YScale = YStartScale 68 | YOffset = YStartOffset 69 | end 70 | 71 | Button.Size = UDim2.new(ceil(XScale), ceil(XOffset), ceil(YScale), ceil(YOffset)) 72 | else 73 | Connection:Disconnect() 74 | Button.Size = EndSize 75 | end 76 | end) 77 | else 78 | -- Shrinking 79 | Clone:Destroy() 80 | local Duration = 0.225 81 | local WidthStart = Duration*0.15 82 | local HeightDuration = Duration*0.95 83 | 84 | Connection = Heartbeat:Connect(function(Step) 85 | ElapsedTime = ElapsedTime + Step 86 | if Duration > ElapsedTime then 87 | local XScale, XOffset, YScale, YOffset 88 | 89 | if HeightDuration > ElapsedTime then 90 | local HeightAlpha = Standard(ElapsedTime, 0, 1, HeightDuration) 91 | YScale = YStartScale + HeightAlpha*YScaleChange 92 | YOffset = YStartOffset + HeightAlpha*YOffsetChange 93 | else 94 | YScale = Button.Size.Y.Scale 95 | YOffset = Button.Size.Y.Offset 96 | end 97 | 98 | if ElapsedTime > WidthStart then 99 | local WidthAlpha = Standard(ElapsedTime - WidthStart, 0, 1, Duration) 100 | XScale = XStartScale + WidthAlpha*XScaleChange 101 | XOffset = XStartOffset + WidthAlpha*XOffsetChange 102 | else 103 | XScale = XStartScale 104 | XOffset = XStartOffset 105 | end 106 | 107 | Button.Size = UDim2.new(ceil(XScale), ceil(XOffset), ceil(YScale), ceil(YOffset)) 108 | else 109 | Connection:Disconnect() 110 | Button.Size = EndSize 111 | --[[if EndSize == UDim2.new() then 112 | Button.Visible = false 113 | end]] 114 | end 115 | end) 116 | end 117 | 118 | return Connection 119 | end 120 | 121 | return AsymmetricTransformation 122 | -------------------------------------------------------------------------------- /Radio.lua: -------------------------------------------------------------------------------- 1 | -- Material Design Radio Button PseudoInstance 2 | -- @author Validark 3 | 4 | local ContentProvider = game:GetService("ContentProvider") 5 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 6 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 7 | 8 | local Color = Resources:LoadLibrary("Color") 9 | local Tween = Resources:LoadLibrary("Tween") 10 | local Typer = Resources:LoadLibrary("Typer") 11 | local Enumeration = Resources:LoadLibrary("Enumeration") 12 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 13 | local SelectionController = Resources:LoadLibrary("SelectionController") 14 | 15 | local CHECKED_IMAGE = "rbxassetid://2012883990" 16 | local UNCHECKED_IMAGE = "rbxassetid://2012883770" 17 | local DEFAULT_SIZE = UDim2.new(0, 24, 0, 24) 18 | 19 | local RadioButton = Instance.new("ImageButton") 20 | RadioButton.BackgroundTransparency = 1 21 | RadioButton.Size = DEFAULT_SIZE 22 | RadioButton.Image = UNCHECKED_IMAGE 23 | RadioButton.ImageColor3 = Color.Black 24 | RadioButton.ImageTransparency = 0.46 25 | 26 | local CLICK_RIPPLE_TRANSPARENCY = 0.77 27 | local HOVER_RIPPLE_TRANSPARENCY = 0.93 28 | local ANIMATION_TIME = 0.1 -- 0.125 29 | 30 | local Circle = Instance.new("ImageLabel") 31 | Circle.AnchorPoint = Vector2.new(0.5, 0.5) 32 | Circle.BackgroundTransparency = 1 33 | Circle.ImageTransparency = 1 34 | Circle.Position = UDim2.new(0.5, 0, 0.5, 0) 35 | Circle.Size = UDim2.new(1, 0, 1, 0) 36 | Circle.Image = "rbxassetid://517259585" 37 | Circle.Name = "InnerCircle" 38 | Circle.Parent = RadioButton 39 | 40 | local RippleCheckedSize = UDim2.new(10 / 24, 0, 10 / 24, 0) 41 | local RippleUncheckedSize = UDim2.new(20 / 24, 0, 20 / 24, 0) 42 | 43 | spawn(function() 44 | ContentProvider:PreloadAsync{CHECKED_IMAGE, UNCHECKED_IMAGE} 45 | end) 46 | 47 | local Deceleration = Enumeration.EasingFunction.Deceleration.Value 48 | 49 | return PseudoInstance:Register("Radio", { 50 | WrappedProperties = { 51 | Button = {"AnchorPoint", "Name", "Parent", "Size", "Position", "LayoutOrder", "NextSelectionDown", "NextSelectionLeft", "NextSelectionRight", "NextSelectionUp"}; 52 | }; 53 | 54 | Internals = { 55 | "InnerCircle"; 56 | 57 | RippleCheckedFinished = function(self, TweenStatus) 58 | if TweenStatus == Enum.TweenStatus.Completed then 59 | self.Button.Image = CHECKED_IMAGE 60 | self.InnerCircle.Visible = false 61 | end 62 | end; 63 | 64 | SetColorAndTransparency = function(self, Color3, Transparency) 65 | local Opacity = (1 - Transparency) 66 | 67 | self.HoverRippler.RippleColor3 = Color3 68 | self.ClickRippler.RippleColor3 = Color3 69 | 70 | self.HoverRippler.RippleTransparency = Opacity * HOVER_RIPPLE_TRANSPARENCY + Transparency 71 | self.ClickRippler.RippleTransparency = Opacity * CLICK_RIPPLE_TRANSPARENCY + Transparency 72 | 73 | self.Button.ImageTransparency = Transparency 74 | self.Button.ImageColor3 = Color3 75 | end; 76 | }; 77 | 78 | Properties = { 79 | Checked = Typer.AssignSignature(2, Typer.Boolean, function(self, Checked) 80 | if Checked then 81 | self:SetColorAndTransparency(self.PrimaryColor3, 0) 82 | self.Button.Image = CHECKED_IMAGE 83 | else 84 | local MyTheme = self.Themes[self.Theme.Value] 85 | 86 | self:SetColorAndTransparency(MyTheme.ImageColor3, MyTheme.ImageTransparency) 87 | self.Button.Image = UNCHECKED_IMAGE 88 | end 89 | 90 | self:rawset("Checked", Checked) 91 | self.OnChecked:Fire(Checked) 92 | end); 93 | 94 | ZIndex = Typer.AssignSignature(2, Typer.Number, function(self, ZIndex) 95 | self.Button.ZIndex = ZIndex 96 | self.InnerCircle.ZIndex = ZIndex 97 | 98 | self:rawset("ZIndex", ZIndex) 99 | end); 100 | }; 101 | 102 | Methods = { 103 | SetChecked = Typer.AssignSignature(2, Typer.OptionalBoolean, function(self, Checked) 104 | if self.Disabled then return true end 105 | if Checked == nil then Checked = true end 106 | local Changed = self.Checked == Checked == false 107 | 108 | if Changed then 109 | local Button = self.Button 110 | local InnerCircle = self.InnerCircle 111 | InnerCircle.ImageColor3 = self.PrimaryColor3 112 | InnerCircle.Visible = true 113 | 114 | if Checked then 115 | self:SetColorAndTransparency(self.PrimaryColor3, 0) 116 | 117 | Tween(InnerCircle, "Size", RippleCheckedSize, Deceleration, ANIMATION_TIME, true) 118 | Tween(InnerCircle, "ImageTransparency", 0, Deceleration, ANIMATION_TIME * 0.8, true, self.RippleCheckedFinished, self) 119 | else 120 | local MyTheme = self.Themes[self.Theme.Value] 121 | self:SetColorAndTransparency(MyTheme.ImageColor3, MyTheme.ImageTransparency) 122 | 123 | Button.Image = UNCHECKED_IMAGE 124 | Tween(InnerCircle, "Size", RippleUncheckedSize, Deceleration, ANIMATION_TIME, true) 125 | Tween(InnerCircle, "ImageTransparency", 1, Deceleration, ANIMATION_TIME * 2, true) 126 | end 127 | end 128 | 129 | self:rawset("Checked", Checked) 130 | self.OnChecked:Fire(Checked) 131 | end); 132 | }; 133 | 134 | Init = function(self) 135 | self.Button = RadioButton:Clone() 136 | self:rawset("Object", self.Button) 137 | self.InnerCircle = self.Button.InnerCircle 138 | self.Janitor:Add(self.Button, "Destroy") 139 | self.Janitor:Add(self.InnerCircle, "Destroy") 140 | self:superinit() 141 | end; 142 | }, SelectionController) 143 | -------------------------------------------------------------------------------- /Shadow.lua: -------------------------------------------------------------------------------- 1 | -- Shadow / Elevation Rendering PseudoInstance 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/Shadow/ 3 | -- @author Validark 4 | -- @author AmaranthineCodices - Made the Shadow images and created the rendering framework 5 | -- @original https://github.com/AmaranthineCodices/roact-material/blob/master/src/Components/Shadow.lua 6 | 7 | local SHADOW_TWEEN_TIME = 0.175 -- 0.275 8 | 9 | local Resources = require(game:GetService("ReplicatedStorage"):WaitForChild("Resources")) 10 | local Debug = Resources:LoadLibrary("Debug") 11 | local Tween = Resources:LoadLibrary("Tween") 12 | local Typer = Resources:LoadLibrary("Typer") 13 | local Enumeration = Resources:LoadLibrary("Enumeration") 14 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 15 | 16 | local Deceleration = Enumeration.EasingFunction.Deceleration.Value 17 | 18 | local ShadowImage = Instance.new("ImageLabel") 19 | ShadowImage.Image = "rbxassetid://1316045217" 20 | ShadowImage.ImageColor3 = Color3.fromRGB(0, 0, 0) 21 | ShadowImage.AnchorPoint = Vector2.new(0.5, 0.5) 22 | ShadowImage.BackgroundTransparency = 1 23 | ShadowImage.ScaleType = Enum.ScaleType.Slice 24 | ShadowImage.SliceCenter = Rect.new(10, 10, 118, 118) 25 | 26 | Enumeration.ShadowElevation = { 27 | Elevation0 = 0; 28 | Elevation1 = 1; 29 | Elevation2 = 2; 30 | Elevation3 = 3; 31 | Elevation4 = 4; 32 | Elevation6 = 6; 33 | Elevation8 = 8; 34 | Elevation9 = 9; 35 | Elevation12 = 12; 36 | Elevation16 = 16; 37 | } 38 | 39 | local ShadowData = { 40 | [0] = { 41 | Ambient = { 42 | Opacity = 0; 43 | Blur = 0; 44 | }; 45 | 46 | Penumbra = { 47 | Opacity = 0; 48 | Blur = 0; 49 | }; 50 | 51 | Umbra = { 52 | Opacity = 0; 53 | Blur = 0; 54 | }; 55 | }; 56 | 57 | [1] = { 58 | Ambient = { 59 | Opacity = 0.2; 60 | Blur = 3; 61 | Offset = UDim2.new(0, 0, 0, 1); 62 | }; 63 | 64 | Penumbra = { 65 | Opacity = 0.12; 66 | Blur = 2; 67 | Offset = UDim2.new(0, 0, 0, 2); 68 | }; 69 | 70 | Umbra = { 71 | Opacity = 0.14; 72 | Blur = 2; 73 | }; 74 | }; 75 | 76 | [2] = { 77 | Ambient = { 78 | Opacity = 0.2; 79 | Blur = 5; 80 | Offset = UDim2.new(0, 0, 0, 1); 81 | }; 82 | 83 | Penumbra = { 84 | Opacity = 0.12; 85 | Blur = 4; 86 | Offset = UDim2.new(0, 0, 0, 3); 87 | }; 88 | 89 | Umbra = { 90 | Opacity = 0.14; 91 | Blur = 4; 92 | }; 93 | }; 94 | 95 | [3] = { 96 | Ambient = { 97 | Opacity = 0.2; 98 | Blur = 8; 99 | Offset = UDim2.new(0, 0, 0, 1); 100 | }; 101 | 102 | Penumbra = { 103 | Opacity = 0.12; 104 | Blur = 4; 105 | Offset = UDim2.new(0, 0, 0, 3); 106 | }; 107 | 108 | Umbra = { 109 | Opacity = 0.14; 110 | Blur = 3; 111 | Offset = UDim2.new(0, 0, 0, 3); 112 | }; 113 | }; 114 | 115 | [4] = { 116 | Ambient = { 117 | Opacity = 0.2; 118 | Blur = 10; 119 | Offset = UDim2.new(0, 0, 0, 1); 120 | }; 121 | 122 | Penumbra = { 123 | Opacity = 0.12; 124 | Blur = 5; 125 | Offset = UDim2.new(0, 0, 0, 4); 126 | }; 127 | 128 | Umbra = { 129 | Opacity = 0.14; 130 | Blur = 4; 131 | Offset = UDim2.new(0, 0, 0, 2); 132 | }; 133 | }; 134 | 135 | [6] = { 136 | Ambient = { 137 | Opacity = 0.2; 138 | Blur = 5; 139 | Offset = UDim2.new(0, 0, 0, 3); 140 | }; 141 | 142 | Penumbra = { 143 | Opacity = 0.12; 144 | Blur = 18; 145 | Offset = UDim2.new(0, 0, 0, 1); 146 | }; 147 | 148 | Umbra = { 149 | Opacity = 0.14; 150 | Blur = 10; 151 | Offset = UDim2.new(0, 0, 0, 6); 152 | }; 153 | }; 154 | 155 | [8] = { 156 | Ambient = { 157 | Opacity = 0.2; 158 | Blur = 15; 159 | Offset = UDim2.new(0, 0, 0, 4); 160 | }; 161 | 162 | Penumbra = { 163 | Opacity = 0.12; 164 | Blur = 14; 165 | Offset = UDim2.new(0, 0, 0, 3); 166 | }; 167 | 168 | Umbra = { 169 | Opacity = 0.14; 170 | Blur = 10; 171 | Offset = UDim2.new(0, 0, 0, 8); 172 | }; 173 | }; 174 | 175 | [9] = { 176 | Ambient = { 177 | Opacity = 0.2; 178 | Blur = 6; 179 | Offset = UDim2.new(0, 0, 0, 5); 180 | }; 181 | 182 | Penumbra = { 183 | Opacity = 0.12; 184 | Blur = 16; 185 | Offset = UDim2.new(0, 0, 0, 3); 186 | }; 187 | 188 | Umbra = { 189 | Opacity = 0.14; 190 | Blur = 12; 191 | Offset = UDim2.new(0, 0, 0, 9); 192 | }; 193 | }; 194 | 195 | [12] = { 196 | Ambient = { 197 | Opacity = 0.2; 198 | Blur = 8; 199 | Offset = UDim2.new(0, 0, 0, 7); 200 | }; 201 | 202 | Penumbra = { 203 | Opacity = 0.12; 204 | Blur = 22; 205 | Offset = UDim2.new(0, 0, 0, 5); 206 | }; 207 | 208 | Umbra = { 209 | Opacity = 0.14; 210 | Blur = 17; 211 | Offset = UDim2.new(0, 0, 0, 12); 212 | }; 213 | }; 214 | 215 | [16] = { 216 | Ambient = { 217 | Opacity = 0.2; 218 | Blur = 10; 219 | Offset = UDim2.new(0, 0, 0, 8); 220 | }; 221 | 222 | Penumbra = { 223 | Opacity = 0.12; 224 | Blur = 30; 225 | Offset = UDim2.new(0, 0, 0, 6); 226 | }; 227 | 228 | Umbra = { 229 | Opacity = 0.14; 230 | Blur = 24; 231 | Offset = UDim2.new(0, 0, 0, 16); 232 | }; 233 | }; 234 | } 235 | 236 | for _, Elevation in next, ShadowData do 237 | for _, Data in next, Elevation do 238 | Data.Size = UDim2.new(1, Data.Blur, 1, Data.Blur) 239 | Data.Position = UDim2.new(0.5, 0, 0.5, 0) + (Data.Offset or UDim2.new()) 240 | Data.ImageTransparency = 1 - Data.Opacity 241 | 242 | Data.Blur = nil 243 | Data.Offset = nil 244 | Data.Opacity = nil 245 | end 246 | end 247 | 248 | local ShadowNames = {"Ambient", "Penumbra", "Umbra"} 249 | 250 | return PseudoInstance:Register("Shadow", { 251 | Properties = { 252 | Elevation = Typer.AssignSignature(2, Typer.EnumerationOfTypeShadowElevation, function(self, Elevation) 253 | if self.Elevation == Elevation then return end 254 | 255 | for Name, Data in next, ShadowData[Elevation.Value] do 256 | local Object = self[Name] 257 | 258 | for Property, EndValue in next, Data do 259 | Tween(Object, Property, Property == "ImageTransparency" and (1 - EndValue) * self.Transparency + EndValue or EndValue, nil, 0, true) 260 | end 261 | end 262 | 263 | self:rawset("Elevation", Elevation) 264 | end); 265 | 266 | Parent = Typer.AssignSignature(2, Typer.OptionalInstanceWhichIsAGuiObject, function(self, Parent) 267 | if Parent then 268 | local function ZIndexChanged() 269 | local ParentZIndex = Parent.ZIndex 270 | 271 | for i = 1, 3 do 272 | self[ShadowNames[i]].ZIndex = ParentZIndex - 1 273 | end 274 | end 275 | 276 | self.Janitor:Add(Parent:GetPropertyChangedSignal("ZIndex"):Connect(ZIndexChanged), "Disconnect", "ZIndexChanged") 277 | ZIndexChanged() 278 | end 279 | 280 | for i = 1, 3 do 281 | self[ShadowNames[i]].Parent = Parent 282 | end 283 | 284 | self:rawset("Parent", Parent) 285 | end); 286 | 287 | Transparency = Typer.AssignSignature(2, Typer.Number, function(self, Transparency) 288 | for ShadowName, Data in next, ShadowData[self.Elevation.Value] do 289 | Tween(self[ShadowName], "ImageTransparency", (1 - Transparency) * Data.ImageTransparency + Transparency, nil, 0, true) -- Stop any ImageTransparency tweens and change to proper Transparency 290 | end 291 | 292 | self:rawset("Transparency", Transparency) 293 | end); 294 | 295 | Visible = Typer.AssignSignature(2, Typer.Boolean, function(self, Visible) 296 | for i = 1, 3 do 297 | self[ShadowNames[i]].Visible = Visible 298 | end 299 | 300 | self:rawset("Visible", Visible) 301 | end); 302 | }; 303 | 304 | Events = {}; 305 | 306 | Methods = { 307 | ChangeElevation = Typer.AssignSignature(2, Typer.EnumerationOfTypeShadowElevation, Typer.OptionalNumber, function(self, Elevation, TweenTime) 308 | if self.Elevation == Elevation then return end 309 | 310 | for Name, Data in next, ShadowData[Elevation.Value] do 311 | local Object = self[Name] 312 | 313 | for Property, EndValue in next, Data do 314 | Tween(Object, Property, Property == "ImageTransparency" and (1 - EndValue) * self.Transparency + EndValue or EndValue, Deceleration, TweenTime or SHADOW_TWEEN_TIME, true) 315 | end 316 | end 317 | 318 | self:rawset("Elevation", Elevation) 319 | end); 320 | }; 321 | 322 | Init = function(self) 323 | for i = 1, 3 do 324 | local Name = ShadowNames[i] 325 | local Shadow = ShadowImage:Clone() 326 | Shadow.Name = Name .. "Shadow" 327 | 328 | self:rawset(Name, Shadow) 329 | self.Janitor:Add(Shadow, "Destroy") 330 | self.Janitor:LinkToInstance(Shadow, true) 331 | end 332 | 333 | self:rawset("Transparency", 0) 334 | self:rawset("Elevation", Enumeration.ShadowElevation.Elevation0) 335 | self:superinit() 336 | end; 337 | }) 338 | -------------------------------------------------------------------------------- /ConfirmationDialog.lua: -------------------------------------------------------------------------------- 1 | local Players = game:GetService("Players") 2 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 3 | local RunService = game:GetService("RunService") 4 | local TextService = game:GetService("TextService") 5 | local UserInputService = game:GetService("UserInputService") 6 | 7 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 8 | local Color = Resources:LoadLibrary("Color") 9 | local Tween = Resources:LoadLibrary("Tween") 10 | local Typer = Resources:LoadLibrary("Typer") 11 | local Enumeration = Resources:LoadLibrary("Enumeration") 12 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 13 | local RoStrapPriorityUI = Resources:LoadLibrary("RoStrapPriorityUI") 14 | 15 | Resources:LoadLibrary("ReplicatedPseudoInstance") 16 | Resources:LoadLibrary("Shadow") 17 | Resources:LoadLibrary("RippleButton") 18 | Resources:LoadLibrary("EasingFunctions") 19 | 20 | local BUTTON_WIDTH_PADDING = 8 21 | local WIDTH = UserInputService.TouchEnabled and 460 or 560 22 | local FRAME_SIZE = Vector2.new(WIDTH - 48, 1 / 0) 23 | local NEW_SIZE = UDim2.new(0, WIDTH, 0, 182) 24 | 25 | local Left = Enum.TextXAlignment.Left.Value 26 | local SourceSans = Enum.Font.SourceSans.Value 27 | 28 | local Flat = Enumeration.ButtonStyle.Flat.Value 29 | local Acceleration = Enumeration.EasingFunction.Acceleration.Value 30 | local Deceleration = Enumeration.EasingFunction.Deceleration.Value 31 | 32 | local LocalPlayer, PlayerGui do 33 | if RunService:IsClient() then 34 | if RunService:IsServer() then 35 | PlayerGui = game:GetService("CoreGui") 36 | else 37 | repeat LocalPlayer = Players.LocalPlayer until LocalPlayer or not wait() 38 | repeat PlayerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui") until PlayerGui or not wait() 39 | end 40 | end 41 | end 42 | 43 | local Frame do 44 | Frame = Instance.new("Frame") 45 | Frame.BackgroundTransparency = 1 46 | Frame.BackgroundColor3 = Color3.new() 47 | Frame.Position = UDim2.new(0.5, 0, 0.5, 0) 48 | Frame.AnchorPoint = Vector2.new(0.5, 0.5) 49 | Frame.Size = UDim2.new(1, 0, 1, 0) 50 | Frame.Name = "ConfirmationDialog" 51 | 52 | local UIScale = Instance.new("UIScale") 53 | UIScale.Scale = 0 54 | UIScale.Name = "UIScale" 55 | UIScale.Parent = Frame 56 | 57 | local Background = Instance.new("ImageLabel") 58 | Background.BackgroundTransparency = 1 59 | Background.ScaleType = Enum.ScaleType.Slice 60 | Background.SliceCenter = Rect.new(4, 4, 256 - 4, 256 - 4) 61 | Background.Image = "rbxassetid://1934624205" 62 | Background.Size = NEW_SIZE 63 | Background.Position = UDim2.new(0.5, 0, 0.5, 0) 64 | Background.AnchorPoint = Vector2.new(0.5, 0.5) 65 | Background.Name = "Background" 66 | Background.ZIndex = 2 67 | Background.Parent = Frame 68 | 69 | local Header = Instance.new("TextLabel") 70 | Header.Font = Enum.Font.SourceSansSemibold 71 | Header.TextSize = 26 72 | Header.Size = UDim2.new(1, -24, 0, 64) 73 | Header.Position = UDim2.new(0, 24, 0, 1) 74 | Header.BackgroundTransparency = 1 75 | Header.TextXAlignment = Left 76 | Header.TextTransparency = 0.13 77 | Header.TextColor3 = Color.Black 78 | Header.Name = "Header" 79 | Header.ZIndex = 3 80 | Header.Parent = Background 81 | 82 | local DialogText = Instance.new("TextLabel") 83 | DialogText.BackgroundTransparency = 1 84 | DialogText.Name = "PrimaryText" 85 | DialogText.Position = UDim2.new(0, 24, 0, 40) 86 | DialogText.Size = UDim2.new(1, -24, 0, 64) 87 | DialogText.ZIndex = 3 88 | DialogText.TextSize = 20 89 | DialogText.Font = SourceSans 90 | DialogText.TextXAlignment = Left 91 | DialogText.TextTransparency = 0.4 92 | DialogText.TextColor3 = Color.Black 93 | DialogText.TextWrapped = true 94 | DialogText.Parent = Background 95 | 96 | local Shadow = PseudoInstance.new("Shadow") 97 | Shadow.Elevation = 8 98 | Shadow.Parent = Background 99 | end 100 | 101 | local function OnDismiss(self) 102 | if not self.Dismissed then 103 | self:Dismiss() 104 | self.OnConfirmed:Fire(LocalPlayer, false) 105 | end 106 | end 107 | 108 | local function OnConfirm(self) 109 | if not self.Dismissed then 110 | self:Dismiss() 111 | self.OnConfirmed:Fire(LocalPlayer, true) 112 | end 113 | end 114 | 115 | local DialogsActive = 0 116 | 117 | local function SubDialogsActive() 118 | DialogsActive = DialogsActive - 1 119 | end 120 | 121 | local function AdjustButtonSize(Button) 122 | Button.Size = UDim2.new(0, Button.TextBounds.X + BUTTON_WIDTH_PADDING * 2, 0, 36) 123 | end 124 | 125 | local function HideUIScale(self) 126 | self.UIScale.Parent = nil 127 | end 128 | 129 | local function RescaleUI(self, Text) 130 | local PrimaryText = self.Object.Background.PrimaryText 131 | 132 | if Text then 133 | local TextSize = TextService:GetTextSize(Text, 20, SourceSans, FRAME_SIZE).Y 134 | local SizeY = TextSize + 4 < 64 and 64 or TextSize + 4 135 | self.Object.Background.Size = UDim2.new(0, WIDTH, 0, TextSize + 40 + 52 + 24) 136 | PrimaryText.Size = UDim2.new(1, -24, 0, SizeY) 137 | PrimaryText.Position = UDim2.new(0, 24, 0, SizeY == 64 and 40 or 40 + (SizeY - 64)) 138 | else 139 | local TextSize = TextService:GetTextSize(PrimaryText.Text, 20, SourceSans, FRAME_SIZE).Y 140 | local SizeY = TextSize + 4 < 64 and 64 or TextSize + 4 141 | self.Object.Background.Size = UDim2.new(0, WIDTH, 0, TextSize + 40 + 52 + 24) 142 | PrimaryText.Size = UDim2.new(1, -24, 0, SizeY) 143 | PrimaryText.Position = UDim2.new(0, 24, 0, SizeY == 64 and 40 or 40 + (SizeY - 64)) 144 | end 145 | end 146 | 147 | return PseudoInstance:Register("ConfirmationDialog", { 148 | Storage = {}; 149 | 150 | Internals = { 151 | "ConfirmButton", "DismissButton", "UIScale"; 152 | SHOULD_BLUR = true; 153 | }; 154 | 155 | Events = {"OnConfirmed"}; 156 | 157 | Methods = { 158 | Enter = function(self) 159 | RescaleUI(self) 160 | self.UIScale.Parent = self.Object 161 | self.Object.Parent = self.SCREEN 162 | AdjustButtonSize(self.DismissButton) 163 | AdjustButtonSize(self.ConfirmButton) 164 | 165 | Tween(self.UIScale, "Scale", 1, Deceleration, self.ENTER_TIME, true, HideUIScale, self) 166 | end; 167 | 168 | Dismiss = function(self) 169 | -- Destroys Dialog when done 170 | if not self.Dismissed then 171 | self.Dismissed = true 172 | Tween(self.UIScale, "Scale", 0, Acceleration, self.DISMISS_TIME, true, self.Janitor) 173 | self.UIScale.Parent = self.Object 174 | self:Unblur() 175 | end 176 | end; 177 | }; 178 | 179 | Properties = { 180 | PrimaryColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, PrimaryColor3) 181 | self.ConfirmButton.PrimaryColor3 = PrimaryColor3 182 | self.DismissButton.PrimaryColor3 = PrimaryColor3 183 | self:rawset("PrimaryColor3", PrimaryColor3) 184 | end); 185 | 186 | HeaderText = Typer.AssignSignature(2, Typer.String, function(self, Text) 187 | self.Object.Background.Header.Text = Text 188 | self:rawset("HeaderText", Text) 189 | end); 190 | 191 | DialogText = Typer.AssignSignature(2, Typer.String, function(self, Text) 192 | RescaleUI(self, Text) 193 | self.Object.Background.PrimaryText.Text = Text 194 | self:rawset("DialogText", Text) 195 | end); 196 | 197 | DismissText = Typer.AssignSignature(2, Typer.String, function(self, Text) 198 | self.DismissButton.Text = Text 199 | self:rawset("DismissText", Text) 200 | end); 201 | 202 | ConfirmText = Typer.AssignSignature(2, Typer.String, function(self, Text) 203 | self.ConfirmButton.Text = Text 204 | self:rawset("ConfirmText", Text) 205 | end); 206 | }; 207 | 208 | Init = function(self, ...) 209 | self:rawset("Object", Frame:Clone()) 210 | self.UIScale = self.Object.UIScale 211 | 212 | local ConfirmButton = PseudoInstance.new("RippleButton") 213 | ConfirmButton.AnchorPoint = Vector2.new(1, 1) 214 | ConfirmButton.Position = UDim2.new(1, -8, 1, -8) 215 | ConfirmButton.BorderRadius = 4 216 | ConfirmButton.ZIndex = 10 217 | ConfirmButton.TextSize = 16 218 | ConfirmButton.TextTransparency = 0.129 219 | ConfirmButton.Style = Flat 220 | ConfirmButton.Parent = self.Object.Background 221 | 222 | local DismissButton = ConfirmButton:Clone() 223 | DismissButton.Position = UDim2.new(0, -8, 1, 0) 224 | DismissButton.Parent = ConfirmButton.Object 225 | 226 | self.Janitor:Add(DismissButton:GetPropertyChangedSignal("TextBounds"):Connect(AdjustButtonSize, DismissButton), "Disconnect") 227 | self.Janitor:Add(ConfirmButton:GetPropertyChangedSignal("TextBounds"):Connect(AdjustButtonSize, ConfirmButton), "Disconnect") 228 | 229 | self.ConfirmButton = ConfirmButton 230 | self.DismissButton = DismissButton 231 | 232 | self.Janitor:Add(ConfirmButton.OnPressed:Connect(OnConfirm, self), "Disconnect") 233 | self.Janitor:Add(DismissButton.OnPressed:Connect(OnDismiss, self), "Disconnect") 234 | self.Janitor:Add(self.UIScale, "Destroy") 235 | self.Janitor:Add(self.Object, "Destroy") 236 | self.Janitor:Add(SubDialogsActive, true) 237 | 238 | self.PrimaryColor3 = Color3.fromRGB(98, 0, 238) 239 | self:superinit(...) 240 | end; 241 | }, RoStrapPriorityUI) 242 | -------------------------------------------------------------------------------- /Snackbar.lua: -------------------------------------------------------------------------------- 1 | -- Snackbar PseudoInstance 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/Snackbar/ 3 | -- @author Validark 4 | 5 | local Players = game:GetService("Players") 6 | local RunService = game:GetService("RunService") 7 | local TextService = game:GetService("TextService") 8 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 9 | 10 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 11 | 12 | local Color = Resources:LoadLibrary("Color") 13 | local Debug = Resources:LoadLibrary("Debug") 14 | local Tween = Resources:LoadLibrary("Tween") 15 | local Typer = Resources:LoadLibrary("Typer") 16 | local Shadow = Resources:LoadLibrary("Shadow") 17 | local RippleButton = Resources:LoadLibrary("RippleButton") 18 | local Enumeration = Resources:LoadLibrary("Enumeration") 19 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 20 | local RoStrapPriorityUI = Resources:LoadLibrary("RoStrapPriorityUI") 21 | local ReplicatedPseudoInstance = Resources:LoadLibrary("ReplicatedPseudoInstance") 22 | 23 | local TEXT_SIZE = 20 24 | local FONT = Enum.Font.SourceSans.Value 25 | local BUTTON_FONT = Enum.Font.SourceSansSemibold.Value 26 | local BUTTON_SIZE = 18 27 | local CORNER_OFFSET = 8 28 | 29 | local HEIGHT = 48 30 | local SMALLEST_WIDTH = 294 31 | 32 | local TweenCompleted = Enum.TweenStatus.Completed 33 | local Deceleration = Enumeration.EasingFunction.Deceleration.Value 34 | local Acceleration = Enumeration.EasingFunction.Acceleration.Value 35 | 36 | Enumeration.SnackbarPosition = { "Left", "Right", "Center" } 37 | 38 | local StatePosition = { 39 | [Enumeration.SnackbarPosition.Left.Value] = { 40 | AnchorPoint = Vector2.new(0, 0); 41 | ExitPosition = UDim2.new(0, 7, 1, 0); 42 | EnterPosition = UDim2.new(0, 7, 1, -HEIGHT - CORNER_OFFSET); 43 | }; 44 | 45 | [Enumeration.SnackbarPosition.Right.Value] = { 46 | AnchorPoint = Vector2.new(1, 0); 47 | ExitPosition = UDim2.new(1, -7, 1, 0); 48 | EnterPosition = UDim2.new(1, -7, 1, -HEIGHT - CORNER_OFFSET); 49 | }; 50 | 51 | [Enumeration.SnackbarPosition.Center.Value] = { 52 | AnchorPoint = Vector2.new(0.5, 0); 53 | ExitPosition = UDim2.new(0.5, 0, 1, 0); 54 | EnterPosition = UDim2.new(0.5, 0, 1, -HEIGHT - CORNER_OFFSET); 55 | }; 56 | } 57 | 58 | local SnackbarImage = Instance.new("ImageLabel") 59 | SnackbarImage.BackgroundTransparency = 1 60 | SnackbarImage.Name = "Snackbar" 61 | SnackbarImage.ZIndex = 2 62 | SnackbarImage.Image = "rbxassetid://1934624205" 63 | SnackbarImage.ImageColor3 = Color3.fromRGB(50, 50, 50) 64 | SnackbarImage.ScaleType = Enum.ScaleType.Slice.Value 65 | SnackbarImage.SliceCenter = Rect.new(4, 4, 252, 252) 66 | SnackbarImage.ZIndex = 3 67 | 68 | local SnackbarText = Instance.new("TextLabel") 69 | SnackbarText.AnchorPoint = Vector2.new(0, 0.5) 70 | SnackbarText.BackgroundTransparency = 1 71 | SnackbarText.Name = "SnackbarText" 72 | SnackbarText.Position = UDim2.new(0, 16, 0.5, 0) 73 | SnackbarText.Size = UDim2.new(1, 0, 1, -12) 74 | SnackbarText.ZIndex = 3 75 | SnackbarText.Font = FONT 76 | SnackbarText.TextColor3 = Color3.fromRGB(255, 255, 255) 77 | SnackbarText.TextSize = TEXT_SIZE 78 | SnackbarText.TextXAlignment = Enum.TextXAlignment.Left.Value 79 | SnackbarText.Parent = SnackbarImage 80 | 81 | local Shadow = PseudoInstance.new("Shadow") 82 | Shadow.Elevation = 6 83 | Shadow.Parent = SnackbarImage 84 | 85 | local LocalPlayer, PlayerGui do 86 | if RunService:IsClient() then 87 | repeat LocalPlayer = Players.LocalPlayer until LocalPlayer or not wait() 88 | repeat PlayerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui") until PlayerGui or not wait() 89 | end 90 | end 91 | 92 | local function OnActionPressed(self) 93 | if not self.Dismissed then 94 | self:Dismiss() 95 | self.OnAction:Fire(LocalPlayer) 96 | end 97 | end 98 | 99 | local LARGE_FRAME_SIZE = Vector2.new(32767, 32767) 100 | 101 | local Storage = {} 102 | 103 | local function IsInputting(CurrentlyInputting) 104 | for _, Bool in next, CurrentlyInputting do 105 | if Bool == true then 106 | return true 107 | end 108 | end 109 | return false 110 | end 111 | 112 | return PseudoInstance:Register("Snackbar", { 113 | Storage = Storage; 114 | 115 | WrappedProperties = { 116 | Object = { "Active", "LayoutOrder", "NextSelectionDown", "NextSelectionLeft", "NextSelectionRight", "NextSelectionUp" }, 117 | }; 118 | 119 | Methods = { 120 | Enter = function(self) 121 | self.Dismissed = false 122 | local SnackbarFrame = self.Object 123 | SnackbarFrame.Parent = self.SCREEN 124 | SnackbarFrame.Position = self.ExitPosition 125 | 126 | if Storage.OpenSnackbar then 127 | Storage.OpenSnackbar:Dismiss() 128 | end 129 | 130 | Storage.OpenSnackbar = self 131 | 132 | local CurrentlyInputting = {} 133 | 134 | SnackbarFrame.InputBegan:Connect(function(InputObject) 135 | CurrentlyInputting[InputObject.UserInputType.Value] = true 136 | end) 137 | 138 | SnackbarFrame.InputEnded:Connect(function(InputObject) 139 | CurrentlyInputting[InputObject.UserInputType.Value] = false 140 | end) 141 | 142 | Tween(SnackbarFrame, "Position", self.EnterPosition, Deceleration, self.ENTER_TIME, false, function(Completed) 143 | if Completed == TweenCompleted and wait(self.DisplayTime) then 144 | while IsInputting(CurrentlyInputting) do 145 | repeat wait() until not IsInputting(CurrentlyInputting) 146 | wait(self.DisplayTime) 147 | end 148 | 149 | self:Dismiss() 150 | end 151 | end) 152 | end; 153 | 154 | Dismiss = function(self) 155 | if not self.Dismissed then 156 | self.Dismissed = true 157 | local SnackbarFrame = self.Object 158 | SnackbarFrame.ZIndex = SnackbarFrame.ZIndex - 1 159 | 160 | Tween(SnackbarFrame, "Position", self.ExitPosition, Acceleration, self.ENTER_TIME, true, function(Completed) 161 | if Completed == TweenCompleted then 162 | SnackbarFrame.Parent = nil 163 | if Storage.OpenSnackbar == self then 164 | Storage.OpenSnackbar = nil 165 | end 166 | end 167 | end) 168 | end 169 | end; 170 | }; 171 | 172 | Events = { 173 | "OnAction"; 174 | }; 175 | 176 | Internals = { 177 | "SnackbarText", "SnackbarAction", "RegisteredRippleInputs", "EnterPosition", "ExitPosition"; 178 | 179 | SHOULD_BLUR = false; 180 | 181 | ActionButtonWidth = 0; 182 | TextWidth = 0; 183 | ENTER_TIME = 0.275; 184 | 185 | AdjustSnackbarSize = function(self) 186 | local Width = self.ActionButtonWidth + self.TextWidth + 16*3 187 | self.Object.Size = UDim2.new(0, Width > SMALLEST_WIDTH and Width or SMALLEST_WIDTH, 0, HEIGHT) 188 | end; 189 | }; 190 | 191 | Properties = { 192 | SnackbarPosition = Typer.AssignSignature(2, Typer.EnumerationOfTypeSnackbarPosition, function(self, Position) 193 | local State = StatePosition[Position.Value] 194 | 195 | self.Object.AnchorPoint = State.AnchorPoint 196 | self.ExitPosition = State.ExitPosition 197 | self.EnterPosition = State.EnterPosition 198 | 199 | self:rawset("SnackbarPosition", Position) 200 | end); 201 | 202 | ActionText = Typer.AssignSignature(2, Typer.String, function(self, ActionText) 203 | if ActionText == "" then 204 | self.SnackbarAction.Parent = nil 205 | self.ActionButtonWidth = 0 206 | else 207 | self.SnackbarAction.Text = ActionText 208 | local Width = TextService:GetTextSize(ActionText, BUTTON_SIZE, BUTTON_FONT, LARGE_FRAME_SIZE).X + 16 209 | self.SnackbarAction.Size = UDim2.new(0, Width, 1, -12) 210 | self.ActionButtonWidth = Width 211 | self.SnackbarAction.Parent = self.Object 212 | self:rawset("ActionText", ActionText) 213 | end 214 | 215 | self:AdjustSnackbarSize() 216 | end); 217 | 218 | ActionColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, Color) 219 | if self.SnackbarAction.Parent ~= nil then 220 | self.SnackbarAction.PrimaryColor3 = Color 221 | self:rawset("ActionColor3", Color) 222 | end 223 | end); 224 | 225 | Text = Typer.AssignSignature(2, Typer.String, function(self, Text) 226 | -- Assign Text to SnackbarText.Text 227 | -- Update Size according to TextBounds, which shouldn't be a property of Snackbar 228 | 229 | self.TextWidth = TextService:GetTextSize(Text, TEXT_SIZE, FONT, LARGE_FRAME_SIZE).X 230 | self.SnackbarText.Text = Text 231 | self:AdjustSnackbarSize() 232 | self:rawset("Text", Text) 233 | end); 234 | 235 | DisplayTime = Typer.AssignSignature(2, Typer.Number, function(self, DisplayTime) 236 | self:rawset("DisplayTime", DisplayTime) 237 | end) 238 | }, 239 | 240 | Init = function(self, ...) 241 | self:rawset("Object", SnackbarImage:Clone()) 242 | self.SnackbarText = self.Object.SnackbarText 243 | self:rawset("DisplayTime", 5) 244 | 245 | local SnackbarAction = PseudoInstance.new("RippleButton") 246 | SnackbarAction.AnchorPoint = Vector2.new(1, 0.5) 247 | SnackbarAction.Name = "SnackbarAction" 248 | SnackbarAction.Position = UDim2.new(1, -8, 0.5, 0) 249 | SnackbarAction.ZIndex = 4 250 | SnackbarAction.Font = BUTTON_FONT 251 | SnackbarAction.PrimaryColor3 = Color.Purple[300] 252 | SnackbarAction.TextSize = BUTTON_SIZE 253 | SnackbarAction.Style = Enumeration.ButtonStyle.Flat.Value 254 | 255 | self.Janitor:Add(SnackbarAction.OnPressed:Connect(OnActionPressed, self), "Disconnect") 256 | 257 | self.SnackbarAction = SnackbarAction 258 | self.SnackbarPosition = Enumeration.SnackbarPosition.Center 259 | 260 | self.Janitor:Add(self.Object, "Destroy") 261 | self.Janitor:Add(self.SnackbarText, "Destroy") 262 | self.Janitor:Add(SnackbarAction, "Destroy") 263 | 264 | self:superinit(...) 265 | end; 266 | }, RoStrapPriorityUI) 267 | -------------------------------------------------------------------------------- /ChoiceDialog.lua: -------------------------------------------------------------------------------- 1 | -- 2-step Choice Dialog ReplicatedPseudoInstance 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/ChoiceDialog/ 3 | -- @author Validark 4 | 5 | -- Note: You should only be using 1 at once, for a given player 6 | 7 | local Players = game:GetService("Players") 8 | local Lighting = game:GetService("Lighting") 9 | local RunService = game:GetService("RunService") 10 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 11 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 12 | 13 | local Color = Resources:LoadLibrary("Color") 14 | local Debug = Resources:LoadLibrary("Debug") 15 | local Tween = Resources:LoadLibrary("Tween") 16 | local Typer = Resources:LoadLibrary("Typer") 17 | local Enumeration = Resources:LoadLibrary("Enumeration") 18 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 19 | local ReplicatedPseudoInstance = Resources:LoadLibrary("ReplicatedPseudoInstance") 20 | 21 | local Radio = Resources:LoadLibrary("Radio") 22 | local Shadow = Resources:LoadLibrary("Shadow") 23 | local RadioGroup = Resources:LoadLibrary("RadioGroup") 24 | local RippleButton = Resources:LoadLibrary("RippleButton") 25 | local RoStrapPriorityUI = Resources:LoadLibrary("RoStrapPriorityUI") 26 | 27 | local BUTTON_WIDTH_PADDING = 8 28 | 29 | local Left = Enum.TextXAlignment.Left.Value 30 | local SourceSansSemibold = Enum.Font.SourceSansSemibold.Value 31 | 32 | local Flat = Enumeration.ButtonStyle.Flat.Value 33 | local InBack = Enumeration.EasingFunction.InBack.Value 34 | local OutBack = Enumeration.EasingFunction.OutBack.Value 35 | 36 | local LocalPlayer, PlayerGui do 37 | if RunService:IsClient() then 38 | if RunService:IsServer() then 39 | PlayerGui = game:GetService("CoreGui") 40 | else 41 | repeat LocalPlayer = Players.LocalPlayer until LocalPlayer or not wait() 42 | repeat PlayerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui") until PlayerGui or not wait() 43 | end 44 | end 45 | end 46 | 47 | local Frame do 48 | Frame = Instance.new("Frame") 49 | Frame.BackgroundTransparency = 1 50 | Frame.Position = UDim2.new(0.5, 0, 0.5, 0) 51 | Frame.AnchorPoint = Vector2.new(0.5, 0.5) 52 | Frame.Size = UDim2.new(1, 0, 1, 0) 53 | Frame.Name = "ChoiceDialog" 54 | 55 | local UIScale = Instance.new("UIScale") 56 | UIScale.Scale = 0 57 | UIScale.Name = "UIScale" 58 | UIScale.Parent = Frame 59 | 60 | local Background = Instance.new("ImageLabel") 61 | Background.BackgroundTransparency = 1 62 | Background.ScaleType = Enum.ScaleType.Slice 63 | Background.SliceCenter = Rect.new(4, 4, 256 - 4, 256 - 4) 64 | Background.Image = "rbxassetid://1934624205" 65 | Background.Size = UDim2.new(0, 280, 0, 117) 66 | Background.Position = UDim2.new(0.5, 0, 0.5, 0) 67 | Background.AnchorPoint = Vector2.new(0.5, 0.5) 68 | Background.Name = "Background" 69 | Background.ZIndex = 2 70 | Background.Parent = Frame 71 | 72 | local Header = Instance.new("TextLabel") 73 | Header.Font = SourceSansSemibold 74 | Header.TextSize = 26 75 | Header.Size = UDim2.new(1, -24, 0, 64) 76 | Header.Position = UDim2.new(0, 24, 0, 1) 77 | Header.BackgroundTransparency = 1 78 | Header.TextXAlignment = Left 79 | Header.TextTransparency = 0.13 80 | Header.TextColor3 = Color.Black 81 | Header.Name = "Header" 82 | Header.ZIndex = 3 83 | Header.Parent = Background 84 | 85 | local Border = Instance.new("Frame") 86 | Border.BackgroundColor3 = Color.Black 87 | Border.BackgroundTransparency = 238 / 255 88 | Border.BorderSizePixel = 0 89 | Border.Position = UDim2.new(0, 0, 0, 64 - 2 + 1) 90 | Border.Size = UDim2.new(1, 0, 0, 1) 91 | Border.ZIndex = 3 92 | Border.Parent = Background 93 | 94 | local BottomBorder = Border:Clone() 95 | BottomBorder.Position = UDim2.new(0, 0, 1, -52 + 2 - 4 + 1) 96 | BottomBorder.Parent = Background 97 | 98 | local Shadow = PseudoInstance.new("Shadow") 99 | Shadow.Elevation = 8 100 | Shadow.Parent = Background 101 | end 102 | 103 | local function OnDismiss(self) 104 | if not self.Dismissed then 105 | self:Dismiss() 106 | self.OnConfirmed:Fire(LocalPlayer, false) 107 | end 108 | end 109 | 110 | local function OnConfirm(self) 111 | if not self.Dismissed then 112 | self:Dismiss() 113 | self.OnConfirmed:Fire(LocalPlayer, self.RadioGroup:GetSelection()) 114 | end 115 | end 116 | 117 | local function ConfirmEnable(ConfirmButton) 118 | ConfirmButton.Disabled = false 119 | end 120 | 121 | local function HideUIScale(self) 122 | self.UIScale.Parent = nil 123 | end 124 | 125 | local DialogsActive = 0 126 | 127 | local function SubDialogsActive() 128 | DialogsActive = DialogsActive - 1 129 | end 130 | 131 | local function AdjustButtonSize(Button) 132 | Button.Size = UDim2.new(0, Button.TextBounds.X + BUTTON_WIDTH_PADDING * 2, 0, 36) 133 | end 134 | 135 | return PseudoInstance:Register("ChoiceDialog", { 136 | Storage = {}; 137 | 138 | Internals = {"ConfirmButton", "DismissButton", "RadioGroup", "AssociatedRadioContainers", "Header", "UIScale", "Background"; 139 | SHOULD_BLUR = true; 140 | }; 141 | 142 | Events = {"OnConfirmed"}; 143 | 144 | Methods = { 145 | Enter = function(self) 146 | self.UIScale.Parent = self.Object 147 | self.Object.Parent = self.SCREEN 148 | AdjustButtonSize(self.DismissButton) 149 | AdjustButtonSize(self.ConfirmButton) 150 | 151 | Tween(self.UIScale, "Scale", 1, OutBack, self.ENTER_TIME, true, HideUIScale, self) 152 | end; 153 | 154 | Dismiss = function(self) 155 | -- Destroys Dialog when done 156 | if not self.Dismissed then 157 | self.Dismissed = true 158 | Tween(self.UIScale, "Scale", 0, InBack, self.DISMISS_TIME, true, self.Janitor) 159 | self.UIScale.Parent = self.Object 160 | self:Unblur() 161 | end 162 | end; 163 | }; 164 | 165 | Properties = { 166 | PrimaryColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, PrimaryColor3) 167 | self.ConfirmButton.PrimaryColor3 = PrimaryColor3 168 | self.DismissButton.PrimaryColor3 = PrimaryColor3 169 | 170 | for Item, ItemContainer in next, self.AssociatedRadioContainers do 171 | Item.PrimaryColor3 = PrimaryColor3 172 | ItemContainer.PrimaryColor3 = PrimaryColor3 173 | end 174 | 175 | self:rawset("PrimaryColor3", PrimaryColor3) 176 | end); 177 | 178 | Options = Typer.AssignSignature(2, Typer.ArrayOfStrings, function(self, Options) 179 | local NumOptions = #Options 180 | self.Background.Size = UDim2.new(0, 280, 0, 117 + 48 * NumOptions) 181 | 182 | for Item, ItemContainer in next, self.AssociatedRadioContainers do 183 | Item:Destroy() 184 | ItemContainer:Destroy() -- ItemDescriptions are destroyed here 185 | self.AssociatedRadioContainers[Item] = nil 186 | end 187 | 188 | if self.RadioGroup then 189 | self.Janitor[self.RadioGroup] = self.RadioGroup:Destroy() 190 | end 191 | 192 | self.RadioGroup = PseudoInstance.new("RadioGroup") 193 | self.Janitor:Add(self.RadioGroup.SelectionChanged:Connect(ConfirmEnable, self.ConfirmButton), "Disconnect") 194 | self.Janitor:Add(self.RadioGroup, "Destroy") 195 | 196 | for i = 1, NumOptions do 197 | local ChoiceName = Options[i] 198 | 199 | local ItemContainer = PseudoInstance.new("RippleButton") 200 | ItemContainer.Position = UDim2.new(0, 0, 0, 64 + 48 * (i - 1)) 201 | ItemContainer.Size = UDim2.new(1, 0, 0, 48) 202 | ItemContainer.BorderRadius = 0 203 | ItemContainer.ZIndex = 5 204 | ItemContainer.Style = Flat 205 | ItemContainer.Parent = self.Background 206 | 207 | local Item = PseudoInstance.new("Radio") 208 | Item.AnchorPoint = Vector2.new(0.5, 0.5) 209 | Item.Position = UDim2.new(0, 36, 0.5, 0) 210 | Item.ZIndex = 8 211 | Item.Parent = ItemContainer.Object 212 | 213 | ItemContainer.PrimaryColor3 = self.PrimaryColor3 214 | Item.PrimaryColor3 = self.PrimaryColor3 215 | 216 | self.AssociatedRadioContainers[Item] = ItemContainer 217 | 218 | self.RadioGroup:Add(Item, ChoiceName) 219 | ItemContainer.OnPressed:Connect(Item.SetChecked, Item) 220 | 221 | local ItemDescription = Instance.new("TextLabel") 222 | ItemDescription.BackgroundTransparency = 1 223 | ItemDescription.Position = UDim2.new(0, 48 + 32, 0.5, 0) 224 | ItemDescription.TextXAlignment = Left 225 | ItemDescription.Font = SourceSansSemibold 226 | ItemDescription.TextSize = 20 227 | ItemDescription.Text = ChoiceName 228 | ItemDescription.TextTransparency = 0.13 229 | ItemDescription.ZIndex = 8 230 | ItemDescription.Parent = ItemContainer.Object 231 | end 232 | 233 | self:rawset("Options", Options) 234 | end); 235 | 236 | HeaderText = Typer.AssignSignature(2, Typer.String, function(self, Text) 237 | self.Header.Text = Text 238 | self:rawset("HeaderText", self.Header.Text) 239 | end); 240 | 241 | DismissText = Typer.AssignSignature(2, Typer.String, function(self, Text) 242 | local DismissButton = self.DismissButton 243 | DismissButton.Text = Text 244 | self:rawset("DismissText", DismissButton.Text) 245 | end); 246 | 247 | ConfirmText = Typer.AssignSignature(2, Typer.String, function(self, Text) 248 | local ConfirmButton = self.ConfirmButton 249 | ConfirmButton.Text = Text 250 | self:rawset("ConfirmText", ConfirmButton.Text) 251 | end); 252 | }; 253 | 254 | Init = function(self, ...) 255 | self:rawset("Object", Frame:Clone()) 256 | self.UIScale = self.Object.UIScale 257 | self.Background = self.Object.Background 258 | self.Header = self.Background.Header 259 | self.AssociatedRadioContainers = {} 260 | 261 | local ConfirmButton = PseudoInstance.new("RippleButton") 262 | ConfirmButton.AnchorPoint = Vector2.new(1, 1) 263 | ConfirmButton.Position = UDim2.new(1, -8, 1, -8) 264 | ConfirmButton.BorderRadius = 4 265 | ConfirmButton.ZIndex = 10 266 | ConfirmButton.TextSize = 16 267 | ConfirmButton.TextTransparency = 0.13 268 | ConfirmButton.Style = Flat 269 | ConfirmButton.Parent = self.Background 270 | 271 | local DismissButton = ConfirmButton:Clone() 272 | DismissButton.Position = UDim2.new(0, -8, 1, 0) 273 | DismissButton.Parent = ConfirmButton.Object 274 | 275 | self.Janitor:Add(DismissButton:GetPropertyChangedSignal("TextBounds"):Connect(AdjustButtonSize, DismissButton), "Disconnect") 276 | self.Janitor:Add(ConfirmButton:GetPropertyChangedSignal("TextBounds"):Connect(AdjustButtonSize, ConfirmButton), "Disconnect") 277 | 278 | ConfirmButton.Disabled = true 279 | 280 | self.ConfirmButton = ConfirmButton 281 | self.DismissButton = DismissButton 282 | self.Janitor:Add(ConfirmButton.OnPressed:Connect(OnConfirm, self), "Disconnect") 283 | self.Janitor:Add(DismissButton.OnPressed:Connect(OnDismiss, self), "Disconnect") 284 | 285 | self.Janitor:Add(self.Object, "Destroy") 286 | self.Janitor:Add(self.UIScale, "Destroy") 287 | self.Janitor:Add(SubDialogsActive, true) 288 | 289 | self.PrimaryColor3 = Color3.fromRGB(98, 0, 238) 290 | self:superinit(...) 291 | end; 292 | }, RoStrapPriorityUI) 293 | -------------------------------------------------------------------------------- /Color.lua: -------------------------------------------------------------------------------- 1 | -- Color utilities with Material Design's 2014 Color Palette 2 | 3 | local Resources = require(game:GetService("ReplicatedStorage"):WaitForChild("Resources")) 4 | local Table = Resources:LoadLibrary("Table") 5 | 6 | local rgb = Color3.fromRGB 7 | 8 | local Color = { 9 | Red = { 10 | [50] = rgb(255, 235, 238); 11 | [100] = rgb(255, 205, 210); 12 | [200] = rgb(239, 154, 154); 13 | [300] = rgb(229, 115, 115); 14 | [400] = rgb(239, 83, 80); 15 | [500] = rgb(244, 67, 54); 16 | [600] = rgb(229, 57, 53); 17 | [700] = rgb(211, 47, 47); 18 | [800] = rgb(198, 40, 40); 19 | [900] = rgb(183, 28, 28); 20 | 21 | Accent = { 22 | [100] = rgb(255, 138, 128); 23 | [200] = rgb(255, 82, 82); 24 | [400] = rgb(255, 23, 68); 25 | [700] = rgb(213, 0, 0); 26 | }; 27 | }; 28 | 29 | Pink = { 30 | [50] = rgb(252, 228, 236); 31 | [100] = rgb(248, 187, 208); 32 | [200] = rgb(244, 143, 177); 33 | [300] = rgb(240, 98, 146); 34 | [400] = rgb(236, 64, 122); 35 | [500] = rgb(233, 30, 99); 36 | [600] = rgb(216, 27, 96); 37 | [700] = rgb(194, 24, 91); 38 | [800] = rgb(173, 20, 87); 39 | [900] = rgb(136, 14, 79); 40 | 41 | Accent = { 42 | [100] = rgb(255, 128, 171); 43 | [200] = rgb(255, 64, 129); 44 | [400] = rgb(245, 0, 87); 45 | [700] = rgb(197, 17, 98); 46 | }; 47 | }; 48 | 49 | Purple = { 50 | [50] = rgb(243, 229, 245); 51 | [100] = rgb(225, 190, 231); 52 | [200] = rgb(206, 147, 216); 53 | [300] = rgb(186, 104, 200); 54 | [400] = rgb(171, 71, 188); 55 | [500] = rgb(156, 39, 176); 56 | [600] = rgb(142, 36, 170); 57 | [700] = rgb(123, 31, 162); 58 | [800] = rgb(106, 27, 154); 59 | [900] = rgb(74, 20, 140); 60 | 61 | Accent = { 62 | [100] = rgb(234, 128, 252); 63 | [200] = rgb(224, 64, 251); 64 | [400] = rgb(213, 0, 249); 65 | [700] = rgb(170, 0, 255); 66 | }; 67 | }; 68 | 69 | DeepPurple = { 70 | [50] = rgb(237, 231, 246); 71 | [100] = rgb(209, 196, 233); 72 | [200] = rgb(179, 157, 219); 73 | [300] = rgb(149, 117, 205); 74 | [400] = rgb(126, 87, 194); 75 | [500] = rgb(103, 58, 183); 76 | [600] = rgb(94, 53, 177); 77 | [700] = rgb(81, 45, 168); 78 | [800] = rgb(69, 39, 160); 79 | [900] = rgb(49, 27, 146); 80 | 81 | Accent = { 82 | [100] = rgb(179, 136, 255); 83 | [200] = rgb(124, 77, 255); 84 | [400] = rgb(101, 31, 255); 85 | [700] = rgb(98, 0, 234); 86 | }; 87 | }; 88 | 89 | Indigo = { 90 | [50] = rgb(232, 234, 246); 91 | [100] = rgb(197, 202, 233); 92 | [200] = rgb(159, 168, 218); 93 | [300] = rgb(121, 134, 203); 94 | [400] = rgb(92, 107, 192); 95 | [500] = rgb(63, 81, 181); 96 | [600] = rgb(57, 73, 171); 97 | [700] = rgb(48, 63, 159); 98 | [800] = rgb(40, 53, 147); 99 | [900] = rgb(26, 35, 126); 100 | 101 | Accent = { 102 | [100] = rgb(140, 158, 255); 103 | [200] = rgb(83, 109, 254); 104 | [400] = rgb(61, 90, 254); 105 | [700] = rgb(48, 79, 254); 106 | }; 107 | }; 108 | 109 | Blue = { 110 | [50] = rgb(227, 242, 253); 111 | [100] = rgb(187, 222, 251); 112 | [200] = rgb(144, 202, 249); 113 | [300] = rgb(100, 181, 246); 114 | [400] = rgb(66, 165, 245); 115 | [500] = rgb(33, 150, 243); 116 | [600] = rgb(30, 136, 229); 117 | [700] = rgb(25, 118, 210); 118 | [800] = rgb(21, 101, 192); 119 | [900] = rgb(13, 71, 161); 120 | 121 | Accent = { 122 | [100] = rgb(130, 177, 255); 123 | [200] = rgb(68, 138, 255); 124 | [400] = rgb(41, 121, 255); 125 | [700] = rgb(41, 98, 255); 126 | }; 127 | }; 128 | 129 | LightBlue = { 130 | [50] = rgb(225, 245, 254); 131 | [100] = rgb(179, 229, 252); 132 | [200] = rgb(129, 212, 250); 133 | [300] = rgb(79, 195, 247); 134 | [400] = rgb(41, 182, 246); 135 | [500] = rgb(3, 169, 244); 136 | [600] = rgb(3, 155, 229); 137 | [700] = rgb(2, 136, 209); 138 | [800] = rgb(2, 119, 189); 139 | [900] = rgb(1, 87, 155); 140 | 141 | Accent = { 142 | [100] = rgb(128, 216, 255); 143 | [200] = rgb(64, 196, 255); 144 | [400] = rgb(0, 176, 255); 145 | [700] = rgb(0, 145, 234); 146 | }; 147 | }; 148 | 149 | Cyan = { 150 | [50] = rgb(224, 247, 250); 151 | [100] = rgb(178, 235, 242); 152 | [200] = rgb(128, 222, 234); 153 | [300] = rgb(77, 208, 225); 154 | [400] = rgb(38, 198, 218); 155 | [500] = rgb(0, 188, 212); 156 | [600] = rgb(0, 172, 193); 157 | [700] = rgb(0, 151, 167); 158 | [800] = rgb(0, 131, 143); 159 | [900] = rgb(0, 96, 100); 160 | 161 | Accent = { 162 | [100] = rgb(132, 255, 255); 163 | [200] = rgb(24, 255, 255); 164 | [400] = rgb(0, 229, 255); 165 | [700] = rgb(0, 184, 212); 166 | }; 167 | }; 168 | 169 | Teal = { 170 | [50] = rgb(224, 242, 241); 171 | [100] = rgb(178, 223, 219); 172 | [200] = rgb(128, 203, 196); 173 | [300] = rgb(77, 182, 172); 174 | [400] = rgb(38, 166, 154); 175 | [500] = rgb(0, 150, 136); 176 | [600] = rgb(0, 137, 123); 177 | [700] = rgb(0, 121, 107); 178 | [800] = rgb(0, 105, 92); 179 | [900] = rgb(0, 77, 64); 180 | 181 | Accent = { 182 | [100] = rgb(167, 255, 235); 183 | [200] = rgb(100, 255, 218); 184 | [400] = rgb(29, 233, 182); 185 | [700] = rgb(0, 191, 165); 186 | }; 187 | }; 188 | 189 | Green = { 190 | [50] = rgb(232, 245, 233); 191 | [100] = rgb(200, 230, 201); 192 | [200] = rgb(165, 214, 167); 193 | [300] = rgb(129, 199, 132); 194 | [400] = rgb(102, 187, 106); 195 | [500] = rgb(76, 175, 80); 196 | [600] = rgb(67, 160, 71); 197 | [700] = rgb(56, 142, 60); 198 | [800] = rgb(46, 125, 50); 199 | [900] = rgb(27, 94, 32); 200 | 201 | Accent = { 202 | [100] = rgb(185, 246, 202); 203 | [200] = rgb(105, 240, 174); 204 | [400] = rgb(0, 230, 118); 205 | [700] = rgb(0, 200, 83); 206 | }; 207 | }; 208 | 209 | LightGreen = { 210 | [50] = rgb(241, 248, 233); 211 | [100] = rgb(220, 237, 200); 212 | [200] = rgb(197, 225, 165); 213 | [300] = rgb(174, 213, 129); 214 | [400] = rgb(156, 204, 101); 215 | [500] = rgb(139, 195, 74); 216 | [600] = rgb(124, 179, 66); 217 | [700] = rgb(104, 159, 56); 218 | [800] = rgb(85, 139, 47); 219 | [900] = rgb(51, 105, 30); 220 | 221 | Accent = { 222 | [100] = rgb(204, 255, 144); 223 | [200] = rgb(178, 255, 89); 224 | [400] = rgb(118, 255, 3); 225 | [700] = rgb(100, 221, 23); 226 | }; 227 | }; 228 | 229 | Lime = { 230 | [50] = rgb(249, 251, 231); 231 | [100] = rgb(240, 244, 195); 232 | [200] = rgb(230, 238, 156); 233 | [300] = rgb(220, 231, 117); 234 | [400] = rgb(212, 225, 87); 235 | [500] = rgb(205, 220, 57); 236 | [600] = rgb(192, 202, 51); 237 | [700] = rgb(175, 180, 43); 238 | [800] = rgb(158, 157, 36); 239 | [900] = rgb(130, 119, 23); 240 | 241 | Accent = { 242 | [100] = rgb(244, 255, 129); 243 | [200] = rgb(238, 255, 65); 244 | [400] = rgb(198, 255, 0); 245 | [700] = rgb(174, 234, 0); 246 | }; 247 | }; 248 | 249 | Yellow = { 250 | [50] = rgb(255, 253, 231); 251 | [100] = rgb(255, 249, 196); 252 | [200] = rgb(255, 245, 157); 253 | [300] = rgb(255, 241, 118); 254 | [400] = rgb(255, 238, 88); 255 | [500] = rgb(255, 235, 59); 256 | [600] = rgb(253, 216, 53); 257 | [700] = rgb(251, 192, 45); 258 | [800] = rgb(249, 168, 37); 259 | [900] = rgb(245, 127, 23); 260 | 261 | Accent = { 262 | [100] = rgb(255, 255, 141); 263 | [200] = rgb(255, 255, 0); 264 | [400] = rgb(255, 234, 0); 265 | [700] = rgb(255, 214, 0); 266 | }; 267 | }; 268 | 269 | Amber = { 270 | [50] = rgb(255, 248, 225); 271 | [100] = rgb(255, 236, 179); 272 | [200] = rgb(255, 224, 130); 273 | [300] = rgb(255, 213, 79); 274 | [400] = rgb(255, 202, 40); 275 | [500] = rgb(255, 193, 7); 276 | [600] = rgb(255, 179, 0); 277 | [700] = rgb(255, 160, 0); 278 | [800] = rgb(255, 143, 0); 279 | [900] = rgb(255, 111, 0); 280 | 281 | Accent = { 282 | [100] = rgb(255, 229, 127); 283 | [200] = rgb(255, 215, 64); 284 | [400] = rgb(255, 196, 0); 285 | [700] = rgb(255, 171, 0); 286 | }; 287 | }; 288 | 289 | Orange = { 290 | [50] = rgb(255, 243, 224); 291 | [100] = rgb(255, 224, 178); 292 | [200] = rgb(255, 204, 128); 293 | [300] = rgb(255, 183, 77); 294 | [400] = rgb(255, 167, 38); 295 | [500] = rgb(255, 152, 0); 296 | [600] = rgb(251, 140, 0); 297 | [700] = rgb(245, 124, 0); 298 | [800] = rgb(239, 108, 0); 299 | [900] = rgb(230, 81, 0); 300 | 301 | Accent = { 302 | [100] = rgb(255, 209, 128); 303 | [200] = rgb(255, 171, 64); 304 | [400] = rgb(255, 145, 0); 305 | [700] = rgb(255, 109, 0); 306 | }; 307 | }; 308 | 309 | DeepOrange = { 310 | [50] = rgb(251, 233, 231); 311 | [100] = rgb(255, 204, 188); 312 | [200] = rgb(255, 171, 145); 313 | [300] = rgb(255, 138, 101); 314 | [400] = rgb(255, 112, 67); 315 | [500] = rgb(255, 87, 34); 316 | [600] = rgb(244, 81, 30); 317 | [700] = rgb(230, 74, 25); 318 | [800] = rgb(216, 67, 21); 319 | [900] = rgb(191, 54, 12); 320 | 321 | Accent = { 322 | [100] = rgb(255, 158, 128); 323 | [200] = rgb(255, 110, 64); 324 | [400] = rgb(255, 61, 0); 325 | [700] = rgb(221, 44, 0); 326 | }; 327 | }; 328 | 329 | Brown = { 330 | [50] = rgb(239, 235, 233); 331 | [100] = rgb(215, 204, 200); 332 | [200] = rgb(188, 170, 164); 333 | [300] = rgb(161, 136, 127); 334 | [400] = rgb(141, 110, 99); 335 | [500] = rgb(121, 85, 72); 336 | [600] = rgb(109, 76, 65); 337 | [700] = rgb(93, 64, 55); 338 | [800] = rgb(78, 52, 46); 339 | [900] = rgb(62, 39, 35); 340 | }; 341 | 342 | Grey = { 343 | [50] = rgb(250, 250, 250); 344 | [100] = rgb(245, 245, 245); 345 | [200] = rgb(238, 238, 238); 346 | [300] = rgb(224, 224, 224); 347 | [400] = rgb(189, 189, 189); 348 | [500] = rgb(158, 158, 158); 349 | [600] = rgb(117, 117, 117); 350 | [700] = rgb(97, 97, 97); 351 | [800] = rgb(66, 66, 66); 352 | [900] = rgb(33, 33, 33); 353 | }; 354 | 355 | BlueGrey = { 356 | [50] = rgb(236, 239, 241); 357 | [100] = rgb(207, 216, 220); 358 | [200] = rgb(176, 190, 197); 359 | [300] = rgb(144, 164, 174); 360 | [400] = rgb(120, 144, 156); 361 | [500] = rgb(96, 125, 139); 362 | [600] = rgb(84, 110, 122); 363 | [700] = rgb(69, 90, 100); 364 | [800] = rgb(55, 71, 79); 365 | [900] = rgb(38, 50, 56); 366 | }; 367 | 368 | Black = rgb(0, 0, 0); 369 | White = rgb(255, 255, 255); 370 | } 371 | 372 | function Color.toRGBString(c, a) 373 | local r = c.r * 255 + 0.5 374 | local g = c.g * 255 + 0.5 375 | local b = c.b * 255 + 0.5 376 | 377 | if a then 378 | return ("rgba(%u, %u, %u, %u)"):format(r, g, b, a * 255 + 0.5) 379 | else 380 | return ("rgb(%u, %u, %u)"):format(r, g, b) 381 | end 382 | end 383 | Color.ToRGBString = Color.toRGBString 384 | 385 | function Color.toHexString(c, a) 386 | local r = c.r * 255 + 0.5 387 | local g = c.g * 255 + 0.5 388 | local b = c.b * 255 + 0.5 389 | 390 | if a then 391 | return ("#%X%X%X%X"):format(r, g, b, a * 255 + 0.5) 392 | else 393 | return ("#%X%X%X"):format(r, g, b) 394 | end 395 | end 396 | Color.ToHexString = Color.toHexString 397 | 398 | local Hash = ("#"):byte() 399 | 400 | function Color.fromHex(Hex) 401 | -- Converts a 3-digit or 6-digit hex color to RGB 402 | -- Takes in a string of the form: "#FFFFFF" or "#FFF" or a 6-digit hexadecimal number 403 | 404 | local Type = type(Hex) 405 | local Digits 406 | 407 | if Type == "string" then 408 | if Hex:byte() == Hash then Hex = Hex:sub(2) end -- Remove # from beginning 409 | 410 | Digits = #Hex 411 | 412 | if Digits == 8 then -- We got some alpha :D 413 | return Color.fromHex(Hex:sub(1, -3)), tonumber(Hex, 16) % 0x000100 / 255 414 | end 415 | 416 | Hex = tonumber(Hex, 16) -- Leverage Lua's base converter :D 417 | elseif Type == "number" then 418 | Digits = 6 -- Assume numbers are 6 digit hex numbers 419 | end 420 | 421 | if Digits == 6 then 422 | -- Isolate R as first digits 5 and 6, G as 3 and 4, B as 1 and 2 423 | 424 | local R = (Hex - Hex % 0x010000) / 0x010000 425 | Hex = Hex - R * 0x010000 426 | local G = (Hex - Hex % 0x000100) / 0x000100 427 | 428 | return rgb(R, G, Hex - G * 0x000100) 429 | elseif Digits == 3 then 430 | -- 3-digit to 6-digit conversion: 123 -> 112233 431 | -- Thus, we isolate each digits' value and multiply by 17 432 | 433 | local R = (Hex - Hex % 0x100) / 0x100 434 | Hex = Hex - R * 0x100 435 | local G = (Hex - Hex % 0x10) / 0x10 436 | 437 | return rgb(R * 0x11, G * 0x11, (Hex - G * 0x10) * 0x11) 438 | end 439 | end 440 | Color.FromHex = Color.fromHex 441 | 442 | local floor = math.floor 443 | 444 | function Color.toHex(Color3) 445 | return floor(Color3.r * 0xFF + 0.5) * 0x010000 + floor(Color3.g * 0xFF + 0.5) * 0x000100 + floor(Color3.b * 0xFF + 0.5) * 0x000001 446 | end 447 | Color.ToHex = Color.toHex 448 | 449 | return Table.Lock(Color) 450 | -------------------------------------------------------------------------------- /Rippler.lua: -------------------------------------------------------------------------------- 1 | -- PseudoInstance to spawn Material Design Ripples inside its Parent (with rounded edge support!) 2 | -- @author Validark 3 | 4 | local ContentProvider = game:GetService("ContentProvider") 5 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 6 | 7 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 8 | local Debug = Resources:LoadLibrary("Debug") 9 | local Tween = Resources:LoadLibrary("Tween") 10 | local Color = Resources:LoadLibrary("Color") 11 | local Typer = Resources:LoadLibrary("Typer") 12 | local Janitor = Resources:LoadLibrary("Janitor") 13 | local Enumeration = Resources:LoadLibrary("Enumeration") 14 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 15 | 16 | local RIPPLE_START_DIAMETER = 0 17 | local RIPPLE_OVERBITE = 1.05 18 | 19 | local RippleContainer = Instance.new("Frame") -- Make sure ZIndex is higher than parent by 1 20 | RippleContainer.AnchorPoint = Vector2.new(0.5, 0.5) 21 | RippleContainer.BackgroundTransparency = 1 22 | RippleContainer.BorderSizePixel = 0 23 | RippleContainer.ClipsDescendants = true 24 | RippleContainer.Name = "RippleContainer" 25 | RippleContainer.Size = UDim2.new(1, 0, 1, 0) 26 | RippleContainer.Position = UDim2.new(0.5, 0, 0.5, 0) 27 | 28 | local RippleStartSize = UDim2.new(0, RIPPLE_START_DIAMETER, 0, RIPPLE_START_DIAMETER) 29 | 30 | local Circle = Instance.new("ImageLabel") 31 | Circle.AnchorPoint = Vector2.new(0.5, 0.5) 32 | Circle.BackgroundTransparency = 1 33 | Circle.Size = RippleStartSize 34 | Circle.Image = "rbxassetid://517259585" 35 | Circle.Name = "Ripple" 36 | 37 | spawn(function() 38 | ContentProvider:PreloadAsync{Circle.Image} 39 | end) 40 | 41 | local Deceleration = Enumeration.EasingFunction.Deceleration.Value 42 | 43 | Enumeration.RipplerStyle = {"Full", "Icon", "Round"} 44 | 45 | local CornerData = { 46 | [2] = { 47 | 0.380, 0.918, 48 | 0.918, 1.000, 49 | }; 50 | 51 | [4] = { 52 | 0.000, 0.200, 0.690, 0.965, 53 | 0.200, 0.965, 1.000, 1.000, 54 | 0.690, 1.000, 1.000, 1.000, 55 | 0.965, 1.000, 1.000, 1.000, 56 | }; 57 | 58 | [8] = { 59 | 0.000, 0.000, 0.000, 0.000, 0.224, 0.596, 0.851, 0.984, 60 | 0.000, 0.000, 0.000, 0.596, 1.000, 1.000, 1.000, 1.000, 61 | 0.000, 0.000, 0.722, 1.000, 1.000, 1.000, 1.000, 1.000, 62 | 0.000, 0.596, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 63 | 0.224, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 64 | 0.596, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 65 | 0.851, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 66 | 0.984, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 67 | }; 68 | } 69 | 70 | do 71 | local t = { 72 | Radius0 = 0; 73 | } 74 | 75 | for BorderRadius, Data in next, CornerData do 76 | t["Radius" .. BorderRadius] = BorderRadius 77 | 78 | for i = 1, #Data do 79 | Data[i] = 1 - Data[i] -- Opacity -> Transparency 80 | end 81 | end 82 | 83 | Enumeration.BorderRadius = t 84 | end 85 | 86 | local function MakeContainer(GlobalContainer, Size, Position, ImageTransparency) 87 | local Container = Instance.new("ImageLabel") 88 | 89 | if ImageTransparency ~= nil and ImageTransparency ~= 0 then 90 | Container.ImageTransparency = ImageTransparency 91 | end 92 | 93 | Container.BackgroundTransparency = 1 94 | Container.ClipsDescendants = true 95 | Container.Position = Position 96 | Container.Size = Size 97 | Container.Parent = GlobalContainer 98 | return Container 99 | end 100 | 101 | local function MakeOuterBorders(RippleFrames, Container, X, Y) -- TODO: Optimize first two frames which can be eliminated 102 | local NumRippleFrames = #RippleFrames 103 | RippleFrames[NumRippleFrames + 1] = MakeContainer(Container, UDim2.new(1, -2*X, 0, 1), UDim2.new(0, X, 0, Y)) 104 | RippleFrames[NumRippleFrames + 2] = MakeContainer(Container, UDim2.new(1, -2*X, 0, 1), UDim2.new(0, X, 1, -Y - 1)) 105 | RippleFrames[NumRippleFrames + 3] = MakeContainer(Container, UDim2.new(0, 1, 1, -2*X), UDim2.new(0, Y, 0, X)) 106 | RippleFrames[NumRippleFrames + 4] = MakeContainer(Container, UDim2.new(0, 1, 1, -2*X), UDim2.new(1, -Y - 1, 0, X)) 107 | end 108 | 109 | local PixelSize = UDim2.new(0, 1, 0, 1) 110 | 111 | local function DestroyRoundRipple(self) 112 | for i = 1, #self do 113 | local Object = self[i] 114 | self[i] = nil 115 | Object:Destroy() 116 | end 117 | end; 118 | 119 | local RoundRippleMetatable = { 120 | __index = function(self, i) 121 | if i == "Size" then 122 | return RippleStartSize 123 | elseif i == "ImageTransparency" then 124 | return self.Transparency 125 | elseif i == "Destroy" then 126 | return DestroyRoundRipple 127 | end 128 | end; 129 | 130 | __newindex = function(self, i, v) 131 | if i == "Size" then 132 | for a = 1, #self do 133 | self[a].Size = v 134 | end 135 | elseif i == "ImageTransparency" then 136 | for a = 1, #self do 137 | local RippleFrame = self[a] 138 | local Parent = RippleFrame.Parent 139 | if Parent then 140 | RippleFrame.ImageTransparency = (1 - v) * Parent.ImageTransparency + v 141 | end 142 | end 143 | end 144 | end; 145 | } 146 | 147 | return PseudoInstance:Register("Rippler", { 148 | Internals = { 149 | "CurrentRipple", "RippleFrames"; 150 | 151 | SetCurrentRipple = function(self, Ripple) 152 | if self.CurrentRipple then 153 | Tween(self.CurrentRipple, "ImageTransparency", 1, Deceleration, self.RippleFadeDuration, false, true) 154 | end 155 | 156 | self.CurrentRipple = Ripple 157 | end 158 | }; 159 | 160 | Events = {}; 161 | 162 | Properties = { 163 | Style = Typer.AssignSignature(2, Typer.EnumerationOfTypeRipplerStyle, function(self, Style) 164 | self:rawset("Style", Style) 165 | end); 166 | 167 | BorderRadius = Typer.AssignSignature(2, Typer.EnumerationOfTypeBorderRadius, function(self, Value) 168 | self:rawset("BorderRadius", Value) 169 | 170 | local BorderRadius = Value.Value 171 | local RippleFrames = self.RippleFrames 172 | 173 | if RippleFrames then 174 | DestroyRoundRipple(RippleFrames) 175 | end 176 | 177 | if BorderRadius == 0 then 178 | self.Style = Enumeration.RipplerStyle.Full 179 | else 180 | self.Style = Enumeration.RipplerStyle.Round 181 | local Data = CornerData[BorderRadius] 182 | 183 | if not RippleFrames then 184 | RippleFrames = {} 185 | self.RippleFrames = RippleFrames 186 | end 187 | 188 | local MiddleSquarePoint 189 | local Container = self.Container 190 | 191 | for j = 0, BorderRadius - 1 do 192 | if Data[BorderRadius * j + (j + 1)] == 0 then 193 | MiddleSquarePoint = j 194 | break 195 | end 196 | end 197 | 198 | MakeOuterBorders(RippleFrames, Container, BorderRadius, 0) 199 | 200 | -- Make large center frame 201 | RippleFrames[#RippleFrames + 1] = MakeContainer(Container, UDim2.new(1, -2 * MiddleSquarePoint, 1, -2 * MiddleSquarePoint), UDim2.new(0, MiddleSquarePoint, 0, MiddleSquarePoint)) 202 | 203 | do -- Make other bars to fill 204 | local X = MiddleSquarePoint 205 | local Y = MiddleSquarePoint - 1 206 | 207 | while Data[BorderRadius * Y + (X + 1)] == 0 do 208 | MakeOuterBorders(RippleFrames, Container, X, Y) 209 | X = X + 1 210 | Y = Y - 1 211 | end 212 | end 213 | 214 | do 215 | local a = 0 216 | local amax = BorderRadius * BorderRadius 217 | local NumRippleFrames = #RippleFrames 218 | while a < amax do 219 | local PixelTransparency = Data[a + 1] 220 | 221 | if PixelTransparency ~= 1 then 222 | if PixelTransparency ~= 0 then 223 | local X = a % BorderRadius 224 | local Y = (a - X) / BorderRadius 225 | local V = -1 - X 226 | local W = -1 - Y 227 | 228 | RippleFrames[NumRippleFrames + 1] = MakeContainer(Container, PixelSize, UDim2.new(0, X, 0, Y), PixelTransparency) 229 | RippleFrames[NumRippleFrames + 2] = MakeContainer(Container, PixelSize, UDim2.new(0, X, 1, W), PixelTransparency) 230 | RippleFrames[NumRippleFrames + 3] = MakeContainer(Container, PixelSize, UDim2.new(1, V, 0, Y), PixelTransparency) 231 | RippleFrames[NumRippleFrames + 4] = MakeContainer(Container, PixelSize, UDim2.new(1, V, 1, W), PixelTransparency) 232 | NumRippleFrames = NumRippleFrames + 4 233 | end 234 | end 235 | 236 | a = a + 1 237 | end 238 | end 239 | end 240 | end); 241 | 242 | RippleFadeDuration = Typer.Number; 243 | MaxRippleDiameter = Typer.Number; 244 | RippleExpandDuration = Typer.Number; 245 | 246 | RippleColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, RippleColor3) 247 | if self.CurrentRipple then 248 | self.CurrentRipple.ImageColor3 = RippleColor3 249 | end 250 | 251 | self:rawset("RippleColor3", RippleColor3) 252 | end); 253 | 254 | RippleTransparency = Typer.AssignSignature(2, Typer.Number, function(self, RippleTransparency) 255 | if self.CurrentRipple then 256 | self.CurrentRipple.ImageTransparency = RippleTransparency 257 | end 258 | 259 | self:rawset("RippleTransparency", RippleTransparency) 260 | end); 261 | 262 | Container = Typer.AssignSignature(2, Typer.InstanceWhichIsAGuiObject, function(self, Container) 263 | if self.BorderRadius.Value ~= 0 then Debug.Error("Can only set container when BorderRadius is 0") end 264 | 265 | self.Janitor:LinkToInstance(Container) 266 | self.Janitor:Add(Container, "Destroy", "Container") 267 | 268 | self:rawset("Container", Container) 269 | end); 270 | 271 | Parent = function(self, Parent) -- Manually check this one 272 | if Parent == nil then 273 | self.Janitor:Remove("ZIndexChanged") 274 | self.Container.Parent = nil 275 | else 276 | local ParentType = typeof(Parent) 277 | local IsGuiObject = ParentType == "Instance" and Parent:IsA("GuiObject") or ParentType == "userdata" and Parent.ClassName == "RoundedFrame" 278 | 279 | if IsGuiObject and self.Container ~= Parent then 280 | local function ZIndexChanged() 281 | self.Container.ZIndex = Parent.ZIndex + 1 282 | end 283 | 284 | self.Janitor:Add(Parent:GetPropertyChangedSignal("ZIndex"):Connect(ZIndexChanged), "Disconnect", "ZIndexChanged") 285 | ZIndexChanged() 286 | self.Container.Parent = Parent 287 | else 288 | Debug.Error("bad argument #2 to Parent, expected GuiObject, got %s", Parent) 289 | end 290 | end 291 | 292 | self:rawset("Parent", Parent) 293 | end; 294 | }; 295 | 296 | Methods = { 297 | Down = Typer.AssignSignature(2, Typer.OptionalNumber, Typer.OptionalNumber, function(self, X, Y) 298 | local Container = self.Container 299 | local Diameter 300 | 301 | local ContainerAbsoluteSizeX = Container.AbsoluteSize.X 302 | local ContainerAbsoluteSizeY = Container.AbsoluteSize.Y 303 | 304 | -- Get near corners 305 | X = (X or (0.5 * ContainerAbsoluteSizeX + Container.AbsolutePosition.X)) - Container.AbsolutePosition.X 306 | Y = (Y or (0.5 * ContainerAbsoluteSizeY + Container.AbsolutePosition.Y)) - Container.AbsolutePosition.Y 307 | 308 | if self.Style == Enumeration.RipplerStyle.Icon then 309 | Diameter = 2 * Container.AbsoluteSize.Y 310 | self.Container.ClipsDescendants = false 311 | else 312 | -- Get far corners 313 | local V = X - ContainerAbsoluteSizeX 314 | local W = Y - ContainerAbsoluteSizeY 315 | 316 | -- Calculate distance between mouse and corners 317 | local a = (X*X + Y*Y) ^ 0.5 318 | local b = (X*X + W*W) ^ 0.5 319 | local c = (V*V + Y*Y) ^ 0.5 320 | local d = (V*V + W*W) ^ 0.5 321 | 322 | -- Find longest distance between mouse and a corner and decide Diameter 323 | Diameter = 2 * (a > b and a > c and a > d and a or b > c and b > d and b or c > d and c or d) * RIPPLE_OVERBITE 324 | 325 | -- Cap Diameter 326 | if self.MaxRippleDiameter < Diameter then 327 | Diameter = self.MaxRippleDiameter 328 | end 329 | end 330 | 331 | -- Create Ripple Object 332 | local Ripple = Circle:Clone() 333 | Ripple.ImageColor3 = self.RippleColor3 334 | Ripple.ImageTransparency = self.RippleTransparency 335 | Ripple.Position = UDim2.new(0, X, 0, Y) 336 | Ripple.ZIndex = Container.ZIndex + 1 337 | Ripple.Parent = Container 338 | 339 | if self.Style == Enumeration.RipplerStyle.Round and self.BorderRadius.Value ~= 0 then 340 | local Ripples = {Transparency = Ripple.ImageTransparency} 341 | local RippleFrames = self.RippleFrames 342 | local NumRipples = #RippleFrames 343 | 344 | for i = 1, NumRipples do 345 | local RippleFrame = RippleFrames[i] 346 | local NewRipple = Ripple:Clone() 347 | local AbsolutePosition = Ripple.AbsolutePosition - RippleFrame.AbsolutePosition + 0.5*Ripple.AbsoluteSize 348 | NewRipple.Position = UDim2.new(0, AbsolutePosition.X, 0, AbsolutePosition.Y) 349 | NewRipple.ImageTransparency = (1 - self.RippleTransparency) * RippleFrame.ImageTransparency + self.RippleTransparency 350 | NewRipple.Parent = RippleFrame 351 | 352 | Ripples[i] = NewRipple 353 | end 354 | Ripple:Destroy() 355 | Ripple = setmetatable(Ripples, RoundRippleMetatable) 356 | end 357 | 358 | self:SetCurrentRipple(Ripple) 359 | 360 | return Tween(Ripple, "Size", UDim2.new(0, Diameter, 0, Diameter), Deceleration, self.RippleExpandDuration) 361 | end); 362 | 363 | Up = function(self) 364 | self:SetCurrentRipple(false) 365 | end; 366 | 367 | Ripple = Typer.AssignSignature(2, Typer.OptionalNumber, Typer.OptionalNumber, Typer.OptionalNumber, function(self, X, Y, Duration) 368 | self:Down(X, Y) 369 | 370 | delay(Duration or 0.15, function() 371 | self:SetCurrentRipple(false) 372 | end) 373 | end); 374 | }; 375 | 376 | Init = function(self, Container) 377 | self.Style = Enumeration.RipplerStyle.Full 378 | self.BorderRadius = 0 379 | self.Container = Container or RippleContainer:Clone() 380 | self.RippleTransparency = 0.84 381 | self.RippleColor3 = Color.White 382 | self.MaxRippleDiameter = math.huge 383 | self.RippleExpandDuration = 0.5 384 | self.RippleFadeDuration = 1 385 | self:superinit() 386 | end; 387 | }) 388 | -------------------------------------------------------------------------------- /RippleButton.lua: -------------------------------------------------------------------------------- 1 | -- Material Design Button PseudoInstances with Ripples 2 | -- @documentation https://rostrap.github.io/Libraries/RoStrapUI/RippleButton/ 3 | -- @author Validark 4 | 5 | local Players = game:GetService("Players") 6 | local TextService = game:GetService("TextService") 7 | local ContentProvider = game:GetService("ContentProvider") 8 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 9 | 10 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 11 | local Debug = Resources:LoadLibrary("Debug") 12 | local Tween = Resources:LoadLibrary("Tween") 13 | local Color = Resources:LoadLibrary("Color") 14 | local Typer = Resources:LoadLibrary("Typer") 15 | local Enumeration = Resources:LoadLibrary("Enumeration") 16 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 17 | 18 | local Shadow = Resources:LoadLibrary("Shadow") 19 | local Rippler = Resources:LoadLibrary("Rippler") 20 | 21 | -- Elevations 22 | local RAISED_BASE_ELEVATION = 3 23 | local RAISED_ELEVATION = 8 24 | 25 | Enumeration.ButtonStyle = {"Flat", "Outlined", "Contained"} 26 | 27 | local StateOpacity = { -- TODO: Derive these values based on the PrimaryColor3's luminosity 28 | -- Material Design specs have values which are more subtle, which I don't think look ideal 29 | [Enumeration.ButtonStyle.Flat.Value] = { 30 | Hover = 0.12; 31 | Pressed = 0.265; 32 | }; 33 | 34 | [Enumeration.ButtonStyle.Outlined.Value] = { 35 | Hover = 0.12; -- 0.035; 36 | Pressed = 0.265; --0.125; 37 | }; 38 | 39 | [Enumeration.ButtonStyle.Contained.Value] = { 40 | Hover = 0.12; --0.075; 41 | Pressed = 0.3; -- 0.265; 42 | }; 43 | } 44 | 45 | local RaisedImages = { 46 | [0] = "rbxassetid://132155326"; 47 | [2] = "rbxassetid://1934672242"; 48 | [4] = "rbxassetid://1934624205"; 49 | [8] = "rbxassetid://1935044829"; 50 | } 51 | 52 | local OutlinedImages = { 53 | [0] = "rbxassetid://2091129360"; 54 | [2] = "rbxassetid://1981015282"; 55 | [4] = "rbxassetid://1981015668"; 56 | [8] = "rbxassetid://1981285569"; 57 | } 58 | 59 | local ImageButton = Instance.new("ImageButton") 60 | ImageButton.BackgroundTransparency = 1 61 | ImageButton.ScaleType = Enum.ScaleType.Slice.Value 62 | 63 | local TextLabel = Instance.new("TextLabel") 64 | TextLabel.BackgroundTransparency = 1 65 | TextLabel.Font = Enum.Font.SourceSansSemibold.Value 66 | TextLabel.Size = UDim2.new(1, 0, 1, 0) 67 | TextLabel.TextSize = 16 68 | TextLabel.Parent = ImageButton 69 | 70 | local OutlineImage = Instance.new("ImageLabel") 71 | OutlineImage.BackgroundTransparency = 1 72 | OutlineImage.Size = UDim2.new(1, 0, 1, 0) 73 | OutlineImage.ScaleType = Enum.ScaleType.Slice.Value 74 | OutlineImage.ImageTransparency = 0.88 75 | OutlineImage.Name = "Outline" 76 | OutlineImage.ImageColor3 = Color.Black 77 | 78 | local TOOLTIP_BORDER_RADIUS = 4 79 | 80 | local TooltipObject = Instance.new("ImageLabel") 81 | TooltipObject.BackgroundTransparency = 1 82 | TooltipObject.ScaleType = Enum.ScaleType.Slice.Value 83 | TooltipObject.ImageTransparency = 0.1 84 | TooltipObject.ImageColor3 = Color3.fromRGB(97, 97, 97) 85 | TooltipObject.Image = RaisedImages[TOOLTIP_BORDER_RADIUS] 86 | TooltipObject.SliceCenter = Rect.new(TOOLTIP_BORDER_RADIUS, TOOLTIP_BORDER_RADIUS, 256 - TOOLTIP_BORDER_RADIUS, 256 - TOOLTIP_BORDER_RADIUS) 87 | TooltipObject.Name = "Tooltip" 88 | TooltipObject.AnchorPoint = Vector2.new(0.5, 0) 89 | TooltipObject.Position = UDim2.new(0.5, 0, 1, 8) 90 | 91 | local ToolTipLabel = TextLabel:Clone() 92 | ToolTipLabel.TextColor3 = Color.White 93 | ToolTipLabel.TextSize = 12 94 | ToolTipLabel.TextTransparency = 1 95 | ToolTipLabel.Name = "TextLabel" 96 | ToolTipLabel.Parent = TooltipObject 97 | 98 | local Touch = Enum.UserInputType.Touch 99 | local MouseButton1 = Enum.UserInputType.MouseButton1 100 | local MouseMovement = Enum.UserInputType.MouseMovement 101 | 102 | local LARGE_FRAME_SIZE = Vector2.new(32767, 32767) 103 | 104 | local Invisify = {UserInputType = MouseMovement} 105 | 106 | return PseudoInstance:Register("RippleButton", { 107 | WrappedProperties = { 108 | Object = {"AnchorPoint", "Active", "Name", "Size", "Position", "LayoutOrder", "NextSelectionDown", "NextSelectionLeft", "NextSelectionRight", "NextSelectionUp", "Parent"}; 109 | TextLabel = {"TextXAlignment", "TextYAlignment"}; 110 | }; 111 | 112 | Internals = { 113 | "TextLabel", "Rippler", "OutlineImage", "OverlayOpacity", "Shadow", "TooltipObject", "InputBegan", "InputEnded", "InputChanged", "RegisteredRippleInputs"; 114 | 115 | Render = function(self) 116 | local PrimaryColor3 = self.PrimaryColor3 117 | local Luminosity = PrimaryColor3.r * 0.299 + PrimaryColor3.g * 0.587 + PrimaryColor3.b * 0.114 118 | 119 | if self.Style == Enumeration.ButtonStyle.Contained then 120 | if self.Disabled then 121 | PrimaryColor3 = Color3.fromRGB(204, 204, 204) 122 | end 123 | 124 | local SecondaryColor3 = self.SecondaryColor3 or 0.5 < Luminosity and Color.Black or Color.White 125 | 126 | self.Rippler.RippleColor3 = SecondaryColor3 127 | self.TextLabel.TextColor3 = SecondaryColor3 128 | self.Object.ImageColor3 = PrimaryColor3 129 | else 130 | if self.Disabled then 131 | PrimaryColor3 = Color.Black 132 | end 133 | self.Rippler.RippleColor3 = PrimaryColor3 134 | self.TextLabel.TextColor3 = PrimaryColor3 135 | self.Object.ImageColor3 = PrimaryColor3 136 | end 137 | end; 138 | }; 139 | 140 | Events = { 141 | OnPressed = function(self) 142 | local RegisteredRippleInputs = self.RegisteredRippleInputs 143 | 144 | return function(Signal) 145 | RegisteredRippleInputs[Enum.UserInputType.Touch.Value] = Signal 146 | RegisteredRippleInputs[Enum.UserInputType.MouseButton1.Value] = Signal 147 | end, function() 148 | RegisteredRippleInputs[Enum.UserInputType.Touch.Value] = nil 149 | RegisteredRippleInputs[Enum.UserInputType.MouseButton1.Value] = nil 150 | end 151 | end; 152 | 153 | OnRightPressed = function(self) 154 | local RegisteredRippleInputs = self.RegisteredRippleInputs 155 | 156 | return function(Signal) 157 | RegisteredRippleInputs[Enum.UserInputType.MouseButton2.Value] = Signal 158 | end, function() 159 | RegisteredRippleInputs[Enum.UserInputType.MouseButton2.Value] = nil 160 | end 161 | end; 162 | 163 | OnMiddlePressed = function(self) 164 | local RegisteredRippleInputs = self.RegisteredRippleInputs 165 | 166 | return function(Signal) 167 | RegisteredRippleInputs[Enum.UserInputType.MouseButton3.Value] = Signal 168 | end, function() 169 | RegisteredRippleInputs[Enum.UserInputType.MouseButton3.Value] = nil 170 | end 171 | end; 172 | }; 173 | 174 | Properties = { 175 | Elevation = Typer.AssignSignature(2, Typer.EnumerationOfTypeShadowElevation, function(self, Elevation) 176 | if Elevation.Value > 0 then 177 | if self.Style ~= Enumeration.ButtonStyle.Contained then 178 | self.Style = Enumeration.ButtonStyle.Contained 179 | end 180 | self.Shadow.Elevation = Elevation 181 | end 182 | self:rawset("Elevation", Elevation) 183 | end); 184 | 185 | Text = Typer.AssignSignature(2, Typer.String, function(self, Text) 186 | self.TextLabel.Text = Text 187 | self:rawset("Text", Text) 188 | self:rawset("TextBounds", TextService:GetTextSize(Text, self.TextSize, self.Font, LARGE_FRAME_SIZE)) 189 | end); 190 | 191 | TextSize = Typer.AssignSignature(2, Typer.Number, function(self, TextSize) 192 | self.TextLabel.TextSize = TextSize 193 | self:rawset("TextSize", TextSize) 194 | self:rawset("TextBounds", TextService:GetTextSize(self.Text, TextSize, self.Font, LARGE_FRAME_SIZE)) 195 | end); 196 | 197 | Font = Typer.AssignSignature(2, Typer.EnumOfTypeFont, function(self, Font) 198 | self.TextLabel.Font = Font 199 | self:rawset("Font", Font) 200 | self:rawset("TextBounds", TextService:GetTextSize(self.Text, self.TextSize, Font, LARGE_FRAME_SIZE)) 201 | end); 202 | 203 | TextTransparency = Typer.AssignSignature(2, Typer.Number, function(self, TextTransparency) 204 | if not self.Disabled then 205 | self.TextLabel.TextTransparency = TextTransparency 206 | end 207 | 208 | self:rawset("TextTransparency", TextTransparency) 209 | end); 210 | 211 | Disabled = Typer.AssignSignature(2, Typer.Boolean, function(self, Disabled) 212 | if self.Disabled ~= Disabled then 213 | 214 | if Disabled then 215 | if self.Style == Enumeration.ButtonStyle.Contained then 216 | self.Shadow.Visible = false 217 | end 218 | 219 | self.TextLabel.TextTransparency = 0.62 220 | 221 | self.Janitor:Remove("InputBegan") 222 | self.Janitor:Remove("InputEnded") 223 | self.Janitor:Remove("InputChanged") 224 | else 225 | if self.Style == Enumeration.ButtonStyle.Contained then 226 | self.Shadow.Visible = true 227 | end 228 | 229 | self.TextLabel.TextTransparency = self.TextTransparency 230 | 231 | self.Janitor:Add(self.Object.InputBegan:Connect(self.InputBegan), "Disconnect", "InputBegan") 232 | self.Janitor:Add(self.Object.InputEnded:Connect(self.InputEnded), "Disconnect", "InputEnded") 233 | self.Janitor:Add(self.Object.InputChanged:Connect(self.InputChanged), "Disconnect", "InputChanged") 234 | end 235 | 236 | self:rawset("Disabled", Disabled) 237 | self:Render() 238 | end 239 | end); 240 | 241 | Tooltip = Typer.AssignSignature(2, Typer.String, function(self, Tip) 242 | if Tip == "" then 243 | self.TooltipObject = nil 244 | self.Janitor:Remove("TooltipObject") 245 | else 246 | self.TooltipObject = TooltipObject:Clone() 247 | self.TooltipObject.ZIndex = self.ZIndex + 1 248 | self.TooltipObject.TextLabel.Text = Tip 249 | self.TooltipObject.TextLabel.ZIndex = self.ZIndex + 2 250 | self.TooltipObject.Parent = self.Object 251 | 252 | self.Janitor:Add(self.TooltipObject, "Destroy", "TooltipObject") 253 | end 254 | 255 | self:rawset("Tooltip", Tip) 256 | end); 257 | 258 | BorderRadius = Typer.AssignSignature(2, Typer.EnumerationOfTypeBorderRadius, function(self, BorderRadius) 259 | local Value = BorderRadius.Value 260 | local SliceCenter = Rect.new(Value, Value, 256 - Value, 256 - Value) 261 | 262 | self.Object.Image = RaisedImages[Value] 263 | self.Object.SliceCenter = SliceCenter 264 | self.Rippler.BorderRadius = Value 265 | 266 | if self.Style == Enumeration.ButtonStyle.Outlined then 267 | self.OutlineImage.Image = OutlinedImages[Value] 268 | self.OutlineImage.SliceCenter = SliceCenter 269 | end 270 | 271 | self:rawset("BorderRadius", BorderRadius) 272 | end); 273 | 274 | Style = Typer.AssignSignature(2, Typer.EnumerationOfTypeButtonStyle, function(self, ButtonStyle) 275 | self:rawset("Style", ButtonStyle) 276 | 277 | local StateData = StateOpacity[ButtonStyle.Value] 278 | self.OverlayOpacity = StateData.Hover 279 | self.Rippler.RippleTransparency = 1 - StateData.Pressed 280 | 281 | local IsOutlined = ButtonStyle == Enumeration.ButtonStyle.Outlined 282 | 283 | if ButtonStyle == Enumeration.ButtonStyle.Flat or IsOutlined then 284 | self.Object.ImageTransparency = 1 285 | self.Object.ImageColor3 = self.PrimaryColor3 286 | 287 | self.Janitor:Remove("Shadow") 288 | self.Shadow = nil 289 | 290 | self:rawset("Elevation", Enumeration.ShadowElevation.Elevation0) 291 | elseif ButtonStyle == Enumeration.ButtonStyle.Contained then 292 | self.Object.ImageTransparency = 0 293 | -- self.Object.ImageColor3 = self.PrimaryColor3 294 | 295 | self.Shadow = PseudoInstance.new("Shadow") 296 | self.Shadow.Parent = self.Object 297 | self.Janitor:Add(self.Shadow, "Destroy", "Shadow") 298 | 299 | self.Elevation = RAISED_BASE_ELEVATION 300 | self.Shadow.Transparency = 0 301 | end 302 | 303 | self:Render() 304 | 305 | if IsOutlined then 306 | self.OutlineImage = OutlineImage:Clone() 307 | self.OutlineImage.ZIndex = self.ZIndex + 2 308 | self.OutlineImage.Parent = self.Object 309 | local Value = self.BorderRadius.Value 310 | 311 | self.OutlineImage.Image = OutlinedImages[Value] 312 | self.OutlineImage.SliceCenter = Rect.new(Value, Value, 256 - Value, 256 - Value) 313 | self.Janitor:Add(self.OutlineImage, "Destroy", "OutlineImage") 314 | else 315 | self.OutlineImage = nil 316 | self.Janitor:Remove("OutlineImage") 317 | end 318 | end); 319 | 320 | PrimaryColor3 = Typer.AssignSignature(2, Typer.Color3, function(self, PrimaryColor3) 321 | self:rawset("PrimaryColor3", PrimaryColor3) 322 | self:Render() 323 | end); 324 | 325 | SecondaryColor3 = Typer.AssignSignature(2, Typer.OptionalColor3, function(self, SecondaryColor3) 326 | self:rawset("SecondaryColor3", SecondaryColor3) 327 | self:Render() 328 | end); 329 | 330 | Visible = Typer.AssignSignature(2, Typer.Boolean, function(self, Visible) 331 | self.Object.Visible = Visible 332 | 333 | if Visible then 334 | -- self.InputBegan(Invisify) 335 | else 336 | self.InputEnded(Invisify) 337 | end 338 | 339 | self:rawset("Visible", Visible) 340 | end); 341 | 342 | ZIndex = Typer.AssignSignature(2, Typer.Number, function(self, ZIndex) 343 | self.Object.ZIndex = ZIndex + 1 344 | self.TextLabel.ZIndex = ZIndex + 3 345 | 346 | if self.TooltipObject then 347 | self.TooltipObject.ZIndex = ZIndex + 1 348 | self.TooltipObject.TextLabel.ZIndex = ZIndex + 2 349 | end 350 | 351 | if self.OutlineImage then 352 | self.OutlineImage.ZIndex = ZIndex + 2 353 | end 354 | 355 | self:rawset("ZIndex", ZIndex) 356 | end); 357 | }; 358 | 359 | Methods = {}; 360 | 361 | Init = function(self) 362 | self:rawset("Object", ImageButton:Clone()) 363 | self:rawset("PrimaryColor3", Color.Black) 364 | self:rawset("SecondaryColor3", nil) 365 | self:rawset("Font", Enum.Font.SourceSansSemibold) 366 | self:rawset("TextSize", 16) 367 | 368 | self.TextLabel = self.Object.TextLabel 369 | 370 | self.Rippler = PseudoInstance.new("Rippler") 371 | self.Rippler.RippleTransparency = 0.68 372 | self.Rippler.Parent = self.Object 373 | 374 | self.Style = Enumeration.ButtonStyle.Flat 375 | self.BorderRadius = 4 376 | self.Tooltip = "" 377 | self.Text = "" 378 | self.ZIndex = 1 379 | self.TextTransparency = 0 380 | 381 | self.Janitor:Add(self.Object, "Destroy") 382 | self.Janitor:Add(self.TextLabel, "Destroy") 383 | self.Janitor:Add(self.Rippler, "Destroy") 384 | 385 | local Int = 0 386 | local IsHovered = false 387 | self.RegisteredRippleInputs = {} 388 | 389 | function self.InputBegan(InputObject) 390 | local Signal = self.RegisteredRippleInputs[InputObject.UserInputType.Value] 391 | 392 | if Signal then 393 | Signal.IsDown = true 394 | self.Rippler:Down(InputObject.Position.X, InputObject.Position.Y) 395 | if self.Style == Enumeration.ButtonStyle.Contained then 396 | self.Shadow:ChangeElevation(RAISED_ELEVATION) 397 | end 398 | elseif InputObject.UserInputType == MouseMovement then 399 | IsHovered = true 400 | 401 | if self.Style == Enumeration.ButtonStyle.Contained then 402 | Tween(self.Object, "ImageColor3", self.PrimaryColor3:Lerp(self.Rippler.RippleColor3, self.OverlayOpacity), Enumeration.EasingFunction.Deceleration, 0.1, true) 403 | else 404 | Tween(self.Object, "ImageTransparency", 1 - self.OverlayOpacity, Enumeration.EasingFunction.Deceleration, 0.1, true) 405 | end 406 | 407 | local TooltipObj = self.TooltipObject 408 | 409 | if TooltipObj then 410 | -- Over 150ms, tooltips fade in and scale up using the deceleration curve. They fade out over 75ms. 411 | 412 | local NewInt = Int + 1 413 | Int = NewInt 414 | 415 | delay(0.5, function() 416 | if NewInt == Int then 417 | Tween(TooltipObj, "Size", UDim2.new(0, TooltipObj.TextLabel.TextBounds.X + 16, 0, 24), Enumeration.EasingFunction.Deceleration, 0.1, true) 418 | Tween(TooltipObj, "ImageTransparency", 0.1, Enumeration.EasingFunction.Deceleration, 0.1, true) 419 | Tween(TooltipObj.TextLabel, "TextTransparency", 0, Enumeration.EasingFunction.Deceleration, 0.1, true) 420 | end 421 | end) 422 | end 423 | end 424 | end 425 | 426 | function self.InputEnded(InputObject) 427 | local UserInputType = InputObject.UserInputType 428 | 429 | self.Rippler:Up() 430 | 431 | local Signal = self.RegisteredRippleInputs[UserInputType.Value] 432 | 433 | if Signal and Signal.IsDown then 434 | Signal.IsDown = false 435 | Signal:Fire() 436 | end 437 | 438 | if self.Style == Enumeration.ButtonStyle.Contained then 439 | self.Shadow:ChangeElevation(self.Elevation) 440 | end 441 | 442 | if UserInputType == MouseMovement then 443 | for _, EventSignal in next, self.RegisteredRippleInputs do 444 | EventSignal.IsDown = false 445 | end 446 | if self.Style == Enumeration.ButtonStyle.Contained then 447 | Tween(self.Object, "ImageColor3", self.PrimaryColor3, Enumeration.EasingFunction.Deceleration, 0.1, true) 448 | else 449 | Tween(self.Object, "ImageTransparency", 1, Enumeration.EasingFunction.Deceleration, 0.1, true) 450 | end 451 | IsHovered = false 452 | end 453 | 454 | Int = Int + 1 455 | 456 | local TooltipObj = self.TooltipObject 457 | 458 | if TooltipObj then 459 | Tween(TooltipObj, "Size", UDim2.new(), Enumeration.EasingFunction.Deceleration, 0.075, true) 460 | Tween(TooltipObj, "ImageTransparency", 1, Enumeration.EasingFunction.Deceleration, 0.075, true) 461 | Tween(TooltipObj.TextLabel, "TextTransparency", 1, Enumeration.EasingFunction.Deceleration, 0.075, true) 462 | end 463 | end 464 | 465 | function self.InputChanged(InputObject) 466 | if InputObject.UserInputType == MouseMovement and not IsHovered then 467 | IsHovered = true 468 | self.InputBegan(InputObject) 469 | end 470 | end 471 | 472 | self.Disabled = false 473 | 474 | self:superinit() 475 | end; 476 | }) 477 | -------------------------------------------------------------------------------- /Checkbox.lua: -------------------------------------------------------------------------------- 1 | -- Material Design Checkbox PseudoInstance 2 | -- @specs https://material.io/guidelines/components/selection-controls.html 3 | -- @author Validark 4 | 5 | local Players = game:GetService("Players") 6 | local ContentProvider = game:GetService("ContentProvider") 7 | local ReplicatedStorage = game:GetService("ReplicatedStorage") 8 | 9 | local Resources = require(ReplicatedStorage:WaitForChild("Resources")) 10 | local Debug = Resources:LoadLibrary("Debug") 11 | local Tween = Resources:LoadLibrary("Tween") 12 | local Color = Resources:LoadLibrary("Color") 13 | local Typer = Resources:LoadLibrary("Typer") 14 | local Enumeration = Resources:LoadLibrary("Enumeration") 15 | local PseudoInstance = Resources:LoadLibrary("PseudoInstance") 16 | local SelectionController = Resources:LoadLibrary("SelectionController") 17 | 18 | local CLICK_RIPPLE_TRANSPARENCY = 0.77 -- 0.88 19 | local HOVER_RIPPLE_TRANSPARENCY = 0.93 -- 0.96 20 | 21 | -- Images 22 | local CHECKED_CHECKBOX_IMAGE = "rbxassetid://1990905054" 23 | local UNCHECKED_CHECKBOX_IMAGE = "rbxassetid://1990916223" 24 | local INDETERMINATE_CHECKBOX_IMAGE = "rbxassetid://1990919246" 25 | 26 | -- Preload Images 27 | spawn(function() 28 | ContentProvider:PreloadAsync{CHECKED_CHECKBOX_IMAGE, UNCHECKED_CHECKBOX_IMAGE} 29 | end) 30 | 31 | -- Configuration 32 | local ANIMATION_TIME = 0.1 33 | 34 | -- Constants 35 | local SHRINK_DURATION = ANIMATION_TIME --* 0.95 36 | local DRAW_DURATION = ANIMATION_TIME * (1 / 0.7501) 37 | local FILL_DURATION = ANIMATION_TIME * (1 / 0.9286) 38 | local CHECKBOX_SIZE = UDim2.new(0, 24, 0, 24) 39 | 40 | local CHECKBOX_THEMES = { 41 | [Enumeration.MaterialTheme.Light.Value] = { 42 | ImageColor3 = Color.Black; 43 | ImageTransparency = 0.46; 44 | DisabledTransparency = 0.74; 45 | }; 46 | 47 | [Enumeration.MaterialTheme.Dark.Value] = { 48 | ImageColor3 = Color.White; 49 | ImageTransparency = 0.3; 50 | DisabledTransparency = 0.7; 51 | }; 52 | }; 53 | 54 | -- Bezier Curves (defined in the `Easing` module) 55 | local CHECKMARK_DRAW_BEZIER = "Deceleration" 56 | local CHECKMARK_ERASE_BEZIER = "Deceleration" 57 | local CENTER_FILL_BEZIER = "Deceleration" 58 | local CENTER_EMPTY_BEZIER = "Deceleration" 59 | local OUTSIDE_TRANSPARENCY_BEZIER = "Deceleration" 60 | 61 | -- Import Math Functions 62 | local ceil = math.ceil 63 | local floor = math.floor 64 | 65 | local SETS = { 66 | Bars = 0; 67 | Corners = 0.69; 68 | Edges = 0.09; 69 | 70 | InnerBars = 0; 71 | InnerCorners = 0; 72 | InnerEdges = 0; 73 | } 74 | 75 | local SETS_GOALS = { 76 | Bars = 1; 77 | Corners = 1; 78 | Edges = 1; 79 | 80 | InnerBars = SETS.Bars; 81 | InnerCorners = SETS.Corners; 82 | InnerEdges = SETS.Edges; 83 | } 84 | 85 | local Checkbox do 86 | local MIDDLE_ANCHOR = Vector2.new(0.5, 0.5) 87 | local MIDDLE_POSITION = UDim2.new(0.5, 0, 0.5, 0) 88 | 89 | Checkbox = Instance.new("ImageButton") 90 | Checkbox.BackgroundTransparency = 1 91 | Checkbox.Size = CHECKBOX_SIZE 92 | Checkbox.Image = UNCHECKED_CHECKBOX_IMAGE 93 | 94 | local Pixel = Instance.new("Frame") 95 | Pixel.BackgroundTransparency = 1 96 | Pixel.BackgroundColor3 = Color.Black 97 | Pixel.BorderSizePixel = 0 98 | Pixel.Size = UDim2.new(0, 1, 0, 1) 99 | 100 | local GridFrame = Instance.new("Frame") 101 | GridFrame.AnchorPoint = MIDDLE_ANCHOR 102 | GridFrame.BackgroundTransparency = 1 103 | GridFrame.Name = "GridFrame" 104 | GridFrame.Position = MIDDLE_POSITION 105 | GridFrame.Size = CHECKBOX_SIZE 106 | GridFrame.Visible = false 107 | GridFrame.Parent = Checkbox 108 | 109 | for a = 1, 14 do 110 | local Existant = 14 * (a - 1) 111 | for b = 1, 14 do 112 | local Pixel = Pixel:Clone() 113 | Pixel.Name = Existant + b 114 | Pixel.Position = UDim2.new(0, b + 4, 0, a + 4) 115 | Pixel.Parent = GridFrame 116 | end 117 | end 118 | 119 | local BackgroundTransparency = CHECKBOX_THEMES[Enumeration.MaterialTheme.Light.Value].ImageTransparency 120 | 121 | local Bar = Instance.new("Frame") 122 | Bar.BackgroundColor3 = Color.Black 123 | Bar.BackgroundTransparency = BackgroundTransparency 124 | Bar.BorderSizePixel = 0 125 | Bar.Name = "Bars" 126 | 127 | local Count = 0 128 | for c = 0, 16, 16 do 129 | for b = 3, 4 do 130 | Count = Count + 1 131 | local d 132 | if Count > 1 and Count < 4 then 133 | d = 6 134 | Bar.Name = "InnerBars" 135 | else 136 | d = 5 137 | Bar.Name = "Bars" 138 | end 139 | local e = (12 - d)*2 140 | 141 | local Horizontal = Bar:Clone() 142 | Horizontal.Position = UDim2.new(0, d, 0, b + c) 143 | Horizontal.Size = UDim2.new(0, e, 0, 1) 144 | 145 | local Vertical = Bar:Clone() 146 | Vertical.Position = UDim2.new(0, b + c, 0, d) 147 | Vertical.Size = UDim2.new(0, 1, 0, e) 148 | 149 | Horizontal.Parent = GridFrame 150 | Vertical.Parent = GridFrame 151 | end 152 | end 153 | 154 | Pixel.Name = "Corners" 155 | local CornerTransparency = (1 - BackgroundTransparency) * SETS.Corners + BackgroundTransparency 156 | 157 | for a = 3, 20, 17 do 158 | local F1 = Pixel:Clone() 159 | F1.BackgroundTransparency = CornerTransparency 160 | F1.Position = UDim2.new(0, a, 0, a) 161 | 162 | local F2 = Pixel:Clone() 163 | F2.BackgroundTransparency = CornerTransparency 164 | F2.Position = UDim2.new(0, a, 0, 23 - a) 165 | 166 | F1.Parent = GridFrame 167 | F2.Parent = GridFrame 168 | end 169 | 170 | Pixel.Name = "InnerCorners" 171 | 172 | for a = 4, 19, 15 do 173 | local F1 = Pixel:Clone() 174 | F1.BackgroundTransparency = BackgroundTransparency 175 | F1.Position = UDim2.new(0, a, 0, a) 176 | 177 | local F2 = Pixel:Clone() 178 | F2.BackgroundTransparency = BackgroundTransparency 179 | F2.Position = UDim2.new(0, a, 0, 23 - a) 180 | 181 | F1.Parent = GridFrame 182 | F2.Parent = GridFrame 183 | end 184 | 185 | Pixel.Name = "Edges" 186 | local EdgeTransparency = (1 - BackgroundTransparency) * SETS.Edges + BackgroundTransparency 187 | 188 | for a = 3, 20, 17 do 189 | for b = 4, 19, 15 do 190 | local F1 = Pixel:Clone() 191 | F1.BackgroundTransparency = EdgeTransparency 192 | F1.Position = UDim2.new(0, a, 0, b) 193 | 194 | local F2 = Pixel:Clone() 195 | F2.BackgroundTransparency = EdgeTransparency 196 | F2.Position = UDim2.new(0, b, 0, a) 197 | 198 | F1.Parent = GridFrame 199 | F2.Parent = GridFrame 200 | end 201 | end 202 | 203 | Pixel.Name = "InnerEdges" 204 | 205 | for a = 4, 19, 15 do 206 | for b = 5, 18, 13 do 207 | local F1 = Pixel:Clone() 208 | F1.BackgroundTransparency = BackgroundTransparency 209 | F1.Position = UDim2.new(0, a, 0, b) 210 | 211 | local F2 = Pixel:Clone() 212 | F2.BackgroundTransparency = BackgroundTransparency 213 | F2.Position = UDim2.new(0, b, 0, a) 214 | 215 | F1.Parent = GridFrame 216 | F2.Parent = GridFrame 217 | end 218 | end 219 | 220 | -- Destroy Clonable Templates 221 | Pixel:Destroy() 222 | Bar:Destroy() 223 | end 224 | 225 | return PseudoInstance:Register("Checkbox", { 226 | Internals = { 227 | "OpenTween", "OpenTween2", "Grid", "", "", "GridFrame"; 228 | 229 | ImageTransparency = 0; 230 | XOffset = 0; 231 | YOffset = 0; 232 | 233 | Template = Checkbox; 234 | 235 | SetColorAndTransparency = function(self, Color3, Transparency) 236 | local Grid = self.Grid 237 | local Opacity = (1 - Transparency) 238 | 239 | self.HoverRippler.RippleColor3 = Color3 240 | self.ClickRippler.RippleColor3 = Color3 241 | 242 | self.HoverRippler.RippleTransparency = Opacity * HOVER_RIPPLE_TRANSPARENCY + Transparency 243 | self.ClickRippler.RippleTransparency = Opacity * CLICK_RIPPLE_TRANSPARENCY + Transparency 244 | 245 | self.Button.ImageTransparency = Transparency 246 | self.Button.ImageColor3 = Color3 247 | 248 | for Name, BackgroundTransparency in next, SETS do 249 | local PixelTransparency = Opacity * BackgroundTransparency + Transparency 250 | local Objects = Grid[Name] 251 | 252 | for a = 1, #Objects do 253 | local Object = Objects[a] 254 | Object.BackgroundColor3 = Color3 255 | Object.BackgroundTransparency = PixelTransparency 256 | end 257 | end 258 | 259 | self.ImageTransparency = Transparency 260 | 261 | for a = 1, 196 do 262 | local Pixel = Grid[a] 263 | Pixel.BackgroundColor3 = Color3 264 | Pixel.BackgroundTransparency = Opacity * Pixel.BackgroundTransparency + Transparency -- CompoundTransparency 265 | end 266 | end; 267 | 268 | ExpandFrame = function(self, x) 269 | x = x or 1 270 | local Grid = self.Grid 271 | local ImageTransparency = self.ImageTransparency 272 | local ImageOpacity = 1 - ImageTransparency 273 | 274 | for Name, Start in next, SETS_GOALS do 275 | Start = ImageOpacity * Start + ImageTransparency 276 | local End = ImageOpacity * SETS[Name] + ImageTransparency - Start 277 | local Objects = Grid[Name] 278 | 279 | for a = 1, #Objects do 280 | Objects[a].BackgroundTransparency = Start + x * End 281 | end 282 | end 283 | end; 284 | 285 | ShrinkFrame = function(self, x) 286 | x = x or 1 287 | local Grid = self.Grid 288 | local ImageTransparency = self.ImageTransparency 289 | local ImageOpacity = 1 - ImageTransparency 290 | 291 | for Name, Start in next, SETS do 292 | Start = ImageOpacity * Start + ImageTransparency 293 | local End = ImageOpacity * SETS_GOALS[Name] + ImageTransparency - Start 294 | local Objects = Grid[Name] 295 | 296 | for a = 1, #Objects do 297 | Objects[a].BackgroundTransparency = Start + x * End 298 | end 299 | end 300 | 301 | if x == 1 then 302 | self.OpenTween2 = Tween.new(SHRINK_DURATION, OUTSIDE_TRANSPARENCY_BEZIER, self.ExpandFrame, self) 303 | end 304 | end; 305 | 306 | DrawCheckmark = function(self, x) 307 | x = x or 1 308 | local Grid = self.Grid 309 | 310 | for c = 1, -1, -2 do 311 | local a = floor(11 + x * (4 - 2*c - 11)) -- Lerp(11, 4 - 2*c, x) 312 | local d = c == 1 and 15 or -4 313 | 314 | for a = a, 10 do 315 | local b = -c*a + d 316 | local e 317 | 318 | if a == 2 and b == 13 then 319 | e = 0.18 320 | elseif a == 3 and b == 12 or a == 4 and b == 11 or a == 5 and b == 10 or a == 9 and (b == 5 or b == 6) then 321 | e = 0.36 322 | elseif a == 6 then 323 | if b == 2 then 324 | e = 0.18 325 | elseif b == 9 then 326 | e = 0.36 327 | end 328 | elseif a == 7 then 329 | if b == 3 then 330 | e = 0.35 331 | elseif b == 8 then 332 | e = 0.36 333 | end 334 | elseif a == 8 then 335 | if b == 4 then 336 | e = 0.35 337 | elseif b == 7 then 338 | e = 0.36 339 | end 340 | elseif a == 10 then 341 | if b == 5 or b == 6 then 342 | e = 0.99 343 | end 344 | end 345 | 346 | Grid[14 * (a - 1) + b].BackgroundTransparency = e 347 | Grid[14 * a + b].BackgroundTransparency = 0.99 348 | Grid[14 * (a + 1) + b].BackgroundTransparency = 1 349 | Grid[14 * (a + 1) + b + c].BackgroundTransparency = 0.5 350 | end 351 | Grid[a * (14 - c) + c + d].BackgroundTransparency = c == 1 and 0.5 or 0.51 -- 14 * a + -c * a + d + c 352 | 353 | if a == 2 then 354 | self.Button.Image = CHECKED_CHECKBOX_IMAGE 355 | self.Button.ImageTransparency = 0 356 | self.GridFrame.Visible = false 357 | end 358 | end 359 | Grid[160].BackgroundTransparency = 0.5 -- 12, 6 360 | end; 361 | 362 | FillCenter = function(self, x) 363 | x = x or 1 364 | local Grid = self.Grid 365 | local CurrentSize = 0.5 * floor(14*(2 - x)) -- Floor(Lerp(14, 7, x), 0.5) 366 | 367 | for i = 1, 14 - CurrentSize do 368 | for a = i, 15 - i do 369 | Grid[14 * (i - 1) + a].BackgroundTransparency = 0 370 | Grid[14 * (a - 1) + i].BackgroundTransparency = 0 371 | Grid[14 * ((15 - i) - 1) + a].BackgroundTransparency = 0 372 | Grid[14 * (a - 1) + 15 - i].BackgroundTransparency = 0 373 | end 374 | end 375 | 376 | if (CurrentSize + 0.5) % 1 == 0 then 377 | local i = 14.5 - CurrentSize 378 | for a = i, 15 - i do 379 | Grid[14 * (i - 1) + a].BackgroundTransparency = 0.5 380 | Grid[14 * (a - 1) + i].BackgroundTransparency = 0.5 381 | Grid[14 * ((15 - i) - 1) + a].BackgroundTransparency = 0.5 382 | Grid[14 * (a - 1) + 15 - i].BackgroundTransparency = 0.5 383 | end 384 | end 385 | 386 | local OpenTween = self.OpenTween 387 | if CurrentSize == 7 and OpenTween then 388 | self.OpenTween:Stop() 389 | self.OpenTween = Tween.new(DRAW_DURATION, CHECKMARK_DRAW_BEZIER, self.DrawCheckmark, self) 390 | end 391 | end; 392 | 393 | EmptyCenter = function(self, x) 394 | x = x or 1 395 | local Grid = self.Grid 396 | local CurrentSize = 0.5 * ceil(14 * x) -- Ceil(Lerp(0, 7, x), 0.5) 397 | 398 | for i = 1, CurrentSize do 399 | local Start = 8 - i 400 | local End = 7 + i 401 | 402 | for a = Start, End do 403 | Grid[14 * (Start - 1) + a].BackgroundTransparency = 1 404 | Grid[14 * (a - 1) + Start].BackgroundTransparency = 1 405 | Grid[14 * (End - 1) + a].BackgroundTransparency = 1 406 | Grid[14 * (a - 1) + End].BackgroundTransparency = 1 407 | end 408 | end 409 | 410 | if (CurrentSize + 0.5) % 1 == 0 then 411 | local i = 0.5 + CurrentSize 412 | local Start = 8 - i 413 | local End = 7 + i 414 | 415 | for a = Start, End do 416 | local BackgroundTransparency = 0.5 * (self.ImageTransparency + 1) -- CompoundTransparency 417 | Grid[14 * (Start - 1) + a].BackgroundTransparency = BackgroundTransparency 418 | Grid[14 * (a - 1) + Start].BackgroundTransparency = BackgroundTransparency 419 | Grid[14 * (End - 1) + a].BackgroundTransparency = BackgroundTransparency 420 | Grid[14 * (a - 1) + End].BackgroundTransparency = BackgroundTransparency 421 | end 422 | end 423 | 424 | if CurrentSize == 7 then 425 | self.Button.Image = UNCHECKED_CHECKBOX_IMAGE 426 | self.Button.ImageTransparency = CHECKBOX_THEMES[self.Theme.Value].ImageTransparency 427 | self.GridFrame.Visible = false 428 | end 429 | end; 430 | 431 | EraseCheckmark = function(self, x) 432 | x = x or 1 433 | local Grid = self.Grid 434 | local ImageTransparency = self.ImageTransparency 435 | local XOffset, YOffset = self.XOffset, self.YOffset 436 | local HalfImageTransparency = 0.5 * (ImageTransparency + 1) -- CompoundTransparency 437 | local a = ceil(8*x + 1) -- ceil(Lerp(1, 9, x)) 438 | 439 | for a = 2, a do 440 | local Object1 = 14 * (a + XOffset - 1) + 15 - a + YOffset 441 | local Object2 = Object1 + 14 442 | local Object3 = Object2 + 1 443 | 444 | if Object1 > 0 then Grid[Object1].BackgroundTransparency = ImageTransparency end 445 | if Object2 > 0 then Grid[Object2].BackgroundTransparency = ImageTransparency end 446 | if Object3 > 0 then Grid[Object3].BackgroundTransparency = ImageTransparency end 447 | 448 | Grid[Object2 + 14].BackgroundTransparency = HalfImageTransparency 449 | Grid[Object3 + 14].BackgroundTransparency = ImageTransparency 450 | end 451 | 452 | local c = ceil(4*x + 5) -- Lerp(5, 9, x) 453 | local d = c - 4 454 | 455 | for c = 6, c do 456 | local e = 14 * (c + XOffset - 1) + c - 4 + YOffset 457 | Grid[e].BackgroundTransparency = ImageTransparency 458 | Grid[e + 13].BackgroundTransparency = ImageTransparency 459 | Grid[e + 14].BackgroundTransparency = ImageTransparency 460 | Grid[e + 28].BackgroundTransparency = HalfImageTransparency 461 | Grid[e + 27].BackgroundTransparency = ImageTransparency 462 | end 463 | 464 | local NewXOffset = floor(-5*x + 1) -- Lerp(1, -3 - 1, x) 465 | local NewYOffset = ceil(3*x - 1) -- Lerp(-1, 2, x) 466 | 467 | local XOffsetChange = XOffset - NewXOffset 468 | local YOffsetChange = NewYOffset - YOffset 469 | 470 | self.XOffset, self.YOffset = NewXOffset, NewYOffset 471 | 472 | -- Shift according to XOffsetChange and YOffsetChange 473 | for a = 1, XOffsetChange do 474 | for b = 1, 14 do 475 | for f = 1, 13 do 476 | Grid[14 * (f - 1) + b].BackgroundTransparency = Grid[14 * f + b].BackgroundTransparency 477 | end 478 | end 479 | end 480 | 481 | for a = 1, YOffsetChange do 482 | for b = 1, 14 do 483 | for f = 14, 2, -1 do 484 | Grid[14 * (b - 1) + f].BackgroundTransparency = Grid[14 * (b - 1) + f - 1].BackgroundTransparency 485 | end 486 | Grid[14 * (b - 1) + 1].BackgroundTransparency = ImageTransparency 487 | end 488 | end 489 | 490 | local OpenTween = self.OpenTween 491 | if a == 9 and OpenTween then 492 | self.OpenTween:Stop() 493 | for a = 1, 196 do 494 | Grid[a].BackgroundTransparency = ImageTransparency 495 | end 496 | self.OpenTween = Tween.new(FILL_DURATION, CENTER_EMPTY_BEZIER, self.EmptyCenter, self) 497 | end 498 | end 499 | }; 500 | 501 | WrappedProperties = { 502 | Button = {"AnchorPoint", "Name", "Parent", "Position", "LayoutOrder", "NextSelectionDown", "NextSelectionLeft", "NextSelectionRight", "NextSelectionUp"}; 503 | }; 504 | 505 | Properties = { 506 | Indeterminate = Typer.AssignSignature(2, Typer.Boolean, function(self, Indeterminate) 507 | if Indeterminate then 508 | if self.Checked then 509 | self:rawset("Checked", false) 510 | end 511 | self:SetColorAndTransparency(self.PrimaryColor3, 0) 512 | self:FillCenter() 513 | self.Button.Image = INDETERMINATE_CHECKBOX_IMAGE 514 | end 515 | 516 | self:rawset("Indeterminate", Indeterminate) 517 | end); 518 | 519 | Checked = Typer.AssignSignature(2, Typer.Boolean, function(self, Value) 520 | self:rawset("Checked", Value) 521 | self.Indeterminate = false 522 | 523 | if self.OpenTween then 524 | self.OpenTween:Stop() 525 | self.OpenTween2:Stop() 526 | self.OpenTween = false 527 | self.OpenTween2 = false 528 | end 529 | 530 | if Value then 531 | self:SetColorAndTransparency(self.PrimaryColor3, 0) 532 | self:FillCenter() 533 | self:DrawCheckmark() 534 | else 535 | local Theme = CHECKBOX_THEMES[self.Theme.Value] 536 | self:SetColorAndTransparency(Theme.ImageColor3, Theme.ImageTransparency) 537 | self:EraseCheckmark() 538 | self:EmptyCenter() 539 | end 540 | 541 | self.OnChecked:Fire(Value) 542 | end); 543 | 544 | ZIndex = Typer.AssignSignature(2, Typer.Number, function(self, ZIndex) 545 | local Grid = self.Grid 546 | self.GridFrame.ZIndex = ZIndex 547 | 548 | for a = 1, 196 do 549 | Grid[a].ZIndex = ZIndex 550 | end 551 | 552 | for Name in next, SETS do 553 | local Set = Grid[Name] 554 | for a = 1, #Set do 555 | Set[a].ZIndex = ZIndex 556 | end 557 | end 558 | 559 | self.Button.ZIndex = ZIndex + 1 560 | self:rawset("ZIndex", ZIndex) 561 | end); 562 | }; 563 | 564 | Events = {}; 565 | 566 | Methods = { 567 | SetChecked = Typer.AssignSignature(2, Typer.OptionalBoolean, function(self, NewChecked) 568 | if NewChecked == nil then NewChecked = not self.Checked end 569 | local Button = self.Button 570 | 571 | if self.OpenTween then 572 | self.OpenTween:Stop() 573 | self.OpenTween2:Stop() 574 | end 575 | 576 | if NewChecked ~= self.Checked then 577 | self.GridFrame.Visible = true 578 | 579 | if Button.Size == CHECKBOX_SIZE then 580 | if NewChecked then 581 | self:SetColorAndTransparency(self.PrimaryColor3, 0) 582 | Button.ImageTransparency = 1 583 | self.OpenTween = self.Indeterminate and Tween.new(DRAW_DURATION, CHECKMARK_DRAW_BEZIER, self.DrawCheckmark, self) or Tween.new(FILL_DURATION, CENTER_FILL_BEZIER, self.FillCenter, self) 584 | self.OpenTween2 = Tween.new(SHRINK_DURATION, OUTSIDE_TRANSPARENCY_BEZIER, self.ShrinkFrame, self) 585 | else 586 | local Theme = CHECKBOX_THEMES[self.Theme.Value] 587 | 588 | self:SetColorAndTransparency(Theme.ImageColor3, Theme.ImageTransparency) 589 | Button.ImageTransparency = 1 590 | self.XOffset, self.YOffset = 0, 0 591 | self.OpenTween = Tween.new(DRAW_DURATION, CHECKMARK_ERASE_BEZIER, self.EraseCheckmark, self) 592 | self.OpenTween2 = Tween.new(SHRINK_DURATION, OUTSIDE_TRANSPARENCY_BEZIER, self.ShrinkFrame, self) 593 | end 594 | 595 | self:rawset("Checked", NewChecked) 596 | self.Indeterminate = false -- These two lines happen implicitly for the self.Checked = NewChecked statement 597 | self.OnChecked:Fire(NewChecked) 598 | else 599 | self.Checked = NewChecked 600 | end 601 | end 602 | end); 603 | }; 604 | 605 | Init = function(self) 606 | self.Button = self.Template:Clone() 607 | self:rawset("Object", self.Button) 608 | local GridFrame = self.Button.GridFrame 609 | local Grid = {} 610 | 611 | -- Private 612 | self.Grid = Grid 613 | self.GridFrame = GridFrame 614 | 615 | for Name in next, SETS do 616 | local Count = 0 617 | local Objects = {} 618 | local Pixel = GridFrame:FindFirstChild(Name) 619 | 620 | while Pixel do 621 | Count = Count + 1 622 | Pixel.Name = "" 623 | Objects[Count] = Pixel 624 | Pixel = GridFrame:FindFirstChild(Name) 625 | end 626 | 627 | Grid[Name] = Objects 628 | end 629 | 630 | -- Track pixel grid 631 | for a = 1, 14*14 do 632 | Grid[a] = GridFrame[a] 633 | end 634 | 635 | local Mouse = Players.LocalPlayer:GetMouse() 636 | 637 | self.Button.MouseEnter:Connect(function() 638 | Mouse.Icon = "rbxassetid://1990755280" 639 | end) 640 | 641 | self.Button.MouseLeave:Connect(function() 642 | Mouse.Icon = "" 643 | end) 644 | 645 | self:superinit() 646 | 647 | -- Public 648 | self.ZIndex = 1 649 | end; 650 | }, SelectionController) 651 | --------------------------------------------------------------------------------