├── .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 |
2 |
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 |
--------------------------------------------------------------------------------