├── .gitattributes ├── .github └── workflows │ └── release.yml ├── .gitignore ├── Addons ├── InterfaceManager.lua └── SaveManager.lua ├── Assets ├── logodark.png └── logolight.png ├── Example.client.lua ├── Example.lua ├── LICENSE ├── README.md ├── aftman.toml ├── build ├── darklua.json ├── header.luau ├── init.luau └── modules │ ├── BuildCodegen.luau │ └── LuaEncode.luau ├── default.project.json ├── package.json ├── place.project.json ├── pnpm-lock.yaml ├── selene.toml ├── sourcemap.json └── src ├── Acrylic ├── AcrylicBlur.lua ├── AcrylicPaint.lua ├── CreateAcrylic.lua ├── Utils.lua └── init.lua ├── Components ├── Assets.lua ├── Button.lua ├── Dialog.lua ├── Element.lua ├── Notification.lua ├── Section.lua ├── Tab.lua ├── Textbox.lua ├── TitleBar.lua └── Window.lua ├── Creator.lua ├── Elements ├── Button.lua ├── Colorpicker.lua ├── Dropdown.lua ├── Input.lua ├── Keybind.lua ├── Paragraph.lua ├── Slider.lua ├── Toggle.lua └── init.lua ├── Icons.lua ├── Packages └── Flipper │ ├── BaseMotor.lua │ ├── BaseMotor.spec.lua │ ├── GroupMotor.lua │ ├── GroupMotor.spec.lua │ ├── Instant.lua │ ├── Instant.spec.lua │ ├── Linear.lua │ ├── Linear.spec.lua │ ├── Signal.lua │ ├── Signal.spec.lua │ ├── SingleMotor.lua │ ├── SingleMotor.spec.lua │ ├── Spring.lua │ ├── Spring.spec.lua │ ├── init.lua │ ├── isMotor.lua │ └── isMotor.spec.lua ├── Themes ├── Amethyst.lua ├── Aqua.lua ├── Dark.lua ├── Darker.lua ├── Light.lua ├── Rose.lua └── init.lua └── init.lua /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [ published, prereleased ] 7 | 8 | permissions: 9 | contents: write 10 | packages: write 11 | 12 | jobs: 13 | publish-release: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: GitHub Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Setup PNPM 21 | uses: pnpm/action-setup@v2.2.4 22 | with: 23 | version: 7.25.1 24 | run_install: true 25 | 26 | - name: Setup Aftman 27 | uses: ok-nick/setup-aftman@v0.3.0 28 | 29 | - name: Install Rojo, Lune and DarkLua 30 | run: aftman install 31 | 32 | - name: Build and Compile 33 | run: | 34 | pnpm run rojo --verbose 35 | 36 | - name: Bundle Luau 37 | run: pnpm run bundle 38 | 39 | - name: Upload Release Assets 40 | uses: alexellis/upload-assets@0.4.0 41 | env: 42 | ACTIONS_STEP_DEBUG: true 43 | GITHUB_TOKEN: ${{ github.token }} 44 | with: 45 | asset_paths: '["dist/main.rbxm", "dist/main.lua"]' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | .vscode -------------------------------------------------------------------------------- /Addons/InterfaceManager.lua: -------------------------------------------------------------------------------- 1 | local httpService = game:GetService("HttpService") 2 | 3 | local InterfaceManager = {} do 4 | InterfaceManager.Folder = "FluentSettings" 5 | InterfaceManager.Settings = { 6 | Theme = "Dark", 7 | Acrylic = true, 8 | Transparency = true, 9 | MenuKeybind = "LeftControl" 10 | } 11 | 12 | function InterfaceManager:SetFolder(folder) 13 | self.Folder = folder; 14 | self:BuildFolderTree() 15 | end 16 | 17 | function InterfaceManager:SetLibrary(library) 18 | self.Library = library 19 | end 20 | 21 | function InterfaceManager:BuildFolderTree() 22 | local paths = {} 23 | 24 | local parts = self.Folder:split("/") 25 | for idx = 1, #parts do 26 | paths[#paths + 1] = table.concat(parts, "/", 1, idx) 27 | end 28 | 29 | table.insert(paths, self.Folder) 30 | table.insert(paths, self.Folder .. "/settings") 31 | 32 | for i = 1, #paths do 33 | local str = paths[i] 34 | if not isfolder(str) then 35 | makefolder(str) 36 | end 37 | end 38 | end 39 | 40 | function InterfaceManager:SaveSettings() 41 | writefile(self.Folder .. "/options.json", httpService:JSONEncode(InterfaceManager.Settings)) 42 | end 43 | 44 | function InterfaceManager:LoadSettings() 45 | local path = self.Folder .. "/options.json" 46 | if isfile(path) then 47 | local data = readfile(path) 48 | local success, decoded = pcall(httpService.JSONDecode, httpService, data) 49 | 50 | if success then 51 | for i, v in next, decoded do 52 | InterfaceManager.Settings[i] = v 53 | end 54 | end 55 | end 56 | end 57 | 58 | function InterfaceManager:BuildInterfaceSection(tab) 59 | assert(self.Library, "Must set InterfaceManager.Library") 60 | local Library = self.Library 61 | local Settings = InterfaceManager.Settings 62 | 63 | InterfaceManager:LoadSettings() 64 | 65 | local section = tab:AddSection("Interface") 66 | 67 | local InterfaceTheme = section:AddDropdown("InterfaceTheme", { 68 | Title = "Theme", 69 | Description = "Changes the interface theme.", 70 | Values = Library.Themes, 71 | Default = Settings.Theme, 72 | Callback = function(Value) 73 | Library:SetTheme(Value) 74 | Settings.Theme = Value 75 | InterfaceManager:SaveSettings() 76 | end 77 | }) 78 | 79 | InterfaceTheme:SetValue(Settings.Theme) 80 | 81 | if Library.UseAcrylic then 82 | section:AddToggle("AcrylicToggle", { 83 | Title = "Acrylic", 84 | Description = "The blurred background requires graphic quality 8+", 85 | Default = Settings.Acrylic, 86 | Callback = function(Value) 87 | Library:ToggleAcrylic(Value) 88 | Settings.Acrylic = Value 89 | InterfaceManager:SaveSettings() 90 | end 91 | }) 92 | end 93 | 94 | section:AddToggle("TransparentToggle", { 95 | Title = "Transparency", 96 | Description = "Makes the interface transparent.", 97 | Default = Settings.Transparency, 98 | Callback = function(Value) 99 | Library:ToggleTransparency(Value) 100 | Settings.Transparency = Value 101 | InterfaceManager:SaveSettings() 102 | end 103 | }) 104 | 105 | local MenuKeybind = section:AddKeybind("MenuKeybind", { Title = "Minimize Bind", Default = Settings.MenuKeybind }) 106 | MenuKeybind:OnChanged(function() 107 | Settings.MenuKeybind = MenuKeybind.Value 108 | InterfaceManager:SaveSettings() 109 | end) 110 | Library.MinimizeKeybind = MenuKeybind 111 | end 112 | end 113 | 114 | return InterfaceManager -------------------------------------------------------------------------------- /Addons/SaveManager.lua: -------------------------------------------------------------------------------- 1 | local httpService = game:GetService("HttpService") 2 | 3 | local SaveManager = {} do 4 | SaveManager.Folder = "FluentSettings" 5 | SaveManager.Ignore = {} 6 | SaveManager.Parser = { 7 | Toggle = { 8 | Save = function(idx, object) 9 | return { type = "Toggle", idx = idx, value = object.Value } 10 | end, 11 | Load = function(idx, data) 12 | if SaveManager.Options[idx] then 13 | SaveManager.Options[idx]:SetValue(data.value) 14 | end 15 | end, 16 | }, 17 | Slider = { 18 | Save = function(idx, object) 19 | return { type = "Slider", idx = idx, value = tostring(object.Value) } 20 | end, 21 | Load = function(idx, data) 22 | if SaveManager.Options[idx] then 23 | SaveManager.Options[idx]:SetValue(data.value) 24 | end 25 | end, 26 | }, 27 | Dropdown = { 28 | Save = function(idx, object) 29 | return { type = "Dropdown", idx = idx, value = object.Value, mutli = object.Multi } 30 | end, 31 | Load = function(idx, data) 32 | if SaveManager.Options[idx] then 33 | SaveManager.Options[idx]:SetValue(data.value) 34 | end 35 | end, 36 | }, 37 | Colorpicker = { 38 | Save = function(idx, object) 39 | return { type = "Colorpicker", idx = idx, value = object.Value:ToHex(), transparency = object.Transparency } 40 | end, 41 | Load = function(idx, data) 42 | if SaveManager.Options[idx] then 43 | SaveManager.Options[idx]:SetValueRGB(Color3.fromHex(data.value), data.transparency) 44 | end 45 | end, 46 | }, 47 | Keybind = { 48 | Save = function(idx, object) 49 | return { type = "Keybind", idx = idx, mode = object.Mode, key = object.Value } 50 | end, 51 | Load = function(idx, data) 52 | if SaveManager.Options[idx] then 53 | SaveManager.Options[idx]:SetValue(data.key, data.mode) 54 | end 55 | end, 56 | }, 57 | 58 | Input = { 59 | Save = function(idx, object) 60 | return { type = "Input", idx = idx, text = object.Value } 61 | end, 62 | Load = function(idx, data) 63 | if SaveManager.Options[idx] and type(data.text) == "string" then 64 | SaveManager.Options[idx]:SetValue(data.text) 65 | end 66 | end, 67 | }, 68 | } 69 | 70 | function SaveManager:SetIgnoreIndexes(list) 71 | for _, key in next, list do 72 | self.Ignore[key] = true 73 | end 74 | end 75 | 76 | function SaveManager:SetFolder(folder) 77 | self.Folder = folder; 78 | self:BuildFolderTree() 79 | end 80 | 81 | function SaveManager:Save(name) 82 | if (not name) then 83 | return false, "no config file is selected" 84 | end 85 | 86 | local fullPath = self.Folder .. "/settings/" .. name .. ".json" 87 | 88 | local data = { 89 | objects = {} 90 | } 91 | 92 | for idx, option in next, SaveManager.Options do 93 | if not self.Parser[option.Type] then continue end 94 | if self.Ignore[idx] then continue end 95 | 96 | table.insert(data.objects, self.Parser[option.Type].Save(idx, option)) 97 | end 98 | 99 | local success, encoded = pcall(httpService.JSONEncode, httpService, data) 100 | if not success then 101 | return false, "failed to encode data" 102 | end 103 | 104 | writefile(fullPath, encoded) 105 | return true 106 | end 107 | 108 | function SaveManager:Load(name) 109 | if (not name) then 110 | return false, "no config file is selected" 111 | end 112 | 113 | local file = self.Folder .. "/settings/" .. name .. ".json" 114 | if not isfile(file) then return false, "invalid file" end 115 | 116 | local success, decoded = pcall(httpService.JSONDecode, httpService, readfile(file)) 117 | if not success then return false, "decode error" end 118 | 119 | for _, option in next, decoded.objects do 120 | if self.Parser[option.type] then 121 | task.spawn(function() self.Parser[option.type].Load(option.idx, option) end) -- task.spawn() so the config loading wont get stuck. 122 | end 123 | end 124 | 125 | return true 126 | end 127 | 128 | function SaveManager:IgnoreThemeSettings() 129 | self:SetIgnoreIndexes({ 130 | "InterfaceTheme", "AcrylicToggle", "TransparentToggle", "MenuKeybind" 131 | }) 132 | end 133 | 134 | function SaveManager:BuildFolderTree() 135 | local paths = { 136 | self.Folder, 137 | self.Folder .. "/settings" 138 | } 139 | 140 | for i = 1, #paths do 141 | local str = paths[i] 142 | if not isfolder(str) then 143 | makefolder(str) 144 | end 145 | end 146 | end 147 | 148 | function SaveManager:RefreshConfigList() 149 | local list = listfiles(self.Folder .. "/settings") 150 | 151 | local out = {} 152 | for i = 1, #list do 153 | local file = list[i] 154 | if file:sub(-5) == ".json" then 155 | local pos = file:find(".json", 1, true) 156 | local start = pos 157 | 158 | local char = file:sub(pos, pos) 159 | while char ~= "/" and char ~= "\\" and char ~= "" do 160 | pos = pos - 1 161 | char = file:sub(pos, pos) 162 | end 163 | 164 | if char == "/" or char == "\\" then 165 | local name = file:sub(pos + 1, start - 1) 166 | if name ~= "options" then 167 | table.insert(out, name) 168 | end 169 | end 170 | end 171 | end 172 | 173 | return out 174 | end 175 | 176 | function SaveManager:SetLibrary(library) 177 | self.Library = library 178 | self.Options = library.Options 179 | end 180 | 181 | function SaveManager:LoadAutoloadConfig() 182 | if isfile(self.Folder .. "/settings/autoload.txt") then 183 | local name = readfile(self.Folder .. "/settings/autoload.txt") 184 | 185 | local success, err = self:Load(name) 186 | if not success then 187 | return self.Library:Notify({ 188 | Title = "Interface", 189 | Content = "Config loader", 190 | SubContent = "Failed to load autoload config: " .. err, 191 | Duration = 7 192 | }) 193 | end 194 | 195 | self.Library:Notify({ 196 | Title = "Interface", 197 | Content = "Config loader", 198 | SubContent = string.format("Auto loaded config %q", name), 199 | Duration = 7 200 | }) 201 | end 202 | end 203 | 204 | function SaveManager:BuildConfigSection(tab) 205 | assert(self.Library, "Must set SaveManager.Library") 206 | 207 | local section = tab:AddSection("Configuration") 208 | 209 | section:AddInput("SaveManager_ConfigName", { Title = "Config name" }) 210 | section:AddDropdown("SaveManager_ConfigList", { Title = "Config list", Values = self:RefreshConfigList(), AllowNull = true }) 211 | 212 | section:AddButton({ 213 | Title = "Create config", 214 | Callback = function() 215 | local name = SaveManager.Options.SaveManager_ConfigName.Value 216 | 217 | if name:gsub(" ", "") == "" then 218 | return self.Library:Notify({ 219 | Title = "Interface", 220 | Content = "Config loader", 221 | SubContent = "Invalid config name (empty)", 222 | Duration = 7 223 | }) 224 | end 225 | 226 | local success, err = self:Save(name) 227 | if not success then 228 | return self.Library:Notify({ 229 | Title = "Interface", 230 | Content = "Config loader", 231 | SubContent = "Failed to save config: " .. err, 232 | Duration = 7 233 | }) 234 | end 235 | 236 | self.Library:Notify({ 237 | Title = "Interface", 238 | Content = "Config loader", 239 | SubContent = string.format("Created config %q", name), 240 | Duration = 7 241 | }) 242 | 243 | SaveManager.Options.SaveManager_ConfigList:SetValues(self:RefreshConfigList()) 244 | SaveManager.Options.SaveManager_ConfigList:SetValue(nil) 245 | end 246 | }) 247 | 248 | section:AddButton({Title = "Load config", Callback = function() 249 | local name = SaveManager.Options.SaveManager_ConfigList.Value 250 | 251 | local success, err = self:Load(name) 252 | if not success then 253 | return self.Library:Notify({ 254 | Title = "Interface", 255 | Content = "Config loader", 256 | SubContent = "Failed to load config: " .. err, 257 | Duration = 7 258 | }) 259 | end 260 | 261 | self.Library:Notify({ 262 | Title = "Interface", 263 | Content = "Config loader", 264 | SubContent = string.format("Loaded config %q", name), 265 | Duration = 7 266 | }) 267 | end}) 268 | 269 | section:AddButton({Title = "Overwrite config", Callback = function() 270 | local name = SaveManager.Options.SaveManager_ConfigList.Value 271 | 272 | local success, err = self:Save(name) 273 | if not success then 274 | return self.Library:Notify({ 275 | Title = "Interface", 276 | Content = "Config loader", 277 | SubContent = "Failed to overwrite config: " .. err, 278 | Duration = 7 279 | }) 280 | end 281 | 282 | self.Library:Notify({ 283 | Title = "Interface", 284 | Content = "Config loader", 285 | SubContent = string.format("Overwrote config %q", name), 286 | Duration = 7 287 | }) 288 | end}) 289 | 290 | section:AddButton({Title = "Refresh list", Callback = function() 291 | SaveManager.Options.SaveManager_ConfigList:SetValues(self:RefreshConfigList()) 292 | SaveManager.Options.SaveManager_ConfigList:SetValue(nil) 293 | end}) 294 | 295 | local AutoloadButton 296 | AutoloadButton = section:AddButton({Title = "Set as autoload", Description = "Current autoload config: none", Callback = function() 297 | local name = SaveManager.Options.SaveManager_ConfigList.Value 298 | writefile(self.Folder .. "/settings/autoload.txt", name) 299 | AutoloadButton:SetDesc("Current autoload config: " .. name) 300 | self.Library:Notify({ 301 | Title = "Interface", 302 | Content = "Config loader", 303 | SubContent = string.format("Set %q to auto load", name), 304 | Duration = 7 305 | }) 306 | end}) 307 | 308 | if isfile(self.Folder .. "/settings/autoload.txt") then 309 | local name = readfile(self.Folder .. "/settings/autoload.txt") 310 | AutoloadButton:SetDesc("Current autoload config: " .. name) 311 | end 312 | 313 | SaveManager:SetIgnoreIndexes({ "SaveManager_ConfigList", "SaveManager_ConfigName" }) 314 | end 315 | 316 | SaveManager:BuildFolderTree() 317 | end 318 | 319 | return SaveManager -------------------------------------------------------------------------------- /Assets/logodark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawid-scripts/Fluent/78ba067d6f927fe45557c79da05853331b89480e/Assets/logodark.png -------------------------------------------------------------------------------- /Assets/logolight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawid-scripts/Fluent/78ba067d6f927fe45557c79da05853331b89480e/Assets/logolight.png -------------------------------------------------------------------------------- /Example.client.lua: -------------------------------------------------------------------------------- 1 | local Main = require(game:GetService("ReplicatedStorage"):WaitForChild("Fluent")) 2 | 3 | local Window = Main:CreateWindow({ 4 | Title = "Fluent " .. Main.Version, 5 | SubTitle = "by dawid", 6 | TabWidth = 160, 7 | Size = UDim2.fromOffset(580, 460), 8 | Acrylic = true, 9 | Theme = "Dark" 10 | }) 11 | 12 | local Tabs = { 13 | Main = Window:AddTab({ Title = "Main", Icon = "box" }), 14 | Settings = Window:AddTab({ Title = "Settings", Icon = "settings" }) 15 | } 16 | 17 | do 18 | Tabs.Main:AddParagraph({ 19 | Title = "Paragraph", 20 | Content = "This is a paragraph.\nSecond line!" 21 | }) 22 | 23 | Tabs.Main:AddButton({ 24 | Title = "Button", 25 | Description = "Very important button", 26 | Callback = function() 27 | Window:Dialog({ 28 | Title = "Title", 29 | Content = "This is a dialog", 30 | Buttons = { 31 | { 32 | Title = "Confirm", 33 | Callback = function() 34 | Window:Dialog({ 35 | Title = "Another Dialog", 36 | Content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse mollis dolor eget erat mattis, id mollis mauris cursus. Proin ornare sollicitudin odio, id posuere diam luctus id.", 37 | Buttons = { { Title = "Ok", Callback = function() print("Ok") end} } 38 | }) 39 | Main.Options.Toggle:Destroy() 40 | end 41 | }, 42 | { 43 | Title = "Cancel", 44 | Callback = function() 45 | print("Cancelled the dialog.") 46 | end 47 | } 48 | } 49 | }) 50 | end 51 | }) 52 | 53 | local Toggle = Tabs.Main:AddToggle("Toggle", {Title = "Toggle", Default = false }) 54 | 55 | local Slider = Tabs.Main:AddSlider("Slider", { 56 | Title = "Slider", 57 | Description = "This is a slider", 58 | Default = 2.0, 59 | Min = 0.0, 60 | Max = 15.5, 61 | Rounding = 1 62 | }) 63 | 64 | 65 | local Dropdown = Tabs.Main:AddDropdown("Dropdown", { 66 | Title = "Dropdown", 67 | Values = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen"}, 68 | Multi = false, 69 | Default = 1, 70 | }) 71 | 72 | Dropdown:SetValue("four") 73 | 74 | local MultiDropdown = Tabs.Main:AddDropdown("MultiDropdown", { 75 | Title = "Dropdown", 76 | Description = "You can select multiple values.", 77 | Values = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen"}, 78 | Multi = true, 79 | Default = {"seven", "twelve"}, 80 | }) 81 | 82 | MultiDropdown:SetValue({ 83 | three = true, 84 | five = true, 85 | seven = false 86 | }) 87 | 88 | local Colorpicker = Tabs.Main:AddColorpicker("Colorpicker", { 89 | Title = "Colorpicker", 90 | Default = Color3.fromRGB(96, 205, 255) 91 | }) 92 | 93 | local TColorpicker = Tabs.Main:AddColorpicker("TransparencyColorpicker", { 94 | Title = "Colorpicker", 95 | Description = "but you can change the transparency.", 96 | Transparency = 0, 97 | Default = Color3.fromRGB(96, 205, 255) 98 | }) 99 | 100 | local Keybind = Tabs.Main:AddKeybind("Keybind", { 101 | Title = "KeyBind", 102 | Mode = "Hold", 103 | Default = "LeftControl", 104 | ChangedCallback = function(New) 105 | print("Keybind changed:", New) 106 | end 107 | }) 108 | 109 | local Input = Tabs.Main:AddInput("Input", { 110 | Title = "Input", 111 | Default = "Default", 112 | Numeric = false, 113 | Finished = false, 114 | Placeholder = "Placeholder text", 115 | Callback = function(Value) 116 | print("Input changed:", Value) 117 | end 118 | }) 119 | 120 | Toggle:OnChanged(function() 121 | print("Toggle changed:", Main.Options["Toggle"].Value) 122 | end) 123 | 124 | Slider:OnChanged(function(Value) 125 | print("Slider changed:", Value) 126 | end) 127 | 128 | Dropdown:OnChanged(function(Value) 129 | print("Dropdown changed:", Value) 130 | end) 131 | 132 | MultiDropdown:OnChanged(function(Value) 133 | local Values = {} 134 | for Value, State in next, Value do 135 | table.insert(Values, Value) 136 | end 137 | print("Mutlidropdown changed:", table.concat(Values, ", ")) 138 | end) 139 | 140 | Colorpicker:OnChanged(function() 141 | print("Colorpicker changed:", TColorpicker.Value) 142 | end) 143 | 144 | TColorpicker:OnChanged(function() 145 | print( 146 | "TColorpicker changed:", TColorpicker.Value, 147 | "Transparency:", TColorpicker.Transparency 148 | ) 149 | end) 150 | 151 | task.spawn(function() 152 | while true do 153 | wait(1) 154 | local state = Keybind:GetState() 155 | if state then 156 | print("Keybind is being held down") 157 | end 158 | if Main.Unloaded then break end 159 | end 160 | end) 161 | 162 | end 163 | 164 | do 165 | 166 | local InterfaceSection = Tabs.Settings:AddSection("Interface") 167 | 168 | InterfaceSection:AddDropdown("InterfaceTheme", { 169 | Title = "Theme", 170 | Description = "Changes the interface theme.", 171 | Values = Main.Themes, 172 | Default = Main.Theme, 173 | Callback = function(Value) 174 | Main:SetTheme(Value) 175 | end 176 | }) 177 | 178 | if Main.UseAcrylic then 179 | InterfaceSection:AddToggle("AcrylicToggle", { 180 | Title = "Acrylic", 181 | Description = "The blurred background requires graphic quality 8+", 182 | Default = Main.Acrylic, 183 | Callback = function(Value) 184 | Main:ToggleAcrylic(Value) 185 | end 186 | }) 187 | end 188 | 189 | InterfaceSection:AddToggle("TransparentToggle", { 190 | Title = "Transparency", 191 | Description = "Makes the interface transparent.", 192 | Default = Main.Transparency, 193 | Callback = function(Value) 194 | Main:ToggleTransparency(Value) 195 | end 196 | }) 197 | 198 | InterfaceSection:AddKeybind("MenuKeybind", { Title = "Minimize Bind", Default = "RightShift" }) 199 | Main.MinimizeKeybind = Main.Options.MenuKeybind 200 | end 201 | 202 | Main:Notify({ 203 | Title = "Fluent", 204 | Content = "The script has been loaded.", 205 | Duration = 8 206 | }) -------------------------------------------------------------------------------- /Example.lua: -------------------------------------------------------------------------------- 1 | local Fluent = loadstring(game:HttpGet("https://github.com/dawid-scripts/Fluent/releases/latest/download/main.lua"))() 2 | local SaveManager = loadstring(game:HttpGet("https://raw.githubusercontent.com/dawid-scripts/Fluent/master/Addons/SaveManager.lua"))() 3 | local InterfaceManager = loadstring(game:HttpGet("https://raw.githubusercontent.com/dawid-scripts/Fluent/master/Addons/InterfaceManager.lua"))() 4 | 5 | local Window = Fluent:CreateWindow({ 6 | Title = "Fluent " .. Fluent.Version, 7 | SubTitle = "by dawid", 8 | TabWidth = 160, 9 | Size = UDim2.fromOffset(580, 460), 10 | Acrylic = true, -- The blur may be detectable, setting this to false disables blur entirely 11 | Theme = "Dark", 12 | MinimizeKey = Enum.KeyCode.LeftControl -- Used when theres no MinimizeKeybind 13 | }) 14 | 15 | --Fluent provides Lucide Icons https://lucide.dev/icons/ for the tabs, icons are optional 16 | local Tabs = { 17 | Main = Window:AddTab({ Title = "Main", Icon = "" }), 18 | Settings = Window:AddTab({ Title = "Settings", Icon = "settings" }) 19 | } 20 | 21 | local Options = Fluent.Options 22 | 23 | do 24 | Fluent:Notify({ 25 | Title = "Notification", 26 | Content = "This is a notification", 27 | SubContent = "SubContent", -- Optional 28 | Duration = 5 -- Set to nil to make the notification not disappear 29 | }) 30 | 31 | 32 | 33 | Tabs.Main:AddParagraph({ 34 | Title = "Paragraph", 35 | Content = "This is a paragraph.\nSecond line!" 36 | }) 37 | 38 | 39 | 40 | Tabs.Main:AddButton({ 41 | Title = "Button", 42 | Description = "Very important button", 43 | Callback = function() 44 | Window:Dialog({ 45 | Title = "Title", 46 | Content = "This is a dialog", 47 | Buttons = { 48 | { 49 | Title = "Confirm", 50 | Callback = function() 51 | print("Confirmed the dialog.") 52 | end 53 | }, 54 | { 55 | Title = "Cancel", 56 | Callback = function() 57 | print("Cancelled the dialog.") 58 | end 59 | } 60 | } 61 | }) 62 | end 63 | }) 64 | 65 | 66 | 67 | local Toggle = Tabs.Main:AddToggle("MyToggle", {Title = "Toggle", Default = false }) 68 | 69 | Toggle:OnChanged(function() 70 | print("Toggle changed:", Options.MyToggle.Value) 71 | end) 72 | 73 | Options.MyToggle:SetValue(false) 74 | 75 | 76 | 77 | local Slider = Tabs.Main:AddSlider("Slider", { 78 | Title = "Slider", 79 | Description = "This is a slider", 80 | Default = 2, 81 | Min = 0, 82 | Max = 5, 83 | Rounding = 1, 84 | Callback = function(Value) 85 | print("Slider was changed:", Value) 86 | end 87 | }) 88 | 89 | Slider:OnChanged(function(Value) 90 | print("Slider changed:", Value) 91 | end) 92 | 93 | Slider:SetValue(3) 94 | 95 | 96 | 97 | local Dropdown = Tabs.Main:AddDropdown("Dropdown", { 98 | Title = "Dropdown", 99 | Values = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen"}, 100 | Multi = false, 101 | Default = 1, 102 | }) 103 | 104 | Dropdown:SetValue("four") 105 | 106 | Dropdown:OnChanged(function(Value) 107 | print("Dropdown changed:", Value) 108 | end) 109 | 110 | 111 | 112 | local MultiDropdown = Tabs.Main:AddDropdown("MultiDropdown", { 113 | Title = "Dropdown", 114 | Description = "You can select multiple values.", 115 | Values = {"one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen"}, 116 | Multi = true, 117 | Default = {"seven", "twelve"}, 118 | }) 119 | 120 | MultiDropdown:SetValue({ 121 | three = true, 122 | five = true, 123 | seven = false 124 | }) 125 | 126 | MultiDropdown:OnChanged(function(Value) 127 | local Values = {} 128 | for Value, State in next, Value do 129 | table.insert(Values, Value) 130 | end 131 | print("Mutlidropdown changed:", table.concat(Values, ", ")) 132 | end) 133 | 134 | 135 | 136 | local Colorpicker = Tabs.Main:AddColorpicker("Colorpicker", { 137 | Title = "Colorpicker", 138 | Default = Color3.fromRGB(96, 205, 255) 139 | }) 140 | 141 | Colorpicker:OnChanged(function() 142 | print("Colorpicker changed:", Colorpicker.Value) 143 | end) 144 | 145 | Colorpicker:SetValueRGB(Color3.fromRGB(0, 255, 140)) 146 | 147 | 148 | 149 | local TColorpicker = Tabs.Main:AddColorpicker("TransparencyColorpicker", { 150 | Title = "Colorpicker", 151 | Description = "but you can change the transparency.", 152 | Transparency = 0, 153 | Default = Color3.fromRGB(96, 205, 255) 154 | }) 155 | 156 | TColorpicker:OnChanged(function() 157 | print( 158 | "TColorpicker changed:", TColorpicker.Value, 159 | "Transparency:", TColorpicker.Transparency 160 | ) 161 | end) 162 | 163 | 164 | 165 | local Keybind = Tabs.Main:AddKeybind("Keybind", { 166 | Title = "KeyBind", 167 | Mode = "Toggle", -- Always, Toggle, Hold 168 | Default = "LeftControl", -- String as the name of the keybind (MB1, MB2 for mouse buttons) 169 | 170 | -- Occurs when the keybind is clicked, Value is `true`/`false` 171 | Callback = function(Value) 172 | print("Keybind clicked!", Value) 173 | end, 174 | 175 | -- Occurs when the keybind itself is changed, `New` is a KeyCode Enum OR a UserInputType Enum 176 | ChangedCallback = function(New) 177 | print("Keybind changed!", New) 178 | end 179 | }) 180 | 181 | -- OnClick is only fired when you press the keybind and the mode is Toggle 182 | -- Otherwise, you will have to use Keybind:GetState() 183 | Keybind:OnClick(function() 184 | print("Keybind clicked:", Keybind:GetState()) 185 | end) 186 | 187 | Keybind:OnChanged(function() 188 | print("Keybind changed:", Keybind.Value) 189 | end) 190 | 191 | task.spawn(function() 192 | while true do 193 | wait(1) 194 | 195 | -- example for checking if a keybind is being pressed 196 | local state = Keybind:GetState() 197 | if state then 198 | print("Keybind is being held down") 199 | end 200 | 201 | if Fluent.Unloaded then break end 202 | end 203 | end) 204 | 205 | Keybind:SetValue("MB2", "Toggle") -- Sets keybind to MB2, mode to Hold 206 | 207 | 208 | local Input = Tabs.Main:AddInput("Input", { 209 | Title = "Input", 210 | Default = "Default", 211 | Placeholder = "Placeholder", 212 | Numeric = false, -- Only allows numbers 213 | Finished = false, -- Only calls callback when you press enter 214 | Callback = function(Value) 215 | print("Input changed:", Value) 216 | end 217 | }) 218 | 219 | Input:OnChanged(function() 220 | print("Input updated:", Input.Value) 221 | end) 222 | end 223 | 224 | 225 | -- Addons: 226 | -- SaveManager (Allows you to have a configuration system) 227 | -- InterfaceManager (Allows you to have a interface managment system) 228 | 229 | -- Hand the library over to our managers 230 | SaveManager:SetLibrary(Fluent) 231 | InterfaceManager:SetLibrary(Fluent) 232 | 233 | -- Ignore keys that are used by ThemeManager. 234 | -- (we dont want configs to save themes, do we?) 235 | SaveManager:IgnoreThemeSettings() 236 | 237 | -- You can add indexes of elements the save manager should ignore 238 | SaveManager:SetIgnoreIndexes({}) 239 | 240 | -- use case for doing it this way: 241 | -- a script hub could have themes in a global folder 242 | -- and game configs in a separate folder per game 243 | InterfaceManager:SetFolder("FluentScriptHub") 244 | SaveManager:SetFolder("FluentScriptHub/specific-game") 245 | 246 | InterfaceManager:BuildInterfaceSection(Tabs.Settings) 247 | SaveManager:BuildConfigSection(Tabs.Settings) 248 | 249 | 250 | Window:SelectTab(1) 251 | 252 | Fluent:Notify({ 253 | Title = "Fluent", 254 | Content = "The script has been loaded.", 255 | Duration = 8 256 | }) 257 | 258 | -- You can use the SaveManager:LoadAutoloadConfig() to load a config 259 | -- which has been marked to be one that auto loads! 260 | SaveManager:LoadAutoloadConfig() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 dawid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fluent 2 | fluent 3 | 4 | ## ⚡ Features 5 | 6 | - Modern design 7 | - Many customization options 8 | - Almost any UI Element you would ever need 9 |
10 | 11 | ## 🔌 Installation 12 | 13 | You can load Fluent through a GitHub Release: 14 | 15 | ```lua 16 | local Fluent = loadstring(game:HttpGet("https://github.com/dawid-scripts/Fluent/releases/latest/download/main.lua"))() 17 | ``` 18 |
19 | 20 | ## 📜 Usage 21 | 22 | [Example Script](https://github.com/dawid-scripts/Fluent/blob/master/Example.lua) 23 |
24 | 25 | ## Credits 26 | 27 | - [richie0866/remote-spy](https://github.com/richie0866/remote-spy) - Assets for the UI, some of the code 28 | - [violin-suzutsuki/LinoriaLib](https://github.com/violin-suzutsuki/LinoriaLib) - Code for most of the elements, save manager 29 | - [7kayoh/Acrylic](https://github.com/7kayoh/Acrylic) - Porting richie0866's acrylic module to lua 30 | - [Latte Softworks & Kotera](https://discord.gg/rMMByr4qas) - Bundler -------------------------------------------------------------------------------- /aftman.toml: -------------------------------------------------------------------------------- 1 | # This file lists tools managed by Aftman, a cross-platform toolchain manager. 2 | # For more information, see https://github.com/LPGhatguy/aftman 3 | 4 | # To add a new tool, add an entry to this table. 5 | [tools] 6 | rojo = "rojo-rbx/rojo@7.3.0" 7 | darklua = "seaofvoices/darklua@0.9.0" 8 | lune = "filiptibell/lune@0.7.6" 9 | -------------------------------------------------------------------------------- /build/darklua.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator": { 3 | "name": "dense", 4 | "column_span": 10000000000 5 | }, 6 | "rules": [ 7 | "convert_local_function_to_assign", 8 | "convert_index_to_field", 9 | "compute_expression", 10 | "group_local_assignment", 11 | "filter_after_early_return", 12 | "remove_comments", 13 | "remove_empty_do", 14 | "remove_function_call_parens", 15 | "remove_nil_declaration", 16 | "remove_method_definition", 17 | "remove_spaces", 18 | "remove_unused_if_branch", 19 | "remove_unused_while", 20 | { 21 | "rule": "rename_variables", 22 | "include_functions": true 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /build/header.luau: -------------------------------------------------------------------------------- 1 | --[[ 2 | Fluent Interface Suite 3 | This script is not intended to be modified. 4 | To view the source code, see the 'src' folder on GitHub! 5 | 6 | Author: dawid 7 | License: MIT 8 | GitHub: https://github.com/dawid-scripts/Fluent 9 | --]] 10 | -------------------------------------------------------------------------------- /build/init.luau: -------------------------------------------------------------------------------- 1 | local fs = require("@lune/fs") 2 | local process = require("@lune/process") 3 | local BuildCodegen = require("modules/BuildCodegen") 4 | 5 | local FilePath = "dist/main.lua" 6 | local TempFile = "dist/temp.lua" 7 | local Header = fs.readFile("build/header.luau") 8 | 9 | local function minify(text) 10 | fs.writeFile(TempFile, text) 11 | process.spawn("darklua", {"process", TempFile, TempFile, "--config", "build/darklua.json"}) 12 | local value = fs.readFile(TempFile) 13 | fs.removeFile(TempFile) 14 | return value 15 | end 16 | 17 | local Module = fs.readFile("dist/main.rbxm") 18 | local ModelCodegen = BuildCodegen(Module, false) 19 | 20 | fs.writeFile(FilePath, Header .. "\n" .. minify(ModelCodegen)) -------------------------------------------------------------------------------- /build/modules/BuildCodegen.luau: -------------------------------------------------------------------------------- 1 | -- MIT License | Copyright (c) 2023 Latte Softworks & Kotera 2 | 3 | local roblox = require("@lune/roblox") 4 | 5 | local LuaEncode = require("LuaEncode") 6 | 7 | -- Init script template for codegen (in this, we'll maintain 5.1 *syntax* compat) 8 | local InitScriptTemplate = [[ 9 | -- Will be used later for getting flattened globals 10 | local ImportGlobals 11 | 12 | -- Holds the actual DOM data 13 | local ObjectTree = ${ObjectTree} 14 | 15 | -- Holds direct closure data 16 | local ClosureBindings = ${ClosureBindings} -- [RefId] = Closure 17 | 18 | -- Set up from data 19 | do 20 | -- Localizing certain libraries and built-ins for runtime efficiency 21 | local task, setmetatable, error, newproxy, getmetatable, next, table, unpack, coroutine, script, type, require, pcall, getfenv, setfenv, rawget= task, setmetatable, error, newproxy, getmetatable, next, table, unpack, coroutine, script, type, require, pcall, getfenv, setfenv, rawget 22 | 23 | local table_insert = table.insert 24 | local table_remove = table.remove 25 | 26 | -- lol 27 | local table_freeze = table.freeze or function(t) return t end 28 | 29 | -- If we're not running on Roblox or Lune runtime, we won't have a task library 30 | local Defer = task and task.defer or function(f, ...) 31 | local Thread = coroutine.create(f) 32 | coroutine.resume(Thread, ...) 33 | return Thread 34 | end 35 | 36 | -- `maui.Version` compat 37 | local Version = "0.0.0-venv" 38 | 39 | local RefBindings = {} -- [RefId] = RealObject 40 | 41 | local ScriptClosures = {} 42 | local StoredModuleValues = {} 43 | local ScriptsToRun = {} 44 | 45 | -- maui.Shared 46 | local SharedEnvironment = {} 47 | 48 | -- We're creating 'fake' instance refs soley for traversal of the DOM for require() compatibility 49 | -- It's meant to be as lazy as possible lol 50 | local RefChildren = {} -- [Ref] = {ChildrenRef, ...} 51 | 52 | -- Implemented instance methods 53 | local InstanceMethods = { 54 | GetChildren = function(self) 55 | local Children = RefChildren[self] 56 | local ReturnArray = {} 57 | 58 | for Child in next, Children do 59 | table_insert(ReturnArray, Child) 60 | end 61 | 62 | return ReturnArray 63 | end, 64 | 65 | -- Not implementing `recursive` arg, as it isn't needed for us here 66 | FindFirstChild = function(self, name) 67 | if not name then 68 | error("Argument 1 missing or nil", 2) 69 | end 70 | 71 | for Child in next, RefChildren[self] do 72 | if Child.Name == name then 73 | return Child 74 | end 75 | end 76 | 77 | return 78 | end, 79 | 80 | GetFullName = function(self) 81 | local Path = self.Name 82 | local ObjectPointer = self.Parent 83 | 84 | while ObjectPointer do 85 | Path = ObjectPointer.Name .. "." .. Path 86 | 87 | -- Move up the DOM (parent will be nil at the end, and this while loop will stop) 88 | ObjectPointer = ObjectPointer.Parent 89 | end 90 | 91 | return "${EnvName}." .. Path 92 | end, 93 | } 94 | 95 | -- "Proxies" to instance methods, with err checks etc 96 | local InstanceMethodProxies = {} 97 | for MethodName, Method in next, InstanceMethods do 98 | InstanceMethodProxies[MethodName] = function(self, ...) 99 | if not RefChildren[self] then 100 | error("Expected ':' not '.' calling member function " .. MethodName, 1) 101 | end 102 | 103 | return Method(self, ...) 104 | end 105 | end 106 | 107 | local function CreateRef(className, name, parent) 108 | -- `name` and `parent` can also be set later by the init script if they're absent 109 | 110 | -- Extras 111 | local StringValue_Value 112 | 113 | -- Will be set to RefChildren later aswell 114 | local Children = setmetatable({}, {__mode = "k"}) 115 | 116 | -- Err funcs 117 | local function InvalidMember(member) 118 | error(member .. " is not a valid (virtual) member of " .. className .. " \"" .. name .. "\"", 1) 119 | end 120 | 121 | local function ReadOnlyProperty(property) 122 | error("Unable to assign (virtual) property " .. property .. ". Property is read only", 1) 123 | end 124 | 125 | local Ref = newproxy(true) 126 | local RefMetatable = getmetatable(Ref) 127 | 128 | RefMetatable.__index = function(_, index) 129 | if index == "ClassName" then -- First check "properties" 130 | return className 131 | elseif index == "Name" then 132 | return name 133 | elseif index == "Parent" then 134 | return parent 135 | elseif className == "StringValue" and index == "Value" then 136 | -- Supporting StringValue.Value for Rojo .txt file conv 137 | return StringValue_Value 138 | else -- Lastly, check "methods" 139 | local InstanceMethod = InstanceMethodProxies[index] 140 | 141 | if InstanceMethod then 142 | return InstanceMethod 143 | end 144 | end 145 | 146 | -- Next we'll look thru child refs 147 | for Child in next, Children do 148 | if Child.Name == index then 149 | return Child 150 | end 151 | end 152 | 153 | -- At this point, no member was found; this is the same err format as Roblox 154 | InvalidMember(index) 155 | end 156 | 157 | RefMetatable.__newindex = function(_, index, value) 158 | -- __newindex is only for props fyi 159 | if index == "ClassName" then 160 | ReadOnlyProperty(index) 161 | elseif index == "Name" then 162 | name = value 163 | elseif index == "Parent" then 164 | -- We'll just ignore the process if it's trying to set itself 165 | if value == Ref then 166 | return 167 | end 168 | 169 | if parent ~= nil then 170 | -- Remove this ref from the CURRENT parent 171 | RefChildren[parent][Ref] = nil 172 | end 173 | 174 | parent = value 175 | 176 | if value ~= nil then 177 | -- And NOW we're setting the new parent 178 | RefChildren[value][Ref] = true 179 | end 180 | elseif className == "StringValue" and index == "Value" then 181 | -- Supporting StringValue.Value for Rojo .txt file conv 182 | StringValue_Value = value 183 | else 184 | -- Same err as __index when no member is found 185 | InvalidMember(index) 186 | end 187 | end 188 | 189 | RefMetatable.__tostring = function() 190 | return name 191 | end 192 | 193 | RefChildren[Ref] = Children 194 | 195 | if parent ~= nil then 196 | RefChildren[parent][Ref] = true 197 | end 198 | 199 | return Ref 200 | end 201 | 202 | -- Create real ref DOM from object tree 203 | local function CreateRefFromObject(object, parent) 204 | local RefId = object[1] 205 | local ClassName = object[2] 206 | local Properties = object[3] 207 | local Children = object[4] -- Optional 208 | 209 | local Name = table_remove(Properties, 1) 210 | 211 | local Ref = CreateRef(ClassName, Name, parent) -- 3rd arg may be nil if this is from root 212 | RefBindings[RefId] = Ref 213 | 214 | if Properties then 215 | for PropertyName, PropertyValue in next, Properties do 216 | Ref[PropertyName] = PropertyValue 217 | end 218 | end 219 | 220 | if Children then 221 | for _, ChildObject in next, Children do 222 | CreateRefFromObject(ChildObject, Ref) 223 | end 224 | end 225 | 226 | return Ref 227 | end 228 | 229 | local RealObjectRoot = {} 230 | for _, Object in next, ObjectTree do 231 | table_insert(RealObjectRoot, CreateRefFromObject(Object)) 232 | end 233 | 234 | -- Now we'll set script closure refs and check if they should be ran as a BaseScript 235 | for RefId, Closure in next, ClosureBindings do 236 | local Ref = RefBindings[RefId] 237 | 238 | ScriptClosures[Ref] = Closure 239 | 240 | local ClassName = Ref.ClassName 241 | if ClassName == "LocalScript" or ClassName == "Script" then 242 | table_insert(ScriptsToRun, Ref) 243 | end 244 | end 245 | 246 | local function LoadScript(scriptRef) 247 | local ScriptClassName = scriptRef.ClassName 248 | 249 | -- First we'll check for a cached module value (packed into a tbl) 250 | local StoredModuleValue = StoredModuleValues[scriptRef] 251 | if StoredModuleValue and ScriptClassName == "ModuleScript" then 252 | return unpack(StoredModuleValue) 253 | end 254 | 255 | local Closure = ScriptClosures[scriptRef] 256 | if not Closure then 257 | return 258 | end 259 | 260 | -- If it's a BaseScript, we'll just run it directly! 261 | if ScriptClassName == "LocalScript" or ScriptClassName == "Script" then 262 | Closure() 263 | return 264 | else 265 | local ClosureReturn = {Closure()} 266 | StoredModuleValues[scriptRef] = ClosureReturn 267 | return unpack(ClosureReturn) 268 | end 269 | end 270 | 271 | -- We'll assign the actual func from the top of this output for flattening user globals at runtime 272 | -- Returns (in a tuple order): maui, script, require, getfenv, setfenv 273 | function ImportGlobals(refId) 274 | local ScriptRef = RefBindings[refId] 275 | 276 | local Closure = ScriptClosures[ScriptRef] 277 | if not Closure then 278 | return 279 | end 280 | 281 | -- This will be set right after the other global funcs, it's for handling proper behavior when 282 | -- getfenv/setfenv is called and safeenv needs to be disabled 283 | local EnvHasBeenSet = false 284 | local RealEnv 285 | local VirtualEnv 286 | local SetEnv 287 | 288 | local Global_maui = table_freeze({ 289 | Version = Version, 290 | Script = script, -- The actual script object for the script this is running on, not a fake ref 291 | Shared = SharedEnvironment, 292 | 293 | -- For compatibility purposes.. 294 | GetScript = function() 295 | return script 296 | end, 297 | GetShared = function() 298 | return SharedEnvironment 299 | end, 300 | }) 301 | 302 | local Global_script = ScriptRef 303 | 304 | local function Global_require(module, ...) 305 | if RefChildren[module] and module.ClassName == "ModuleScript" and ScriptClosures[module] then 306 | return LoadScript(module) 307 | end 308 | 309 | return require(module, ...) 310 | end 311 | 312 | -- Calling these flattened getfenv/setfenv functions will disable safeenv for the WHOLE SCRIPT 313 | local function Global_getfenv(stackLevel, ...) 314 | -- Now we have to set the env for the other variables used here to be valid 315 | if not EnvHasBeenSet then 316 | SetEnv() 317 | end 318 | 319 | if type(stackLevel) == "number" and stackLevel >= 0 then 320 | if stackLevel == 0 then 321 | return VirtualEnv 322 | else 323 | -- Offset by 1 for the actual env 324 | stackLevel = stackLevel + 1 325 | 326 | local GetOk, FunctionEnv = pcall(getfenv, stackLevel) 327 | if GetOk and FunctionEnv == RealEnv then 328 | return VirtualEnv 329 | end 330 | end 331 | end 332 | 333 | return getfenv(stackLevel, ...) 334 | end 335 | 336 | local function Global_setfenv(stackLevel, newEnv, ...) 337 | if not EnvHasBeenSet then 338 | SetEnv() 339 | end 340 | 341 | if type(stackLevel) == "number" and stackLevel >= 0 then 342 | if stackLevel == 0 then 343 | return setfenv(VirtualEnv, newEnv) 344 | else 345 | stackLevel = stackLevel + 1 346 | 347 | local GetOk, FunctionEnv = pcall(getfenv, stackLevel) 348 | if GetOk and FunctionEnv == RealEnv then 349 | return setfenv(VirtualEnv, newEnv) 350 | end 351 | end 352 | end 353 | 354 | return setfenv(stackLevel, newEnv, ...) 355 | end 356 | 357 | -- From earlier, will ONLY be set if needed 358 | function SetEnv() 359 | RealEnv = getfenv(0) 360 | 361 | local GlobalEnvOverride = { 362 | ["maui"] = Global_maui, 363 | ["script"] = Global_script, 364 | ["require"] = Global_require, 365 | ["getfenv"] = Global_getfenv, 366 | ["setfenv"] = Global_setfenv, 367 | } 368 | 369 | VirtualEnv = setmetatable({}, { 370 | __index = function(_, index) 371 | local IndexInVirtualEnv = rawget(VirtualEnv, index) 372 | if IndexInVirtualEnv ~= nil then 373 | return IndexInVirtualEnv 374 | end 375 | 376 | local IndexInGlobalEnvOverride = GlobalEnvOverride[index] 377 | if IndexInGlobalEnvOverride ~= nil then 378 | return IndexInGlobalEnvOverride 379 | end 380 | 381 | return RealEnv[index] 382 | end 383 | }) 384 | 385 | setfenv(Closure, VirtualEnv) 386 | EnvHasBeenSet = true 387 | end 388 | 389 | -- Now, return flattened globals ready for direct runtime exec 390 | return Global_maui, Global_script, Global_require, Global_getfenv, Global_setfenv 391 | end 392 | 393 | for _, ScriptRef in next, ScriptsToRun do 394 | Defer(LoadScript, ScriptRef) 395 | end 396 | 397 | -- If there's a "MainModule" top-level modulescript, we'll return it from the output's closure directly 398 | do 399 | local MainModule 400 | for _, Ref in next, RealObjectRoot do 401 | if Ref.ClassName == "ModuleScript" and Ref.Name == "MainModule" then 402 | MainModule = Ref 403 | break 404 | end 405 | end 406 | 407 | if MainModule then 408 | return LoadScript(MainModule) 409 | end 410 | end 411 | 412 | -- If any scripts are currently running now from task scheduler, the scope won't close until all running threads are closed 413 | -- (thanks for coming to my ted talk) 414 | end 415 | 416 | ]] 417 | 418 | -- VERY simple function to interpolate a certain string with a set of replacements, following 419 | -- Luau's basic variable syntax rules (used for codegen) 420 | local function ReplaceString(inputString: string, replacements: {[string]: string}): string 421 | return string.gsub(inputString, "${([A-Za-z_][A-Za-z0-9_]*)}", replacements) 422 | end 423 | 424 | -- Building codegen directly from a model file path (.rbxm/.rbxmx) using Lune's Roblox library 425 | local function BuildCodegen(modelData: string, minifyTables: boolean?, envName: string?): string 426 | minifyTables = if minifyTables == nil then true else minifyTables 427 | envName = envName or "VirtualEnv" 428 | 429 | local ModelRoot = roblox.deserializeModel(modelData) 430 | 431 | -- We'll track how long it takes for us to scrape the object tree 432 | local ScrapingStartTime = os.clock() 433 | 434 | -- We'll initialize the output object tree, then walk through what we need to 435 | local ObjectTree = {} 436 | local ClosureBindings = {} -- [RefId] = Closure 437 | 438 | local ScrapedInstanceTree = {} -- [RealRef] = {Child, ...} 439 | local RefIds = {} -- [RefId] = RealRef 440 | 441 | -- Recursive function to actually walk through the real instance tree, and assign refs 442 | local function ScrapeInstanceChildren(instance) 443 | -- Add a reference id for this instance 444 | table.insert(RefIds, instance) 445 | 446 | local ScrapedChildren = {} 447 | for _, Child in instance:GetChildren() do 448 | ScrapedChildren[Child] = ScrapeInstanceChildren(Child) 449 | end 450 | 451 | return ScrapedChildren 452 | end 453 | 454 | -- Initialize the scraped instance tree and assign all refs from root 455 | for _, RealInstance in ModelRoot do 456 | ScrapedInstanceTree[RealInstance] = ScrapeInstanceChildren(RealInstance) 457 | end 458 | 459 | -- Now, we'll recursively create the fake object tree 460 | local function CreateObjectTree(instance, children) 461 | local RefId = table.find(RefIds, instance) 462 | local ClassName = instance.ClassName 463 | 464 | local InstanceIsABaseScript = ClassName == "LocalScript" or ClassName == "Script" 465 | local InstanceIsAScript = InstanceIsABaseScript or ClassName == "ModuleScript" 466 | 467 | --[[ 468 | { 469 | [1] = RefId, 470 | [2] = ClassName, 471 | [3] = Properties, 472 | [4] = Children? 473 | } 474 | ]] 475 | 476 | local ObjectTree = { 477 | [1] = RefId, 478 | [2] = ClassName 479 | } 480 | 481 | -- If it's statically disabled, we just won't include the closure to run 482 | if InstanceIsAScript and not (InstanceIsABaseScript and instance.Disabled) then 483 | local ScriptSource = instance.Source 484 | 485 | local EndStatement = " end" do 486 | local SplitSource = string.split(ScriptSource, "\n") 487 | local LastLine = SplitSource[#SplitSource] 488 | 489 | -- If the last line has a comment NOT followed by at least a "[" character, it means 490 | -- that it's a full-line comment, and we need to call `end` on the next line 491 | if LastLine and string.match(LastLine, "%-%-[^%[]") then 492 | EndStatement = "\nend" 493 | end 494 | end 495 | 496 | -- We can't compile check *yet* due to loadstring() being omitted, but soon we'll have a `luau` library 497 | 498 | -- We're using `FunctionsReturnRaw` on LuaEncode later, this will set the return 499 | -- to the rew value, which is the script closure 500 | ClosureBindings[RefId] = function() 501 | return "function()local maui,script,require,getfenv,setfenv=ImportGlobals(" .. RefId .. ")" .. ScriptSource .. EndStatement 502 | end 503 | end 504 | 505 | -- Add any properties 506 | local Properties = {[1] = instance.Name} -- For byte preservation (lol) the name is just set as the property index 1, and not "Name" 507 | 508 | if ClassName == "StringValue" then 509 | Properties.Value = instance.Value 510 | end 511 | 512 | ObjectTree[3] = Properties 513 | 514 | -- Recursively add children 515 | if next(children) then 516 | local ObjectChildren = {} 517 | 518 | for Child, ChildrenOfChild in children do 519 | table.insert(ObjectChildren, CreateObjectTree(Child, ChildrenOfChild)) 520 | end 521 | 522 | ObjectTree[4] = ObjectChildren 523 | end 524 | 525 | return ObjectTree 526 | end 527 | 528 | for RealInstance, Children in ScrapedInstanceTree do 529 | table.insert(ObjectTree, CreateObjectTree(RealInstance, Children)) 530 | end 531 | 532 | -- And we're done scraping everything! 533 | local ScrapingEndTime = os.clock() 534 | print(`Finished scraping object tree! Total build time: {string.format("%.4f", ScrapingEndTime - ScrapingStartTime)} (seconds)`) 535 | 536 | local Prettify = if minifyTables == false then true else false 537 | 538 | local SerializedObjectTree = LuaEncode(ObjectTree, { 539 | Prettify = Prettify, 540 | StackLimit = math.huge, 541 | }) 542 | 543 | local SerializedClosureBindings = LuaEncode(ClosureBindings, { 544 | Prettify = Prettify, 545 | StackLimit = math.huge, 546 | FunctionsReturnRaw = true, -- For Script.Source function closures 547 | }) 548 | 549 | local CodegenOutput = ReplaceString(InitScriptTemplate, { 550 | ObjectTree = SerializedObjectTree, 551 | ClosureBindings = SerializedClosureBindings, 552 | EnvName = envName, 553 | }) 554 | 555 | return CodegenOutput 556 | end 557 | 558 | return BuildCodegen 559 | -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MainModule", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fluentlibrary", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "init.lua", 6 | "scripts": { 7 | "rojo": "mkdirp dist && rojo build -o dist/main.rbxm", 8 | "bundle": "pnpm run rojo && lune build" 9 | }, 10 | "keywords": [], 11 | "author": "dawid", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "mkdirp": "^3.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /place.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fluent", 3 | "tree": { 4 | "$className": "DataModel", 5 | "ReplicatedStorage": { 6 | "Fluent": { 7 | "$path": "src" 8 | } 9 | }, 10 | "StarterPlayer": { 11 | "StarterPlayerScripts": { 12 | "Example": { 13 | "$path": "Example.client.lua" 14 | } 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | mkdirp: 9 | specifier: ^3.0.1 10 | version: 3.0.1 11 | 12 | packages: 13 | 14 | /mkdirp@3.0.1: 15 | resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} 16 | engines: {node: '>=10'} 17 | hasBin: true 18 | dev: true 19 | -------------------------------------------------------------------------------- /selene.toml: -------------------------------------------------------------------------------- 1 | std = "roblox" 2 | [lints] 3 | global_usage = "allow" -------------------------------------------------------------------------------- /sourcemap.json: -------------------------------------------------------------------------------- 1 | {"name":"MainModule","className":"ModuleScript","filePaths":["src\\init.lua","default.project.json"],"children":[{"name":"Acrylic","className":"ModuleScript","filePaths":["src\\Acrylic\\init.lua"],"children":[{"name":"AcrylicBlur","className":"ModuleScript","filePaths":["src\\Acrylic\\AcrylicBlur.lua"]},{"name":"AcrylicPaint","className":"ModuleScript","filePaths":["src\\Acrylic\\AcrylicPaint.lua"]},{"name":"CreateAcrylic","className":"ModuleScript","filePaths":["src\\Acrylic\\CreateAcrylic.lua"]},{"name":"Utils","className":"ModuleScript","filePaths":["src\\Acrylic\\Utils.lua"]}]},{"name":"Components","className":"Folder","children":[{"name":"Assets","className":"ModuleScript","filePaths":["src\\Components\\Assets.lua"]},{"name":"Button","className":"ModuleScript","filePaths":["src\\Components\\Button.lua"]},{"name":"Dialog","className":"ModuleScript","filePaths":["src\\Components\\Dialog.lua"]},{"name":"Element","className":"ModuleScript","filePaths":["src\\Components\\Element.lua"]},{"name":"Notification","className":"ModuleScript","filePaths":["src\\Components\\Notification.lua"]},{"name":"Section","className":"ModuleScript","filePaths":["src\\Components\\Section.lua"]},{"name":"Tab","className":"ModuleScript","filePaths":["src\\Components\\Tab.lua"]},{"name":"Textbox","className":"ModuleScript","filePaths":["src\\Components\\Textbox.lua"]},{"name":"TitleBar","className":"ModuleScript","filePaths":["src\\Components\\TitleBar.lua"]},{"name":"Window","className":"ModuleScript","filePaths":["src\\Components\\Window.lua"]}]},{"name":"Creator","className":"ModuleScript","filePaths":["src\\Creator.lua"]},{"name":"Elements","className":"ModuleScript","filePaths":["src\\Elements\\init.lua"],"children":[{"name":"Button","className":"ModuleScript","filePaths":["src\\Elements\\Button.lua"]},{"name":"Colorpicker","className":"ModuleScript","filePaths":["src\\Elements\\Colorpicker.lua"]},{"name":"Dropdown","className":"ModuleScript","filePaths":["src\\Elements\\Dropdown.lua"]},{"name":"Input","className":"ModuleScript","filePaths":["src\\Elements\\Input.lua"]},{"name":"Keybind","className":"ModuleScript","filePaths":["src\\Elements\\Keybind.lua"]},{"name":"Paragraph","className":"ModuleScript","filePaths":["src\\Elements\\Paragraph.lua"]},{"name":"Slider","className":"ModuleScript","filePaths":["src\\Elements\\Slider.lua"]},{"name":"Toggle","className":"ModuleScript","filePaths":["src\\Elements\\Toggle.lua"]}]},{"name":"Icons","className":"ModuleScript","filePaths":["src\\Icons.lua"]},{"name":"Packages","className":"Folder","children":[{"name":"Flipper","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\init.lua"],"children":[{"name":"BaseMotor","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\BaseMotor.lua"]},{"name":"BaseMotor.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\BaseMotor.spec.lua"]},{"name":"GroupMotor","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\GroupMotor.lua"]},{"name":"GroupMotor.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\GroupMotor.spec.lua"]},{"name":"Instant","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Instant.lua"]},{"name":"Instant.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Instant.spec.lua"]},{"name":"Linear","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Linear.lua"]},{"name":"Linear.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Linear.spec.lua"]},{"name":"Signal","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Signal.lua"]},{"name":"Signal.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Signal.spec.lua"]},{"name":"SingleMotor","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\SingleMotor.lua"]},{"name":"SingleMotor.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\SingleMotor.spec.lua"]},{"name":"Spring","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Spring.lua"]},{"name":"Spring.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\Spring.spec.lua"]},{"name":"isMotor","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\isMotor.lua"]},{"name":"isMotor.spec","className":"ModuleScript","filePaths":["src\\Packages\\Flipper\\isMotor.spec.lua"]}]}]},{"name":"Themes","className":"ModuleScript","filePaths":["src\\Themes\\init.lua"],"children":[{"name":"Amethyst","className":"ModuleScript","filePaths":["src\\Themes\\Amethyst.lua"]},{"name":"Aqua","className":"ModuleScript","filePaths":["src\\Themes\\Aqua.lua"]},{"name":"Dark","className":"ModuleScript","filePaths":["src\\Themes\\Dark.lua"]},{"name":"Darker","className":"ModuleScript","filePaths":["src\\Themes\\Darker.lua"]},{"name":"Light","className":"ModuleScript","filePaths":["src\\Themes\\Light.lua"]},{"name":"Rose","className":"ModuleScript","filePaths":["src\\Themes\\Rose.lua"]}]}]} -------------------------------------------------------------------------------- /src/Acrylic/AcrylicBlur.lua: -------------------------------------------------------------------------------- 1 | local Creator = require(script.Parent.Parent.Creator) 2 | local createAcrylic = require(script.Parent.CreateAcrylic) 3 | local viewportPointToWorld, getOffset = unpack(require(script.Parent.Utils)) 4 | 5 | local BlurFolder = Instance.new("Folder", game:GetService("Workspace").CurrentCamera) 6 | 7 | local function createAcrylicBlur(distance) 8 | local cleanups = {} 9 | 10 | distance = distance or 0.001 11 | local positions = { 12 | topLeft = Vector2.new(), 13 | topRight = Vector2.new(), 14 | bottomRight = Vector2.new(), 15 | } 16 | local model = createAcrylic() 17 | model.Parent = BlurFolder 18 | 19 | local function updatePositions(size, position) 20 | positions.topLeft = position 21 | positions.topRight = position + Vector2.new(size.X, 0) 22 | positions.bottomRight = position + size 23 | end 24 | 25 | local function render() 26 | local res = game:GetService("Workspace").CurrentCamera 27 | if res then 28 | res = res.CFrame 29 | end 30 | local cond = res 31 | if not cond then 32 | cond = CFrame.new() 33 | end 34 | 35 | local camera = cond 36 | local topLeft = positions.topLeft 37 | local topRight = positions.topRight 38 | local bottomRight = positions.bottomRight 39 | 40 | local topLeft3D = viewportPointToWorld(topLeft, distance) 41 | local topRight3D = viewportPointToWorld(topRight, distance) 42 | local bottomRight3D = viewportPointToWorld(bottomRight, distance) 43 | 44 | local width = (topRight3D - topLeft3D).Magnitude 45 | local height = (topRight3D - bottomRight3D).Magnitude 46 | 47 | model.CFrame = 48 | CFrame.fromMatrix((topLeft3D + bottomRight3D) / 2, camera.XVector, camera.YVector, camera.ZVector) 49 | model.Mesh.Scale = Vector3.new(width, height, 0) 50 | end 51 | 52 | local function onChange(rbx) 53 | local offset = getOffset() 54 | local size = rbx.AbsoluteSize - Vector2.new(offset, offset) 55 | local position = rbx.AbsolutePosition + Vector2.new(offset / 2, offset / 2) 56 | 57 | updatePositions(size, position) 58 | task.spawn(render) 59 | end 60 | 61 | local function renderOnChange() 62 | local camera = game:GetService("Workspace").CurrentCamera 63 | if not camera then 64 | return 65 | end 66 | 67 | table.insert(cleanups, camera:GetPropertyChangedSignal("CFrame"):Connect(render)) 68 | table.insert(cleanups, camera:GetPropertyChangedSignal("ViewportSize"):Connect(render)) 69 | table.insert(cleanups, camera:GetPropertyChangedSignal("FieldOfView"):Connect(render)) 70 | task.spawn(render) 71 | end 72 | 73 | model.Destroying:Connect(function() 74 | for _, item in cleanups do 75 | pcall(function() 76 | item:Disconnect() 77 | end) 78 | end 79 | end) 80 | 81 | renderOnChange() 82 | 83 | return onChange, model 84 | end 85 | 86 | return function(distance) 87 | local Blur = {} 88 | local onChange, model = createAcrylicBlur(distance) 89 | 90 | local comp = Creator.New("Frame", { 91 | BackgroundTransparency = 1, 92 | Size = UDim2.fromScale(1, 1), 93 | }) 94 | 95 | Creator.AddSignal(comp:GetPropertyChangedSignal("AbsolutePosition"), function() 96 | onChange(comp) 97 | end) 98 | 99 | Creator.AddSignal(comp:GetPropertyChangedSignal("AbsoluteSize"), function() 100 | onChange(comp) 101 | end) 102 | 103 | Blur.AddParent = function(Parent) 104 | Creator.AddSignal(Parent:GetPropertyChangedSignal("Visible"), function() 105 | Blur.SetVisibility(Parent.Visible) 106 | end) 107 | end 108 | 109 | Blur.SetVisibility = function(Value) 110 | model.Transparency = Value and 0.98 or 1 111 | end 112 | 113 | Blur.Frame = comp 114 | Blur.Model = model 115 | 116 | return Blur 117 | end 118 | -------------------------------------------------------------------------------- /src/Acrylic/AcrylicPaint.lua: -------------------------------------------------------------------------------- 1 | local Creator = require(script.Parent.Parent.Creator) 2 | local AcrylicBlur = require(script.Parent.AcrylicBlur) 3 | 4 | local New = Creator.New 5 | 6 | return function(props) 7 | local AcrylicPaint = {} 8 | 9 | AcrylicPaint.Frame = New("Frame", { 10 | Size = UDim2.fromScale(1, 1), 11 | BackgroundTransparency = 0.9, 12 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 13 | BorderSizePixel = 0, 14 | }, { 15 | New("ImageLabel", { 16 | Image = "rbxassetid://8992230677", 17 | ScaleType = "Slice", 18 | SliceCenter = Rect.new(Vector2.new(99, 99), Vector2.new(99, 99)), 19 | AnchorPoint = Vector2.new(0.5, 0.5), 20 | Size = UDim2.new(1, 120, 1, 116), 21 | Position = UDim2.new(0.5, 0, 0.5, 0), 22 | BackgroundTransparency = 1, 23 | ImageColor3 = Color3.fromRGB(0, 0, 0), 24 | ImageTransparency = 0.7, 25 | }), 26 | 27 | New("UICorner", { 28 | CornerRadius = UDim.new(0, 8), 29 | }), 30 | 31 | New("Frame", { 32 | BackgroundTransparency = 0.45, 33 | Size = UDim2.fromScale(1, 1), 34 | Name = "Background", 35 | ThemeTag = { 36 | BackgroundColor3 = "AcrylicMain", 37 | }, 38 | }, { 39 | New("UICorner", { 40 | CornerRadius = UDim.new(0, 8), 41 | }), 42 | }), 43 | 44 | New("Frame", { 45 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 46 | BackgroundTransparency = 0.4, 47 | Size = UDim2.fromScale(1, 1), 48 | }, { 49 | New("UICorner", { 50 | CornerRadius = UDim.new(0, 8), 51 | }), 52 | 53 | New("UIGradient", { 54 | Rotation = 90, 55 | ThemeTag = { 56 | Color = "AcrylicGradient", 57 | }, 58 | }), 59 | }), 60 | 61 | New("ImageLabel", { 62 | Image = "rbxassetid://9968344105", 63 | ImageTransparency = 0.98, 64 | ScaleType = Enum.ScaleType.Tile, 65 | TileSize = UDim2.new(0, 128, 0, 128), 66 | Size = UDim2.fromScale(1, 1), 67 | BackgroundTransparency = 1, 68 | }, { 69 | New("UICorner", { 70 | CornerRadius = UDim.new(0, 8), 71 | }), 72 | }), 73 | 74 | New("ImageLabel", { 75 | Image = "rbxassetid://9968344227", 76 | ImageTransparency = 0.9, 77 | ScaleType = Enum.ScaleType.Tile, 78 | TileSize = UDim2.new(0, 128, 0, 128), 79 | Size = UDim2.fromScale(1, 1), 80 | BackgroundTransparency = 1, 81 | ThemeTag = { 82 | ImageTransparency = "AcrylicNoise", 83 | }, 84 | }, { 85 | New("UICorner", { 86 | CornerRadius = UDim.new(0, 8), 87 | }), 88 | }), 89 | 90 | New("Frame", { 91 | BackgroundTransparency = 1, 92 | Size = UDim2.fromScale(1, 1), 93 | ZIndex = 2, 94 | }, { 95 | New("UICorner", { 96 | CornerRadius = UDim.new(0, 8), 97 | }), 98 | New("UIStroke", { 99 | Transparency = 0.5, 100 | Thickness = 1, 101 | ThemeTag = { 102 | Color = "AcrylicBorder", 103 | }, 104 | }), 105 | }), 106 | }) 107 | 108 | local Blur 109 | 110 | if require(script.Parent.Parent).UseAcrylic then 111 | Blur = AcrylicBlur() 112 | Blur.Frame.Parent = AcrylicPaint.Frame 113 | AcrylicPaint.Model = Blur.Model 114 | AcrylicPaint.AddParent = Blur.AddParent 115 | AcrylicPaint.SetVisibility = Blur.SetVisibility 116 | end 117 | 118 | return AcrylicPaint 119 | end 120 | -------------------------------------------------------------------------------- /src/Acrylic/CreateAcrylic.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Creator = require(Root.Creator) 3 | 4 | local function createAcrylic() 5 | local Part = Creator.New("Part", { 6 | Name = "Body", 7 | Color = Color3.new(0, 0, 0), 8 | Material = Enum.Material.Glass, 9 | Size = Vector3.new(1, 1, 0), 10 | Anchored = true, 11 | CanCollide = false, 12 | Locked = true, 13 | CastShadow = false, 14 | Transparency = 0.98, 15 | }, { 16 | Creator.New("SpecialMesh", { 17 | MeshType = Enum.MeshType.Brick, 18 | Offset = Vector3.new(0, 0, -0.000001), 19 | }), 20 | }) 21 | 22 | return Part 23 | end 24 | 25 | return createAcrylic 26 | -------------------------------------------------------------------------------- /src/Acrylic/Utils.lua: -------------------------------------------------------------------------------- 1 | local function map(value, inMin, inMax, outMin, outMax) 2 | return (value - inMin) * (outMax - outMin) / (inMax - inMin) + outMin 3 | end 4 | 5 | local function viewportPointToWorld(location, distance) 6 | local unitRay = game:GetService("Workspace").CurrentCamera:ScreenPointToRay(location.X, location.Y) 7 | return unitRay.Origin + unitRay.Direction * distance 8 | end 9 | 10 | local function getOffset() 11 | local viewportSizeY = game:GetService("Workspace").CurrentCamera.ViewportSize.Y 12 | return map(viewportSizeY, 0, 2560, 8, 56) 13 | end 14 | 15 | return { viewportPointToWorld, getOffset } 16 | -------------------------------------------------------------------------------- /src/Acrylic/init.lua: -------------------------------------------------------------------------------- 1 | local Acrylic = { 2 | AcrylicBlur = require(script.AcrylicBlur), 3 | CreateAcrylic = require(script.CreateAcrylic), 4 | AcrylicPaint = require(script.AcrylicPaint), 5 | } 6 | 7 | function Acrylic.init() 8 | local baseEffect = Instance.new("DepthOfFieldEffect") 9 | baseEffect.FarIntensity = 0 10 | baseEffect.InFocusRadius = 0.1 11 | baseEffect.NearIntensity = 1 12 | 13 | local depthOfFieldDefaults = {} 14 | 15 | function Acrylic.Enable() 16 | for _, effect in pairs(depthOfFieldDefaults) do 17 | effect.Enabled = false 18 | end 19 | baseEffect.Parent = game:GetService("Lighting") 20 | end 21 | 22 | function Acrylic.Disable() 23 | for _, effect in pairs(depthOfFieldDefaults) do 24 | effect.Enabled = effect.enabled 25 | end 26 | baseEffect.Parent = nil 27 | end 28 | 29 | local function registerDefaults() 30 | local function register(object) 31 | if object:IsA("DepthOfFieldEffect") then 32 | depthOfFieldDefaults[object] = { enabled = object.Enabled } 33 | end 34 | end 35 | 36 | for _, child in pairs(game:GetService("Lighting"):GetChildren()) do 37 | register(child) 38 | end 39 | 40 | if game:GetService("Workspace").CurrentCamera then 41 | for _, child in pairs(game:GetService("Workspace").CurrentCamera:GetChildren()) do 42 | register(child) 43 | end 44 | end 45 | end 46 | 47 | registerDefaults() 48 | Acrylic.Enable() 49 | end 50 | 51 | return Acrylic 52 | -------------------------------------------------------------------------------- /src/Components/Assets.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Close = "rbxassetid://9886659671", 3 | Min = "rbxassetid://9886659276", 4 | Max = "rbxassetid://9886659406", 5 | Restore = "rbxassetid://9886659001", 6 | } 7 | -------------------------------------------------------------------------------- /src/Components/Button.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Flipper = require(Root.Packages.Flipper) 3 | local Creator = require(Root.Creator) 4 | local New = Creator.New 5 | 6 | local Spring = Flipper.Spring.new 7 | 8 | return function(Theme, Parent, DialogCheck) 9 | DialogCheck = DialogCheck or false 10 | local Button = {} 11 | 12 | Button.Title = New("TextLabel", { 13 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 14 | TextColor3 = Color3.fromRGB(200, 200, 200), 15 | TextSize = 14, 16 | TextWrapped = true, 17 | TextXAlignment = Enum.TextXAlignment.Center, 18 | TextYAlignment = Enum.TextYAlignment.Center, 19 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 20 | AutomaticSize = Enum.AutomaticSize.Y, 21 | BackgroundTransparency = 1, 22 | Size = UDim2.fromScale(1, 1), 23 | ThemeTag = { 24 | TextColor3 = "Text", 25 | }, 26 | }) 27 | 28 | Button.HoverFrame = New("Frame", { 29 | Size = UDim2.fromScale(1, 1), 30 | BackgroundTransparency = 1, 31 | ThemeTag = { 32 | BackgroundColor3 = "Hover", 33 | }, 34 | }, { 35 | New("UICorner", { 36 | CornerRadius = UDim.new(0, 4), 37 | }), 38 | }) 39 | 40 | Button.Frame = New("TextButton", { 41 | Size = UDim2.new(0, 0, 0, 32), 42 | Parent = Parent, 43 | ThemeTag = { 44 | BackgroundColor3 = "DialogButton", 45 | }, 46 | }, { 47 | New("UICorner", { 48 | CornerRadius = UDim.new(0, 4), 49 | }), 50 | New("UIStroke", { 51 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 52 | Transparency = 0.65, 53 | ThemeTag = { 54 | Color = "DialogButtonBorder", 55 | }, 56 | }), 57 | Button.HoverFrame, 58 | Button.Title, 59 | }) 60 | 61 | local Motor, SetTransparency = Creator.SpringMotor(1, Button.HoverFrame, "BackgroundTransparency", DialogCheck) 62 | Creator.AddSignal(Button.Frame.MouseEnter, function() 63 | SetTransparency(0.97) 64 | end) 65 | Creator.AddSignal(Button.Frame.MouseLeave, function() 66 | SetTransparency(1) 67 | end) 68 | Creator.AddSignal(Button.Frame.MouseButton1Down, function() 69 | SetTransparency(1) 70 | end) 71 | Creator.AddSignal(Button.Frame.MouseButton1Up, function() 72 | SetTransparency(0.97) 73 | end) 74 | 75 | return Button 76 | end 77 | -------------------------------------------------------------------------------- /src/Components/Dialog.lua: -------------------------------------------------------------------------------- 1 | local UserInputService = game:GetService("UserInputService") 2 | local Mouse = game:GetService("Players").LocalPlayer:GetMouse() 3 | local Camera = game:GetService("Workspace").CurrentCamera 4 | 5 | local Root = script.Parent.Parent 6 | local Flipper = require(Root.Packages.Flipper) 7 | local Creator = require(Root.Creator) 8 | 9 | local Spring = Flipper.Spring.new 10 | local Instant = Flipper.Instant.new 11 | local New = Creator.New 12 | 13 | local Dialog = { 14 | Window = nil, 15 | } 16 | 17 | function Dialog:Init(Window) 18 | Dialog.Window = Window 19 | return Dialog 20 | end 21 | 22 | function Dialog:Create() 23 | local NewDialog = { 24 | Buttons = 0, 25 | } 26 | 27 | NewDialog.TintFrame = New("TextButton", { 28 | Text = "", 29 | Size = UDim2.fromScale(1, 1), 30 | BackgroundColor3 = Color3.fromRGB(0, 0, 0), 31 | BackgroundTransparency = 1, 32 | Parent = Dialog.Window.Root, 33 | }, { 34 | New("UICorner", { 35 | CornerRadius = UDim.new(0, 8), 36 | }), 37 | }) 38 | 39 | local TintMotor, TintTransparency = Creator.SpringMotor(1, NewDialog.TintFrame, "BackgroundTransparency", true) 40 | 41 | NewDialog.ButtonHolder = New("Frame", { 42 | Size = UDim2.new(1, -40, 1, -40), 43 | AnchorPoint = Vector2.new(0.5, 0.5), 44 | Position = UDim2.fromScale(0.5, 0.5), 45 | BackgroundTransparency = 1, 46 | }, { 47 | New("UIListLayout", { 48 | Padding = UDim.new(0, 10), 49 | FillDirection = Enum.FillDirection.Horizontal, 50 | HorizontalAlignment = Enum.HorizontalAlignment.Center, 51 | SortOrder = Enum.SortOrder.LayoutOrder, 52 | }), 53 | }) 54 | 55 | NewDialog.ButtonHolderFrame = New("Frame", { 56 | Size = UDim2.new(1, 0, 0, 70), 57 | Position = UDim2.new(0, 0, 1, -70), 58 | ThemeTag = { 59 | BackgroundColor3 = "DialogHolder", 60 | }, 61 | }, { 62 | New("Frame", { 63 | Size = UDim2.new(1, 0, 0, 1), 64 | ThemeTag = { 65 | BackgroundColor3 = "DialogHolderLine", 66 | }, 67 | }), 68 | NewDialog.ButtonHolder, 69 | }) 70 | 71 | NewDialog.Title = New("TextLabel", { 72 | FontFace = Font.new( 73 | "rbxasset://fonts/families/GothamSSm.json", 74 | Enum.FontWeight.SemiBold, 75 | Enum.FontStyle.Normal 76 | ), 77 | Text = "Dialog", 78 | TextColor3 = Color3.fromRGB(240, 240, 240), 79 | TextSize = 22, 80 | TextXAlignment = Enum.TextXAlignment.Left, 81 | Size = UDim2.new(1, 0, 0, 22), 82 | Position = UDim2.fromOffset(20, 25), 83 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 84 | BackgroundTransparency = 1, 85 | ThemeTag = { 86 | TextColor3 = "Text", 87 | }, 88 | }) 89 | 90 | NewDialog.Scale = New("UIScale", { 91 | Scale = 1, 92 | }) 93 | 94 | local ScaleMotor, Scale = Creator.SpringMotor(1.1, NewDialog.Scale, "Scale") 95 | 96 | NewDialog.Root = New("CanvasGroup", { 97 | Size = UDim2.fromOffset(300, 165), 98 | AnchorPoint = Vector2.new(0.5, 0.5), 99 | Position = UDim2.fromScale(0.5, 0.5), 100 | GroupTransparency = 1, 101 | Parent = NewDialog.TintFrame, 102 | ThemeTag = { 103 | BackgroundColor3 = "Dialog", 104 | }, 105 | }, { 106 | New("UICorner", { 107 | CornerRadius = UDim.new(0, 8), 108 | }), 109 | New("UIStroke", { 110 | Transparency = 0.5, 111 | ThemeTag = { 112 | Color = "DialogBorder", 113 | }, 114 | }), 115 | NewDialog.Scale, 116 | NewDialog.Title, 117 | NewDialog.ButtonHolderFrame, 118 | }) 119 | 120 | local RootMotor, RootTransparency = Creator.SpringMotor(1, NewDialog.Root, "GroupTransparency") 121 | 122 | function NewDialog:Open() 123 | require(Root).DialogOpen = true 124 | NewDialog.Scale.Scale = 1.1 125 | TintTransparency(0.75) 126 | RootTransparency(0) 127 | Scale(1) 128 | end 129 | 130 | function NewDialog:Close() 131 | require(Root).DialogOpen = false 132 | TintTransparency(1) 133 | RootTransparency(1) 134 | Scale(1.1) 135 | NewDialog.Root.UIStroke:Destroy() 136 | task.wait(0.15) 137 | NewDialog.TintFrame:Destroy() 138 | end 139 | 140 | function NewDialog:Button(Title, Callback) 141 | NewDialog.Buttons = NewDialog.Buttons + 1 142 | Title = Title or "Button" 143 | Callback = Callback or function() end 144 | 145 | local Button = require(Root.Components.Button)("", NewDialog.ButtonHolder, true) 146 | Button.Title.Text = Title 147 | 148 | for _, Btn in next, NewDialog.ButtonHolder:GetChildren() do 149 | if Btn:IsA("TextButton") then 150 | Btn.Size = 151 | UDim2.new(1 / NewDialog.Buttons, -(((NewDialog.Buttons - 1) * 10) / NewDialog.Buttons), 0, 32) 152 | end 153 | end 154 | 155 | Creator.AddSignal(Button.Frame.MouseButton1Click, function() 156 | require(Root):SafeCallback(Callback) 157 | pcall(function() 158 | NewDialog:Close() 159 | end) 160 | end) 161 | 162 | return Button 163 | end 164 | 165 | return NewDialog 166 | end 167 | 168 | return Dialog 169 | -------------------------------------------------------------------------------- /src/Components/Element.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Flipper = require(Root.Packages.Flipper) 3 | local Creator = require(Root.Creator) 4 | local New = Creator.New 5 | 6 | local Spring = Flipper.Spring.new 7 | 8 | return function(Title, Desc, Parent, Hover) 9 | local Element = {} 10 | 11 | Element.TitleLabel = New("TextLabel", { 12 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Medium, Enum.FontStyle.Normal), 13 | Text = Title, 14 | TextColor3 = Color3.fromRGB(240, 240, 240), 15 | TextSize = 13, 16 | TextXAlignment = Enum.TextXAlignment.Left, 17 | Size = UDim2.new(1, 0, 0, 14), 18 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 19 | BackgroundTransparency = 1, 20 | ThemeTag = { 21 | TextColor3 = "Text", 22 | }, 23 | }) 24 | 25 | Element.DescLabel = New("TextLabel", { 26 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 27 | Text = Desc, 28 | TextColor3 = Color3.fromRGB(200, 200, 200), 29 | TextSize = 12, 30 | TextWrapped = true, 31 | TextXAlignment = Enum.TextXAlignment.Left, 32 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 33 | AutomaticSize = Enum.AutomaticSize.Y, 34 | BackgroundTransparency = 1, 35 | Size = UDim2.new(1, 0, 0, 14), 36 | ThemeTag = { 37 | TextColor3 = "SubText", 38 | }, 39 | }) 40 | 41 | Element.LabelHolder = New("Frame", { 42 | AutomaticSize = Enum.AutomaticSize.Y, 43 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 44 | BackgroundTransparency = 1, 45 | Position = UDim2.fromOffset(10, 0), 46 | Size = UDim2.new(1, -28, 0, 0), 47 | }, { 48 | New("UIListLayout", { 49 | SortOrder = Enum.SortOrder.LayoutOrder, 50 | VerticalAlignment = Enum.VerticalAlignment.Center, 51 | }), 52 | New("UIPadding", { 53 | PaddingBottom = UDim.new(0, 13), 54 | PaddingTop = UDim.new(0, 13), 55 | }), 56 | Element.TitleLabel, 57 | Element.DescLabel, 58 | }) 59 | 60 | Element.Border = New("UIStroke", { 61 | Transparency = 0.5, 62 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 63 | Color = Color3.fromRGB(0, 0, 0), 64 | ThemeTag = { 65 | Color = "ElementBorder", 66 | }, 67 | }) 68 | 69 | Element.Frame = New("TextButton", { 70 | Size = UDim2.new(1, 0, 0, 0), 71 | BackgroundTransparency = 0.89, 72 | BackgroundColor3 = Color3.fromRGB(130, 130, 130), 73 | Parent = Parent, 74 | AutomaticSize = Enum.AutomaticSize.Y, 75 | Text = "", 76 | LayoutOrder = 7, 77 | ThemeTag = { 78 | BackgroundColor3 = "Element", 79 | BackgroundTransparency = "ElementTransparency", 80 | }, 81 | }, { 82 | New("UICorner", { 83 | CornerRadius = UDim.new(0, 4), 84 | }), 85 | Element.Border, 86 | Element.LabelHolder, 87 | }) 88 | 89 | function Element:SetTitle(Set) 90 | Element.TitleLabel.Text = Set 91 | end 92 | 93 | function Element:SetDesc(Set) 94 | if Set == nil then 95 | Set = "" 96 | end 97 | if Set == "" then 98 | Element.DescLabel.Visible = false 99 | else 100 | Element.DescLabel.Visible = true 101 | end 102 | Element.DescLabel.Text = Set 103 | end 104 | 105 | function Element:Destroy() 106 | Element.Frame:Destroy() 107 | end 108 | 109 | Element:SetTitle(Title) 110 | Element:SetDesc(Desc) 111 | 112 | if Hover then 113 | local Themes = Root.Themes 114 | local Motor, SetTransparency = Creator.SpringMotor( 115 | Creator.GetThemeProperty("ElementTransparency"), 116 | Element.Frame, 117 | "BackgroundTransparency", 118 | false, 119 | true 120 | ) 121 | 122 | Creator.AddSignal(Element.Frame.MouseEnter, function() 123 | SetTransparency(Creator.GetThemeProperty("ElementTransparency") - Creator.GetThemeProperty("HoverChange")) 124 | end) 125 | Creator.AddSignal(Element.Frame.MouseLeave, function() 126 | SetTransparency(Creator.GetThemeProperty("ElementTransparency")) 127 | end) 128 | Creator.AddSignal(Element.Frame.MouseButton1Down, function() 129 | SetTransparency(Creator.GetThemeProperty("ElementTransparency") + Creator.GetThemeProperty("HoverChange")) 130 | end) 131 | Creator.AddSignal(Element.Frame.MouseButton1Up, function() 132 | SetTransparency(Creator.GetThemeProperty("ElementTransparency") - Creator.GetThemeProperty("HoverChange")) 133 | end) 134 | end 135 | 136 | return Element 137 | end 138 | -------------------------------------------------------------------------------- /src/Components/Notification.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Flipper = require(Root.Packages.Flipper) 3 | local Creator = require(Root.Creator) 4 | local Acrylic = require(Root.Acrylic) 5 | 6 | local Spring = Flipper.Spring.new 7 | local Instant = Flipper.Instant.new 8 | local New = Creator.New 9 | 10 | local Notification = {} 11 | 12 | function Notification:Init(GUI) 13 | Notification.Holder = New("Frame", { 14 | Position = UDim2.new(1, -30, 1, -30), 15 | Size = UDim2.new(0, 310, 1, -30), 16 | AnchorPoint = Vector2.new(1, 1), 17 | BackgroundTransparency = 1, 18 | Parent = GUI, 19 | }, { 20 | New("UIListLayout", { 21 | HorizontalAlignment = Enum.HorizontalAlignment.Center, 22 | SortOrder = Enum.SortOrder.LayoutOrder, 23 | VerticalAlignment = Enum.VerticalAlignment.Bottom, 24 | Padding = UDim.new(0, 20), 25 | }), 26 | }) 27 | end 28 | 29 | function Notification:New(Config) 30 | Config.Title = Config.Title or "Title" 31 | Config.Content = Config.Content or "Content" 32 | Config.SubContent = Config.SubContent or "" 33 | Config.Duration = Config.Duration or nil 34 | Config.Buttons = Config.Buttons or {} 35 | local NewNotification = { 36 | Closed = false, 37 | } 38 | 39 | NewNotification.AcrylicPaint = Acrylic.AcrylicPaint() 40 | 41 | NewNotification.Title = New("TextLabel", { 42 | Position = UDim2.new(0, 14, 0, 17), 43 | Text = Config.Title, 44 | RichText = true, 45 | TextColor3 = Color3.fromRGB(255, 255, 255), 46 | TextTransparency = 0, 47 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 48 | TextSize = 13, 49 | TextXAlignment = "Left", 50 | TextYAlignment = "Center", 51 | Size = UDim2.new(1, -12, 0, 12), 52 | TextWrapped = true, 53 | BackgroundTransparency = 1, 54 | ThemeTag = { 55 | TextColor3 = "Text", 56 | }, 57 | }) 58 | 59 | NewNotification.ContentLabel = New("TextLabel", { 60 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 61 | Text = Config.Content, 62 | TextColor3 = Color3.fromRGB(240, 240, 240), 63 | TextSize = 14, 64 | TextXAlignment = Enum.TextXAlignment.Left, 65 | AutomaticSize = Enum.AutomaticSize.Y, 66 | Size = UDim2.new(1, 0, 0, 14), 67 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 68 | BackgroundTransparency = 1, 69 | TextWrapped = true, 70 | ThemeTag = { 71 | TextColor3 = "Text", 72 | }, 73 | }) 74 | 75 | NewNotification.SubContentLabel = New("TextLabel", { 76 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 77 | Text = Config.SubContent, 78 | TextColor3 = Color3.fromRGB(240, 240, 240), 79 | TextSize = 14, 80 | TextXAlignment = Enum.TextXAlignment.Left, 81 | AutomaticSize = Enum.AutomaticSize.Y, 82 | Size = UDim2.new(1, 0, 0, 14), 83 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 84 | BackgroundTransparency = 1, 85 | TextWrapped = true, 86 | ThemeTag = { 87 | TextColor3 = "SubText", 88 | }, 89 | }) 90 | 91 | NewNotification.LabelHolder = New("Frame", { 92 | AutomaticSize = Enum.AutomaticSize.Y, 93 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 94 | BackgroundTransparency = 1, 95 | Position = UDim2.fromOffset(14, 40), 96 | Size = UDim2.new(1, -28, 0, 0), 97 | }, { 98 | New("UIListLayout", { 99 | SortOrder = Enum.SortOrder.LayoutOrder, 100 | VerticalAlignment = Enum.VerticalAlignment.Center, 101 | Padding = UDim.new(0, 3), 102 | }), 103 | NewNotification.ContentLabel, 104 | NewNotification.SubContentLabel, 105 | }) 106 | 107 | NewNotification.CloseButton = New("TextButton", { 108 | Text = "", 109 | Position = UDim2.new(1, -14, 0, 13), 110 | Size = UDim2.fromOffset(20, 20), 111 | AnchorPoint = Vector2.new(1, 0), 112 | BackgroundTransparency = 1, 113 | }, { 114 | New("ImageLabel", { 115 | Image = require(script.Parent.Assets).Close, 116 | Size = UDim2.fromOffset(16, 16), 117 | Position = UDim2.fromScale(0.5, 0.5), 118 | AnchorPoint = Vector2.new(0.5, 0.5), 119 | BackgroundTransparency = 1, 120 | ThemeTag = { 121 | ImageColor3 = "Text", 122 | }, 123 | }), 124 | }) 125 | 126 | NewNotification.Root = New("Frame", { 127 | BackgroundTransparency = 1, 128 | Size = UDim2.new(1, 0, 1, 0), 129 | Position = UDim2.fromScale(1, 0), 130 | }, { 131 | NewNotification.AcrylicPaint.Frame, 132 | NewNotification.Title, 133 | NewNotification.CloseButton, 134 | NewNotification.LabelHolder, 135 | }) 136 | 137 | if Config.Content == "" then 138 | NewNotification.ContentLabel.Visible = false 139 | end 140 | 141 | if Config.SubContent == "" then 142 | NewNotification.SubContentLabel.Visible = false 143 | end 144 | 145 | NewNotification.Holder = New("Frame", { 146 | BackgroundTransparency = 1, 147 | Size = UDim2.new(1, 0, 0, 200), 148 | Parent = Notification.Holder, 149 | }, { 150 | NewNotification.Root, 151 | }) 152 | 153 | local RootMotor = Flipper.GroupMotor.new({ 154 | Scale = 1, 155 | Offset = 60, 156 | }) 157 | 158 | RootMotor:onStep(function(Values) 159 | NewNotification.Root.Position = UDim2.new(Values.Scale, Values.Offset, 0, 0) 160 | end) 161 | 162 | Creator.AddSignal(NewNotification.CloseButton.MouseButton1Click, function() 163 | NewNotification:Close() 164 | end) 165 | 166 | function NewNotification:Open() 167 | local ContentSize = NewNotification.LabelHolder.AbsoluteSize.Y 168 | NewNotification.Holder.Size = UDim2.new(1, 0, 0, 58 + ContentSize) 169 | 170 | RootMotor:setGoal({ 171 | Scale = Spring(0, { frequency = 5 }), 172 | Offset = Spring(0, { frequency = 5 }), 173 | }) 174 | end 175 | 176 | function NewNotification:Close() 177 | if not NewNotification.Closed then 178 | NewNotification.Closed = true 179 | task.spawn(function() 180 | RootMotor:setGoal({ 181 | Scale = Spring(1, { frequency = 5 }), 182 | Offset = Spring(60, { frequency = 5 }), 183 | }) 184 | task.wait(0.4) 185 | if require(Root).UseAcrylic then 186 | NewNotification.AcrylicPaint.Model:Destroy() 187 | end 188 | NewNotification.Holder:Destroy() 189 | end) 190 | end 191 | end 192 | 193 | NewNotification:Open() 194 | if Config.Duration then 195 | task.delay(Config.Duration, function() 196 | NewNotification:Close() 197 | end) 198 | end 199 | return NewNotification 200 | end 201 | 202 | return Notification 203 | -------------------------------------------------------------------------------- /src/Components/Section.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Creator = require(Root.Creator) 3 | 4 | local New = Creator.New 5 | 6 | return function(Title, Parent) 7 | local Section = {} 8 | 9 | Section.Layout = New("UIListLayout", { 10 | Padding = UDim.new(0, 5), 11 | }) 12 | 13 | Section.Container = New("Frame", { 14 | Size = UDim2.new(1, 0, 0, 26), 15 | Position = UDim2.fromOffset(0, 24), 16 | BackgroundTransparency = 1, 17 | }, { 18 | Section.Layout, 19 | }) 20 | 21 | Section.Root = New("Frame", { 22 | BackgroundTransparency = 1, 23 | Size = UDim2.new(1, 0, 0, 26), 24 | LayoutOrder = 7, 25 | Parent = Parent, 26 | }, { 27 | New("TextLabel", { 28 | RichText = true, 29 | Text = Title, 30 | TextTransparency = 0, 31 | FontFace = Font.new("rbxassetid://12187365364", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal), 32 | TextSize = 18, 33 | TextXAlignment = "Left", 34 | TextYAlignment = "Center", 35 | Size = UDim2.new(1, -16, 0, 18), 36 | Position = UDim2.fromOffset(0, 2), 37 | ThemeTag = { 38 | TextColor3 = "Text", 39 | }, 40 | }), 41 | Section.Container, 42 | }) 43 | 44 | Creator.AddSignal(Section.Layout:GetPropertyChangedSignal("AbsoluteContentSize"), function() 45 | Section.Container.Size = UDim2.new(1, 0, 0, Section.Layout.AbsoluteContentSize.Y) 46 | Section.Root.Size = UDim2.new(1, 0, 0, Section.Layout.AbsoluteContentSize.Y + 25) 47 | end) 48 | return Section 49 | end 50 | -------------------------------------------------------------------------------- /src/Components/Tab.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Flipper = require(Root.Packages.Flipper) 3 | local Creator = require(Root.Creator) 4 | 5 | local New = Creator.New 6 | local Spring = Flipper.Spring.new 7 | local Instant = Flipper.Instant.new 8 | local Components = Root.Components 9 | 10 | local TabModule = { 11 | Window = nil, 12 | Tabs = {}, 13 | Containers = {}, 14 | SelectedTab = 0, 15 | TabCount = 0, 16 | } 17 | 18 | function TabModule:Init(Window) 19 | TabModule.Window = Window 20 | return TabModule 21 | end 22 | 23 | function TabModule:GetCurrentTabPos() 24 | local TabHolderPos = TabModule.Window.TabHolder.AbsolutePosition.Y 25 | local TabPos = TabModule.Tabs[TabModule.SelectedTab].Frame.AbsolutePosition.Y 26 | 27 | return TabPos - TabHolderPos 28 | end 29 | 30 | function TabModule:New(Title, Icon, Parent) 31 | local Library = require(Root) 32 | local Window = TabModule.Window 33 | local Elements = Library.Elements 34 | 35 | TabModule.TabCount = TabModule.TabCount + 1 36 | local TabIndex = TabModule.TabCount 37 | 38 | local Tab = { 39 | Selected = false, 40 | Name = Title, 41 | Type = "Tab", 42 | } 43 | 44 | if Library:GetIcon(Icon) then 45 | Icon = Library:GetIcon(Icon) 46 | end 47 | 48 | if Icon == "" or nil then 49 | Icon = nil 50 | end 51 | 52 | Tab.Frame = New("TextButton", { 53 | Size = UDim2.new(1, 0, 0, 34), 54 | BackgroundTransparency = 1, 55 | Parent = Parent, 56 | ThemeTag = { 57 | BackgroundColor3 = "Tab", 58 | }, 59 | }, { 60 | New("UICorner", { 61 | CornerRadius = UDim.new(0, 6), 62 | }), 63 | New("TextLabel", { 64 | AnchorPoint = Vector2.new(0, 0.5), 65 | Position = Icon and UDim2.new(0, 30, 0.5, 0) or UDim2.new(0, 12, 0.5, 0), 66 | Text = Title, 67 | RichText = true, 68 | TextColor3 = Color3.fromRGB(255, 255, 255), 69 | TextTransparency = 0, 70 | FontFace = Font.new( 71 | "rbxasset://fonts/families/GothamSSm.json", 72 | Enum.FontWeight.Regular, 73 | Enum.FontStyle.Normal 74 | ), 75 | TextSize = 12, 76 | TextXAlignment = "Left", 77 | TextYAlignment = "Center", 78 | Size = UDim2.new(1, -12, 1, 0), 79 | BackgroundTransparency = 1, 80 | ThemeTag = { 81 | TextColor3 = "Text", 82 | }, 83 | }), 84 | New("ImageLabel", { 85 | AnchorPoint = Vector2.new(0, 0.5), 86 | Size = UDim2.fromOffset(16, 16), 87 | Position = UDim2.new(0, 8, 0.5, 0), 88 | BackgroundTransparency = 1, 89 | Image = Icon and Icon or nil, 90 | ThemeTag = { 91 | ImageColor3 = "Text", 92 | }, 93 | }), 94 | }) 95 | 96 | local ContainerLayout = New("UIListLayout", { 97 | Padding = UDim.new(0, 5), 98 | SortOrder = Enum.SortOrder.LayoutOrder, 99 | }) 100 | 101 | Tab.ContainerFrame = New("ScrollingFrame", { 102 | Size = UDim2.fromScale(1, 1), 103 | BackgroundTransparency = 1, 104 | Parent = Window.ContainerHolder, 105 | Visible = false, 106 | BottomImage = "rbxassetid://6889812791", 107 | MidImage = "rbxassetid://6889812721", 108 | TopImage = "rbxassetid://6276641225", 109 | ScrollBarImageColor3 = Color3.fromRGB(255, 255, 255), 110 | ScrollBarImageTransparency = 0.95, 111 | ScrollBarThickness = 3, 112 | BorderSizePixel = 0, 113 | CanvasSize = UDim2.fromScale(0, 0), 114 | ScrollingDirection = Enum.ScrollingDirection.Y, 115 | }, { 116 | ContainerLayout, 117 | New("UIPadding", { 118 | PaddingRight = UDim.new(0, 10), 119 | PaddingLeft = UDim.new(0, 1), 120 | PaddingTop = UDim.new(0, 1), 121 | PaddingBottom = UDim.new(0, 1), 122 | }), 123 | }) 124 | 125 | Creator.AddSignal(ContainerLayout:GetPropertyChangedSignal("AbsoluteContentSize"), function() 126 | Tab.ContainerFrame.CanvasSize = UDim2.new(0, 0, 0, ContainerLayout.AbsoluteContentSize.Y + 2) 127 | end) 128 | 129 | Tab.Motor, Tab.SetTransparency = Creator.SpringMotor(1, Tab.Frame, "BackgroundTransparency") 130 | 131 | Creator.AddSignal(Tab.Frame.MouseEnter, function() 132 | Tab.SetTransparency(Tab.Selected and 0.85 or 0.89) 133 | end) 134 | Creator.AddSignal(Tab.Frame.MouseLeave, function() 135 | Tab.SetTransparency(Tab.Selected and 0.89 or 1) 136 | end) 137 | Creator.AddSignal(Tab.Frame.MouseButton1Down, function() 138 | Tab.SetTransparency(0.92) 139 | end) 140 | Creator.AddSignal(Tab.Frame.MouseButton1Up, function() 141 | Tab.SetTransparency(Tab.Selected and 0.85 or 0.89) 142 | end) 143 | Creator.AddSignal(Tab.Frame.MouseButton1Click, function() 144 | TabModule:SelectTab(TabIndex) 145 | end) 146 | 147 | TabModule.Containers[TabIndex] = Tab.ContainerFrame 148 | TabModule.Tabs[TabIndex] = Tab 149 | 150 | Tab.Container = Tab.ContainerFrame 151 | Tab.ScrollFrame = Tab.Container 152 | 153 | function Tab:AddSection(SectionTitle) 154 | local Section = { Type = "Section" } 155 | 156 | local SectionFrame = require(Components.Section)(SectionTitle, Tab.Container) 157 | Section.Container = SectionFrame.Container 158 | Section.ScrollFrame = Tab.Container 159 | 160 | setmetatable(Section, Elements) 161 | return Section 162 | end 163 | 164 | setmetatable(Tab, Elements) 165 | return Tab 166 | end 167 | 168 | function TabModule:SelectTab(Tab) 169 | local Window = TabModule.Window 170 | 171 | TabModule.SelectedTab = Tab 172 | 173 | for _, TabObject in next, TabModule.Tabs do 174 | TabObject.SetTransparency(1) 175 | TabObject.Selected = false 176 | end 177 | TabModule.Tabs[Tab].SetTransparency(0.89) 178 | TabModule.Tabs[Tab].Selected = true 179 | 180 | Window.TabDisplay.Text = TabModule.Tabs[Tab].Name 181 | Window.SelectorPosMotor:setGoal(Spring(TabModule:GetCurrentTabPos(), { frequency = 6 })) 182 | 183 | task.spawn(function() 184 | Window.ContainerHolder.Parent = Window.ContainerAnim 185 | 186 | Window.ContainerPosMotor:setGoal(Spring(15, { frequency = 10 })) 187 | Window.ContainerBackMotor:setGoal(Spring(1, { frequency = 10 })) 188 | task.wait(0.12) 189 | for _, Container in next, TabModule.Containers do 190 | Container.Visible = false 191 | end 192 | TabModule.Containers[Tab].Visible = true 193 | Window.ContainerPosMotor:setGoal(Spring(0, { frequency = 5 })) 194 | Window.ContainerBackMotor:setGoal(Spring(0, { frequency = 8 })) 195 | task.wait(0.12) 196 | Window.ContainerHolder.Parent = Window.ContainerCanvas 197 | end) 198 | end 199 | 200 | return TabModule 201 | -------------------------------------------------------------------------------- /src/Components/Textbox.lua: -------------------------------------------------------------------------------- 1 | local TextService = game:GetService("TextService") 2 | local Root = script.Parent.Parent 3 | local Flipper = require(Root.Packages.Flipper) 4 | local Creator = require(Root.Creator) 5 | local New = Creator.New 6 | 7 | return function(Parent, Acrylic) 8 | Acrylic = Acrylic or false 9 | local Textbox = {} 10 | 11 | Textbox.Input = New("TextBox", { 12 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 13 | TextColor3 = Color3.fromRGB(200, 200, 200), 14 | TextSize = 14, 15 | TextXAlignment = Enum.TextXAlignment.Left, 16 | TextYAlignment = Enum.TextYAlignment.Center, 17 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 18 | AutomaticSize = Enum.AutomaticSize.Y, 19 | BackgroundTransparency = 1, 20 | Size = UDim2.fromScale(1, 1), 21 | Position = UDim2.fromOffset(10, 0), 22 | ThemeTag = { 23 | TextColor3 = "Text", 24 | PlaceholderColor3 = "SubText", 25 | }, 26 | }) 27 | 28 | Textbox.Container = New("Frame", { 29 | BackgroundTransparency = 1, 30 | ClipsDescendants = true, 31 | Position = UDim2.new(0, 6, 0, 0), 32 | Size = UDim2.new(1, -12, 1, 0), 33 | }, { 34 | Textbox.Input, 35 | }) 36 | 37 | Textbox.Indicator = New("Frame", { 38 | Size = UDim2.new(1, -4, 0, 1), 39 | Position = UDim2.new(0, 2, 1, 0), 40 | AnchorPoint = Vector2.new(0, 1), 41 | BackgroundTransparency = Acrylic and 0.5 or 0, 42 | ThemeTag = { 43 | BackgroundColor3 = Acrylic and "InputIndicator" or "DialogInputLine", 44 | }, 45 | }) 46 | 47 | Textbox.Frame = New("Frame", { 48 | Size = UDim2.new(0, 0, 0, 30), 49 | BackgroundTransparency = Acrylic and 0.9 or 0, 50 | Parent = Parent, 51 | ThemeTag = { 52 | BackgroundColor3 = Acrylic and "Input" or "DialogInput", 53 | }, 54 | }, { 55 | New("UICorner", { 56 | CornerRadius = UDim.new(0, 4), 57 | }), 58 | New("UIStroke", { 59 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 60 | Transparency = Acrylic and 0.5 or 0.65, 61 | ThemeTag = { 62 | Color = Acrylic and "InElementBorder" or "DialogButtonBorder", 63 | }, 64 | }), 65 | Textbox.Indicator, 66 | Textbox.Container, 67 | }) 68 | 69 | local function Update() 70 | local PADDING = 2 71 | local Reveal = Textbox.Container.AbsoluteSize.X 72 | 73 | if not Textbox.Input:IsFocused() or Textbox.Input.TextBounds.X <= Reveal - 2 * PADDING then 74 | Textbox.Input.Position = UDim2.new(0, PADDING, 0, 0) 75 | else 76 | local Cursor = Textbox.Input.CursorPosition 77 | if Cursor ~= -1 then 78 | local subtext = string.sub(Textbox.Input.Text, 1, Cursor - 1) 79 | local width = TextService:GetTextSize( 80 | subtext, 81 | Textbox.Input.TextSize, 82 | Textbox.Input.Font, 83 | Vector2.new(math.huge, math.huge) 84 | ).X 85 | 86 | local CurrentCursorPos = Textbox.Input.Position.X.Offset + width 87 | if CurrentCursorPos < PADDING then 88 | Textbox.Input.Position = UDim2.fromOffset(PADDING - width, 0) 89 | elseif CurrentCursorPos > Reveal - PADDING - 1 then 90 | Textbox.Input.Position = UDim2.fromOffset(Reveal - width - PADDING - 1, 0) 91 | end 92 | end 93 | end 94 | end 95 | 96 | task.spawn(Update) 97 | 98 | Creator.AddSignal(Textbox.Input:GetPropertyChangedSignal("Text"), Update) 99 | Creator.AddSignal(Textbox.Input:GetPropertyChangedSignal("CursorPosition"), Update) 100 | 101 | Creator.AddSignal(Textbox.Input.Focused, function() 102 | Update() 103 | Textbox.Indicator.Size = UDim2.new(1, -2, 0, 2) 104 | Textbox.Indicator.Position = UDim2.new(0, 1, 1, 0) 105 | Textbox.Indicator.BackgroundTransparency = 0 106 | Creator.OverrideTag(Textbox.Frame, { BackgroundColor3 = Acrylic and "InputFocused" or "DialogHolder" }) 107 | Creator.OverrideTag(Textbox.Indicator, { BackgroundColor3 = "Accent" }) 108 | end) 109 | 110 | Creator.AddSignal(Textbox.Input.FocusLost, function() 111 | Update() 112 | Textbox.Indicator.Size = UDim2.new(1, -4, 0, 1) 113 | Textbox.Indicator.Position = UDim2.new(0, 2, 1, 0) 114 | Textbox.Indicator.BackgroundTransparency = 0.5 115 | Creator.OverrideTag(Textbox.Frame, { BackgroundColor3 = Acrylic and "Input" or "DialogInput" }) 116 | Creator.OverrideTag(Textbox.Indicator, { BackgroundColor3 = Acrylic and "InputIndicator" or "DialogInputLine" }) 117 | end) 118 | 119 | return Textbox 120 | end 121 | -------------------------------------------------------------------------------- /src/Components/TitleBar.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Assets = require(script.Parent.Assets) 3 | local Creator = require(Root.Creator) 4 | local Flipper = require(Root.Packages.Flipper) 5 | 6 | local New = Creator.New 7 | local AddSignal = Creator.AddSignal 8 | 9 | return function(Config) 10 | local TitleBar = {} 11 | 12 | local Library = require(Root) 13 | 14 | local function BarButton(Icon, Pos, Parent, Callback) 15 | local Button = { 16 | Callback = Callback or function() end, 17 | } 18 | 19 | Button.Frame = New("TextButton", { 20 | Size = UDim2.new(0, 34, 1, -8), 21 | AnchorPoint = Vector2.new(1, 0), 22 | BackgroundTransparency = 1, 23 | Parent = Parent, 24 | Position = Pos, 25 | Text = "", 26 | ThemeTag = { 27 | BackgroundColor3 = "Text", 28 | }, 29 | }, { 30 | New("UICorner", { 31 | CornerRadius = UDim.new(0, 7), 32 | }), 33 | New("ImageLabel", { 34 | Image = Icon, 35 | Size = UDim2.fromOffset(16, 16), 36 | Position = UDim2.fromScale(0.5, 0.5), 37 | AnchorPoint = Vector2.new(0.5, 0.5), 38 | BackgroundTransparency = 1, 39 | Name = "Icon", 40 | ThemeTag = { 41 | ImageColor3 = "Text", 42 | }, 43 | }), 44 | }) 45 | 46 | local Motor, SetTransparency = Creator.SpringMotor(1, Button.Frame, "BackgroundTransparency") 47 | 48 | AddSignal(Button.Frame.MouseEnter, function() 49 | SetTransparency(0.94) 50 | end) 51 | AddSignal(Button.Frame.MouseLeave, function() 52 | SetTransparency(1, true) 53 | end) 54 | AddSignal(Button.Frame.MouseButton1Down, function() 55 | SetTransparency(0.96) 56 | end) 57 | AddSignal(Button.Frame.MouseButton1Up, function() 58 | SetTransparency(0.94) 59 | end) 60 | AddSignal(Button.Frame.MouseButton1Click, Button.Callback) 61 | 62 | Button.SetCallback = function(Func) 63 | Button.Callback = Func 64 | end 65 | 66 | return Button 67 | end 68 | 69 | TitleBar.Frame = New("Frame", { 70 | Size = UDim2.new(1, 0, 0, 42), 71 | BackgroundTransparency = 1, 72 | Parent = Config.Parent, 73 | }, { 74 | New("Frame", { 75 | Size = UDim2.new(1, -16, 1, 0), 76 | Position = UDim2.new(0, 16, 0, 0), 77 | BackgroundTransparency = 1, 78 | }, { 79 | New("UIListLayout", { 80 | Padding = UDim.new(0, 5), 81 | FillDirection = Enum.FillDirection.Horizontal, 82 | SortOrder = Enum.SortOrder.LayoutOrder, 83 | }), 84 | New("TextLabel", { 85 | RichText = true, 86 | Text = Config.Title, 87 | FontFace = Font.new( 88 | "rbxasset://fonts/families/GothamSSm.json", 89 | Enum.FontWeight.Regular, 90 | Enum.FontStyle.Normal 91 | ), 92 | TextSize = 12, 93 | TextXAlignment = "Left", 94 | TextYAlignment = "Center", 95 | Size = UDim2.fromScale(0, 1), 96 | AutomaticSize = Enum.AutomaticSize.X, 97 | BackgroundTransparency = 1, 98 | ThemeTag = { 99 | TextColor3 = "Text", 100 | }, 101 | }), 102 | New("TextLabel", { 103 | RichText = true, 104 | Text = Config.SubTitle, 105 | TextTransparency = 0.4, 106 | FontFace = Font.new( 107 | "rbxasset://fonts/families/GothamSSm.json", 108 | Enum.FontWeight.Regular, 109 | Enum.FontStyle.Normal 110 | ), 111 | TextSize = 12, 112 | TextXAlignment = "Left", 113 | TextYAlignment = "Center", 114 | Size = UDim2.fromScale(0, 1), 115 | AutomaticSize = Enum.AutomaticSize.X, 116 | BackgroundTransparency = 1, 117 | ThemeTag = { 118 | TextColor3 = "Text", 119 | }, 120 | }), 121 | }), 122 | New("Frame", { 123 | BackgroundTransparency = 0.5, 124 | Size = UDim2.new(1, 0, 0, 1), 125 | Position = UDim2.new(0, 0, 1, 0), 126 | ThemeTag = { 127 | BackgroundColor3 = "TitleBarLine", 128 | }, 129 | }), 130 | }) 131 | 132 | TitleBar.CloseButton = BarButton(Assets.Close, UDim2.new(1, -4, 0, 4), TitleBar.Frame, function() 133 | Library.Window:Dialog({ 134 | Title = "Close", 135 | Content = "Are you sure you want to unload the interface?", 136 | Buttons = { 137 | { 138 | Title = "Yes", 139 | Callback = function() 140 | Library:Destroy() 141 | end, 142 | }, 143 | { 144 | Title = "No", 145 | }, 146 | }, 147 | }) 148 | end) 149 | TitleBar.MaxButton = BarButton(Assets.Max, UDim2.new(1, -40, 0, 4), TitleBar.Frame, function() 150 | Config.Window.Maximize(not Config.Window.Maximized) 151 | end) 152 | TitleBar.MinButton = BarButton(Assets.Min, UDim2.new(1, -80, 0, 4), TitleBar.Frame, function() 153 | Library.Window:Minimize() 154 | end) 155 | 156 | return TitleBar 157 | end 158 | -------------------------------------------------------------------------------- /src/Components/Window.lua: -------------------------------------------------------------------------------- 1 | -- i will rewrite this someday 2 | local UserInputService = game:GetService("UserInputService") 3 | local Mouse = game:GetService("Players").LocalPlayer:GetMouse() 4 | local Camera = game:GetService("Workspace").CurrentCamera 5 | 6 | local Root = script.Parent.Parent 7 | local Flipper = require(Root.Packages.Flipper) 8 | local Creator = require(Root.Creator) 9 | local Acrylic = require(Root.Acrylic) 10 | local Assets = require(script.Parent.Assets) 11 | local Components = script.Parent 12 | 13 | local Spring = Flipper.Spring.new 14 | local Instant = Flipper.Instant.new 15 | local New = Creator.New 16 | 17 | return function(Config) 18 | local Library = require(Root) 19 | 20 | local Window = { 21 | Minimized = false, 22 | Maximized = false, 23 | Size = Config.Size, 24 | CurrentPos = 0, 25 | TabWidth = 0, 26 | Position = UDim2.fromOffset( 27 | Camera.ViewportSize.X / 2 - Config.Size.X.Offset / 2, 28 | Camera.ViewportSize.Y / 2 - Config.Size.Y.Offset / 2 29 | ), 30 | } 31 | 32 | local Dragging, DragInput, MousePos, StartPos = false 33 | local Resizing, ResizePos = false 34 | local MinimizeNotif = false 35 | 36 | Window.AcrylicPaint = Acrylic.AcrylicPaint() 37 | Window.TabWidth = Config.TabWidth 38 | 39 | local Selector = New("Frame", { 40 | Size = UDim2.fromOffset(4, 0), 41 | BackgroundColor3 = Color3.fromRGB(76, 194, 255), 42 | Position = UDim2.fromOffset(0, 17), 43 | AnchorPoint = Vector2.new(0, 0.5), 44 | ThemeTag = { 45 | BackgroundColor3 = "Accent", 46 | }, 47 | }, { 48 | New("UICorner", { 49 | CornerRadius = UDim.new(0, 2), 50 | }), 51 | }) 52 | 53 | local ResizeStartFrame = New("Frame", { 54 | Size = UDim2.fromOffset(20, 20), 55 | BackgroundTransparency = 1, 56 | Position = UDim2.new(1, -20, 1, -20), 57 | }) 58 | 59 | Window.TabHolder = New("ScrollingFrame", { 60 | Size = UDim2.fromScale(1, 1), 61 | BackgroundTransparency = 1, 62 | ScrollBarImageTransparency = 1, 63 | ScrollBarThickness = 0, 64 | BorderSizePixel = 0, 65 | CanvasSize = UDim2.fromScale(0, 0), 66 | ScrollingDirection = Enum.ScrollingDirection.Y, 67 | }, { 68 | New("UIListLayout", { 69 | Padding = UDim.new(0, 4), 70 | }), 71 | }) 72 | 73 | local TabFrame = New("Frame", { 74 | Size = UDim2.new(0, Window.TabWidth, 1, -66), 75 | Position = UDim2.new(0, 12, 0, 54), 76 | BackgroundTransparency = 1, 77 | ClipsDescendants = true, 78 | }, { 79 | Window.TabHolder, 80 | Selector, 81 | }) 82 | 83 | Window.TabDisplay = New("TextLabel", { 84 | RichText = true, 85 | Text = "Tab", 86 | TextTransparency = 0, 87 | FontFace = Font.new("rbxassetid://12187365364", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal), 88 | TextSize = 28, 89 | TextXAlignment = "Left", 90 | TextYAlignment = "Center", 91 | Size = UDim2.new(1, -16, 0, 28), 92 | Position = UDim2.fromOffset(Window.TabWidth + 26, 56), 93 | BackgroundTransparency = 1, 94 | ThemeTag = { 95 | TextColor3 = "Text", 96 | }, 97 | }) 98 | 99 | Window.ContainerHolder = New("Frame", { 100 | Size = UDim2.fromScale(1, 1), 101 | BackgroundTransparency = 1, 102 | }) 103 | 104 | Window.ContainerAnim = New("CanvasGroup", { 105 | Size = UDim2.fromScale(1, 1), 106 | BackgroundTransparency = 1, 107 | }) 108 | 109 | Window.ContainerCanvas = New("Frame", { 110 | Size = UDim2.new(1, -Window.TabWidth - 32, 1, -102), 111 | Position = UDim2.fromOffset(Window.TabWidth + 26, 90), 112 | BackgroundTransparency = 1, 113 | }, { 114 | Window.ContainerAnim, 115 | Window.ContainerHolder 116 | }) 117 | 118 | Window.Root = New("Frame", { 119 | BackgroundTransparency = 1, 120 | Size = Window.Size, 121 | Position = Window.Position, 122 | Parent = Config.Parent, 123 | }, { 124 | Window.AcrylicPaint.Frame, 125 | Window.TabDisplay, 126 | Window.ContainerCanvas, 127 | TabFrame, 128 | ResizeStartFrame, 129 | }) 130 | 131 | Window.TitleBar = require(script.Parent.TitleBar)({ 132 | Title = Config.Title, 133 | SubTitle = Config.SubTitle, 134 | Parent = Window.Root, 135 | Window = Window, 136 | }) 137 | 138 | if require(Root).UseAcrylic then 139 | Window.AcrylicPaint.AddParent(Window.Root) 140 | end 141 | 142 | local SizeMotor = Flipper.GroupMotor.new({ 143 | X = Window.Size.X.Offset, 144 | Y = Window.Size.Y.Offset, 145 | }) 146 | 147 | local PosMotor = Flipper.GroupMotor.new({ 148 | X = Window.Position.X.Offset, 149 | Y = Window.Position.Y.Offset, 150 | }) 151 | 152 | Window.SelectorPosMotor = Flipper.SingleMotor.new(17) 153 | Window.SelectorSizeMotor = Flipper.SingleMotor.new(0) 154 | Window.ContainerBackMotor = Flipper.SingleMotor.new(0) 155 | Window.ContainerPosMotor = Flipper.SingleMotor.new(94) 156 | 157 | SizeMotor:onStep(function(values) 158 | Window.Root.Size = UDim2.new(0, values.X, 0, values.Y) 159 | end) 160 | 161 | PosMotor:onStep(function(values) 162 | Window.Root.Position = UDim2.new(0, values.X, 0, values.Y) 163 | end) 164 | 165 | local LastValue = 0 166 | local LastTime = 0 167 | Window.SelectorPosMotor:onStep(function(Value) 168 | Selector.Position = UDim2.new(0, 0, 0, Value + 17) 169 | local Now = tick() 170 | local DeltaTime = Now - LastTime 171 | 172 | if LastValue ~= nil then 173 | Window.SelectorSizeMotor:setGoal(Spring((math.abs(Value - LastValue) / (DeltaTime * 60)) + 16)) 174 | LastValue = Value 175 | end 176 | LastTime = Now 177 | end) 178 | 179 | Window.SelectorSizeMotor:onStep(function(Value) 180 | Selector.Size = UDim2.new(0, 4, 0, Value) 181 | end) 182 | 183 | Window.ContainerBackMotor:onStep(function(Value) 184 | Window.ContainerAnim.GroupTransparency = Value 185 | end) 186 | 187 | Window.ContainerPosMotor:onStep(function(Value) 188 | Window.ContainerAnim.Position = UDim2.fromOffset(0, Value) 189 | end) 190 | 191 | local OldSizeX 192 | local OldSizeY 193 | Window.Maximize = function(Value, NoPos, Instant) 194 | Window.Maximized = Value 195 | Window.TitleBar.MaxButton.Frame.Icon.Image = Value and Assets.Restore or Assets.Max 196 | 197 | if Value then 198 | OldSizeX = Window.Size.X.Offset 199 | OldSizeY = Window.Size.Y.Offset 200 | end 201 | local SizeX = Value and Camera.ViewportSize.X or OldSizeX 202 | local SizeY = Value and Camera.ViewportSize.Y or OldSizeY 203 | SizeMotor:setGoal({ 204 | X = Flipper[Instant and "Instant" or "Spring"].new(SizeX, { frequency = 6 }), 205 | Y = Flipper[Instant and "Instant" or "Spring"].new(SizeY, { frequency = 6 }), 206 | }) 207 | Window.Size = UDim2.fromOffset(SizeX, SizeY) 208 | 209 | if not NoPos then 210 | PosMotor:setGoal({ 211 | X = Spring(Value and 0 or Window.Position.X.Offset, { frequency = 6 }), 212 | Y = Spring(Value and 0 or Window.Position.Y.Offset, { frequency = 6 }), 213 | }) 214 | end 215 | end 216 | 217 | Creator.AddSignal(Window.TitleBar.Frame.InputBegan, function(Input) 218 | if 219 | Input.UserInputType == Enum.UserInputType.MouseButton1 220 | or Input.UserInputType == Enum.UserInputType.Touch 221 | then 222 | Dragging = true 223 | MousePos = Input.Position 224 | StartPos = Window.Root.Position 225 | 226 | if Window.Maximized then 227 | StartPos = UDim2.fromOffset( 228 | Mouse.X - (Mouse.X * ((OldSizeX - 100) / Window.Root.AbsoluteSize.X)), 229 | Mouse.Y - (Mouse.Y * (OldSizeY / Window.Root.AbsoluteSize.Y)) 230 | ) 231 | end 232 | 233 | Input.Changed:Connect(function() 234 | if Input.UserInputState == Enum.UserInputState.End then 235 | Dragging = false 236 | end 237 | end) 238 | end 239 | end) 240 | 241 | Creator.AddSignal(Window.TitleBar.Frame.InputChanged, function(Input) 242 | if 243 | Input.UserInputType == Enum.UserInputType.MouseMovement 244 | or Input.UserInputType == Enum.UserInputType.Touch 245 | then 246 | DragInput = Input 247 | end 248 | end) 249 | 250 | Creator.AddSignal(ResizeStartFrame.InputBegan, function(Input) 251 | if 252 | Input.UserInputType == Enum.UserInputType.MouseButton1 253 | or Input.UserInputType == Enum.UserInputType.Touch 254 | then 255 | Resizing = true 256 | ResizePos = Input.Position 257 | end 258 | end) 259 | 260 | Creator.AddSignal(UserInputService.InputChanged, function(Input) 261 | if Input == DragInput and Dragging then 262 | local Delta = Input.Position - MousePos 263 | Window.Position = UDim2.fromOffset(StartPos.X.Offset + Delta.X, StartPos.Y.Offset + Delta.Y) 264 | PosMotor:setGoal({ 265 | X = Instant(Window.Position.X.Offset), 266 | Y = Instant(Window.Position.Y.Offset), 267 | }) 268 | 269 | if Window.Maximized then 270 | Window.Maximize(false, true, true) 271 | end 272 | end 273 | 274 | if 275 | (Input.UserInputType == Enum.UserInputType.MouseMovement or Input.UserInputType == Enum.UserInputType.Touch) 276 | and Resizing 277 | then 278 | local Delta = Input.Position - ResizePos 279 | local StartSize = Window.Size 280 | 281 | local TargetSize = Vector3.new(StartSize.X.Offset, StartSize.Y.Offset, 0) + Vector3.new(1, 1, 0) * Delta 282 | local TargetSizeClamped = 283 | Vector2.new(math.clamp(TargetSize.X, 470, 2048), math.clamp(TargetSize.Y, 380, 2048)) 284 | 285 | SizeMotor:setGoal({ 286 | X = Flipper.Instant.new(TargetSizeClamped.X), 287 | Y = Flipper.Instant.new(TargetSizeClamped.Y), 288 | }) 289 | end 290 | end) 291 | 292 | Creator.AddSignal(UserInputService.InputEnded, function(Input) 293 | if Resizing == true or Input.UserInputType == Enum.UserInputType.Touch then 294 | Resizing = false 295 | Window.Size = UDim2.fromOffset(SizeMotor:getValue().X, SizeMotor:getValue().Y) 296 | end 297 | end) 298 | 299 | Creator.AddSignal(Window.TabHolder.UIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"), function() 300 | Window.TabHolder.CanvasSize = UDim2.new(0, 0, 0, Window.TabHolder.UIListLayout.AbsoluteContentSize.Y) 301 | end) 302 | 303 | Creator.AddSignal(UserInputService.InputBegan, function(Input) 304 | if 305 | type(Library.MinimizeKeybind) == "table" 306 | and Library.MinimizeKeybind.Type == "Keybind" 307 | and not UserInputService:GetFocusedTextBox() 308 | then 309 | if Input.KeyCode.Name == Library.MinimizeKeybind.Value then 310 | Window:Minimize() 311 | end 312 | elseif Input.KeyCode == Library.MinimizeKey and not UserInputService:GetFocusedTextBox() then 313 | Window:Minimize() 314 | end 315 | end) 316 | 317 | function Window:Minimize() 318 | Window.Minimized = not Window.Minimized 319 | Window.Root.Visible = not Window.Minimized 320 | if not MinimizeNotif then 321 | MinimizeNotif = true 322 | local Key = Library.MinimizeKeybind and Library.MinimizeKeybind.Value or Library.MinimizeKey.Name 323 | Library:Notify({ 324 | Title = "Interface", 325 | Content = "Press " .. Key .. " to toggle the interface.", 326 | Duration = 6 327 | }) 328 | end 329 | end 330 | 331 | function Window:Destroy() 332 | if require(Root).UseAcrylic then 333 | Window.AcrylicPaint.Model:Destroy() 334 | end 335 | Window.Root:Destroy() 336 | end 337 | 338 | local DialogModule = require(Components.Dialog):Init(Window) 339 | function Window:Dialog(Config) 340 | local Dialog = DialogModule:Create() 341 | Dialog.Title.Text = Config.Title 342 | 343 | local Content = New("TextLabel", { 344 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 345 | Text = Config.Content, 346 | TextColor3 = Color3.fromRGB(240, 240, 240), 347 | TextSize = 14, 348 | TextXAlignment = Enum.TextXAlignment.Left, 349 | TextYAlignment = Enum.TextYAlignment.Top, 350 | Size = UDim2.new(1, -40, 1, 0), 351 | Position = UDim2.fromOffset(20, 60), 352 | BackgroundTransparency = 1, 353 | Parent = Dialog.Root, 354 | ClipsDescendants = false, 355 | ThemeTag = { 356 | TextColor3 = "Text", 357 | }, 358 | }) 359 | 360 | New("UISizeConstraint", { 361 | MinSize = Vector2.new(300, 165), 362 | MaxSize = Vector2.new(620, math.huge), 363 | Parent = Dialog.Root, 364 | }) 365 | 366 | Dialog.Root.Size = UDim2.fromOffset(Content.TextBounds.X + 40, 165) 367 | if Content.TextBounds.X + 40 > Window.Size.X.Offset - 120 then 368 | Dialog.Root.Size = UDim2.fromOffset(Window.Size.X.Offset - 120, 165) 369 | Content.TextWrapped = true 370 | Dialog.Root.Size = UDim2.fromOffset(Window.Size.X.Offset - 120, Content.TextBounds.Y + 150) 371 | end 372 | 373 | for _, Button in next, Config.Buttons do 374 | Dialog:Button(Button.Title, Button.Callback) 375 | end 376 | 377 | Dialog:Open() 378 | end 379 | 380 | local TabModule = require(Components.Tab):Init(Window) 381 | function Window:AddTab(TabConfig) 382 | return TabModule:New(TabConfig.Title, TabConfig.Icon, Window.TabHolder) 383 | end 384 | 385 | function Window:SelectTab(Tab) 386 | TabModule:SelectTab(1) 387 | end 388 | 389 | Creator.AddSignal(Window.TabHolder:GetPropertyChangedSignal("CanvasPosition"), function() 390 | LastValue = TabModule:GetCurrentTabPos() + 16 391 | LastTime = 0 392 | Window.SelectorPosMotor:setGoal(Instant(TabModule:GetCurrentTabPos())) 393 | end) 394 | 395 | return Window 396 | end 397 | -------------------------------------------------------------------------------- /src/Creator.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent 2 | local Themes = require(Root.Themes) 3 | local Flipper = require(Root.Packages.Flipper) 4 | 5 | local Creator = { 6 | Registry = {}, 7 | Signals = {}, 8 | TransparencyMotors = {}, 9 | DefaultProperties = { 10 | ScreenGui = { 11 | ResetOnSpawn = false, 12 | ZIndexBehavior = Enum.ZIndexBehavior.Sibling, 13 | }, 14 | Frame = { 15 | BackgroundColor3 = Color3.new(1, 1, 1), 16 | BorderColor3 = Color3.new(0, 0, 0), 17 | BorderSizePixel = 0, 18 | }, 19 | ScrollingFrame = { 20 | BackgroundColor3 = Color3.new(1, 1, 1), 21 | BorderColor3 = Color3.new(0, 0, 0), 22 | ScrollBarImageColor3 = Color3.new(0, 0, 0), 23 | }, 24 | TextLabel = { 25 | BackgroundColor3 = Color3.new(1, 1, 1), 26 | BorderColor3 = Color3.new(0, 0, 0), 27 | Font = Enum.Font.SourceSans, 28 | Text = "", 29 | TextColor3 = Color3.new(0, 0, 0), 30 | BackgroundTransparency = 1, 31 | TextSize = 14, 32 | }, 33 | TextButton = { 34 | BackgroundColor3 = Color3.new(1, 1, 1), 35 | BorderColor3 = Color3.new(0, 0, 0), 36 | AutoButtonColor = false, 37 | Font = Enum.Font.SourceSans, 38 | Text = "", 39 | TextColor3 = Color3.new(0, 0, 0), 40 | TextSize = 14, 41 | }, 42 | TextBox = { 43 | BackgroundColor3 = Color3.new(1, 1, 1), 44 | BorderColor3 = Color3.new(0, 0, 0), 45 | ClearTextOnFocus = false, 46 | Font = Enum.Font.SourceSans, 47 | Text = "", 48 | TextColor3 = Color3.new(0, 0, 0), 49 | TextSize = 14, 50 | }, 51 | ImageLabel = { 52 | BackgroundTransparency = 1, 53 | BackgroundColor3 = Color3.new(1, 1, 1), 54 | BorderColor3 = Color3.new(0, 0, 0), 55 | BorderSizePixel = 0, 56 | }, 57 | ImageButton = { 58 | BackgroundColor3 = Color3.new(1, 1, 1), 59 | BorderColor3 = Color3.new(0, 0, 0), 60 | AutoButtonColor = false, 61 | }, 62 | CanvasGroup = { 63 | BackgroundColor3 = Color3.new(1, 1, 1), 64 | BorderColor3 = Color3.new(0, 0, 0), 65 | BorderSizePixel = 0, 66 | }, 67 | }, 68 | } 69 | 70 | local function ApplyCustomProps(Object, Props) 71 | if Props.ThemeTag then 72 | Creator.AddThemeObject(Object, Props.ThemeTag) 73 | end 74 | end 75 | 76 | function Creator.AddSignal(Signal, Function) 77 | table.insert(Creator.Signals, Signal:Connect(Function)) 78 | end 79 | 80 | function Creator.Disconnect() 81 | for Idx = #Creator.Signals, 1, -1 do 82 | local Connection = table.remove(Creator.Signals, Idx) 83 | Connection:Disconnect() 84 | end 85 | end 86 | 87 | function Creator.GetThemeProperty(Property) 88 | if Themes[require(Root).Theme][Property] then 89 | return Themes[require(Root).Theme][Property] 90 | end 91 | return Themes["Dark"][Property] 92 | end 93 | 94 | function Creator.UpdateTheme() 95 | for Instance, Object in next, Creator.Registry do 96 | for Property, ColorIdx in next, Object.Properties do 97 | Instance[Property] = Creator.GetThemeProperty(ColorIdx) 98 | end 99 | end 100 | 101 | for _, Motor in next, Creator.TransparencyMotors do 102 | Motor:setGoal(Flipper.Instant.new(Creator.GetThemeProperty("ElementTransparency"))) 103 | end 104 | end 105 | 106 | function Creator.AddThemeObject(Object, Properties) 107 | local Idx = #Creator.Registry + 1 108 | local Data = { 109 | Object = Object, 110 | Properties = Properties, 111 | Idx = Idx, 112 | } 113 | 114 | Creator.Registry[Object] = Data 115 | Creator.UpdateTheme() 116 | return Object 117 | end 118 | 119 | function Creator.OverrideTag(Object, Properties) 120 | Creator.Registry[Object].Properties = Properties 121 | Creator.UpdateTheme() 122 | end 123 | 124 | function Creator.New(Name, Properties, Children) 125 | local Object = Instance.new(Name) 126 | 127 | -- Default properties 128 | for Name, Value in next, Creator.DefaultProperties[Name] or {} do 129 | Object[Name] = Value 130 | end 131 | 132 | -- Properties 133 | for Name, Value in next, Properties or {} do 134 | if Name ~= "ThemeTag" then 135 | Object[Name] = Value 136 | end 137 | end 138 | 139 | -- Children 140 | for _, Child in next, Children or {} do 141 | Child.Parent = Object 142 | end 143 | 144 | ApplyCustomProps(Object, Properties) 145 | return Object 146 | end 147 | 148 | function Creator.SpringMotor(Initial, Instance, Prop, IgnoreDialogCheck, ResetOnThemeChange) 149 | IgnoreDialogCheck = IgnoreDialogCheck or false 150 | ResetOnThemeChange = ResetOnThemeChange or false 151 | local Motor = Flipper.SingleMotor.new(Initial) 152 | Motor:onStep(function(value) 153 | Instance[Prop] = value 154 | end) 155 | 156 | if ResetOnThemeChange then 157 | table.insert(Creator.TransparencyMotors, Motor) 158 | end 159 | 160 | local function SetValue(Value, Ignore) 161 | Ignore = Ignore or false 162 | if not IgnoreDialogCheck then 163 | if not Ignore then 164 | if Prop == "BackgroundTransparency" and require(Root).DialogOpen then 165 | return 166 | end 167 | end 168 | end 169 | Motor:setGoal(Flipper.Spring.new(Value, { frequency = 8 })) 170 | end 171 | 172 | return Motor, SetValue 173 | end 174 | 175 | return Creator 176 | -------------------------------------------------------------------------------- /src/Elements/Button.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Creator = require(Root.Creator) 3 | 4 | local New = Creator.New 5 | local Components = Root.Components 6 | 7 | local Element = {} 8 | Element.__index = Element 9 | Element.__type = "Button" 10 | 11 | function Element:New(Config) 12 | assert(Config.Title, "Button - Missing Title") 13 | Config.Callback = Config.Callback or function() end 14 | 15 | local ButtonFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, true) 16 | 17 | local ButtonIco = New("ImageLabel", { 18 | Image = "rbxassetid://10709791437", 19 | Size = UDim2.fromOffset(16, 16), 20 | AnchorPoint = Vector2.new(1, 0.5), 21 | Position = UDim2.new(1, -10, 0.5, 0), 22 | BackgroundTransparency = 1, 23 | Parent = ButtonFrame.Frame, 24 | ThemeTag = { 25 | ImageColor3 = "Text", 26 | }, 27 | }) 28 | 29 | Creator.AddSignal(ButtonFrame.Frame.MouseButton1Click, function() 30 | self.Library:SafeCallback(Config.Callback) 31 | end) 32 | 33 | return ButtonFrame 34 | end 35 | 36 | return Element 37 | -------------------------------------------------------------------------------- /src/Elements/Colorpicker.lua: -------------------------------------------------------------------------------- 1 | local UserInputService = game:GetService("UserInputService") 2 | local TouchInputService = game:GetService("TouchInputService") 3 | local RunService = game:GetService("RunService") 4 | local Players = game:GetService("Players") 5 | 6 | local RenderStepped = RunService.RenderStepped 7 | local LocalPlayer = Players.LocalPlayer 8 | local Mouse = LocalPlayer:GetMouse() 9 | 10 | local Root = script.Parent.Parent 11 | local Creator = require(Root.Creator) 12 | 13 | local New = Creator.New 14 | local Components = Root.Components 15 | 16 | local Element = {} 17 | Element.__index = Element 18 | Element.__type = "Colorpicker" 19 | 20 | function Element:New(Idx, Config) 21 | local Library = self.Library 22 | assert(Config.Title, "Colorpicker - Missing Title") 23 | assert(Config.Default, "AddColorPicker: Missing default value.") 24 | 25 | local Colorpicker = { 26 | Value = Config.Default, 27 | Transparency = Config.Transparency or 0, 28 | Type = "Colorpicker", 29 | Title = type(Config.Title) == "string" and Config.Title or "Colorpicker", 30 | Callback = Config.Callback or function(Color) end, 31 | } 32 | 33 | function Colorpicker:SetHSVFromRGB(Color) 34 | local H, S, V = Color3.toHSV(Color) 35 | Colorpicker.Hue = H 36 | Colorpicker.Sat = S 37 | Colorpicker.Vib = V 38 | end 39 | 40 | Colorpicker:SetHSVFromRGB(Colorpicker.Value) 41 | 42 | local ColorpickerFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, true) 43 | 44 | Colorpicker.SetTitle = ColorpickerFrame.SetTitle 45 | Colorpicker.SetDesc = ColorpickerFrame.SetDesc 46 | 47 | local DisplayFrameColor = New("Frame", { 48 | Size = UDim2.fromScale(1, 1), 49 | BackgroundColor3 = Colorpicker.Value, 50 | Parent = ColorpickerFrame.Frame, 51 | }, { 52 | New("UICorner", { 53 | CornerRadius = UDim.new(0, 4), 54 | }), 55 | }) 56 | 57 | local DisplayFrame = New("ImageLabel", { 58 | Size = UDim2.fromOffset(26, 26), 59 | Position = UDim2.new(1, -10, 0.5, 0), 60 | AnchorPoint = Vector2.new(1, 0.5), 61 | Parent = ColorpickerFrame.Frame, 62 | Image = "http://www.roblox.com/asset/?id=14204231522", 63 | ImageTransparency = 0.45, 64 | ScaleType = Enum.ScaleType.Tile, 65 | TileSize = UDim2.fromOffset(40, 40), 66 | }, { 67 | New("UICorner", { 68 | CornerRadius = UDim.new(0, 4), 69 | }), 70 | DisplayFrameColor, 71 | }) 72 | 73 | local function CreateColorDialog() 74 | local Dialog = require(Components.Dialog):Create() 75 | Dialog.Title.Text = Colorpicker.Title 76 | Dialog.Root.Size = UDim2.fromOffset(430, 330) 77 | 78 | local Hue, Sat, Vib = Colorpicker.Hue, Colorpicker.Sat, Colorpicker.Vib 79 | local Transparency = Colorpicker.Transparency 80 | 81 | local function CreateInput() 82 | local Box = require(Components.Textbox)() 83 | Box.Frame.Parent = Dialog.Root 84 | Box.Frame.Size = UDim2.new(0, 90, 0, 32) 85 | 86 | return Box 87 | end 88 | 89 | local function CreateInputLabel(Text, Pos) 90 | return New("TextLabel", { 91 | FontFace = Font.new( 92 | "rbxasset://fonts/families/GothamSSm.json", 93 | Enum.FontWeight.Medium, 94 | Enum.FontStyle.Normal 95 | ), 96 | Text = Text, 97 | TextColor3 = Color3.fromRGB(240, 240, 240), 98 | TextSize = 13, 99 | TextXAlignment = Enum.TextXAlignment.Left, 100 | Size = UDim2.new(1, 0, 0, 32), 101 | Position = Pos, 102 | BackgroundTransparency = 1, 103 | Parent = Dialog.Root, 104 | ThemeTag = { 105 | TextColor3 = "Text", 106 | }, 107 | }) 108 | end 109 | 110 | local function GetRGB() 111 | local Value = Color3.fromHSV(Hue, Sat, Vib) 112 | return { R = math.floor(Value.r * 255), G = math.floor(Value.g * 255), B = math.floor(Value.b * 255) } 113 | end 114 | 115 | local SatCursor = New("ImageLabel", { 116 | Size = UDim2.new(0, 18, 0, 18), 117 | ScaleType = Enum.ScaleType.Fit, 118 | AnchorPoint = Vector2.new(0.5, 0.5), 119 | BackgroundTransparency = 1, 120 | Image = "http://www.roblox.com/asset/?id=4805639000", 121 | }) 122 | 123 | local SatVibMap = New("ImageLabel", { 124 | Size = UDim2.fromOffset(180, 160), 125 | Position = UDim2.fromOffset(20, 55), 126 | Image = "rbxassetid://4155801252", 127 | BackgroundColor3 = Colorpicker.Value, 128 | BackgroundTransparency = 0, 129 | Parent = Dialog.Root, 130 | }, { 131 | New("UICorner", { 132 | CornerRadius = UDim.new(0, 4), 133 | }), 134 | SatCursor, 135 | }) 136 | 137 | local OldColorFrame = New("Frame", { 138 | BackgroundColor3 = Colorpicker.Value, 139 | Size = UDim2.fromScale(1, 1), 140 | BackgroundTransparency = Colorpicker.Transparency, 141 | }, { 142 | New("UICorner", { 143 | CornerRadius = UDim.new(0, 4), 144 | }), 145 | }) 146 | 147 | local OldColorFrameChecker = New("ImageLabel", { 148 | Image = "http://www.roblox.com/asset/?id=14204231522", 149 | ImageTransparency = 0.45, 150 | ScaleType = Enum.ScaleType.Tile, 151 | TileSize = UDim2.fromOffset(40, 40), 152 | BackgroundTransparency = 1, 153 | Position = UDim2.fromOffset(112, 220), 154 | Size = UDim2.fromOffset(88, 24), 155 | Parent = Dialog.Root, 156 | }, { 157 | New("UICorner", { 158 | CornerRadius = UDim.new(0, 4), 159 | }), 160 | New("UIStroke", { 161 | Thickness = 2, 162 | Transparency = 0.75, 163 | }), 164 | OldColorFrame, 165 | }) 166 | 167 | local DialogDisplayFrame = New("Frame", { 168 | BackgroundColor3 = Colorpicker.Value, 169 | Size = UDim2.fromScale(1, 1), 170 | BackgroundTransparency = 0, 171 | }, { 172 | New("UICorner", { 173 | CornerRadius = UDim.new(0, 4), 174 | }), 175 | }) 176 | 177 | local DialogDisplayFrameChecker = New("ImageLabel", { 178 | Image = "http://www.roblox.com/asset/?id=14204231522", 179 | ImageTransparency = 0.45, 180 | ScaleType = Enum.ScaleType.Tile, 181 | TileSize = UDim2.fromOffset(40, 40), 182 | BackgroundTransparency = 1, 183 | Position = UDim2.fromOffset(20, 220), 184 | Size = UDim2.fromOffset(88, 24), 185 | Parent = Dialog.Root, 186 | }, { 187 | New("UICorner", { 188 | CornerRadius = UDim.new(0, 4), 189 | }), 190 | New("UIStroke", { 191 | Thickness = 2, 192 | Transparency = 0.75, 193 | }), 194 | DialogDisplayFrame, 195 | }) 196 | 197 | local SequenceTable = {} 198 | 199 | for Color = 0, 1, 0.1 do 200 | table.insert(SequenceTable, ColorSequenceKeypoint.new(Color, Color3.fromHSV(Color, 1, 1))) 201 | end 202 | 203 | local HueSliderGradient = New("UIGradient", { 204 | Color = ColorSequence.new(SequenceTable), 205 | Rotation = 90, 206 | }) 207 | 208 | local HueDragHolder = New("Frame", { 209 | Size = UDim2.new(1, 0, 1, -10), 210 | Position = UDim2.fromOffset(0, 5), 211 | BackgroundTransparency = 1, 212 | }) 213 | 214 | local HueDrag = New("ImageLabel", { 215 | Size = UDim2.fromOffset(14, 14), 216 | Image = "http://www.roblox.com/asset/?id=12266946128", 217 | Parent = HueDragHolder, 218 | ThemeTag = { 219 | ImageColor3 = "DialogInput", 220 | }, 221 | }) 222 | 223 | local HueSlider = New("Frame", { 224 | Size = UDim2.fromOffset(12, 190), 225 | Position = UDim2.fromOffset(210, 55), 226 | Parent = Dialog.Root, 227 | }, { 228 | New("UICorner", { 229 | CornerRadius = UDim.new(1, 0), 230 | }), 231 | HueSliderGradient, 232 | HueDragHolder, 233 | }) 234 | 235 | local HexInput = CreateInput() 236 | HexInput.Frame.Position = UDim2.fromOffset(Config.Transparency and 260 or 240, 55) 237 | CreateInputLabel("Hex", UDim2.fromOffset(Config.Transparency and 360 or 340, 55)) 238 | 239 | local RedInput = CreateInput() 240 | RedInput.Frame.Position = UDim2.fromOffset(Config.Transparency and 260 or 240, 95) 241 | CreateInputLabel("Red", UDim2.fromOffset(Config.Transparency and 360 or 340, 95)) 242 | 243 | local GreenInput = CreateInput() 244 | GreenInput.Frame.Position = UDim2.fromOffset(Config.Transparency and 260 or 240, 135) 245 | CreateInputLabel("Green", UDim2.fromOffset(Config.Transparency and 360 or 340, 135)) 246 | 247 | local BlueInput = CreateInput() 248 | BlueInput.Frame.Position = UDim2.fromOffset(Config.Transparency and 260 or 240, 175) 249 | CreateInputLabel("Blue", UDim2.fromOffset(Config.Transparency and 360 or 340, 175)) 250 | 251 | local AlphaInput 252 | if Config.Transparency then 253 | AlphaInput = CreateInput() 254 | AlphaInput.Frame.Position = UDim2.fromOffset(260, 215) 255 | CreateInputLabel("Alpha", UDim2.fromOffset(360, 215)) 256 | end 257 | 258 | local TransparencySlider, TransparencyDrag, TransparencyColor 259 | if Config.Transparency then 260 | local TransparencyDragHolder = New("Frame", { 261 | Size = UDim2.new(1, 0, 1, -10), 262 | Position = UDim2.fromOffset(0, 5), 263 | BackgroundTransparency = 1, 264 | }) 265 | 266 | TransparencyDrag = New("ImageLabel", { 267 | Size = UDim2.fromOffset(14, 14), 268 | Image = "http://www.roblox.com/asset/?id=12266946128", 269 | Parent = TransparencyDragHolder, 270 | ThemeTag = { 271 | ImageColor3 = "DialogInput", 272 | }, 273 | }) 274 | 275 | TransparencyColor = New("Frame", { 276 | Size = UDim2.fromScale(1, 1), 277 | }, { 278 | New("UIGradient", { 279 | Transparency = NumberSequence.new({ 280 | NumberSequenceKeypoint.new(0, 0), 281 | NumberSequenceKeypoint.new(1, 1), 282 | }), 283 | Rotation = 270, 284 | }), 285 | New("UICorner", { 286 | CornerRadius = UDim.new(1, 0), 287 | }), 288 | }) 289 | 290 | TransparencySlider = New("Frame", { 291 | Size = UDim2.fromOffset(12, 190), 292 | Position = UDim2.fromOffset(230, 55), 293 | Parent = Dialog.Root, 294 | BackgroundTransparency = 1, 295 | }, { 296 | New("UICorner", { 297 | CornerRadius = UDim.new(1, 0), 298 | }), 299 | New("ImageLabel", { 300 | Image = "http://www.roblox.com/asset/?id=14204231522", 301 | ImageTransparency = 0.45, 302 | ScaleType = Enum.ScaleType.Tile, 303 | TileSize = UDim2.fromOffset(40, 40), 304 | BackgroundTransparency = 1, 305 | Size = UDim2.fromScale(1, 1), 306 | Parent = Dialog.Root, 307 | }, { 308 | New("UICorner", { 309 | CornerRadius = UDim.new(1, 0), 310 | }), 311 | }), 312 | TransparencyColor, 313 | TransparencyDragHolder, 314 | }) 315 | end 316 | 317 | local function Display() 318 | SatVibMap.BackgroundColor3 = Color3.fromHSV(Hue, 1, 1) 319 | HueDrag.Position = UDim2.new(0, -1, Hue, -6) 320 | SatCursor.Position = UDim2.new(Sat, 0, 1 - Vib, 0) 321 | DialogDisplayFrame.BackgroundColor3 = Color3.fromHSV(Hue, Sat, Vib) 322 | 323 | HexInput.Input.Text = "#" .. Color3.fromHSV(Hue, Sat, Vib):ToHex() 324 | RedInput.Input.Text = GetRGB()["R"] 325 | GreenInput.Input.Text = GetRGB()["G"] 326 | BlueInput.Input.Text = GetRGB()["B"] 327 | 328 | if Config.Transparency then 329 | TransparencyColor.BackgroundColor3 = Color3.fromHSV(Hue, Sat, Vib) 330 | DialogDisplayFrame.BackgroundTransparency = Transparency 331 | TransparencyDrag.Position = UDim2.new(0, -1, 1 - Transparency, -6) 332 | AlphaInput.Input.Text = require(Root):Round((1 - Transparency) * 100, 0) .. "%" 333 | end 334 | end 335 | 336 | Creator.AddSignal(HexInput.Input.FocusLost, function(Enter) 337 | if Enter then 338 | local Success, Result = pcall(Color3.fromHex, HexInput.Input.Text) 339 | if Success and typeof(Result) == "Color3" then 340 | Hue, Sat, Vib = Color3.toHSV(Result) 341 | end 342 | end 343 | Display() 344 | end) 345 | 346 | Creator.AddSignal(RedInput.Input.FocusLost, function(Enter) 347 | if Enter then 348 | local CurrentColor = GetRGB() 349 | local Success, Result = pcall(Color3.fromRGB, RedInput.Input.Text, CurrentColor["G"], CurrentColor["B"]) 350 | if Success and typeof(Result) == "Color3" then 351 | if tonumber(RedInput.Input.Text) <= 255 then 352 | Hue, Sat, Vib = Color3.toHSV(Result) 353 | end 354 | end 355 | end 356 | Display() 357 | end) 358 | 359 | Creator.AddSignal(GreenInput.Input.FocusLost, function(Enter) 360 | if Enter then 361 | local CurrentColor = GetRGB() 362 | local Success, Result = 363 | pcall(Color3.fromRGB, CurrentColor["R"], GreenInput.Input.Text, CurrentColor["B"]) 364 | if Success and typeof(Result) == "Color3" then 365 | if tonumber(GreenInput.Input.Text) <= 255 then 366 | Hue, Sat, Vib = Color3.toHSV(Result) 367 | end 368 | end 369 | end 370 | Display() 371 | end) 372 | 373 | Creator.AddSignal(BlueInput.Input.FocusLost, function(Enter) 374 | if Enter then 375 | local CurrentColor = GetRGB() 376 | local Success, Result = 377 | pcall(Color3.fromRGB, CurrentColor["R"], CurrentColor["G"], BlueInput.Input.Text) 378 | if Success and typeof(Result) == "Color3" then 379 | if tonumber(BlueInput.Input.Text) <= 255 then 380 | Hue, Sat, Vib = Color3.toHSV(Result) 381 | end 382 | end 383 | end 384 | Display() 385 | end) 386 | 387 | if Config.Transparency then 388 | Creator.AddSignal(AlphaInput.Input.FocusLost, function(Enter) 389 | if Enter then 390 | pcall(function() 391 | local Value = tonumber(AlphaInput.Input.Text) 392 | if Value >= 0 and Value <= 100 then 393 | Transparency = 1 - Value * 0.01 394 | end 395 | end) 396 | end 397 | Display() 398 | end) 399 | end 400 | 401 | Creator.AddSignal(SatVibMap.InputBegan, function(Input) 402 | if 403 | Input.UserInputType == Enum.UserInputType.MouseButton1 404 | or Input.UserInputType == Enum.UserInputType.Touch 405 | then 406 | while UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do 407 | local MinX = SatVibMap.AbsolutePosition.X 408 | local MaxX = MinX + SatVibMap.AbsoluteSize.X 409 | local MouseX = math.clamp(Mouse.X, MinX, MaxX) 410 | 411 | local MinY = SatVibMap.AbsolutePosition.Y 412 | local MaxY = MinY + SatVibMap.AbsoluteSize.Y 413 | local MouseY = math.clamp(Mouse.Y, MinY, MaxY) 414 | 415 | Sat = (MouseX - MinX) / (MaxX - MinX) 416 | Vib = 1 - ((MouseY - MinY) / (MaxY - MinY)) 417 | Display() 418 | 419 | RenderStepped:Wait() 420 | end 421 | end 422 | end) 423 | 424 | Creator.AddSignal(HueSlider.InputBegan, function(Input) 425 | if 426 | Input.UserInputType == Enum.UserInputType.MouseButton1 427 | or Input.UserInputType == Enum.UserInputType.Touch 428 | then 429 | while UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do 430 | local MinY = HueSlider.AbsolutePosition.Y 431 | local MaxY = MinY + HueSlider.AbsoluteSize.Y 432 | local MouseY = math.clamp(Mouse.Y, MinY, MaxY) 433 | 434 | Hue = ((MouseY - MinY) / (MaxY - MinY)) 435 | Display() 436 | 437 | RenderStepped:Wait() 438 | end 439 | end 440 | end) 441 | 442 | if Config.Transparency then 443 | Creator.AddSignal(TransparencySlider.InputBegan, function(Input) 444 | if Input.UserInputType == Enum.UserInputType.MouseButton1 then 445 | while UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do 446 | local MinY = TransparencySlider.AbsolutePosition.Y 447 | local MaxY = MinY + TransparencySlider.AbsoluteSize.Y 448 | local MouseY = math.clamp(Mouse.Y, MinY, MaxY) 449 | 450 | Transparency = 1 - ((MouseY - MinY) / (MaxY - MinY)) 451 | Display() 452 | 453 | RenderStepped:Wait() 454 | end 455 | end 456 | end) 457 | end 458 | 459 | Display() 460 | 461 | Dialog:Button("Done", function() 462 | Colorpicker:SetValue({ Hue, Sat, Vib }, Transparency) 463 | end) 464 | Dialog:Button("Cancel") 465 | Dialog:Open() 466 | end 467 | 468 | function Colorpicker:Display() 469 | Colorpicker.Value = Color3.fromHSV(Colorpicker.Hue, Colorpicker.Sat, Colorpicker.Vib) 470 | 471 | DisplayFrameColor.BackgroundColor3 = Colorpicker.Value 472 | DisplayFrameColor.BackgroundTransparency = Colorpicker.Transparency 473 | 474 | Element.Library:SafeCallback(Colorpicker.Callback, Colorpicker.Value) 475 | Element.Library:SafeCallback(Colorpicker.Changed, Colorpicker.Value) 476 | end 477 | 478 | function Colorpicker:SetValue(HSV, Transparency) 479 | local Color = Color3.fromHSV(HSV[1], HSV[2], HSV[3]) 480 | 481 | Colorpicker.Transparency = Transparency or 0 482 | Colorpicker:SetHSVFromRGB(Color) 483 | Colorpicker:Display() 484 | end 485 | 486 | function Colorpicker:SetValueRGB(Color, Transparency) 487 | Colorpicker.Transparency = Transparency or 0 488 | Colorpicker:SetHSVFromRGB(Color) 489 | Colorpicker:Display() 490 | end 491 | 492 | function Colorpicker:OnChanged(Func) 493 | Colorpicker.Changed = Func 494 | Func(Colorpicker.Value) 495 | end 496 | 497 | function Colorpicker:Destroy() 498 | ColorpickerFrame:Destroy() 499 | Library.Options[Idx] = nil 500 | end 501 | 502 | Creator.AddSignal(ColorpickerFrame.Frame.MouseButton1Click, function() 503 | CreateColorDialog() 504 | end) 505 | 506 | Colorpicker:Display() 507 | 508 | Library.Options[Idx] = Colorpicker 509 | return Colorpicker 510 | end 511 | 512 | return Element 513 | -------------------------------------------------------------------------------- /src/Elements/Dropdown.lua: -------------------------------------------------------------------------------- 1 | local TweenService = game:GetService("TweenService") 2 | local UserInputService = game:GetService("UserInputService") 3 | local Mouse = game:GetService("Players").LocalPlayer:GetMouse() 4 | local Camera = game:GetService("Workspace").CurrentCamera 5 | 6 | local Root = script.Parent.Parent 7 | local Creator = require(Root.Creator) 8 | local Flipper = require(Root.Packages.Flipper) 9 | 10 | local New = Creator.New 11 | local Components = Root.Components 12 | 13 | local Element = {} 14 | Element.__index = Element 15 | Element.__type = "Dropdown" 16 | 17 | function Element:New(Idx, Config) 18 | local Library = self.Library 19 | 20 | local Dropdown = { 21 | Values = Config.Values, 22 | Value = Config.Default, 23 | Multi = Config.Multi, 24 | Buttons = {}, 25 | Opened = false, 26 | Type = "Dropdown", 27 | Callback = Config.Callback or function() end, 28 | } 29 | 30 | local DropdownFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, false) 31 | DropdownFrame.DescLabel.Size = UDim2.new(1, -170, 0, 14) 32 | 33 | Dropdown.SetTitle = DropdownFrame.SetTitle 34 | Dropdown.SetDesc = DropdownFrame.SetDesc 35 | 36 | local DropdownDisplay = New("TextLabel", { 37 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Regular, Enum.FontStyle.Normal), 38 | Text = "Value", 39 | TextColor3 = Color3.fromRGB(240, 240, 240), 40 | TextSize = 13, 41 | TextXAlignment = Enum.TextXAlignment.Left, 42 | Size = UDim2.new(1, -30, 0, 14), 43 | Position = UDim2.new(0, 8, 0.5, 0), 44 | AnchorPoint = Vector2.new(0, 0.5), 45 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 46 | BackgroundTransparency = 1, 47 | TextTruncate = Enum.TextTruncate.AtEnd, 48 | ThemeTag = { 49 | TextColor3 = "Text", 50 | }, 51 | }) 52 | 53 | local DropdownIco = New("ImageLabel", { 54 | Image = "rbxassetid://10709790948", 55 | Size = UDim2.fromOffset(16, 16), 56 | AnchorPoint = Vector2.new(1, 0.5), 57 | Position = UDim2.new(1, -8, 0.5, 0), 58 | BackgroundTransparency = 1, 59 | ThemeTag = { 60 | ImageColor3 = "SubText", 61 | }, 62 | }) 63 | 64 | local DropdownInner = New("TextButton", { 65 | Size = UDim2.fromOffset(160, 30), 66 | Position = UDim2.new(1, -10, 0.5, 0), 67 | AnchorPoint = Vector2.new(1, 0.5), 68 | BackgroundTransparency = 0.9, 69 | Parent = DropdownFrame.Frame, 70 | ThemeTag = { 71 | BackgroundColor3 = "DropdownFrame", 72 | }, 73 | }, { 74 | New("UICorner", { 75 | CornerRadius = UDim.new(0, 5), 76 | }), 77 | New("UIStroke", { 78 | Transparency = 0.5, 79 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 80 | ThemeTag = { 81 | Color = "InElementBorder", 82 | }, 83 | }), 84 | DropdownIco, 85 | DropdownDisplay, 86 | }) 87 | 88 | local DropdownListLayout = New("UIListLayout", { 89 | Padding = UDim.new(0, 3), 90 | }) 91 | 92 | local DropdownScrollFrame = New("ScrollingFrame", { 93 | Size = UDim2.new(1, -5, 1, -10), 94 | Position = UDim2.fromOffset(5, 5), 95 | BackgroundTransparency = 1, 96 | BottomImage = "rbxassetid://6889812791", 97 | MidImage = "rbxassetid://6889812721", 98 | TopImage = "rbxassetid://6276641225", 99 | ScrollBarImageColor3 = Color3.fromRGB(255, 255, 255), 100 | ScrollBarImageTransparency = 0.95, 101 | ScrollBarThickness = 4, 102 | BorderSizePixel = 0, 103 | CanvasSize = UDim2.fromScale(0, 0), 104 | }, { 105 | DropdownListLayout, 106 | }) 107 | 108 | local DropdownHolderFrame = New("Frame", { 109 | Size = UDim2.fromScale(1, 0.6), 110 | ThemeTag = { 111 | BackgroundColor3 = "DropdownHolder", 112 | }, 113 | }, { 114 | DropdownScrollFrame, 115 | New("UICorner", { 116 | CornerRadius = UDim.new(0, 7), 117 | }), 118 | New("UIStroke", { 119 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 120 | ThemeTag = { 121 | Color = "DropdownBorder", 122 | }, 123 | }), 124 | New("ImageLabel", { 125 | BackgroundTransparency = 1, 126 | Image = "http://www.roblox.com/asset/?id=5554236805", 127 | ScaleType = Enum.ScaleType.Slice, 128 | SliceCenter = Rect.new(23, 23, 277, 277), 129 | Size = UDim2.fromScale(1, 1) + UDim2.fromOffset(30, 30), 130 | Position = UDim2.fromOffset(-15, -15), 131 | ImageColor3 = Color3.fromRGB(0, 0, 0), 132 | ImageTransparency = 0.1, 133 | }), 134 | }) 135 | 136 | local DropdownHolderCanvas = New("Frame", { 137 | BackgroundTransparency = 1, 138 | Size = UDim2.fromOffset(170, 300), 139 | Parent = self.Library.GUI, 140 | Visible = false, 141 | }, { 142 | DropdownHolderFrame, 143 | New("UISizeConstraint", { 144 | MinSize = Vector2.new(170, 0), 145 | }), 146 | }) 147 | table.insert(Library.OpenFrames, DropdownHolderCanvas) 148 | 149 | local function RecalculateListPosition() 150 | local Add = 0 151 | if Camera.ViewportSize.Y - DropdownInner.AbsolutePosition.Y < DropdownHolderCanvas.AbsoluteSize.Y - 5 then 152 | Add = DropdownHolderCanvas.AbsoluteSize.Y 153 | - 5 154 | - (Camera.ViewportSize.Y - DropdownInner.AbsolutePosition.Y) 155 | + 40 156 | end 157 | DropdownHolderCanvas.Position = 158 | UDim2.fromOffset(DropdownInner.AbsolutePosition.X - 1, DropdownInner.AbsolutePosition.Y - 5 - Add) 159 | end 160 | 161 | local ListSizeX = 0 162 | local function RecalculateListSize() 163 | if #Dropdown.Values > 10 then 164 | DropdownHolderCanvas.Size = UDim2.fromOffset(ListSizeX, 392) 165 | else 166 | DropdownHolderCanvas.Size = UDim2.fromOffset(ListSizeX, DropdownListLayout.AbsoluteContentSize.Y + 10) 167 | end 168 | end 169 | 170 | local function RecalculateCanvasSize() 171 | DropdownScrollFrame.CanvasSize = UDim2.fromOffset(0, DropdownListLayout.AbsoluteContentSize.Y) 172 | end 173 | 174 | RecalculateListPosition() 175 | RecalculateListSize() 176 | 177 | Creator.AddSignal(DropdownInner:GetPropertyChangedSignal("AbsolutePosition"), RecalculateListPosition) 178 | 179 | Creator.AddSignal(DropdownInner.MouseButton1Click, function() 180 | Dropdown:Open() 181 | end) 182 | 183 | Creator.AddSignal(UserInputService.InputBegan, function(Input) 184 | if 185 | Input.UserInputType == Enum.UserInputType.MouseButton1 186 | or Input.UserInputType == Enum.UserInputType.Touch 187 | then 188 | local AbsPos, AbsSize = DropdownHolderFrame.AbsolutePosition, DropdownHolderFrame.AbsoluteSize 189 | if 190 | Mouse.X < AbsPos.X 191 | or Mouse.X > AbsPos.X + AbsSize.X 192 | or Mouse.Y < (AbsPos.Y - 20 - 1) 193 | or Mouse.Y > AbsPos.Y + AbsSize.Y 194 | then 195 | Dropdown:Close() 196 | end 197 | end 198 | end) 199 | 200 | local ScrollFrame = self.ScrollFrame 201 | function Dropdown:Open() 202 | Dropdown.Opened = true 203 | ScrollFrame.ScrollingEnabled = false 204 | DropdownHolderCanvas.Visible = true 205 | TweenService:Create( 206 | DropdownHolderFrame, 207 | TweenInfo.new(0.2, Enum.EasingStyle.Quart, Enum.EasingDirection.Out), 208 | { Size = UDim2.fromScale(1, 1) } 209 | ):Play() 210 | end 211 | 212 | function Dropdown:Close() 213 | Dropdown.Opened = false 214 | ScrollFrame.ScrollingEnabled = true 215 | DropdownHolderFrame.Size = UDim2.fromScale(1, 0.6) 216 | DropdownHolderCanvas.Visible = false 217 | end 218 | 219 | function Dropdown:Display() 220 | local Values = Dropdown.Values 221 | local Str = "" 222 | 223 | if Config.Multi then 224 | for Idx, Value in next, Values do 225 | if Dropdown.Value[Value] then 226 | Str = Str .. Value .. ", " 227 | end 228 | end 229 | Str = Str:sub(1, #Str - 2) 230 | else 231 | Str = Dropdown.Value or "" 232 | end 233 | 234 | DropdownDisplay.Text = (Str == "" and "--" or Str) 235 | end 236 | 237 | function Dropdown:GetActiveValues() 238 | if Config.Multi then 239 | local T = {} 240 | 241 | for Value, Bool in next, Dropdown.Value do 242 | table.insert(T, Value) 243 | end 244 | 245 | return T 246 | else 247 | return Dropdown.Value and 1 or 0 248 | end 249 | end 250 | 251 | function Dropdown:BuildDropdownList() 252 | local Values = Dropdown.Values 253 | local Buttons = {} 254 | 255 | for _, Element in next, DropdownScrollFrame:GetChildren() do 256 | if not Element:IsA("UIListLayout") then 257 | Element:Destroy() 258 | end 259 | end 260 | 261 | local Count = 0 262 | 263 | for Idx, Value in next, Values do 264 | local Table = {} 265 | 266 | Count = Count + 1 267 | 268 | local ButtonSelector = New("Frame", { 269 | Size = UDim2.fromOffset(4, 14), 270 | BackgroundColor3 = Color3.fromRGB(76, 194, 255), 271 | Position = UDim2.fromOffset(-1, 16), 272 | AnchorPoint = Vector2.new(0, 0.5), 273 | ThemeTag = { 274 | BackgroundColor3 = "Accent", 275 | }, 276 | }, { 277 | New("UICorner", { 278 | CornerRadius = UDim.new(0, 2), 279 | }), 280 | }) 281 | 282 | local ButtonLabel = New("TextLabel", { 283 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 284 | Text = Value, 285 | TextColor3 = Color3.fromRGB(200, 200, 200), 286 | TextSize = 13, 287 | TextXAlignment = Enum.TextXAlignment.Left, 288 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 289 | AutomaticSize = Enum.AutomaticSize.Y, 290 | BackgroundTransparency = 1, 291 | Size = UDim2.fromScale(1, 1), 292 | Position = UDim2.fromOffset(10, 0), 293 | Name = "ButtonLabel", 294 | ThemeTag = { 295 | TextColor3 = "Text", 296 | }, 297 | }) 298 | 299 | local Button = New("TextButton", { 300 | Size = UDim2.new(1, -5, 0, 32), 301 | BackgroundTransparency = 1, 302 | ZIndex = 23, 303 | Text = "", 304 | Parent = DropdownScrollFrame, 305 | ThemeTag = { 306 | BackgroundColor3 = "DropdownOption", 307 | }, 308 | }, { 309 | ButtonSelector, 310 | ButtonLabel, 311 | New("UICorner", { 312 | CornerRadius = UDim.new(0, 6), 313 | }), 314 | }) 315 | 316 | local Selected 317 | 318 | if Config.Multi then 319 | Selected = Dropdown.Value[Value] 320 | else 321 | Selected = Dropdown.Value == Value 322 | end 323 | 324 | local BackMotor, SetBackTransparency = Creator.SpringMotor(1, Button, "BackgroundTransparency") 325 | local SelMotor, SetSelTransparency = Creator.SpringMotor(1, ButtonSelector, "BackgroundTransparency") 326 | local SelectorSizeMotor = Flipper.SingleMotor.new(6) 327 | 328 | SelectorSizeMotor:onStep(function(value) 329 | ButtonSelector.Size = UDim2.new(0, 4, 0, value) 330 | end) 331 | 332 | Creator.AddSignal(Button.MouseEnter, function() 333 | SetBackTransparency(Selected and 0.85 or 0.89) 334 | end) 335 | Creator.AddSignal(Button.MouseLeave, function() 336 | SetBackTransparency(Selected and 0.89 or 1) 337 | end) 338 | Creator.AddSignal(Button.MouseButton1Down, function() 339 | SetBackTransparency(0.92) 340 | end) 341 | Creator.AddSignal(Button.MouseButton1Up, function() 342 | SetBackTransparency(Selected and 0.85 or 0.89) 343 | end) 344 | 345 | function Table:UpdateButton() 346 | if Config.Multi then 347 | Selected = Dropdown.Value[Value] 348 | if Selected then 349 | SetBackTransparency(0.89) 350 | end 351 | else 352 | Selected = Dropdown.Value == Value 353 | SetBackTransparency(Selected and 0.89 or 1) 354 | end 355 | 356 | SelectorSizeMotor:setGoal(Flipper.Spring.new(Selected and 14 or 6, { frequency = 6 })) 357 | SetSelTransparency(Selected and 0 or 1) 358 | end 359 | 360 | ButtonLabel.InputBegan:Connect(function(Input) 361 | if 362 | Input.UserInputType == Enum.UserInputType.MouseButton1 363 | or Input.UserInputType == Enum.UserInputType.Touch 364 | then 365 | local Try = not Selected 366 | 367 | if Dropdown:GetActiveValues() == 1 and not Try and not Config.AllowNull then 368 | else 369 | if Config.Multi then 370 | Selected = Try 371 | Dropdown.Value[Value] = Selected and true or nil 372 | else 373 | Selected = Try 374 | Dropdown.Value = Selected and Value or nil 375 | 376 | for _, OtherButton in next, Buttons do 377 | OtherButton:UpdateButton() 378 | end 379 | end 380 | 381 | Table:UpdateButton() 382 | Dropdown:Display() 383 | 384 | Library:SafeCallback(Dropdown.Callback, Dropdown.Value) 385 | Library:SafeCallback(Dropdown.Changed, Dropdown.Value) 386 | end 387 | end 388 | end) 389 | 390 | Table:UpdateButton() 391 | Dropdown:Display() 392 | 393 | Buttons[Button] = Table 394 | end 395 | 396 | ListSizeX = 0 397 | for Button, Table in next, Buttons do 398 | if Button.ButtonLabel then 399 | if Button.ButtonLabel.TextBounds.X > ListSizeX then 400 | ListSizeX = Button.ButtonLabel.TextBounds.X 401 | end 402 | end 403 | end 404 | ListSizeX = ListSizeX + 30 405 | 406 | RecalculateCanvasSize() 407 | RecalculateListSize() 408 | end 409 | 410 | function Dropdown:SetValues(NewValues) 411 | if NewValues then 412 | Dropdown.Values = NewValues 413 | end 414 | 415 | Dropdown:BuildDropdownList() 416 | end 417 | 418 | function Dropdown:OnChanged(Func) 419 | Dropdown.Changed = Func 420 | Func(Dropdown.Value) 421 | end 422 | 423 | function Dropdown:SetValue(Val) 424 | if Dropdown.Multi then 425 | local nTable = {} 426 | 427 | for Value, Bool in next, Val do 428 | if table.find(Dropdown.Values, Value) then 429 | nTable[Value] = true 430 | end 431 | end 432 | 433 | Dropdown.Value = nTable 434 | else 435 | if not Val then 436 | Dropdown.Value = nil 437 | elseif table.find(Dropdown.Values, Val) then 438 | Dropdown.Value = Val 439 | end 440 | end 441 | 442 | Dropdown:BuildDropdownList() 443 | 444 | Library:SafeCallback(Dropdown.Callback, Dropdown.Value) 445 | Library:SafeCallback(Dropdown.Changed, Dropdown.Value) 446 | end 447 | 448 | function Dropdown:Destroy() 449 | DropdownFrame:Destroy() 450 | Library.Options[Idx] = nil 451 | end 452 | 453 | Dropdown:BuildDropdownList() 454 | Dropdown:Display() 455 | 456 | local Defaults = {} 457 | 458 | if type(Config.Default) == "string" then 459 | local Idx = table.find(Dropdown.Values, Config.Default) 460 | if Idx then 461 | table.insert(Defaults, Idx) 462 | end 463 | elseif type(Config.Default) == "table" then 464 | for _, Value in next, Config.Default do 465 | local Idx = table.find(Dropdown.Values, Value) 466 | if Idx then 467 | table.insert(Defaults, Idx) 468 | end 469 | end 470 | elseif type(Config.Default) == "number" and Dropdown.Values[Config.Default] ~= nil then 471 | table.insert(Defaults, Config.Default) 472 | end 473 | 474 | if next(Defaults) then 475 | for i = 1, #Defaults do 476 | local Index = Defaults[i] 477 | if Config.Multi then 478 | Dropdown.Value[Dropdown.Values[Index]] = true 479 | else 480 | Dropdown.Value = Dropdown.Values[Index] 481 | end 482 | 483 | if not Config.Multi then 484 | break 485 | end 486 | end 487 | 488 | Dropdown:BuildDropdownList() 489 | Dropdown:Display() 490 | end 491 | 492 | Library.Options[Idx] = Dropdown 493 | return Dropdown 494 | end 495 | 496 | return Element 497 | -------------------------------------------------------------------------------- /src/Elements/Input.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Creator = require(Root.Creator) 3 | 4 | local New = Creator.New 5 | local AddSignal = Creator.AddSignal 6 | local Components = Root.Components 7 | 8 | local Element = {} 9 | Element.__index = Element 10 | Element.__type = "Input" 11 | 12 | function Element:New(Idx, Config) 13 | local Library = self.Library 14 | assert(Config.Title, "Input - Missing Title") 15 | Config.Callback = Config.Callback or function() end 16 | 17 | local Input = { 18 | Value = Config.Default or "", 19 | Numeric = Config.Numeric or false, 20 | Finished = Config.Finished or false, 21 | Callback = Config.Callback or function(Value) end, 22 | Type = "Input", 23 | } 24 | 25 | local InputFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, false) 26 | 27 | Input.SetTitle = InputFrame.SetTitle 28 | Input.SetDesc = InputFrame.SetDesc 29 | 30 | local Textbox = require(Components.Textbox)(InputFrame.Frame, true) 31 | Textbox.Frame.Position = UDim2.new(1, -10, 0.5, 0) 32 | Textbox.Frame.AnchorPoint = Vector2.new(1, 0.5) 33 | Textbox.Frame.Size = UDim2.fromOffset(160, 30) 34 | Textbox.Input.Text = Config.Default or "" 35 | Textbox.Input.PlaceholderText = Config.Placeholder or "" 36 | 37 | local Box = Textbox.Input 38 | 39 | function Input:SetValue(Text) 40 | if Config.MaxLength and #Text > Config.MaxLength then 41 | Text = Text:sub(1, Config.MaxLength) 42 | end 43 | 44 | if Input.Numeric then 45 | if (not tonumber(Text)) and Text:len() > 0 then 46 | Text = Input.Value 47 | end 48 | end 49 | 50 | Input.Value = Text 51 | Box.Text = Text 52 | 53 | Library:SafeCallback(Input.Callback, Input.Value) 54 | Library:SafeCallback(Input.Changed, Input.Value) 55 | end 56 | 57 | if Input.Finished then 58 | AddSignal(Box.FocusLost, function(enter) 59 | if not enter then 60 | return 61 | end 62 | Input:SetValue(Box.Text) 63 | end) 64 | else 65 | AddSignal(Box:GetPropertyChangedSignal("Text"), function() 66 | Input:SetValue(Box.Text) 67 | end) 68 | end 69 | 70 | function Input:OnChanged(Func) 71 | Input.Changed = Func 72 | Func(Input.Value) 73 | end 74 | 75 | function Input:Destroy() 76 | InputFrame:Destroy() 77 | Library.Options[Idx] = nil 78 | end 79 | 80 | Library.Options[Idx] = Input 81 | return Input 82 | end 83 | 84 | return Element 85 | -------------------------------------------------------------------------------- /src/Elements/Keybind.lua: -------------------------------------------------------------------------------- 1 | local UserInputService = game:GetService("UserInputService") 2 | 3 | local Root = script.Parent.Parent 4 | local Creator = require(Root.Creator) 5 | 6 | local New = Creator.New 7 | local Components = Root.Components 8 | 9 | local Element = {} 10 | Element.__index = Element 11 | Element.__type = "Keybind" 12 | 13 | function Element:New(Idx, Config) 14 | local Library = self.Library 15 | assert(Config.Title, "KeyBind - Missing Title") 16 | assert(Config.Default, "KeyBind - Missing default value.") 17 | 18 | local Keybind = { 19 | Value = Config.Default, 20 | Toggled = false, 21 | Mode = Config.Mode or "Toggle", 22 | Type = "Keybind", 23 | Callback = Config.Callback or function(Value) end, 24 | ChangedCallback = Config.ChangedCallback or function(New) end, 25 | } 26 | 27 | local Picking = false 28 | 29 | local KeybindFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, true) 30 | 31 | Keybind.SetTitle = KeybindFrame.SetTitle 32 | Keybind.SetDesc = KeybindFrame.SetDesc 33 | 34 | local KeybindDisplayLabel = New("TextLabel", { 35 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json", Enum.FontWeight.Regular, Enum.FontStyle.Normal), 36 | Text = Config.Default, 37 | TextColor3 = Color3.fromRGB(240, 240, 240), 38 | TextSize = 13, 39 | TextXAlignment = Enum.TextXAlignment.Center, 40 | Size = UDim2.new(0, 0, 0, 14), 41 | Position = UDim2.new(0, 0, 0.5, 0), 42 | AnchorPoint = Vector2.new(0, 0.5), 43 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 44 | AutomaticSize = Enum.AutomaticSize.X, 45 | BackgroundTransparency = 1, 46 | ThemeTag = { 47 | TextColor3 = "Text", 48 | }, 49 | }) 50 | 51 | local KeybindDisplayFrame = New("TextButton", { 52 | Size = UDim2.fromOffset(0, 30), 53 | Position = UDim2.new(1, -10, 0.5, 0), 54 | AnchorPoint = Vector2.new(1, 0.5), 55 | BackgroundTransparency = 0.9, 56 | Parent = KeybindFrame.Frame, 57 | AutomaticSize = Enum.AutomaticSize.X, 58 | ThemeTag = { 59 | BackgroundColor3 = "Keybind", 60 | }, 61 | }, { 62 | New("UICorner", { 63 | CornerRadius = UDim.new(0, 5), 64 | }), 65 | New("UIPadding", { 66 | PaddingLeft = UDim.new(0, 8), 67 | PaddingRight = UDim.new(0, 8), 68 | }), 69 | New("UIStroke", { 70 | Transparency = 0.5, 71 | ApplyStrokeMode = Enum.ApplyStrokeMode.Border, 72 | ThemeTag = { 73 | Color = "InElementBorder", 74 | }, 75 | }), 76 | KeybindDisplayLabel, 77 | }) 78 | 79 | function Keybind:GetState() 80 | if UserInputService:GetFocusedTextBox() and Keybind.Mode ~= "Always" then 81 | return false 82 | end 83 | 84 | if Keybind.Mode == "Always" then 85 | return true 86 | elseif Keybind.Mode == "Hold" then 87 | if Keybind.Value == "None" then 88 | return false 89 | end 90 | 91 | local Key = Keybind.Value 92 | 93 | if Key == "MouseLeft" or Key == "MouseRight" then 94 | return Key == "MouseLeft" and UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) 95 | or Key == "MouseRight" 96 | and UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) 97 | else 98 | return UserInputService:IsKeyDown(Enum.KeyCode[Keybind.Value]) 99 | end 100 | else 101 | return Keybind.Toggled 102 | end 103 | end 104 | 105 | function Keybind:SetValue(Key, Mode) 106 | Key = Key or Keybind.Key 107 | Mode = Mode or Keybind.Mode 108 | 109 | KeybindDisplayLabel.Text = Key 110 | Keybind.Value = Key 111 | Keybind.Mode = Mode 112 | end 113 | 114 | function Keybind:OnClick(Callback) 115 | Keybind.Clicked = Callback 116 | end 117 | 118 | function Keybind:OnChanged(Callback) 119 | Keybind.Changed = Callback 120 | Callback(Keybind.Value) 121 | end 122 | 123 | function Keybind:DoClick() 124 | Library:SafeCallback(Keybind.Callback, Keybind.Toggled) 125 | Library:SafeCallback(Keybind.Clicked, Keybind.Toggled) 126 | end 127 | 128 | function Keybind:Destroy() 129 | KeybindFrame:Destroy() 130 | Library.Options[Idx] = nil 131 | end 132 | 133 | Creator.AddSignal(KeybindDisplayFrame.InputBegan, function(Input) 134 | if 135 | Input.UserInputType == Enum.UserInputType.MouseButton1 136 | or Input.UserInputType == Enum.UserInputType.Touch 137 | then 138 | Picking = true 139 | KeybindDisplayLabel.Text = "..." 140 | 141 | wait(0.2) 142 | 143 | local Event 144 | Event = UserInputService.InputBegan:Connect(function(Input) 145 | local Key 146 | 147 | if Input.UserInputType == Enum.UserInputType.Keyboard then 148 | Key = Input.KeyCode.Name 149 | elseif Input.UserInputType == Enum.UserInputType.MouseButton1 then 150 | Key = "MouseLeft" 151 | elseif Input.UserInputType == Enum.UserInputType.MouseButton2 then 152 | Key = "MouseRight" 153 | end 154 | 155 | local EndedEvent 156 | EndedEvent = UserInputService.InputEnded:Connect(function(Input) 157 | if 158 | Input.KeyCode.Name == Key 159 | or Key == "MouseLeft" and Input.UserInputType == Enum.UserInputType.MouseButton1 160 | or Key == "MouseRight" and Input.UserInputType == Enum.UserInputType.MouseButton2 161 | then 162 | Picking = false 163 | 164 | KeybindDisplayLabel.Text = Key 165 | Keybind.Value = Key 166 | 167 | Library:SafeCallback(Keybind.ChangedCallback, Input.KeyCode or Input.UserInputType) 168 | Library:SafeCallback(Keybind.Changed, Input.KeyCode or Input.UserInputType) 169 | 170 | Event:Disconnect() 171 | EndedEvent:Disconnect() 172 | end 173 | end) 174 | end) 175 | end 176 | end) 177 | 178 | Creator.AddSignal(UserInputService.InputBegan, function(Input) 179 | if not Picking and not UserInputService:GetFocusedTextBox() then 180 | if Keybind.Mode == "Toggle" then 181 | local Key = Keybind.Value 182 | 183 | if Key == "MouseLeft" or Key == "MouseRight" then 184 | if 185 | Key == "MouseLeft" and Input.UserInputType == Enum.UserInputType.MouseButton1 186 | or Key == "MouseRight" and Input.UserInputType == Enum.UserInputType.MouseButton2 187 | then 188 | Keybind.Toggled = not Keybind.Toggled 189 | Keybind:DoClick() 190 | end 191 | elseif Input.UserInputType == Enum.UserInputType.Keyboard then 192 | if Input.KeyCode.Name == Key then 193 | Keybind.Toggled = not Keybind.Toggled 194 | Keybind:DoClick() 195 | end 196 | end 197 | end 198 | end 199 | end) 200 | 201 | Library.Options[Idx] = Keybind 202 | return Keybind 203 | end 204 | 205 | return Element 206 | -------------------------------------------------------------------------------- /src/Elements/Paragraph.lua: -------------------------------------------------------------------------------- 1 | local Root = script.Parent.Parent 2 | local Components = Root.Components 3 | local Flipper = require(Root.Packages.Flipper) 4 | local Creator = require(Root.Creator) 5 | 6 | local Paragraph = {} 7 | Paragraph.__index = Paragraph 8 | Paragraph.__type = "Paragraph" 9 | 10 | function Paragraph:New(Config) 11 | assert(Config.Title, "Paragraph - Missing Title") 12 | Config.Content = Config.Content or "" 13 | 14 | local Paragraph = require(Components.Element)(Config.Title, Config.Content, Paragraph.Container, false) 15 | Paragraph.Frame.BackgroundTransparency = 0.92 16 | Paragraph.Border.Transparency = 0.6 17 | 18 | return Paragraph 19 | end 20 | 21 | return Paragraph 22 | -------------------------------------------------------------------------------- /src/Elements/Slider.lua: -------------------------------------------------------------------------------- 1 | local UserInputService = game:GetService("UserInputService") 2 | local Root = script.Parent.Parent 3 | local Creator = require(Root.Creator) 4 | 5 | local New = Creator.New 6 | local Components = Root.Components 7 | 8 | local Element = {} 9 | Element.__index = Element 10 | Element.__type = "Slider" 11 | 12 | function Element:New(Idx, Config) 13 | local Library = self.Library 14 | assert(Config.Title, "Slider - Missing Title.") 15 | assert(Config.Default, "Slider - Missing default value.") 16 | assert(Config.Min, "Slider - Missing minimum value.") 17 | assert(Config.Max, "Slider - Missing maximum value.") 18 | assert(Config.Rounding, "Slider - Missing rounding value.") 19 | 20 | local Slider = { 21 | Value = nil, 22 | Min = Config.Min, 23 | Max = Config.Max, 24 | Rounding = Config.Rounding, 25 | Callback = Config.Callback or function(Value) end, 26 | Type = "Slider", 27 | } 28 | 29 | local Dragging = false 30 | 31 | local SliderFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, false) 32 | SliderFrame.DescLabel.Size = UDim2.new(1, -170, 0, 14) 33 | 34 | Slider.SetTitle = SliderFrame.SetTitle 35 | Slider.SetDesc = SliderFrame.SetDesc 36 | 37 | local SliderDot = New("ImageLabel", { 38 | AnchorPoint = Vector2.new(0, 0.5), 39 | Position = UDim2.new(0, -7, 0.5, 0), 40 | Size = UDim2.fromOffset(14, 14), 41 | Image = "http://www.roblox.com/asset/?id=12266946128", 42 | ThemeTag = { 43 | ImageColor3 = "Accent", 44 | }, 45 | }) 46 | 47 | local SliderRail = New("Frame", { 48 | BackgroundTransparency = 1, 49 | Position = UDim2.fromOffset(7, 0), 50 | Size = UDim2.new(1, -14, 1, 0), 51 | }, { 52 | SliderDot, 53 | }) 54 | 55 | local SliderFill = New("Frame", { 56 | Size = UDim2.new(0, 0, 1, 0), 57 | ThemeTag = { 58 | BackgroundColor3 = "Accent", 59 | }, 60 | }, { 61 | New("UICorner", { 62 | CornerRadius = UDim.new(1, 0), 63 | }), 64 | }) 65 | 66 | local SliderDisplay = New("TextLabel", { 67 | FontFace = Font.new("rbxasset://fonts/families/GothamSSm.json"), 68 | Text = "Value", 69 | TextSize = 12, 70 | TextWrapped = true, 71 | TextXAlignment = Enum.TextXAlignment.Right, 72 | BackgroundColor3 = Color3.fromRGB(255, 255, 255), 73 | BackgroundTransparency = 1, 74 | Size = UDim2.new(0, 100, 0, 14), 75 | Position = UDim2.new(0, -4, 0.5, 0), 76 | AnchorPoint = Vector2.new(1, 0.5), 77 | ThemeTag = { 78 | TextColor3 = "SubText", 79 | }, 80 | }) 81 | 82 | local SliderInner = New("Frame", { 83 | Size = UDim2.new(1, 0, 0, 4), 84 | AnchorPoint = Vector2.new(1, 0.5), 85 | Position = UDim2.new(1, -10, 0.5, 0), 86 | BackgroundTransparency = 0.4, 87 | Parent = SliderFrame.Frame, 88 | ThemeTag = { 89 | BackgroundColor3 = "SliderRail", 90 | }, 91 | }, { 92 | New("UICorner", { 93 | CornerRadius = UDim.new(1, 0), 94 | }), 95 | New("UISizeConstraint", { 96 | MaxSize = Vector2.new(150, math.huge), 97 | }), 98 | SliderDisplay, 99 | SliderFill, 100 | SliderRail, 101 | }) 102 | 103 | Creator.AddSignal(SliderDot.InputBegan, function(Input) 104 | if 105 | Input.UserInputType == Enum.UserInputType.MouseButton1 106 | or Input.UserInputType == Enum.UserInputType.Touch 107 | then 108 | Dragging = true 109 | end 110 | end) 111 | 112 | Creator.AddSignal(SliderDot.InputEnded, function(Input) 113 | if 114 | Input.UserInputType == Enum.UserInputType.MouseButton1 115 | or Input.UserInputType == Enum.UserInputType.Touch 116 | then 117 | Dragging = false 118 | end 119 | end) 120 | 121 | Creator.AddSignal(UserInputService.InputChanged, function(Input) 122 | if 123 | Dragging 124 | and ( 125 | Input.UserInputType == Enum.UserInputType.MouseMovement 126 | or Input.UserInputType == Enum.UserInputType.Touch 127 | ) 128 | then 129 | local SizeScale = 130 | math.clamp((Input.Position.X - SliderRail.AbsolutePosition.X) / SliderRail.AbsoluteSize.X, 0, 1) 131 | Slider:SetValue(Slider.Min + ((Slider.Max - Slider.Min) * SizeScale)) 132 | end 133 | end) 134 | 135 | function Slider:OnChanged(Func) 136 | Slider.Changed = Func 137 | Func(Slider.Value) 138 | end 139 | 140 | function Slider:SetValue(Value) 141 | self.Value = Library:Round(math.clamp(Value, Slider.Min, Slider.Max), Slider.Rounding) 142 | SliderDot.Position = UDim2.new((self.Value - Slider.Min) / (Slider.Max - Slider.Min), -7, 0.5, 0) 143 | SliderFill.Size = UDim2.fromScale((self.Value - Slider.Min) / (Slider.Max - Slider.Min), 1) 144 | SliderDisplay.Text = tostring(self.Value) 145 | 146 | Library:SafeCallback(Slider.Callback, self.Value) 147 | Library:SafeCallback(Slider.Changed, self.Value) 148 | end 149 | 150 | function Slider:Destroy() 151 | SliderFrame:Destroy() 152 | Library.Options[Idx] = nil 153 | end 154 | 155 | Slider:SetValue(Config.Default) 156 | 157 | Library.Options[Idx] = Slider 158 | return Slider 159 | end 160 | 161 | return Element 162 | -------------------------------------------------------------------------------- /src/Elements/Toggle.lua: -------------------------------------------------------------------------------- 1 | local TweenService = game:GetService("TweenService") 2 | local Root = script.Parent.Parent 3 | local Creator = require(Root.Creator) 4 | 5 | local New = Creator.New 6 | local Components = Root.Components 7 | 8 | local Element = {} 9 | Element.__index = Element 10 | Element.__type = "Toggle" 11 | 12 | function Element:New(Idx, Config) 13 | local Library = self.Library 14 | assert(Config.Title, "Toggle - Missing Title") 15 | 16 | local Toggle = { 17 | Value = Config.Default or false, 18 | Callback = Config.Callback or function(Value) end, 19 | Type = "Toggle", 20 | } 21 | 22 | local ToggleFrame = require(Components.Element)(Config.Title, Config.Description, self.Container, true) 23 | ToggleFrame.DescLabel.Size = UDim2.new(1, -54, 0, 14) 24 | 25 | Toggle.SetTitle = ToggleFrame.SetTitle 26 | Toggle.SetDesc = ToggleFrame.SetDesc 27 | 28 | local ToggleCircle = New("ImageLabel", { 29 | AnchorPoint = Vector2.new(0, 0.5), 30 | Size = UDim2.fromOffset(14, 14), 31 | Position = UDim2.new(0, 2, 0.5, 0), 32 | Image = "http://www.roblox.com/asset/?id=12266946128", 33 | ImageTransparency = 0.5, 34 | ThemeTag = { 35 | ImageColor3 = "ToggleSlider", 36 | }, 37 | }) 38 | 39 | local ToggleBorder = New("UIStroke", { 40 | Transparency = 0.5, 41 | ThemeTag = { 42 | Color = "ToggleSlider", 43 | }, 44 | }) 45 | 46 | local ToggleSlider = New("Frame", { 47 | Size = UDim2.fromOffset(36, 18), 48 | AnchorPoint = Vector2.new(1, 0.5), 49 | Position = UDim2.new(1, -10, 0.5, 0), 50 | Parent = ToggleFrame.Frame, 51 | BackgroundTransparency = 1, 52 | ThemeTag = { 53 | BackgroundColor3 = "Accent", 54 | }, 55 | }, { 56 | New("UICorner", { 57 | CornerRadius = UDim.new(0, 9), 58 | }), 59 | ToggleBorder, 60 | ToggleCircle, 61 | }) 62 | 63 | function Toggle:OnChanged(Func) 64 | Toggle.Changed = Func 65 | Func(Toggle.Value) 66 | end 67 | 68 | function Toggle:SetValue(Value) 69 | Value = not not Value 70 | Toggle.Value = Value 71 | 72 | Creator.OverrideTag(ToggleBorder, { Color = Toggle.Value and "Accent" or "ToggleSlider" }) 73 | Creator.OverrideTag(ToggleCircle, { ImageColor3 = Toggle.Value and "ToggleToggled" or "ToggleSlider" }) 74 | TweenService:Create( 75 | ToggleCircle, 76 | TweenInfo.new(0.25, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), 77 | { Position = UDim2.new(0, Toggle.Value and 19 or 2, 0.5, 0) } 78 | ):Play() 79 | TweenService:Create( 80 | ToggleSlider, 81 | TweenInfo.new(0.25, Enum.EasingStyle.Quint, Enum.EasingDirection.Out), 82 | { BackgroundTransparency = Toggle.Value and 0 or 1 } 83 | ):Play() 84 | ToggleCircle.ImageTransparency = Toggle.Value and 0 or 0.5 85 | 86 | Library:SafeCallback(Toggle.Callback, Toggle.Value) 87 | Library:SafeCallback(Toggle.Changed, Toggle.Value) 88 | end 89 | 90 | function Toggle:Destroy() 91 | ToggleFrame:Destroy() 92 | Library.Options[Idx] = nil 93 | end 94 | 95 | Creator.AddSignal(ToggleFrame.Frame.MouseButton1Click, function() 96 | Toggle:SetValue(not Toggle.Value) 97 | end) 98 | 99 | Toggle:SetValue(Toggle.Value) 100 | 101 | Library.Options[Idx] = Toggle 102 | return Toggle 103 | end 104 | 105 | return Element 106 | -------------------------------------------------------------------------------- /src/Elements/init.lua: -------------------------------------------------------------------------------- 1 | local Elements = {} 2 | 3 | for _, Theme in next, script:GetChildren() do 4 | table.insert(Elements, require(Theme)) 5 | end 6 | 7 | return Elements 8 | -------------------------------------------------------------------------------- /src/Packages/Flipper/BaseMotor.lua: -------------------------------------------------------------------------------- 1 | local RunService = game:GetService("RunService") 2 | 3 | local Signal = require(script.Parent.Signal) 4 | 5 | local noop = function() end 6 | 7 | local BaseMotor = {} 8 | BaseMotor.__index = BaseMotor 9 | 10 | function BaseMotor.new() 11 | return setmetatable({ 12 | _onStep = Signal.new(), 13 | _onStart = Signal.new(), 14 | _onComplete = Signal.new(), 15 | }, BaseMotor) 16 | end 17 | 18 | function BaseMotor:onStep(handler) 19 | return self._onStep:connect(handler) 20 | end 21 | 22 | function BaseMotor:onStart(handler) 23 | return self._onStart:connect(handler) 24 | end 25 | 26 | function BaseMotor:onComplete(handler) 27 | return self._onComplete:connect(handler) 28 | end 29 | 30 | function BaseMotor:start() 31 | if not self._connection then 32 | self._connection = RunService.RenderStepped:Connect(function(deltaTime) 33 | self:step(deltaTime) 34 | end) 35 | end 36 | end 37 | 38 | function BaseMotor:stop() 39 | if self._connection then 40 | self._connection:Disconnect() 41 | self._connection = nil 42 | end 43 | end 44 | 45 | BaseMotor.destroy = BaseMotor.stop 46 | 47 | BaseMotor.step = noop 48 | BaseMotor.getValue = noop 49 | BaseMotor.setGoal = noop 50 | 51 | function BaseMotor:__tostring() 52 | return "Motor" 53 | end 54 | 55 | return BaseMotor 56 | -------------------------------------------------------------------------------- /src/Packages/Flipper/BaseMotor.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local RunService = game:GetService("RunService") 3 | 4 | local BaseMotor = require(script.Parent.BaseMotor) 5 | 6 | describe("connection management", function() 7 | local motor = BaseMotor.new() 8 | 9 | it("should hook up connections on :start()", function() 10 | motor:start() 11 | expect(typeof(motor._connection)).to.equal("RBXScriptConnection") 12 | end) 13 | 14 | it("should remove connections on :stop() or :destroy()", function() 15 | motor:stop() 16 | expect(motor._connection).to.equal(nil) 17 | end) 18 | end) 19 | 20 | it("should call :step() with deltaTime", function() 21 | local motor = BaseMotor.new() 22 | 23 | local argumentsProvided 24 | function motor:step(...) 25 | argumentsProvided = { ... } 26 | motor:stop() 27 | end 28 | 29 | motor:start() 30 | 31 | local expectedDeltaTime = RunService.RenderStepped:Wait() 32 | 33 | -- Give it another frame, because connections tend to be invoked later than :Wait() calls 34 | RunService.RenderStepped:Wait() 35 | 36 | expect(argumentsProvided).to.be.ok() 37 | expect(argumentsProvided[1]).to.equal(expectedDeltaTime) 38 | end) 39 | end 40 | -------------------------------------------------------------------------------- /src/Packages/Flipper/GroupMotor.lua: -------------------------------------------------------------------------------- 1 | local BaseMotor = require(script.Parent.BaseMotor) 2 | local SingleMotor = require(script.Parent.SingleMotor) 3 | 4 | local isMotor = require(script.Parent.isMotor) 5 | 6 | local GroupMotor = setmetatable({}, BaseMotor) 7 | GroupMotor.__index = GroupMotor 8 | 9 | local function toMotor(value) 10 | if isMotor(value) then 11 | return value 12 | end 13 | 14 | local valueType = typeof(value) 15 | 16 | if valueType == "number" then 17 | return SingleMotor.new(value, false) 18 | elseif valueType == "table" then 19 | return GroupMotor.new(value, false) 20 | end 21 | 22 | error(("Unable to convert %q to motor; type %s is unsupported"):format(value, valueType), 2) 23 | end 24 | 25 | function GroupMotor.new(initialValues, useImplicitConnections) 26 | assert(initialValues, "Missing argument #1: initialValues") 27 | assert(typeof(initialValues) == "table", "initialValues must be a table!") 28 | assert( 29 | not initialValues.step, 30 | 'initialValues contains disallowed property "step". Did you mean to put a table of values here?' 31 | ) 32 | 33 | local self = setmetatable(BaseMotor.new(), GroupMotor) 34 | 35 | if useImplicitConnections ~= nil then 36 | self._useImplicitConnections = useImplicitConnections 37 | else 38 | self._useImplicitConnections = true 39 | end 40 | 41 | self._complete = true 42 | self._motors = {} 43 | 44 | for key, value in pairs(initialValues) do 45 | self._motors[key] = toMotor(value) 46 | end 47 | 48 | return self 49 | end 50 | 51 | function GroupMotor:step(deltaTime) 52 | if self._complete then 53 | return true 54 | end 55 | 56 | local allMotorsComplete = true 57 | 58 | for _, motor in pairs(self._motors) do 59 | local complete = motor:step(deltaTime) 60 | if not complete then 61 | -- If any of the sub-motors are incomplete, the group motor will not be complete either 62 | allMotorsComplete = false 63 | end 64 | end 65 | 66 | self._onStep:fire(self:getValue()) 67 | 68 | if allMotorsComplete then 69 | if self._useImplicitConnections then 70 | self:stop() 71 | end 72 | 73 | self._complete = true 74 | self._onComplete:fire() 75 | end 76 | 77 | return allMotorsComplete 78 | end 79 | 80 | function GroupMotor:setGoal(goals) 81 | assert(not goals.step, 'goals contains disallowed property "step". Did you mean to put a table of goals here?') 82 | 83 | self._complete = false 84 | self._onStart:fire() 85 | 86 | for key, goal in pairs(goals) do 87 | local motor = assert(self._motors[key], ("Unknown motor for key %s"):format(key)) 88 | motor:setGoal(goal) 89 | end 90 | 91 | if self._useImplicitConnections then 92 | self:start() 93 | end 94 | end 95 | 96 | function GroupMotor:getValue() 97 | local values = {} 98 | 99 | for key, motor in pairs(self._motors) do 100 | values[key] = motor:getValue() 101 | end 102 | 103 | return values 104 | end 105 | 106 | function GroupMotor:__tostring() 107 | return "Motor(Group)" 108 | end 109 | 110 | return GroupMotor 111 | -------------------------------------------------------------------------------- /src/Packages/Flipper/GroupMotor.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local GroupMotor = require(script.Parent.GroupMotor) 3 | 4 | local Instant = require(script.Parent.Instant) 5 | local Spring = require(script.Parent.Spring) 6 | 7 | it("should complete when all child motors are complete", function() 8 | local motor = GroupMotor.new({ 9 | A = 1, 10 | B = 2, 11 | }, false) 12 | 13 | expect(motor._complete).to.equal(true) 14 | 15 | motor:setGoal({ 16 | A = Instant.new(3), 17 | B = Spring.new(4, { frequency = 7.5, dampingRatio = 1 }), 18 | }) 19 | 20 | expect(motor._complete).to.equal(false) 21 | 22 | motor:step(1 / 60) 23 | 24 | expect(motor._complete).to.equal(false) 25 | 26 | for _ = 1, 0.5 * 60 do 27 | motor:step(1 / 60) 28 | end 29 | 30 | expect(motor._complete).to.equal(true) 31 | end) 32 | 33 | it("should start when the goal is set", function() 34 | local motor = GroupMotor.new({ 35 | A = 0, 36 | }, false) 37 | 38 | local bool = false 39 | motor:onStart(function() 40 | bool = not bool 41 | end) 42 | 43 | motor:setGoal({ 44 | A = Instant.new(1), 45 | }) 46 | 47 | expect(bool).to.equal(true) 48 | 49 | motor:setGoal({ 50 | A = Instant.new(1), 51 | }) 52 | 53 | expect(bool).to.equal(false) 54 | end) 55 | 56 | it("should properly return all values", function() 57 | local motor = GroupMotor.new({ 58 | A = 1, 59 | B = 2, 60 | }, false) 61 | 62 | local value = motor:getValue() 63 | 64 | expect(value.A).to.equal(1) 65 | expect(value.B).to.equal(2) 66 | end) 67 | 68 | it("should error when a goal is given to GroupMotor.new", function() 69 | local success = pcall(function() 70 | GroupMotor.new(Instant.new(0)) 71 | end) 72 | 73 | expect(success).to.equal(false) 74 | end) 75 | 76 | it("should error when a single goal is provided to GroupMotor:step", function() 77 | local success = pcall(function() 78 | GroupMotor.new({ a = 1 }):setGoal(Instant.new(0)) 79 | end) 80 | 81 | expect(success).to.equal(false) 82 | end) 83 | end 84 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Instant.lua: -------------------------------------------------------------------------------- 1 | local Instant = {} 2 | Instant.__index = Instant 3 | 4 | function Instant.new(targetValue) 5 | return setmetatable({ 6 | _targetValue = targetValue, 7 | }, Instant) 8 | end 9 | 10 | function Instant:step() 11 | return { 12 | complete = true, 13 | value = self._targetValue, 14 | } 15 | end 16 | 17 | return Instant 18 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Instant.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local Instant = require(script.Parent.Instant) 3 | 4 | it("should return a completed state with the provided value", function() 5 | local goal = Instant.new(1.23) 6 | local state = goal:step(0.1, { value = 0, complete = false }) 7 | expect(state.complete).to.equal(true) 8 | expect(state.value).to.equal(1.23) 9 | end) 10 | end 11 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Linear.lua: -------------------------------------------------------------------------------- 1 | local Linear = {} 2 | Linear.__index = Linear 3 | 4 | function Linear.new(targetValue, options) 5 | assert(targetValue, "Missing argument #1: targetValue") 6 | 7 | options = options or {} 8 | 9 | return setmetatable({ 10 | _targetValue = targetValue, 11 | _velocity = options.velocity or 1, 12 | }, Linear) 13 | end 14 | 15 | function Linear:step(state, dt) 16 | local position = state.value 17 | local velocity = self._velocity -- Linear motion ignores the state's velocity 18 | local goal = self._targetValue 19 | 20 | local dPos = dt * velocity 21 | 22 | local complete = dPos >= math.abs(goal - position) 23 | position = position + dPos * (goal > position and 1 or -1) 24 | if complete then 25 | position = self._targetValue 26 | velocity = 0 27 | end 28 | 29 | return { 30 | complete = complete, 31 | value = position, 32 | velocity = velocity, 33 | } 34 | end 35 | 36 | return Linear 37 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Linear.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local SingleMotor = require(script.Parent.SingleMotor) 3 | local Linear = require(script.Parent.Linear) 4 | 5 | describe("completed state", function() 6 | local motor = SingleMotor.new(0, false) 7 | 8 | local goal = Linear.new(1, { velocity = 1 }) 9 | motor:setGoal(goal) 10 | 11 | for _ = 1, 60 do 12 | motor:step(1 / 60) 13 | end 14 | 15 | it("should complete", function() 16 | expect(motor._state.complete).to.equal(true) 17 | end) 18 | 19 | it("should be exactly the goal value when completed", function() 20 | expect(motor._state.value).to.equal(1) 21 | end) 22 | end) 23 | 24 | describe("uncompleted state", function() 25 | local motor = SingleMotor.new(0, false) 26 | 27 | local goal = Linear.new(1, { velocity = 1 }) 28 | motor:setGoal(goal) 29 | 30 | for _ = 1, 59 do 31 | motor:step(1 / 60) 32 | end 33 | 34 | it("should be uncomplete", function() 35 | expect(motor._state.complete).to.equal(false) 36 | end) 37 | end) 38 | 39 | describe("negative velocity", function() 40 | local motor = SingleMotor.new(1, false) 41 | 42 | local goal = Linear.new(0, { velocity = 1 }) 43 | motor:setGoal(goal) 44 | 45 | for _ = 1, 60 do 46 | motor:step(1 / 60) 47 | end 48 | 49 | it("should complete", function() 50 | expect(motor._state.complete).to.equal(true) 51 | end) 52 | 53 | it("should be exactly the goal value when completed", function() 54 | expect(motor._state.value).to.equal(0) 55 | end) 56 | end) 57 | end 58 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Signal.lua: -------------------------------------------------------------------------------- 1 | local Connection = {} 2 | Connection.__index = Connection 3 | 4 | function Connection.new(signal, handler) 5 | return setmetatable({ 6 | signal = signal, 7 | connected = true, 8 | _handler = handler, 9 | }, Connection) 10 | end 11 | 12 | function Connection:disconnect() 13 | if self.connected then 14 | self.connected = false 15 | 16 | for index, connection in pairs(self.signal._connections) do 17 | if connection == self then 18 | table.remove(self.signal._connections, index) 19 | return 20 | end 21 | end 22 | end 23 | end 24 | 25 | local Signal = {} 26 | Signal.__index = Signal 27 | 28 | function Signal.new() 29 | return setmetatable({ 30 | _connections = {}, 31 | _threads = {}, 32 | }, Signal) 33 | end 34 | 35 | function Signal:fire(...) 36 | for _, connection in pairs(self._connections) do 37 | connection._handler(...) 38 | end 39 | 40 | for _, thread in pairs(self._threads) do 41 | coroutine.resume(thread, ...) 42 | end 43 | 44 | self._threads = {} 45 | end 46 | 47 | function Signal:connect(handler) 48 | local connection = Connection.new(self, handler) 49 | table.insert(self._connections, connection) 50 | return connection 51 | end 52 | 53 | function Signal:wait() 54 | table.insert(self._threads, coroutine.running()) 55 | return coroutine.yield() 56 | end 57 | 58 | return Signal 59 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Signal.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local Signal = require(script.Parent.Signal) 3 | 4 | it("should invoke all connections, instantly", function() 5 | local signal = Signal.new() 6 | 7 | local a, b 8 | 9 | signal:connect(function(value) 10 | a = value 11 | end) 12 | 13 | signal:connect(function(value) 14 | b = value 15 | end) 16 | 17 | signal:fire("hello") 18 | 19 | expect(a).to.equal("hello") 20 | expect(b).to.equal("hello") 21 | end) 22 | 23 | it("should return values when :wait() is called", function() 24 | local signal = Signal.new() 25 | 26 | spawn(function() 27 | signal:fire(123, "hello") 28 | end) 29 | 30 | local a, b = signal:wait() 31 | 32 | expect(a).to.equal(123) 33 | expect(b).to.equal("hello") 34 | end) 35 | 36 | it("should properly handle disconnections", function() 37 | local signal = Signal.new() 38 | 39 | local didRun = false 40 | 41 | local connection = signal:connect(function() 42 | didRun = true 43 | end) 44 | connection:disconnect() 45 | 46 | signal:fire() 47 | expect(didRun).to.equal(false) 48 | end) 49 | end 50 | -------------------------------------------------------------------------------- /src/Packages/Flipper/SingleMotor.lua: -------------------------------------------------------------------------------- 1 | local BaseMotor = require(script.Parent.BaseMotor) 2 | 3 | local SingleMotor = setmetatable({}, BaseMotor) 4 | SingleMotor.__index = SingleMotor 5 | 6 | function SingleMotor.new(initialValue, useImplicitConnections) 7 | assert(initialValue, "Missing argument #1: initialValue") 8 | assert(typeof(initialValue) == "number", "initialValue must be a number!") 9 | 10 | local self = setmetatable(BaseMotor.new(), SingleMotor) 11 | 12 | if useImplicitConnections ~= nil then 13 | self._useImplicitConnections = useImplicitConnections 14 | else 15 | self._useImplicitConnections = true 16 | end 17 | 18 | self._goal = nil 19 | self._state = { 20 | complete = true, 21 | value = initialValue, 22 | } 23 | 24 | return self 25 | end 26 | 27 | function SingleMotor:step(deltaTime) 28 | if self._state.complete then 29 | return true 30 | end 31 | 32 | local newState = self._goal:step(self._state, deltaTime) 33 | 34 | self._state = newState 35 | self._onStep:fire(newState.value) 36 | 37 | if newState.complete then 38 | if self._useImplicitConnections then 39 | self:stop() 40 | end 41 | 42 | self._onComplete:fire() 43 | end 44 | 45 | return newState.complete 46 | end 47 | 48 | function SingleMotor:getValue() 49 | return self._state.value 50 | end 51 | 52 | function SingleMotor:setGoal(goal) 53 | self._state.complete = false 54 | self._goal = goal 55 | 56 | self._onStart:fire() 57 | 58 | if self._useImplicitConnections then 59 | self:start() 60 | end 61 | end 62 | 63 | function SingleMotor:__tostring() 64 | return "Motor(Single)" 65 | end 66 | 67 | return SingleMotor 68 | -------------------------------------------------------------------------------- /src/Packages/Flipper/SingleMotor.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local SingleMotor = require(script.Parent.SingleMotor) 3 | local Instant = require(script.Parent.Instant) 4 | 5 | it("should assign new state on step", function() 6 | local motor = SingleMotor.new(0, false) 7 | 8 | motor:setGoal(Instant.new(5)) 9 | motor:step(1 / 60) 10 | 11 | expect(motor._state.complete).to.equal(true) 12 | expect(motor._state.value).to.equal(5) 13 | end) 14 | 15 | it("should invoke onComplete listeners when the goal is completed", function() 16 | local motor = SingleMotor.new(0, false) 17 | 18 | local didComplete = false 19 | motor:onComplete(function() 20 | didComplete = true 21 | end) 22 | 23 | motor:setGoal(Instant.new(5)) 24 | motor:step(1 / 60) 25 | 26 | expect(didComplete).to.equal(true) 27 | end) 28 | 29 | it("should start when the goal is set", function() 30 | local motor = SingleMotor.new(0, false) 31 | 32 | local bool = false 33 | motor:onStart(function() 34 | bool = not bool 35 | end) 36 | 37 | motor:setGoal(Instant.new(5)) 38 | 39 | expect(bool).to.equal(true) 40 | 41 | motor:setGoal(Instant.new(5)) 42 | 43 | expect(bool).to.equal(false) 44 | end) 45 | end 46 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Spring.lua: -------------------------------------------------------------------------------- 1 | local VELOCITY_THRESHOLD = 0.001 2 | local POSITION_THRESHOLD = 0.001 3 | 4 | local EPS = 0.0001 5 | 6 | local Spring = {} 7 | Spring.__index = Spring 8 | 9 | function Spring.new(targetValue, options) 10 | assert(targetValue, "Missing argument #1: targetValue") 11 | options = options or {} 12 | 13 | return setmetatable({ 14 | _targetValue = targetValue, 15 | _frequency = options.frequency or 4, 16 | _dampingRatio = options.dampingRatio or 1, 17 | }, Spring) 18 | end 19 | 20 | function Spring:step(state, dt) 21 | -- Copyright 2018 Parker Stebbins (parker@fractality.io) 22 | -- github.com/Fraktality/Spring 23 | -- Distributed under the MIT license 24 | 25 | local d = self._dampingRatio 26 | local f = self._frequency * 2 * math.pi 27 | local g = self._targetValue 28 | local p0 = state.value 29 | local v0 = state.velocity or 0 30 | 31 | local offset = p0 - g 32 | local decay = math.exp(-d * f * dt) 33 | 34 | local p1, v1 35 | 36 | if d == 1 then -- Critically damped 37 | p1 = (offset * (1 + f * dt) + v0 * dt) * decay + g 38 | v1 = (v0 * (1 - f * dt) - offset * (f * f * dt)) * decay 39 | elseif d < 1 then -- Underdamped 40 | local c = math.sqrt(1 - d * d) 41 | 42 | local i = math.cos(f * c * dt) 43 | local j = math.sin(f * c * dt) 44 | 45 | -- Damping ratios approaching 1 can cause division by small numbers. 46 | -- To fix that, group terms around z=j/c and find an approximation for z. 47 | -- Start with the definition of z: 48 | -- z = sin(dt*f*c)/c 49 | -- Substitute a=dt*f: 50 | -- z = sin(a*c)/c 51 | -- Take the Maclaurin expansion of z with respect to c: 52 | -- z = a - (a^3*c^2)/6 + (a^5*c^4)/120 + O(c^6) 53 | -- z ≈ a - (a^3*c^2)/6 + (a^5*c^4)/120 54 | -- Rewrite in Horner form: 55 | -- z ≈ a + ((a*a)*(c*c)*(c*c)/20 - c*c)*(a*a*a)/6 56 | 57 | local z 58 | if c > EPS then 59 | z = j / c 60 | else 61 | local a = dt * f 62 | z = a + ((a * a) * (c * c) * (c * c) / 20 - c * c) * (a * a * a) / 6 63 | end 64 | 65 | -- Frequencies approaching 0 present a similar problem. 66 | -- We want an approximation for y as f approaches 0, where: 67 | -- y = sin(dt*f*c)/(f*c) 68 | -- Substitute b=dt*c: 69 | -- y = sin(b*c)/b 70 | -- Now reapply the process from z. 71 | 72 | local y 73 | if f * c > EPS then 74 | y = j / (f * c) 75 | else 76 | local b = f * c 77 | y = dt + ((dt * dt) * (b * b) * (b * b) / 20 - b * b) * (dt * dt * dt) / 6 78 | end 79 | 80 | p1 = (offset * (i + d * z) + v0 * y) * decay + g 81 | v1 = (v0 * (i - z * d) - offset * (z * f)) * decay 82 | else -- Overdamped 83 | local c = math.sqrt(d * d - 1) 84 | 85 | local r1 = -f * (d - c) 86 | local r2 = -f * (d + c) 87 | 88 | local co2 = (v0 - offset * r1) / (2 * f * c) 89 | local co1 = offset - co2 90 | 91 | local e1 = co1 * math.exp(r1 * dt) 92 | local e2 = co2 * math.exp(r2 * dt) 93 | 94 | p1 = e1 + e2 + g 95 | v1 = e1 * r1 + e2 * r2 96 | end 97 | 98 | local complete = math.abs(v1) < VELOCITY_THRESHOLD and math.abs(p1 - g) < POSITION_THRESHOLD 99 | 100 | return { 101 | complete = complete, 102 | value = complete and g or p1, 103 | velocity = v1, 104 | } 105 | end 106 | 107 | return Spring 108 | -------------------------------------------------------------------------------- /src/Packages/Flipper/Spring.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local SingleMotor = require(script.Parent.SingleMotor) 3 | local Spring = require(script.Parent.Spring) 4 | 5 | describe("completed state", function() 6 | local motor = SingleMotor.new(0, false) 7 | 8 | local goal = Spring.new(1, { frequency = 2, dampingRatio = 0.75 }) 9 | motor:setGoal(goal) 10 | 11 | for _ = 1, 100 do 12 | motor:step(1 / 60) 13 | end 14 | 15 | it("should complete", function() 16 | expect(motor._state.complete).to.equal(true) 17 | end) 18 | 19 | it("should be exactly the goal value when completed", function() 20 | expect(motor._state.value).to.equal(1) 21 | end) 22 | end) 23 | 24 | it("should inherit velocity", function() 25 | local motor = SingleMotor.new(0, false) 26 | motor._state = { complete = false, value = 0, velocity = -5 } 27 | 28 | local goal = Spring.new(1, { frequency = 2, dampingRatio = 1 }) 29 | 30 | motor:setGoal(goal) 31 | motor:step(1 / 60) 32 | 33 | expect(motor._state.velocity < 0).to.equal(true) 34 | end) 35 | end 36 | -------------------------------------------------------------------------------- /src/Packages/Flipper/init.lua: -------------------------------------------------------------------------------- 1 | local Flipper = { 2 | SingleMotor = require(script.SingleMotor), 3 | GroupMotor = require(script.GroupMotor), 4 | 5 | Instant = require(script.Instant), 6 | Linear = require(script.Linear), 7 | Spring = require(script.Spring), 8 | 9 | isMotor = require(script.isMotor), 10 | } 11 | 12 | return Flipper 13 | -------------------------------------------------------------------------------- /src/Packages/Flipper/isMotor.lua: -------------------------------------------------------------------------------- 1 | local function isMotor(value) 2 | local motorType = tostring(value):match("^Motor%((.+)%)$") 3 | 4 | if motorType then 5 | return true, motorType 6 | else 7 | return false 8 | end 9 | end 10 | 11 | return isMotor 12 | -------------------------------------------------------------------------------- /src/Packages/Flipper/isMotor.spec.lua: -------------------------------------------------------------------------------- 1 | return function() 2 | local isMotor = require(script.Parent.isMotor) 3 | 4 | local SingleMotor = require(script.Parent.SingleMotor) 5 | local GroupMotor = require(script.Parent.GroupMotor) 6 | 7 | local singleMotor = SingleMotor.new(0) 8 | local groupMotor = GroupMotor.new({}) 9 | 10 | it("should properly detect motors", function() 11 | expect(isMotor(singleMotor)).to.equal(true) 12 | expect(isMotor(groupMotor)).to.equal(true) 13 | end) 14 | 15 | it("shouldn't detect things that aren't motors", function() 16 | expect(isMotor({})).to.equal(false) 17 | end) 18 | 19 | it("should return the proper motor type", function() 20 | local _, singleMotorType = isMotor(singleMotor) 21 | local _, groupMotorType = isMotor(groupMotor) 22 | 23 | expect(singleMotorType).to.equal("Single") 24 | expect(groupMotorType).to.equal("Group") 25 | end) 26 | end 27 | -------------------------------------------------------------------------------- /src/Themes/Amethyst.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Amethyst", 3 | Accent = Color3.fromRGB(97, 62, 167), 4 | 5 | AcrylicMain = Color3.fromRGB(20, 20, 20), 6 | AcrylicBorder = Color3.fromRGB(110, 90, 130), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(85, 57, 139), Color3.fromRGB(40, 25, 65)), 8 | AcrylicNoise = 0.92, 9 | 10 | TitleBarLine = Color3.fromRGB(95, 75, 110), 11 | Tab = Color3.fromRGB(160, 140, 180), 12 | 13 | Element = Color3.fromRGB(140, 120, 160), 14 | ElementBorder = Color3.fromRGB(60, 50, 70), 15 | InElementBorder = Color3.fromRGB(100, 90, 110), 16 | ElementTransparency = 0.87, 17 | 18 | ToggleSlider = Color3.fromRGB(140, 120, 160), 19 | ToggleToggled = Color3.fromRGB(0, 0, 0), 20 | 21 | SliderRail = Color3.fromRGB(140, 120, 160), 22 | 23 | DropdownFrame = Color3.fromRGB(170, 160, 200), 24 | DropdownHolder = Color3.fromRGB(60, 45, 80), 25 | DropdownBorder = Color3.fromRGB(50, 40, 65), 26 | DropdownOption = Color3.fromRGB(140, 120, 160), 27 | 28 | Keybind = Color3.fromRGB(140, 120, 160), 29 | 30 | Input = Color3.fromRGB(140, 120, 160), 31 | InputFocused = Color3.fromRGB(20, 10, 30), 32 | InputIndicator = Color3.fromRGB(170, 150, 190), 33 | 34 | Dialog = Color3.fromRGB(60, 45, 80), 35 | DialogHolder = Color3.fromRGB(45, 30, 65), 36 | DialogHolderLine = Color3.fromRGB(40, 25, 60), 37 | DialogButton = Color3.fromRGB(60, 45, 80), 38 | DialogButtonBorder = Color3.fromRGB(95, 80, 110), 39 | DialogBorder = Color3.fromRGB(85, 70, 100), 40 | DialogInput = Color3.fromRGB(70, 55, 85), 41 | DialogInputLine = Color3.fromRGB(175, 160, 190), 42 | 43 | Text = Color3.fromRGB(240, 240, 240), 44 | SubText = Color3.fromRGB(170, 170, 170), 45 | Hover = Color3.fromRGB(140, 120, 160), 46 | HoverChange = 0.04, 47 | } 48 | -------------------------------------------------------------------------------- /src/Themes/Aqua.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Aqua", 3 | Accent = Color3.fromRGB(60, 165, 165), 4 | 5 | AcrylicMain = Color3.fromRGB(20, 20, 20), 6 | AcrylicBorder = Color3.fromRGB(50, 100, 100), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(60, 140, 140), Color3.fromRGB(40, 80, 80)), 8 | AcrylicNoise = 0.92, 9 | 10 | TitleBarLine = Color3.fromRGB(60, 120, 120), 11 | Tab = Color3.fromRGB(140, 180, 180), 12 | 13 | Element = Color3.fromRGB(110, 160, 160), 14 | ElementBorder = Color3.fromRGB(40, 70, 70), 15 | InElementBorder = Color3.fromRGB(80, 110, 110), 16 | ElementTransparency = 0.84, 17 | 18 | ToggleSlider = Color3.fromRGB(110, 160, 160), 19 | ToggleToggled = Color3.fromRGB(0, 0, 0), 20 | 21 | SliderRail = Color3.fromRGB(110, 160, 160), 22 | 23 | DropdownFrame = Color3.fromRGB(160, 200, 200), 24 | DropdownHolder = Color3.fromRGB(40, 80, 80), 25 | DropdownBorder = Color3.fromRGB(40, 65, 65), 26 | DropdownOption = Color3.fromRGB(110, 160, 160), 27 | 28 | Keybind = Color3.fromRGB(110, 160, 160), 29 | 30 | Input = Color3.fromRGB(110, 160, 160), 31 | InputFocused = Color3.fromRGB(20, 10, 30), 32 | InputIndicator = Color3.fromRGB(130, 170, 170), 33 | 34 | Dialog = Color3.fromRGB(40, 80, 80), 35 | DialogHolder = Color3.fromRGB(30, 60, 60), 36 | DialogHolderLine = Color3.fromRGB(25, 50, 50), 37 | DialogButton = Color3.fromRGB(40, 80, 80), 38 | DialogButtonBorder = Color3.fromRGB(80, 110, 110), 39 | DialogBorder = Color3.fromRGB(50, 100, 100), 40 | DialogInput = Color3.fromRGB(45, 90, 90), 41 | DialogInputLine = Color3.fromRGB(130, 170, 170), 42 | 43 | Text = Color3.fromRGB(240, 240, 240), 44 | SubText = Color3.fromRGB(170, 170, 170), 45 | Hover = Color3.fromRGB(110, 160, 160), 46 | HoverChange = 0.04, 47 | } 48 | -------------------------------------------------------------------------------- /src/Themes/Dark.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Dark", 3 | Accent = Color3.fromRGB(96, 205, 255), 4 | 5 | AcrylicMain = Color3.fromRGB(60, 60, 60), 6 | AcrylicBorder = Color3.fromRGB(90, 90, 90), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(40, 40, 40), Color3.fromRGB(40, 40, 40)), 8 | AcrylicNoise = 0.9, 9 | 10 | TitleBarLine = Color3.fromRGB(75, 75, 75), 11 | Tab = Color3.fromRGB(120, 120, 120), 12 | 13 | Element = Color3.fromRGB(120, 120, 120), 14 | ElementBorder = Color3.fromRGB(35, 35, 35), 15 | InElementBorder = Color3.fromRGB(90, 90, 90), 16 | ElementTransparency = 0.87, 17 | 18 | ToggleSlider = Color3.fromRGB(120, 120, 120), 19 | ToggleToggled = Color3.fromRGB(0, 0, 0), 20 | 21 | SliderRail = Color3.fromRGB(120, 120, 120), 22 | 23 | DropdownFrame = Color3.fromRGB(160, 160, 160), 24 | DropdownHolder = Color3.fromRGB(45, 45, 45), 25 | DropdownBorder = Color3.fromRGB(35, 35, 35), 26 | DropdownOption = Color3.fromRGB(120, 120, 120), 27 | 28 | Keybind = Color3.fromRGB(120, 120, 120), 29 | 30 | Input = Color3.fromRGB(160, 160, 160), 31 | InputFocused = Color3.fromRGB(10, 10, 10), 32 | InputIndicator = Color3.fromRGB(150, 150, 150), 33 | 34 | Dialog = Color3.fromRGB(45, 45, 45), 35 | DialogHolder = Color3.fromRGB(35, 35, 35), 36 | DialogHolderLine = Color3.fromRGB(30, 30, 30), 37 | DialogButton = Color3.fromRGB(45, 45, 45), 38 | DialogButtonBorder = Color3.fromRGB(80, 80, 80), 39 | DialogBorder = Color3.fromRGB(70, 70, 70), 40 | DialogInput = Color3.fromRGB(55, 55, 55), 41 | DialogInputLine = Color3.fromRGB(160, 160, 160), 42 | 43 | Text = Color3.fromRGB(240, 240, 240), 44 | SubText = Color3.fromRGB(170, 170, 170), 45 | Hover = Color3.fromRGB(120, 120, 120), 46 | HoverChange = 0.07, 47 | } 48 | -------------------------------------------------------------------------------- /src/Themes/Darker.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Darker", 3 | Accent = Color3.fromRGB(72, 138, 182), 4 | 5 | AcrylicMain = Color3.fromRGB(30, 30, 30), 6 | AcrylicBorder = Color3.fromRGB(60, 60, 60), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(25, 25, 25), Color3.fromRGB(15, 15, 15)), 8 | AcrylicNoise = 0.94, 9 | 10 | TitleBarLine = Color3.fromRGB(65, 65, 65), 11 | Tab = Color3.fromRGB(100, 100, 100), 12 | 13 | Element = Color3.fromRGB(70, 70, 70), 14 | ElementBorder = Color3.fromRGB(25, 25, 25), 15 | InElementBorder = Color3.fromRGB(55, 55, 55), 16 | ElementTransparency = 0.82, 17 | 18 | DropdownFrame = Color3.fromRGB(120, 120, 120), 19 | DropdownHolder = Color3.fromRGB(35, 35, 35), 20 | DropdownBorder = Color3.fromRGB(25, 25, 25), 21 | 22 | Dialog = Color3.fromRGB(35, 35, 35), 23 | DialogHolder = Color3.fromRGB(25, 25, 25), 24 | DialogHolderLine = Color3.fromRGB(20, 20, 20), 25 | DialogButton = Color3.fromRGB(35, 35, 35), 26 | DialogButtonBorder = Color3.fromRGB(55, 55, 55), 27 | DialogBorder = Color3.fromRGB(50, 50, 50), 28 | DialogInput = Color3.fromRGB(45, 45, 45), 29 | DialogInputLine = Color3.fromRGB(120, 120, 120), 30 | } 31 | -------------------------------------------------------------------------------- /src/Themes/Light.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Light", 3 | Accent = Color3.fromRGB(0, 103, 192), 4 | 5 | AcrylicMain = Color3.fromRGB(200, 200, 200), 6 | AcrylicBorder = Color3.fromRGB(120, 120, 120), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(255, 255, 255), Color3.fromRGB(255, 255, 255)), 8 | AcrylicNoise = 0.96, 9 | 10 | TitleBarLine = Color3.fromRGB(160, 160, 160), 11 | Tab = Color3.fromRGB(90, 90, 90), 12 | 13 | Element = Color3.fromRGB(255, 255, 255), 14 | ElementBorder = Color3.fromRGB(180, 180, 180), 15 | InElementBorder = Color3.fromRGB(150, 150, 150), 16 | ElementTransparency = 0.65, 17 | 18 | ToggleSlider = Color3.fromRGB(40, 40, 40), 19 | ToggleToggled = Color3.fromRGB(255, 255, 255), 20 | 21 | SliderRail = Color3.fromRGB(40, 40, 40), 22 | 23 | DropdownFrame = Color3.fromRGB(200, 200, 200), 24 | DropdownHolder = Color3.fromRGB(240, 240, 240), 25 | DropdownBorder = Color3.fromRGB(200, 200, 200), 26 | DropdownOption = Color3.fromRGB(150, 150, 150), 27 | 28 | Keybind = Color3.fromRGB(120, 120, 120), 29 | 30 | Input = Color3.fromRGB(200, 200, 200), 31 | InputFocused = Color3.fromRGB(100, 100, 100), 32 | InputIndicator = Color3.fromRGB(80, 80, 80), 33 | 34 | Dialog = Color3.fromRGB(255, 255, 255), 35 | DialogHolder = Color3.fromRGB(240, 240, 240), 36 | DialogHolderLine = Color3.fromRGB(228, 228, 228), 37 | DialogButton = Color3.fromRGB(255, 255, 255), 38 | DialogButtonBorder = Color3.fromRGB(190, 190, 190), 39 | DialogBorder = Color3.fromRGB(140, 140, 140), 40 | DialogInput = Color3.fromRGB(250, 250, 250), 41 | DialogInputLine = Color3.fromRGB(160, 160, 160), 42 | 43 | Text = Color3.fromRGB(0, 0, 0), 44 | SubText = Color3.fromRGB(40, 40, 40), 45 | Hover = Color3.fromRGB(50, 50, 50), 46 | HoverChange = 0.16, 47 | } 48 | -------------------------------------------------------------------------------- /src/Themes/Rose.lua: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Rose", 3 | Accent = Color3.fromRGB(180, 55, 90), 4 | 5 | AcrylicMain = Color3.fromRGB(40, 40, 40), 6 | AcrylicBorder = Color3.fromRGB(130, 90, 110), 7 | AcrylicGradient = ColorSequence.new(Color3.fromRGB(190, 60, 135), Color3.fromRGB(165, 50, 70)), 8 | AcrylicNoise = 0.92, 9 | 10 | TitleBarLine = Color3.fromRGB(140, 85, 105), 11 | Tab = Color3.fromRGB(180, 140, 160), 12 | 13 | Element = Color3.fromRGB(200, 120, 170), 14 | ElementBorder = Color3.fromRGB(110, 70, 85), 15 | InElementBorder = Color3.fromRGB(120, 90, 90), 16 | ElementTransparency = 0.86, 17 | 18 | ToggleSlider = Color3.fromRGB(200, 120, 170), 19 | ToggleToggled = Color3.fromRGB(0, 0, 0), 20 | 21 | SliderRail = Color3.fromRGB(200, 120, 170), 22 | 23 | DropdownFrame = Color3.fromRGB(200, 160, 180), 24 | DropdownHolder = Color3.fromRGB(120, 50, 75), 25 | DropdownBorder = Color3.fromRGB(90, 40, 55), 26 | DropdownOption = Color3.fromRGB(200, 120, 170), 27 | 28 | Keybind = Color3.fromRGB(200, 120, 170), 29 | 30 | Input = Color3.fromRGB(200, 120, 170), 31 | InputFocused = Color3.fromRGB(20, 10, 30), 32 | InputIndicator = Color3.fromRGB(170, 150, 190), 33 | 34 | Dialog = Color3.fromRGB(120, 50, 75), 35 | DialogHolder = Color3.fromRGB(95, 40, 60), 36 | DialogHolderLine = Color3.fromRGB(90, 35, 55), 37 | DialogButton = Color3.fromRGB(120, 50, 75), 38 | DialogButtonBorder = Color3.fromRGB(155, 90, 115), 39 | DialogBorder = Color3.fromRGB(100, 70, 90), 40 | DialogInput = Color3.fromRGB(135, 55, 80), 41 | DialogInputLine = Color3.fromRGB(190, 160, 180), 42 | 43 | Text = Color3.fromRGB(240, 240, 240), 44 | SubText = Color3.fromRGB(170, 170, 170), 45 | Hover = Color3.fromRGB(200, 120, 170), 46 | HoverChange = 0.04, 47 | } 48 | -------------------------------------------------------------------------------- /src/Themes/init.lua: -------------------------------------------------------------------------------- 1 | local Themes = { 2 | Names = { 3 | "Dark", 4 | "Darker", 5 | "Light", 6 | "Aqua", 7 | "Amethyst", 8 | "Rose", 9 | }, 10 | } 11 | 12 | for _, Theme in next, script:GetChildren() do 13 | local Required = require(Theme) 14 | Themes[Required.Name] = Required 15 | end 16 | 17 | return Themes 18 | -------------------------------------------------------------------------------- /src/init.lua: -------------------------------------------------------------------------------- 1 | local Lighting = game:GetService("Lighting") 2 | local RunService = game:GetService("RunService") 3 | local LocalPlayer = game:GetService("Players").LocalPlayer 4 | local UserInputService = game:GetService("UserInputService") 5 | local TweenService = game:GetService("TweenService") 6 | local Camera = game:GetService("Workspace").CurrentCamera 7 | local Mouse = LocalPlayer:GetMouse() 8 | 9 | local Root = script 10 | local Creator = require(Root.Creator) 11 | local ElementsTable = require(Root.Elements) 12 | local Acrylic = require(Root.Acrylic) 13 | local Components = Root.Components 14 | local NotificationModule = require(Components.Notification) 15 | 16 | local New = Creator.New 17 | 18 | local ProtectGui = protectgui or (syn and syn.protect_gui) or function() end 19 | local GUI = New("ScreenGui", { 20 | Parent = RunService:IsStudio() and LocalPlayer.PlayerGui or game:GetService("CoreGui"), 21 | }) 22 | ProtectGui(GUI) 23 | NotificationModule:Init(GUI) 24 | 25 | local Library = { 26 | Version = "1.1.0", 27 | 28 | OpenFrames = {}, 29 | Options = {}, 30 | Themes = require(Root.Themes).Names, 31 | 32 | Window = nil, 33 | WindowFrame = nil, 34 | Unloaded = false, 35 | 36 | Theme = "Dark", 37 | DialogOpen = false, 38 | UseAcrylic = false, 39 | Acrylic = false, 40 | Transparency = true, 41 | MinimizeKeybind = nil, 42 | MinimizeKey = Enum.KeyCode.LeftControl, 43 | 44 | GUI = GUI, 45 | } 46 | 47 | function Library:SafeCallback(Function, ...) 48 | if not Function then 49 | return 50 | end 51 | 52 | local Success, Event = pcall(Function, ...) 53 | if not Success then 54 | local _, i = Event:find(":%d+: ") 55 | 56 | if not i then 57 | return Library:Notify({ 58 | Title = "Interface", 59 | Content = "Callback error", 60 | SubContent = Event, 61 | Duration = 5, 62 | }) 63 | end 64 | 65 | return Library:Notify({ 66 | Title = "Interface", 67 | Content = "Callback error", 68 | SubContent = Event:sub(i + 1), 69 | Duration = 5, 70 | }) 71 | end 72 | end 73 | 74 | function Library:Round(Number, Factor) 75 | if Factor == 0 then 76 | return math.floor(Number) 77 | end 78 | Number = tostring(Number) 79 | return Number:find("%.") and tonumber(Number:sub(1, Number:find("%.") + Factor)) or Number 80 | end 81 | 82 | local Icons = require(Root.Icons).assets 83 | function Library:GetIcon(Name) 84 | if Name ~= nil and Icons["lucide-" .. Name] then 85 | return Icons["lucide-" .. Name] 86 | end 87 | return nil 88 | end 89 | 90 | local Elements = {} 91 | Elements.__index = Elements 92 | Elements.__namecall = function(Table, Key, ...) 93 | return Elements[Key](...) 94 | end 95 | 96 | for _, ElementComponent in ipairs(ElementsTable) do 97 | Elements["Add" .. ElementComponent.__type] = function(self, Idx, Config) 98 | ElementComponent.Container = self.Container 99 | ElementComponent.Type = self.Type 100 | ElementComponent.ScrollFrame = self.ScrollFrame 101 | ElementComponent.Library = Library 102 | 103 | return ElementComponent:New(Idx, Config) 104 | end 105 | end 106 | 107 | Library.Elements = Elements 108 | 109 | function Library:CreateWindow(Config) 110 | assert(Config.Title, "Window - Missing Title") 111 | 112 | if Library.Window then 113 | print("You cannot create more than one window.") 114 | return 115 | end 116 | 117 | Library.MinimizeKey = Config.MinimizeKey or Enum.KeyCode.LeftControl 118 | Library.UseAcrylic = Config.Acrylic or false 119 | Library.Acrylic = Config.Acrylic or false 120 | Library.Theme = Config.Theme or "Dark" 121 | if Config.Acrylic then 122 | Acrylic.init() 123 | end 124 | 125 | local Window = require(Components.Window)({ 126 | Parent = GUI, 127 | Size = Config.Size, 128 | Title = Config.Title, 129 | SubTitle = Config.SubTitle, 130 | TabWidth = Config.TabWidth, 131 | }) 132 | 133 | Library.Window = Window 134 | Library:SetTheme(Config.Theme) 135 | 136 | return Window 137 | end 138 | 139 | function Library:SetTheme(Value) 140 | if Library.Window and table.find(Library.Themes, Value) then 141 | Library.Theme = Value 142 | Creator.UpdateTheme() 143 | end 144 | end 145 | 146 | function Library:Destroy() 147 | if Library.Window then 148 | Library.Unloaded = true 149 | if Library.UseAcrylic then 150 | Library.Window.AcrylicPaint.Model:Destroy() 151 | end 152 | Creator.Disconnect() 153 | Library.GUI:Destroy() 154 | end 155 | end 156 | 157 | function Library:ToggleAcrylic(Value) 158 | if Library.Window then 159 | if Library.UseAcrylic then 160 | Library.Acrylic = Value 161 | Library.Window.AcrylicPaint.Model.Transparency = Value and 0.98 or 1 162 | if Value then 163 | Acrylic.Enable() 164 | else 165 | Acrylic.Disable() 166 | end 167 | end 168 | end 169 | end 170 | 171 | function Library:ToggleTransparency(Value) 172 | if Library.Window then 173 | Library.Window.AcrylicPaint.Frame.Background.BackgroundTransparency = Value and 0.35 or 0 174 | end 175 | end 176 | 177 | function Library:Notify(Config) 178 | return NotificationModule:New(Config) 179 | end 180 | 181 | if getgenv then 182 | getgenv().Fluent = Library 183 | end 184 | 185 | return Library 186 | --------------------------------------------------------------------------------