├── .gitignore ├── README.md ├── aftman.toml ├── default.project.json ├── package.json └── src └── StudioMacros ├── init.server.lua ├── macros ├── Color │ ├── ChangeBackgroundColor.luau │ ├── ChangeStrokeColor.luau │ ├── ChangeTextColor.luau │ ├── GroupData.luau │ ├── InsertGradient.luau │ └── ToggleColor.luau ├── GuiObjects │ ├── ConvertToOffset.luau │ ├── ConvertToScale.luau │ ├── DecrementStrokeThickness.luau │ ├── Fill.luau │ ├── FillHorizontal.luau │ ├── FillVertical.luau │ ├── GroupData.luau │ ├── IncrementStrokeThickness.luau │ ├── ToggleBackgroundTransparency.luau │ └── ToggleClipsDescendants.luau ├── Instances │ ├── CreateScreenGui.luau │ ├── GroupData.luau │ ├── InsertAspectRatio.luau │ ├── InsertFrame.luau │ ├── InsertGridLayout.luau │ ├── InsertHitbox.luau │ ├── InsertHorizontalLayout.luau │ ├── InsertImageLabel.luau │ ├── InsertLabel.luau │ ├── InsertPadding.luau │ ├── InsertScrollingFrame.luau │ ├── InsertStroke.luau │ ├── InsertTextBox.luau │ ├── InsertUICorner.luau │ └── InsertVerticalLayout.luau ├── Layout │ ├── AlignBottom.luau │ ├── AlignCenter.luau │ ├── AlignLeft.luau │ ├── AlignRight.luau │ ├── AlignTop.luau │ ├── CyclePaddingOffset.luau │ ├── CyclePaddingScale.luau │ ├── DecrementLayoutOrder.luau │ ├── DecrementZIndex.luau │ ├── FormatContainer.luau │ ├── GroupData.luau │ ├── IncrementLayoutOrder.luau │ ├── IncrementZIndex.luau │ ├── MoveDown.luau │ ├── MoveLeft.luau │ ├── MoveRight.luau │ └── MoveUp.luau ├── Misc │ ├── GroupData.luau │ └── ToggleUIEditor.luau ├── Properties │ ├── GroupData.luau │ └── ToggleVisibility.luau └── Text │ ├── ChangeFont.luau │ ├── ChangeFontStyle.luau │ ├── DecrementTextSize.luau │ ├── GroupData.luau │ ├── IncrementTextSize.luau │ ├── TextAlignDown.luau │ ├── TextAlignLeft.luau │ ├── TextAlignRight.luau │ ├── TextAlignUp.luau │ ├── TextColorBlack.luau │ ├── TextColorWhite.luau │ ├── ToggleRichText.luau │ ├── ToggleTextScaled.luau │ ├── ToggleTextTransparency.luau │ └── ToggleTextWrapped.luau └── modules ├── Shared ├── CommandPalette │ ├── CommandEntry.luau │ ├── CommandGroup.luau │ ├── CommandPalette.luau │ └── Input │ │ ├── Color │ │ ├── ColorEntry.luau │ │ └── ColorPickerPane.luau │ │ └── Font │ │ ├── FontEntry.luau │ │ └── FontStyleEntry.luau ├── Fonts │ └── FontData.luau └── SearchUtils.luau └── node_modules.project.json /.gitignore: -------------------------------------------------------------------------------- 1 | # This file specifies ignored files from Git 2 | 3 | *.sublime-workspace 4 | *.rbxl.lock 5 | node_modules 6 | roblox.toml 7 | roblox.yml 8 | dist 9 | sourcemap.json 10 | package-lock.json 11 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StudioMacros 2 | wip ui keyboard shortcuts plugin 3 | 4 | https://github.com/user-attachments/assets/b850abe1-c4c5-4035-aed8-b6a23bbfbbf4 5 | 6 | ## how to install 7 | either download the plugin file from the [latest 8 | release](https://github.com/unrooot/StudioMacros/releases) and copy it to your 9 | studio plugins folder, or build it from source with npm and rojo. 10 | 11 | ## how to build from source 12 | 1. clone the repo and cd into the directory 13 | ``` 14 | git clone https://github.com/unrooot/studiomacros && cd studiomacros 15 | ``` 16 | 17 | 2. install dependencies with npm (node.js required) 18 | ``` 19 | npm i 20 | ``` 21 | 22 | 3. build the plugin with rojo 23 | ``` 24 | rojo build --plugin StudioMacros.rbxm 25 | ``` 26 | 27 | --- 28 | 29 | ## how to use 30 | in Roblox Studio, go to File -> Advanced -> Customize Shortcuts and bind 31 | "StudioMacros Commands" to a key (e.g. `Shift+Space`), then select an instance 32 | and press the shortcut to open the command palette. 33 | 34 | ### keyboard shortcuts 35 | * `Up` and `Down` to navigate the command palette (or `Ctrl+J` and `Ctrl+K` on macOS) 36 | * `Enter` to run the selected command 37 | * `Shift+Enter` to run the selected command without closing the command palette 38 | * `Esc` to close the command palette (or clear the search) 39 | * `Ctrl+D` to toggle descriptions on the macros in the list 40 | * `Shift+Escape` to release focus from the command palette 41 | * `Tab` and `Shift+Tab` to cycle through custom result inputs (currently only implemented in the color picker) 42 | 43 | ### a note on input: 44 | roblox studio plugins currently do not have a good way of handling input and 45 | focus (see [here](https://devforum.roblox.com/t/plugins-need-a-way-to-listen-to-app-input/479597)) 46 | so the plugin will not work if the viewport in studio is not focused. this 47 | means if you select an instance in the explorer, and try to use the command 48 | palette, despite the UI opening and the textbox being focused, you won't be 49 | able to type. you can work around this by right clicking the viewport to move 50 | your camera without deselecting the current instance before opening the command 51 | palette. 52 | 53 | --- 54 | 55 | ## how to add new macros 56 | you will need to build the plugin to add new macros. create a new file in any 57 | folder inside of `src/StudioMacros/macros` with the following structure: 58 | 59 | ```lua 60 | return { 61 | Name = "Macro Name", 62 | Description = "A description of the macro", 63 | 64 | -- optional values, when provided the macro will use the custom results to 65 | -- select arguments which will be passed to the macro. see: 66 | -- ChangeBackgroundColor.luau or ChangeFont.luau for an example 67 | CustomResults = "Color" | "Font" | "FontStyle", 68 | TargetProperty = "BackgroundColor3", 69 | 70 | -- optional predicate function that should return true if the macro should 71 | -- work + be available for the given instance 72 | Predicate = function(instance) 73 | return instance:IsA("BasePart") 74 | end; 75 | 76 | Macro = function(instance) 77 | instance.Position = Vector3.zero 78 | instance.Size = Vector3.one 79 | 80 | -- optionally return an instance (or table of instances) that will be 81 | -- selected after the macro is run 82 | return instance 83 | end; 84 | } 85 | ``` 86 | 87 | ### creating groups 88 | to create a group of macros, create a new folder inside of the macros folder, 89 | and create a file called `GroupData.luau` with the following structure: 90 | 91 | ```lua 92 | return { 93 | Name = "Group Name"; 94 | Icon = "rbxassetid://1234567"; 95 | } 96 | ``` 97 | 98 | if you're actively creating and testing new macros, you can run `rojo build` 99 | with the `--watch` flag to automatically rebuild the plugin when you save a 100 | file (this requires "Plugin Debugging Enabled" setting to be enabled in 101 | studio). -------------------------------------------------------------------------------- /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 | [tools] 4 | rojo = "quenty/rojo@7.4.1-quenty-npm-canary.3" 5 | selene = "Kampfkarren/selene@0.26.1" 6 | moonwave-extractor = "UpliftGames/moonwave@1.1.2" -------------------------------------------------------------------------------- /default.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studiomacros", 3 | "tree": { 4 | "$path": "src" 5 | } 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "studiomacros", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [ 6 | "Roblox", 7 | "Nevermore", 8 | "Lua" 9 | ], 10 | "license": "UNLICENSED", 11 | "dependencies": { 12 | "@quenty/basicpane": "^13.16.0", 13 | "@quenty/blend": "^12.17.0", 14 | "@quenty/brio": "^14.16.0", 15 | "@quenty/buttonhighlightmodel": "^14.17.0", 16 | "@quenty/fzy": "^5.8.0", 17 | "@quenty/lipsum": "^14.17.0", 18 | "@quenty/loader": "^10.8.0", 19 | "@quenty/observablecollection": "^12.19.0", 20 | "@quenty/rx": "^13.16.0", 21 | "@quenty/servicebag": "^11.11.0", 22 | "@quenty/signal": "^7.10.0", 23 | "@quenty/string": "^3.3.0", 24 | "@quenty/valueobject": "^13.16.0" 25 | }, 26 | "publishConfig": { 27 | "access": "restricted" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/StudioMacros/init.server.lua: -------------------------------------------------------------------------------- 1 | local modules = script:WaitForChild("modules") 2 | local loader = script.Parent:FindFirstChild("LoaderUtils", true).Parent 3 | local require = require(loader).bootstrapPlugin(modules) 4 | 5 | local ChangeHistoryService = game:GetService("ChangeHistoryService") 6 | local CoreGui = game:GetService("CoreGui") 7 | local Selection = game:GetService("Selection") 8 | local UserInputService = game:GetService("UserInputService") 9 | 10 | local Blend = require("Blend") 11 | local CommandGroup = require("CommandGroup") 12 | local CommandPalette = require("CommandPalette") 13 | local Maid = require("Maid") 14 | local RxInstanceUtils = require("RxInstanceUtils") 15 | local ValueObject = require("ValueObject") 16 | 17 | local function initialize(plugin) 18 | local maid = Maid.new() 19 | 20 | local pane = maid:Add(CommandPalette.new()) 21 | 22 | maid:GiveTask(Blend.New "ScreenGui" { 23 | Name = "StudioMacrosCommands"; 24 | DisplayOrder = 1000; 25 | Parent = CoreGui; 26 | 27 | pane:Render(); 28 | }:Subscribe()) 29 | 30 | local uiEditorVisible = maid:Add(ValueObject.new(plugin:GetSetting("UIEditorDisabled") or true)) 31 | 32 | local toggleCommand = plugin:CreatePluginAction( 33 | "StudioMacros Commands", 34 | "StudioMacros Commands", 35 | "Toggle the StudioMacros command palette", 36 | "rbxassetid://5972593639", 37 | true 38 | ) 39 | 40 | maid:GiveTask(toggleCommand.Triggered:Connect(function() 41 | if pane:IsVisible() then 42 | pane:CaptureFocus() 43 | end 44 | 45 | pane.TargetSelection.Value = Selection:Get() 46 | pane:Show() 47 | end)) 48 | 49 | maid:GiveTask(plugin.Unloading:Connect(function() 50 | maid:Destroy() 51 | end)) 52 | 53 | for index, group in script.macros:GetChildren() do 54 | if group:IsA("Folder") then 55 | local groupData = group:FindFirstChild("GroupData") 56 | if not groupData then 57 | continue 58 | end 59 | 60 | local groupEntry = pane:AddGroup(require(groupData)) 61 | groupEntry.LayoutOrder.Value = index 62 | 63 | if index ~= 1 then 64 | groupEntry:SetIsCollapsed(true) 65 | end 66 | 67 | local activeMacro, leaveActiveMacroOpen 68 | maid:GiveTask(pane.CustomResultsReset:Connect(function() 69 | activeMacro = nil 70 | end)) 71 | 72 | for macroIndex, macro in group:GetChildren() do 73 | if macro.Name == "GroupData" or not macro:IsA("ModuleScript") then 74 | continue 75 | end 76 | 77 | local macroData = require(macro) 78 | local pluginAction = plugin:CreatePluginAction( 79 | macroData.Name, 80 | macroData.Name, 81 | "[StudioMacros]: " .. macroData.Description, 82 | "rbxassetid://5972593639", 83 | true 84 | ) 85 | 86 | local macroEntry = groupEntry:AddEntry(macroData) 87 | macroEntry:SetDefaultIndex(macroIndex) 88 | 89 | if macro.Name == "ToggleUIEditor" then 90 | maid:GiveTask(RxInstanceUtils.observeLastNamedChildBrio(CoreGui, "Folder", "RobloxGUIEditor") 91 | :Subscribe(function(editorBrio) 92 | if editorBrio:IsDead() then 93 | return 94 | end 95 | 96 | local editor = editorBrio:GetValue() 97 | 98 | editorBrio:ToMaid():GiveTask(RxInstanceUtils.observeDescendantsOfClassBrio(editor, "ScreenGui") 99 | :Subscribe(function(screenGuiBrio) 100 | if screenGuiBrio:IsDead() then 101 | return 102 | end 103 | 104 | local screenGui = screenGuiBrio:GetValue() 105 | 106 | screenGuiBrio:ToMaid():GiveTask(uiEditorVisible:Observe():Subscribe(function(isVisible) 107 | task.defer(function() 108 | screenGui.Enabled = isVisible 109 | end) 110 | end)) 111 | end)) 112 | end)) 113 | 114 | maid:GiveTask(RxInstanceUtils.observeLastNamedChildBrio(CoreGui, "ScreenGui", "RobloxGui") 115 | :Subscribe(function(screenGuiBrio) 116 | if screenGuiBrio:IsDead() then 117 | return 118 | end 119 | 120 | local screenGui = screenGuiBrio:GetValue() 121 | 122 | screenGuiBrio:ToMaid():GiveTask(uiEditorVisible:Observe():Subscribe(function(isVisible) 123 | screenGui.Enabled = isVisible 124 | end)) 125 | end)) 126 | end 127 | 128 | local function activated(leavePaneOpen: boolean?, ...) 129 | if macroEntry:IsGroupHeader() then 130 | return 131 | end 132 | 133 | if macro.Name == "ToggleUIEditor" then 134 | uiEditorVisible.Value = not uiEditorVisible.Value 135 | if not leavePaneOpen then 136 | pane:Hide() 137 | end 138 | return 139 | end 140 | 141 | local customResults = macroData.CustomResults 142 | local arguments = {...} 143 | if customResults and #arguments == 0 then 144 | pane.TargetProperty.Value = macroData.TargetProperty 145 | pane:SetCustomResults(customResults) 146 | activeMacro = macroData 147 | 148 | if leavePaneOpen then 149 | leaveActiveMacroOpen = true 150 | end 151 | 152 | return 153 | end 154 | 155 | if not leavePaneOpen then 156 | activeMacro = nil 157 | end 158 | 159 | local newSelection = {} 160 | local selectedInstances = Selection:Get() 161 | 162 | -- HACK: This is necessary because if you click a 163 | -- TextButton in the palette, your selection will be 164 | -- cleared, which is not desired. idk if this will lead to 165 | -- more unintended behavior yet, also if you collapse a 166 | -- group it will clear your selection before a macro is 167 | -- selected. 168 | local revertSelection = false 169 | if (not selectedInstances or #selectedInstances == 0) and pane.TargetSelection.Value then 170 | selectedInstances = pane.TargetSelection.Value 171 | revertSelection = true 172 | end 173 | 174 | local undoRecording 175 | local function startRecording() 176 | undoRecording = ChangeHistoryService:TryBeginRecording(macroData.Name) 177 | if not undoRecording then 178 | warn("[StudioMacros]: Failed to begin recording for", macroData.Name) 179 | end 180 | end 181 | 182 | if #selectedInstances > 0 then 183 | startRecording() 184 | for _, selectedInstance in selectedInstances do 185 | if macroData.Predicate then 186 | local validInstance = macroData.Predicate(selectedInstance) 187 | if not validInstance then 188 | print(macroData.Name, "failed predicate", selectedInstance) 189 | continue 190 | end 191 | end 192 | 193 | local newInstance = macroData.Macro(selectedInstance, plugin, ...) 194 | 195 | if not leavePaneOpen then 196 | if leaveActiveMacroOpen then 197 | pane:SetCustomResults(nil) 198 | else 199 | pane:Hide() 200 | end 201 | end 202 | 203 | if newInstance then 204 | table.insert(newSelection, newInstance) 205 | end 206 | end 207 | else 208 | if not macroData.Predicate then 209 | startRecording() 210 | local newInstance = macroData.Macro(nil, plugin, ...) 211 | 212 | if not leavePaneOpen then 213 | if leaveActiveMacroOpen then 214 | pane:SetCustomResults(nil) 215 | else 216 | pane:Hide() 217 | end 218 | end 219 | 220 | if newInstance then 221 | table.insert(newSelection, newInstance) 222 | end 223 | end 224 | end 225 | 226 | leaveActiveMacroOpen = nil 227 | 228 | if #newSelection > 0 then 229 | Selection:Set(newSelection) 230 | pane.TargetSelection.Value = newSelection 231 | elseif revertSelection then 232 | Selection:Set(selectedInstances) 233 | pane.TargetSelection.Value = selectedInstances 234 | end 235 | 236 | if undoRecording then 237 | ChangeHistoryService:FinishRecording(undoRecording, Enum.FinishRecordingOperation.Commit) 238 | end 239 | end 240 | 241 | maid:GiveTask(pane.CustomResultActivated:Connect(function(customResult, ...) 242 | if activeMacro ~= macroData then 243 | return 244 | end 245 | 246 | if customResult == macroData.CustomResults then 247 | activated(...) 248 | end 249 | end)) 250 | 251 | maid:GiveTask(macroEntry.Activated:Connect(activated)) 252 | maid:GiveTask(pluginAction.Triggered:Connect(activated)) 253 | end 254 | end 255 | end 256 | 257 | return maid 258 | end 259 | 260 | if plugin then 261 | initialize(plugin) 262 | end -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/ChangeBackgroundColor.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Change Background Color"; 3 | Description = "Change the BackgroundColor3 of a GuiObject."; 4 | CustomResults = "Color"; 5 | TargetProperty = "BackgroundColor3"; 6 | 7 | Predicate = function(gui) 8 | return gui:IsA("GuiObject") 9 | end; 10 | 11 | Macro = function(gui, _, color) 12 | if not color then 13 | return 14 | end 15 | 16 | if typeof(color) == "Color3" then 17 | gui.BackgroundColor3 = color 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/ChangeStrokeColor.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Change Stroke Color"; 3 | Description = "Change the color of a UIStroke."; 4 | CustomResults = "Color"; 5 | TargetProperty = "Color"; 6 | 7 | Predicate = function(gui) 8 | return gui:IsA("UIStroke") 9 | end; 10 | 11 | Macro = function(gui, _, color) 12 | if not color then 13 | return 14 | end 15 | 16 | if typeof(color) == "Color3" then 17 | gui.Color = color 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/ChangeTextColor.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Change Text Color"; 3 | Description = "Change the TextColor3 of a GuiObject."; 4 | CustomResults = "Color"; 5 | TargetProperty = "TextColor3"; 6 | 7 | Predicate = function(gui) 8 | return gui:IsA("TextLabel") 9 | or gui:IsA("TextButton") 10 | or gui:IsA("TextBox") 11 | end; 12 | 13 | Macro = function(gui, _, color) 14 | if not color then 15 | return 16 | end 17 | 18 | if typeof(color) == "Color3" then 19 | gui.TextColor3 = color 20 | end 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Color"; 3 | Icon = "rbxassetid://6031625148"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/InsertGradient.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert UIGradient"; 3 | Description = "Insert a UIGradient to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local gradient = Instance.new("UIGradient") 11 | gradient.Parent = gui 12 | 13 | return gradient 14 | end; 15 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Color/ToggleColor.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle Color"; 3 | Description = "Toggle the object's color between black and white."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("Frame") 7 | or gui:IsA("TextLabel") 8 | or gui:IsA("TextButton") 9 | or gui:IsA("TextBox") 10 | or gui:IsA("UIStroke") 11 | end; 12 | 13 | Macro = function(gui) 14 | if gui:IsA("TextLabel") or gui:IsA("TextButton") or gui:IsA("TextBox") then 15 | gui.TextColor3 = gui.TextColor3 == Color3.new(1, 1, 1) and Color3.new(0, 0, 0) or Color3.new(1, 1, 1) 16 | elseif gui:IsA("UIStroke") then 17 | gui.Color = gui.Color == Color3.new(1, 1, 1) and Color3.new(0, 0, 0) or Color3.new(1, 1, 1) 18 | else 19 | gui.BackgroundColor3 = gui.BackgroundColor3 == Color3.new(1, 1, 1) and Color3.new(0, 0, 0) or Color3.new(1, 1, 1) 20 | end 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/ConvertToOffset.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Convert to Offset"; 3 | Description = "Convert the GuiObject's size from scale to offset."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | local currentSize = gui.Size 11 | local parent = gui.Parent 12 | 13 | local bounds 14 | if parent:IsA("ScreenGui") then 15 | bounds = workspace.CurrentCamera.ViewportSize 16 | else 17 | bounds = parent.AbsoluteSize 18 | end 19 | 20 | gui.Size = UDim2.fromOffset(currentSize.X.Scale * bounds.X, currentSize.Y.Scale * bounds.Y) 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/ConvertToScale.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Convert to Scale"; 3 | Description = "Convert the GuiObject's size from offset to scale."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | local currentSize = gui.AbsoluteSize 11 | local parent = gui.Parent 12 | 13 | local bounds 14 | if parent:IsA("ScreenGui") then 15 | bounds = workspace.CurrentCamera.ViewportSize 16 | else 17 | bounds = parent.AbsoluteSize 18 | end 19 | 20 | gui.Size = UDim2.fromScale(currentSize.X / bounds.X, currentSize.Y / bounds.Y) 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/DecrementStrokeThickness.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Decrement Stroke Thickness"; 3 | Description = "Decrement a UIStroke's thickness."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("UIStroke") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui.Thickness - 1 < 0 then 11 | return 12 | end 13 | 14 | gui.Thickness -= 1 15 | end; 16 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/Fill.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Fill Size (XY)"; 3 | Description = "Resize the object to take up 100% of the parent's size."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.Size = UDim2.fromScale(1, 1) 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/FillHorizontal.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Fill Horizontal (X)"; 3 | Description = "Resize the object to take up 100% of the parent's width."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.Size = UDim2.new(1, 0, gui.Size.Y.Scale, gui.Size.Y.Offset) 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/FillVertical.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Fill Vertical (Y)"; 3 | Description = "Resize the object to take up 100% of the parent's height."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.Size = UDim2.new(gui.Size.X.Scale, gui.Size.X.Offset, 1, 0) 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "GuiObjects"; 3 | Icon = "rbxassetid://6034837803"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/IncrementStrokeThickness.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Increment Stroke Thickness"; 3 | Description = "Increment a UIStroke's thickness."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("UIStroke") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.Thickness += 1 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/ToggleBackgroundTransparency.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle BackgroundTransparency"; 3 | Description = "Toggle an object's BackgroundTransparency between 0 and 1."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.BackgroundTransparency = gui.BackgroundTransparency == 0 and 1 or 0 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/GuiObjects/ToggleClipsDescendants.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle ClipsDescendants"; 3 | Description = "Toggle ClipsDescendants on an object."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.ClipsDescendants = not gui.ClipsDescendants 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/CreateScreenGui.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Create ScreenGui"; 3 | Description = "Create a new ScreenGui in StarterGui."; 4 | 5 | Macro = function() 6 | local screenGui = Instance.new("ScreenGui") 7 | screenGui.ResetOnSpawn = false 8 | screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling 9 | screenGui.Parent = game:GetService("StarterGui") 10 | 11 | return screenGui 12 | end; 13 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Instances"; 3 | Icon = "rbxassetid://6023565894"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertAspectRatio.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert UIAspectRatioConstraint"; 3 | Description = "Insert a UIAspectRatioConstraint to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local aspectRatio = Instance.new("UIAspectRatioConstraint") 11 | aspectRatio.Parent = gui 12 | 13 | return aspectRatio 14 | end; 15 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertFrame.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Frame"; 3 | Description = "Insert a Frame to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local frame = Instance.new("Frame") 11 | frame.BorderSizePixel = 0 12 | frame.Size = UDim2.new(0, 100, 0, 100) 13 | frame.BackgroundColor3 = Color3.fromRGB(255, 255, 255) 14 | frame.Parent = gui 15 | 16 | return frame 17 | end; 18 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertGridLayout.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Grid Layout"; 3 | Description = "Insert a UIGridLayout to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local grid = Instance.new("UIGridLayout") 11 | grid.CellPadding = UDim2.new(0, 5, 0, 5) 12 | grid.CellSize = UDim2.new(0, 100, 0, 100) 13 | grid.Parent = gui 14 | 15 | return grid 16 | end; 17 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertHitbox.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Hitbox"; 3 | Description = "Insert a hitbox (invisible TextButton) to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local button = Instance.new("TextButton") 11 | button.Active = true 12 | button.BackgroundTransparency = 1 13 | button.Name = "button" 14 | button.Size = UDim2.fromScale(1, 1) 15 | button.Text = "" 16 | button.ZIndex = 5 17 | button.Parent = gui 18 | 19 | return button 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertHorizontalLayout.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Horizontal Layout"; 3 | Description = "Insert a UIListLayout to the selected object with a horizontal FillDirection."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local list = Instance.new("UIListLayout") 11 | list.FillDirection = Enum.FillDirection.Horizontal 12 | list.HorizontalAlignment = Enum.HorizontalAlignment.Center 13 | list.VerticalAlignment = Enum.VerticalAlignment.Center 14 | list.SortOrder = Enum.SortOrder.LayoutOrder 15 | list.Parent = gui 16 | 17 | return list 18 | end; 19 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertImageLabel.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert ImageLabel"; 3 | Description = "Insert an ImageLabel to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local image = Instance.new("ImageLabel") 11 | image.BorderSizePixel = 0 12 | image.BackgroundColor3 = Color3.fromRGB(255, 255, 255) 13 | image.Size = UDim2.fromOffset(100, 100) 14 | image.Parent = gui 15 | 16 | return image 17 | end; 18 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertLabel.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Label"; 3 | Description = "Insert a TextLabel to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local label = Instance.new("TextLabel") 11 | label.BorderSizePixel = 0 12 | label.BackgroundColor3 = Color3.fromRGB(255, 255, 255) 13 | label.Size = UDim2.fromOffset(200, 50) 14 | label.Font = Enum.Font.SourceSans 15 | label.TextSize = 14 16 | label.TextColor3 = Color3.fromRGB(0, 0, 0) 17 | label.Parent = gui 18 | 19 | return label 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertPadding.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Padding"; 3 | Description = "Insert a UIPadding to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local padding = Instance.new("UIPadding") 11 | padding.Parent = gui 12 | 13 | return padding 14 | end; 15 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertScrollingFrame.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert ScrollingFrame"; 3 | Description = "Insert a ScrollingFrame to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local frame = Instance.new("ScrollingFrame") 11 | frame.BackgroundColor3 = Color3.fromRGB(255, 255, 255) 12 | frame.BackgroundTransparency = 1 13 | frame.BorderSizePixel = 0 14 | frame.BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png" 15 | frame.CanvasSize = UDim2.new() 16 | frame.ScrollBarImageColor3 = Color3.fromRGB(0, 0, 0) 17 | frame.ScrollBarThickness = 8 18 | frame.ScrollingDirection = Enum.ScrollingDirection.Y 19 | frame.TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png" 20 | frame.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar 21 | frame.Parent = gui 22 | 23 | return frame 24 | end; 25 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertStroke.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert UIStroke"; 3 | Description = "Insert a UIStroke to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local stroke = Instance.new("UIStroke") 11 | stroke.Parent = gui 12 | 13 | return stroke 14 | end; 15 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertTextBox.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Text Input"; 3 | Description = "Insert a TextBox to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local input = Instance.new("TextBox") 11 | input.BackgroundTransparency = 1 12 | input.Name = "input" 13 | input.Size = UDim2.fromScale(1, 1) 14 | input.ZIndex = 5 15 | input.Parent = gui 16 | 17 | return input 18 | end; 19 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertUICorner.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert UICorner"; 3 | Description = "Insert a UICorner to the selected object."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local corner = Instance.new("UICorner") 11 | corner.Parent = gui 12 | 13 | return corner 14 | end; 15 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Instances/InsertVerticalLayout.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Insert Vertical Layout"; 3 | Description = "Insert a UIListLayout to the selected object with a vertical FillDirection."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil 7 | end; 8 | 9 | Macro = function(gui) 10 | local list = Instance.new("UIListLayout") 11 | list.FillDirection = Enum.FillDirection.Vertical 12 | list.HorizontalAlignment = Enum.HorizontalAlignment.Center 13 | list.VerticalAlignment = Enum.VerticalAlignment.Center 14 | list.SortOrder = Enum.SortOrder.LayoutOrder 15 | list.Parent = gui 16 | 17 | return list 18 | end; 19 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/AlignBottom.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Align Bottom"; 3 | Description = "Align the object to the bottom."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | gui.VerticalAlignment = Enum.VerticalAlignment.Bottom 12 | else 13 | gui.AnchorPoint = Vector2.new(gui.AnchorPoint.X, 1) 14 | gui.Position = UDim2.fromScale(gui.Position.X.Scale, 1) 15 | end 16 | end; 17 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/AlignCenter.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Center"; 3 | Description = "Center the object to the parent."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | gui.VerticalAlignment = Enum.VerticalAlignment.Center 12 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Center 13 | else 14 | gui.AnchorPoint = Vector2.new(0.5, 0.5) 15 | gui.Position = UDim2.fromScale(0.5, 0.5) 16 | end 17 | end; 18 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/AlignLeft.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Align Left"; 3 | Description = "Align the object to the left."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Left 12 | else 13 | gui.AnchorPoint = Vector2.new(0, gui.AnchorPoint.Y) 14 | gui.Position = UDim2.fromScale(0, gui.Position.Y.Scale) 15 | end 16 | end; 17 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/AlignRight.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Align Right"; 3 | Description = "Align the object to the right."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Right 12 | else 13 | gui.AnchorPoint = Vector2.new(1, gui.AnchorPoint.Y) 14 | gui.Position = UDim2.fromScale(1, gui.Position.Y.Scale) 15 | end 16 | end; 17 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/AlignTop.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Align Top"; 3 | Description = "Align the object to the top."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | gui.VerticalAlignment = Enum.VerticalAlignment.Top 12 | else 13 | gui.AnchorPoint = Vector2.new(gui.AnchorPoint.X, 0) 14 | gui.Position = UDim2.fromScale(gui.Position.X.Scale, 0) 15 | end 16 | end; 17 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/CyclePaddingOffset.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Cycle Padding (5px, offset)"; 3 | Description = "Toggle the UIPadding offset by 5px (5px, 10px, 15px)."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("UIPadding") 7 | end; 8 | 9 | Macro = function(gui) 10 | local padding = gui.PaddingLeft.Offset 11 | 12 | if padding == 5 then 13 | padding = 10 14 | elseif padding == 10 then 15 | padding = 15 16 | elseif padding == 15 then 17 | padding = 5 18 | else 19 | if padding < 5 then 20 | padding = 5 21 | elseif padding < 10 then 22 | padding = 10 23 | elseif padding < 15 then 24 | padding = 15 25 | else 26 | padding = 5 27 | end 28 | end 29 | 30 | gui.PaddingLeft = UDim.new(0, padding) 31 | gui.PaddingRight = UDim.new(0, padding) 32 | gui.PaddingTop = UDim.new(0, padding) 33 | gui.PaddingBottom = UDim.new(0, padding) 34 | end; 35 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/CyclePaddingScale.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Cycle Padding (5px, scale)"; 3 | Description = "Toggle the UIPadding scale by 5px (5px, 10px, 15px)."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("UIPadding") 7 | end; 8 | 9 | Macro = function(gui) 10 | local width = gui.Parent.AbsoluteSize.X 11 | local padding = math.round(width * gui.PaddingLeft.Scale) 12 | 13 | if padding == 5 then 14 | padding = 10 15 | elseif padding == 10 then 16 | padding = 15 17 | elseif padding == 15 then 18 | padding = 5 19 | else 20 | if padding < 5 then 21 | padding = 5 22 | elseif padding < 10 then 23 | padding = 10 24 | elseif padding < 15 then 25 | padding = 15 26 | else 27 | padding = 5 28 | end 29 | end 30 | 31 | local sidePadding = padding / gui.Parent.AbsoluteSize.X 32 | local topPadding = padding / gui.Parent.AbsoluteSize.Y 33 | 34 | gui.PaddingLeft = UDim.new(sidePadding, 0) 35 | gui.PaddingRight = UDim.new(sidePadding, 0) 36 | gui.PaddingTop = UDim.new(topPadding, 0) 37 | gui.PaddingBottom = UDim.new(topPadding, 0) 38 | end; 39 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/DecrementLayoutOrder.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Decrement Layout Order"; 3 | Description = "Decrement the object's LayoutOrder."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.LayoutOrder -= 1 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/DecrementZIndex.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Decrement ZIndex"; 3 | Description = "Decrement the object's ZIndex."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.ZIndex -= 1 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/FormatContainer.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Format Container"; 3 | Description = "Take a GuiObject with an offset size and convert it to scale with UIAspectRatio/UISizeConstraints."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | local size = gui.AbsoluteSize 11 | 12 | local sizeConstraint = Instance.new("UISizeConstraint") 13 | sizeConstraint.MaxSize = size 14 | sizeConstraint.Parent = gui 15 | 16 | local aspectRatio = Instance.new("UIAspectRatioConstraint") 17 | aspectRatio.AspectRatio = size.X / size.Y 18 | aspectRatio.Parent = gui 19 | 20 | gui.Size = UDim2.fromScale(1, 1) 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Layout"; 3 | Icon = "rbxassetid://6022668883"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/IncrementLayoutOrder.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Increment Layout Order"; 3 | Description = "Increment the object's LayoutOrder."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.LayoutOrder += 1 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/IncrementZIndex.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Increment ZIndex"; 3 | Description = "Increment the object's ZIndex."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") 7 | end; 8 | 9 | Macro = function(gui) 10 | gui.ZIndex += 1 11 | end; 12 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/MoveDown.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move Down"; 3 | Description = "Move the object's alignment down."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | if gui.VerticalAlignment == Enum.VerticalAlignment.Top then 12 | gui.VerticalAlignment = Enum.VerticalAlignment.Center 13 | elseif gui.VerticalAlignment == Enum.VerticalAlignment.Center then 14 | gui.VerticalAlignment = Enum.VerticalAlignment.Bottom 15 | end 16 | else 17 | gui.AnchorPoint = Vector2.new(gui.AnchorPoint.X, math.clamp(gui.AnchorPoint.Y + 0.5, 0, 1)) 18 | gui.Position = UDim2.new(gui.Position.X.Scale, 0, math.clamp(gui.Position.Y.Scale + 0.5, 0, 1), 0) 19 | end 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/MoveLeft.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move Left"; 3 | Description = "Move the object's alignment to the left."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | if gui.HorizontalAlignment == Enum.HorizontalAlignment.Right then 12 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Center 13 | elseif gui.HorizontalAlignment == Enum.HorizontalAlignment.Center then 14 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Left 15 | end 16 | else 17 | gui.AnchorPoint = Vector2.new(math.clamp(gui.AnchorPoint.X - 0.5, 0, 1), gui.AnchorPoint.Y) 18 | gui.Position = UDim2.new(math.clamp(gui.Position.X.Scale - 0.5, 0, 1), 0, gui.Position.Y.Scale, 0) 19 | end 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/MoveRight.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move Right"; 3 | Description = "Move the object's alignment to the right."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | if gui.HorizontalAlignment == Enum.HorizontalAlignment.Left then 12 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Center 13 | elseif gui.HorizontalAlignment == Enum.HorizontalAlignment.Center then 14 | gui.HorizontalAlignment = Enum.HorizontalAlignment.Right 15 | end 16 | else 17 | gui.AnchorPoint = Vector2.new(math.clamp(gui.AnchorPoint.X + 0.5, 0, 1), gui.AnchorPoint.Y) 18 | gui.Position = UDim2.new(math.clamp(gui.Position.X.Scale + 0.5, 0, 1), 0, gui.Position.Y.Scale, 0) 19 | end 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Layout/MoveUp.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move Up"; 3 | Description = "Move the object's alignment up."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("GuiObject") or gui:IsA("UIListLayout") 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("UIListLayout") then 11 | if gui.VerticalAlignment == Enum.VerticalAlignment.Bottom then 12 | gui.VerticalAlignment = Enum.VerticalAlignment.Center 13 | elseif gui.VerticalAlignment == Enum.VerticalAlignment.Center then 14 | gui.VerticalAlignment = Enum.VerticalAlignment.Top 15 | end 16 | else 17 | gui.AnchorPoint = Vector2.new(gui.AnchorPoint.X, math.clamp(gui.AnchorPoint.Y - 0.5, 0, 1)) 18 | gui.Position = UDim2.new(gui.Position.X.Scale, 0, math.clamp(gui.Position.Y.Scale - 0.5, 0, 1), 0) 19 | end 20 | end; 21 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Misc/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Misc"; 3 | Icon = "rbxassetid://6034275725"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Misc/ToggleUIEditor.luau: -------------------------------------------------------------------------------- 1 | local disabled = false 2 | 3 | return { 4 | Name = "Toggle UI Editor"; 5 | Description = "Toggle the default Studio UI editor."; 6 | 7 | Macro = function(_, plugin) 8 | disabled = plugin:GetSetting("UIEditorDisabled") or false 9 | plugin:SetSetting("UIEditorDisabled", not disabled) 10 | end; 11 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Properties/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Properties"; 3 | Icon = "rbxassetid://6031075931"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Properties/ToggleVisibility.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle Visibility"; 3 | Description = "Toggle the object's visibility."; 4 | 5 | Predicate = function(gui) 6 | return gui ~= nil and (gui:IsA("GuiObject") or gui:IsA("ScreenGui") or gui:IsA("UIStroke") or gui:IsA("UIGradient")) 7 | end; 8 | 9 | Macro = function(gui) 10 | if gui:IsA("ScreenGui") or gui:IsA("UIStroke") or gui:IsA("UIGradient") then 11 | gui.Enabled = not gui.Enabled 12 | else 13 | gui.Visible = not gui.Visible 14 | end 15 | end; 16 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ChangeFont.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Change Font"; 3 | Description = "Change the font on a text object."; 4 | CustomResults = "Font"; 5 | 6 | Predicate = function(gui) 7 | return gui:IsA("TextLabel") 8 | or gui:IsA("TextButton") 9 | or gui:IsA("TextBox") 10 | end; 11 | 12 | Macro = function(gui, _, font) 13 | if not font then 14 | return 15 | end 16 | 17 | gui.FontFace = font 18 | end; 19 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ChangeFontStyle.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Change Font Style"; 3 | Description = "Change the font style, including font weight for bold and italic text."; 4 | CustomResults = "FontStyle"; 5 | 6 | Predicate = function(gui) 7 | return gui:IsA("TextLabel") 8 | or gui:IsA("TextButton") 9 | or gui:IsA("TextBox") 10 | end; 11 | 12 | Macro = function(gui, _, fontWeight, isItalic) 13 | if not fontWeight then 14 | return 15 | end 16 | 17 | local currentFontFace = gui.FontFace 18 | local fontStyle = isItalic and Enum.FontStyle.Italic or Enum.FontStyle.Normal 19 | 20 | gui.FontFace = Font.new(currentFontFace.Family, fontWeight, fontStyle) 21 | end; 22 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/DecrementTextSize.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Decrement Text Size"; 3 | Description = "Decrement the object's TextSize."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextSize -= 1 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/GroupData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Text"; 3 | Icon = "rbxassetid://6034910909"; 4 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/IncrementTextSize.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Increment Text Size"; 3 | Description = "Increment the object's TextSize."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextSize += 1 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextAlignDown.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move TextYAlignment Down"; 3 | Description = "Move a TextLabel's text alignment down."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | local currentAlignment = gui.TextYAlignment 13 | 14 | if currentAlignment == Enum.TextYAlignment.Top then 15 | gui.TextYAlignment = Enum.TextYAlignment.Center 16 | elseif currentAlignment == Enum.TextYAlignment.Center then 17 | gui.TextYAlignment = Enum.TextYAlignment.Bottom 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextAlignLeft.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move TextXAlignment Left"; 3 | Description = "Move a TextLabel's text alignment left."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | local currentAlignment = gui.TextXAlignment 13 | 14 | if currentAlignment == Enum.TextXAlignment.Right then 15 | gui.TextXAlignment = Enum.TextXAlignment.Center 16 | elseif currentAlignment == Enum.TextXAlignment.Center then 17 | gui.TextXAlignment = Enum.TextXAlignment.Left 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextAlignRight.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move TextXAlignment Right"; 3 | Description = "Move a TextLabel's text alignment right."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | local currentAlignment = gui.TextXAlignment 13 | 14 | if currentAlignment == Enum.TextXAlignment.Left then 15 | gui.TextXAlignment = Enum.TextXAlignment.Center 16 | elseif currentAlignment == Enum.TextXAlignment.Center then 17 | gui.TextXAlignment = Enum.TextXAlignment.Right 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextAlignUp.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Move TextYAlignment Up"; 3 | Description = "Move a TextLabel's text alignment up."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | local currentAlignment = gui.TextYAlignment 13 | 14 | if currentAlignment == Enum.TextYAlignment.Bottom then 15 | gui.TextYAlignment = Enum.TextYAlignment.Center 16 | elseif currentAlignment == Enum.TextYAlignment.Center then 17 | gui.TextYAlignment = Enum.TextYAlignment.Top 18 | end 19 | end; 20 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextColorBlack.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Set Text Color (Black)"; 3 | Description = "Set the text color of a TextLabel to black."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextColor3 = Color3.new(0, 0, 0) 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/TextColorWhite.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Set Text Color (White)"; 3 | Description = "Set the text color of a TextLabel to white."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextColor3 = Color3.new(1, 1, 1) 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ToggleRichText.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle RichText"; 3 | Description = "Toggle RichText on a TextObject."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.RichText = not gui.RichText 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ToggleTextScaled.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle TextScaled"; 3 | Description = "Toggle TextScaled on a TextLabel."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextScaled = not gui.TextScaled 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ToggleTextTransparency.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle TextTransparency"; 3 | Description = "Switch between 0 and 1 TextTransparency on a TextLabel."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextTransparency = gui.TextTransparency == 0 and 1 or 0 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/macros/Text/ToggleTextWrapped.luau: -------------------------------------------------------------------------------- 1 | return { 2 | Name = "Toggle TextWrapped"; 3 | Description = "Toggle TextWrapped on a TextLabel."; 4 | 5 | Predicate = function(gui) 6 | return gui:IsA("TextLabel") 7 | or gui:IsA("TextButton") 8 | or gui:IsA("TextBox") 9 | end; 10 | 11 | Macro = function(gui) 12 | gui.TextWrapped = not gui.TextWrapped 13 | end; 14 | } -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/CommandEntry.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local BasicPane = require("BasicPane") 4 | local Blend = require("Blend") 5 | local ButtonHighlightModel = require("ButtonHighlightModel") 6 | local Fzy = require("Fzy") 7 | local Rx = require("Rx") 8 | local SearchUtils = require("SearchUtils") 9 | local Signal = require("Signal") 10 | local ValueObject = require("ValueObject") 11 | 12 | local CommandEntry = setmetatable({}, BasicPane) 13 | CommandEntry.ClassName = "CommandEntry" 14 | CommandEntry.__index = CommandEntry 15 | 16 | function CommandEntry.new() 17 | local self = setmetatable(BasicPane.new(), CommandEntry) 18 | 19 | self._groupIcon = self._maid:Add(ValueObject.new("")) 20 | self._defaultIndex = self._maid:Add(ValueObject.new(0)) 21 | self._groupName = self._maid:Add(ValueObject.new("")) 22 | self._groupVisible = self._maid:Add(ValueObject.new(false)) 23 | self._isGroupHeader = self._maid:Add(ValueObject.new(false)) 24 | self._macroDescription = self._maid:Add(ValueObject.new("")) 25 | self._macroName = self._maid:Add(ValueObject.new("")) 26 | self._model = self._maid:Add(ButtonHighlightModel.new()) 27 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 28 | self._yPosition = self._maid:Add(ValueObject.new(0)) 29 | 30 | self.Activated = self._maid:Add(Signal.new()) 31 | self.SearchScore = self._maid:Add(ValueObject.new(0)) 32 | 33 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 34 | self._model:SetInteractionEnabled(isVisible) 35 | self._percentVisibleTarget.Value = isVisible and 1 or 0 36 | end)) 37 | 38 | return self 39 | end 40 | 41 | function CommandEntry:SetIsGroupHeader(isGroupHeader: boolean) 42 | self._isGroupHeader.Value = isGroupHeader 43 | end 44 | 45 | function CommandEntry:IsGroupHeader() 46 | return self._isGroupHeader.Value 47 | end 48 | 49 | function CommandEntry:SetGroupVisible(isVisible: boolean) 50 | self._groupVisible.Value = isVisible 51 | end 52 | 53 | function CommandEntry:SetGroupName(name: string) 54 | self._groupName.Value = name 55 | end 56 | 57 | function CommandEntry:SetGroupIcon(icon: string) 58 | self._groupIcon.Value = icon 59 | end 60 | 61 | function CommandEntry:SetName(name: string) 62 | self._macroName.Value = name 63 | end 64 | 65 | function CommandEntry:SetDescription(description: string) 66 | self._macroDescription.Value = description 67 | end 68 | 69 | function CommandEntry:GetYPosition() 70 | return self._yPosition.Value 71 | end 72 | 73 | function CommandEntry:SetDefaultIndex(index: number) 74 | self._defaultIndex.Value = index 75 | end 76 | 77 | function CommandEntry:SetIsSelected(isSelected: boolean) 78 | self._model:SetIsChoosen(isSelected) 79 | self._model._isMouseOver.Value = isSelected 80 | end 81 | 82 | function CommandEntry:Render(props) 83 | local target = self._percentVisibleTarget:Observe() 84 | 85 | local percentHighlighted = Blend.Spring(self._model:ObservePercentHighlightedTarget(), 35, 0.7) 86 | local percentPressed = Blend.Spring(self._model:ObservePercentPressedTarget(), 35, 0.55) 87 | local percentSelected = Blend.AccelTween(self._model:ObservePercentChoosenTarget(), 400) 88 | 89 | local foregroundColor = Blend.Computed(percentSelected, function(percent) 90 | return Color3.fromRGB(150, 150, 150):Lerp(Color3.fromRGB(255, 255, 255), percent) 91 | end); 92 | 93 | local fzyConfig = props.FzyConfig 94 | local transparency = props.Transparency 95 | 96 | local groupCollapsed = props.GroupCollapsed 97 | 98 | self._maid:GiveTask(Blend.Computed(self._macroName, self._macroDescription, props.SearchQuery, function(name, description, searchQuery) 99 | if searchQuery == "" then 100 | self.SearchScore.Value = nil 101 | return 102 | end 103 | 104 | self.SearchScore.Value = Fzy.score(fzyConfig, searchQuery, string.lower(`{name} {description}`)) 105 | end):Subscribe()) 106 | 107 | self._maid:GiveTask(self._model:ObserveIsMouseOrTouchOver():Subscribe(function(isHovered) 108 | if isHovered then 109 | return 110 | end 111 | 112 | if not isHovered then 113 | if self._model:IsChoosen() then 114 | self._model._isMouseOver.Value = true 115 | else 116 | self._model._isMouseOver.Value = false 117 | end 118 | end 119 | end)) 120 | 121 | local verticalPadding = Blend.Computed(props.EntryHeight, function(height) 122 | return UDim.new(0, height == 45 and 14 or 7) 123 | end); 124 | 125 | return Blend.New "Frame" { 126 | Name = "CommandEntry"; 127 | BackgroundTransparency = 1; 128 | Parent = props.TargetParent; 129 | 130 | LayoutOrder = Blend.Computed(self._isGroupHeader, self.SearchScore, self._defaultIndex, function(isGroupHeader, score, defaultIndex) 131 | if isGroupHeader then 132 | return -1 133 | end 134 | 135 | return score and -(score * 10000) or defaultIndex 136 | end); 137 | 138 | Size = Blend.Computed(props.EntryHeight, function(height) 139 | return UDim2.new(1, 0, 0, height) 140 | end); 141 | 142 | Visible = Blend.Computed(self._isGroupHeader, groupCollapsed, props.SearchQuery, self.SearchScore, props.CustomResult, props.Enabled, function(isGroupEntry, isCollapsed, searchQuery, searchScore, customResult, isEnabled) 143 | if customResult then 144 | return isEnabled 145 | end 146 | 147 | if isGroupEntry then 148 | return searchQuery == "" 149 | else 150 | if searchQuery ~= "" and searchScore then 151 | return searchScore > 0 152 | else 153 | return not isCollapsed 154 | end 155 | end 156 | end); 157 | 158 | ZIndex = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 159 | return score and -(score * 10000) or defaultIndex 160 | end); 161 | 162 | [Blend.OnChange "AbsolutePosition"] = function(position) 163 | if not self:IsVisible() then 164 | return 165 | end 166 | 167 | self._yPosition.Value = math.round(position.Y) 168 | end; 169 | 170 | Blend.New "Frame" { 171 | Name = "wrapper"; 172 | BackgroundColor3 = Color3.fromRGB(50, 50, 50); 173 | Size = UDim2.fromScale(1, 1); 174 | 175 | BackgroundTransparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 176 | return 1 - percentHighlight + percent 177 | end); 178 | 179 | Blend.New "UIListLayout" { 180 | FillDirection = Enum.FillDirection.Horizontal; 181 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 182 | Padding = UDim.new(0, 5); 183 | VerticalAlignment = Enum.VerticalAlignment.Center; 184 | }; 185 | 186 | Blend.New "UIPadding" { 187 | PaddingBottom = verticalPadding; 188 | PaddingRight = UDim.new(0, 7); 189 | PaddingTop = verticalPadding; 190 | 191 | PaddingLeft = Blend.Computed(self._isGroupHeader, self._groupVisible, props.SearchQuery, function(isGroupEntry, groupVisible, searchQuery) 192 | if not isGroupEntry then 193 | if searchQuery ~= "" then 194 | return UDim.new(0, 7) 195 | else 196 | return UDim.new(0, not groupVisible and 30 or 7) 197 | end 198 | else 199 | return UDim.new(0, 7) 200 | end 201 | end); 202 | }; 203 | 204 | Blend.New "UIStroke" { 205 | Color = Color3.fromRGB(85, 85, 85); 206 | Transparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 207 | return math.clamp(1 - percentHighlight + percent, 0, 1) 208 | end); 209 | }; 210 | 211 | Blend.New "UICorner" { 212 | CornerRadius = UDim.new(0, 5); 213 | }; 214 | 215 | Blend.New "Frame" { 216 | Name = "group"; 217 | BackgroundTransparency = 1; 218 | LayoutOrder = 1; 219 | 220 | Size = Blend.Computed(self._isGroupHeader, props.SearchQuery, function(isGroupHeader) 221 | return isGroupHeader and UDim2.fromScale(0.5, 1) or UDim2.fromScale(0.15, 1) 222 | end); 223 | 224 | Visible = Blend.Computed(self._isGroupHeader, props.SearchQuery, function(isGroupHeader, searchQuery) 225 | if isGroupHeader then 226 | return true 227 | else 228 | return searchQuery ~= "" 229 | end 230 | end); 231 | 232 | Blend.New "UIListLayout" { 233 | FillDirection = Enum.FillDirection.Horizontal; 234 | Padding = UDim.new(0, 5); 235 | VerticalAlignment = Enum.VerticalAlignment.Center; 236 | }; 237 | 238 | Blend.New "ImageLabel" { 239 | Name = "groupIcon"; 240 | BackgroundTransparency = 1; 241 | Image = self._groupIcon; 242 | ImageColor3 = foregroundColor; 243 | ImageTransparency = transparency; 244 | LayoutOrder = 1; 245 | Size = UDim2.fromOffset(15, 15); 246 | }; 247 | 248 | Blend.New "TextLabel" { 249 | Name = "groupName"; 250 | AutomaticSize = Enum.AutomaticSize.X; 251 | BackgroundTransparency = 1; 252 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 253 | LayoutOrder = 2; 254 | Size = UDim2.fromOffset(0, 15); 255 | Text = self._groupName; 256 | TextColor3 = foregroundColor; 257 | TextSize = 15; 258 | TextTransparency = transparency; 259 | TextWrapped = true; 260 | TextXAlignment = Enum.TextXAlignment.Left; 261 | }; 262 | }; 263 | 264 | Blend.New "ImageLabel" { 265 | Name = "groupArrow"; 266 | BackgroundTransparency = 1; 267 | Image = "rbxassetid://6034818365"; 268 | ImageColor3 = foregroundColor; 269 | ImageTransparency = transparency; 270 | LayoutOrder = 2; 271 | Size = UDim2.fromScale(1, 1); 272 | 273 | Visible = Blend.Computed(self._isGroupHeader, props.SearchQuery, function(isGroupHeader, searchQuery) 274 | if isGroupHeader then 275 | return false 276 | else 277 | return searchQuery ~= "" 278 | end 279 | end); 280 | 281 | Blend.New "UIAspectRatioConstraint" { 282 | AspectRatio = 1; 283 | }; 284 | }; 285 | 286 | Blend.New "Frame" { 287 | Name = "container"; 288 | BackgroundTransparency = 1; 289 | LayoutOrder = 3; 290 | Position = UDim2.fromScale(0.081324, 0); 291 | Size = UDim2.fromScale(0.851873, 1); 292 | 293 | Visible = self._isGroupHeader:Observe():Pipe({ 294 | Rx.map(function(isGroupEntry) 295 | return not isGroupEntry 296 | end) 297 | }); 298 | 299 | Blend.New "UIListLayout" { 300 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 301 | VerticalAlignment = Enum.VerticalAlignment.Center; 302 | }; 303 | 304 | Blend.New "TextLabel" { 305 | Name = "commandName"; 306 | BackgroundTransparency = 1; 307 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 308 | LayoutOrder = 2; 309 | RichText = true; 310 | Size = UDim2.new(1, 0, 0, 15); 311 | TextColor3 = Color3.fromRGB(255, 255, 255); 312 | TextSize = 15; 313 | TextTransparency = transparency; 314 | TextWrapped = true; 315 | TextXAlignment = Enum.TextXAlignment.Left; 316 | 317 | Text = Blend.Computed(self._macroName, props.SearchQuery, function(macroName, searchQuery) 318 | if searchQuery ~= "" then 319 | return SearchUtils.getMatchedString(fzyConfig, macroName, searchQuery) 320 | else 321 | return macroName 322 | end 323 | end); 324 | }; 325 | 326 | Blend.New "TextLabel" { 327 | Name = "commandDescription"; 328 | BackgroundTransparency = 1; 329 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 330 | LayoutOrder = 2; 331 | RichText = true; 332 | Size = UDim2.new(1, 0, 0, 15); 333 | TextSize = 13; 334 | TextTransparency = transparency; 335 | TextWrapped = true; 336 | TextXAlignment = Enum.TextXAlignment.Left; 337 | 338 | TextColor3 = Blend.Computed(percentSelected, function(percent) 339 | return Color3.fromRGB(110, 110, 110):Lerp(Color3.fromRGB(255, 255, 255), percent) 340 | end); 341 | 342 | Text = Blend.Computed(self._macroDescription, props.SearchQuery, function(description, searchQuery) 343 | if searchQuery ~= "" then 344 | return SearchUtils.getMatchedString(fzyConfig, description, searchQuery) 345 | else 346 | return description 347 | end 348 | end); 349 | 350 | Visible = Blend.Computed(props.EntryHeight, function(height) 351 | return height == 45 352 | end); 353 | }; 354 | }; 355 | 356 | Blend.New "Frame" { 357 | Name = "collapsed"; 358 | BackgroundTransparency = 1; 359 | Size = UDim2.fromScale(1, 1); 360 | LayoutOrder = 4; 361 | 362 | Blend.New "UIAspectRatioConstraint" { 363 | AspectRatio = 1; 364 | }; 365 | 366 | Blend.New "ImageLabel" { 367 | Name = "collapseArrow"; 368 | BackgroundTransparency = 1; 369 | Image = "rbxassetid://6034818365"; 370 | ImageColor3 = foregroundColor; 371 | ImageTransparency = transparency; 372 | LayoutOrder = 3; 373 | Size = UDim2.fromScale(1, 1); 374 | Visible = self._isGroupHeader; 375 | 376 | Rotation = Blend.Computed(groupCollapsed, function(isCollapsed) 377 | return isCollapsed and 0 or 90 378 | end); 379 | }; 380 | }; 381 | }; 382 | 383 | Blend.New "Frame" { 384 | Name = "divider"; 385 | AnchorPoint = Vector2.new(0, 1); 386 | BackgroundColor3 = Color3.fromRGB(35, 35, 35); 387 | BackgroundTransparency = transparency; 388 | Position = UDim2.new(0, 0, 1, 2); 389 | Size = UDim2.new(1, 0, 0, 1); 390 | }; 391 | 392 | Blend.New "TextButton" { 393 | Name = "button"; 394 | BackgroundTransparency = 1; 395 | Size = UDim2.fromScale(1, 1); 396 | Visible = self:ObserveVisible(); 397 | ZIndex = 5; 398 | 399 | [Blend.OnEvent "Activated"] = function() 400 | self.Activated:Fire() 401 | end; 402 | 403 | [Blend.Instance] = function(button) 404 | self._model:SetButton(button) 405 | end; 406 | }; 407 | 408 | Blend.New "Frame" { 409 | Name = "shortcut"; 410 | AnchorPoint = Vector2.new(1, 0.5); 411 | AutomaticSize = Enum.AutomaticSize.X; 412 | BackgroundColor3 = Color3.fromRGB(50, 50, 50); 413 | BackgroundTransparency = transparency; 414 | LayoutOrder = 3; 415 | Position = UDim2.new(1, -7, 0.5, 0); 416 | Size = UDim2.fromOffset(0, 20); 417 | Visible = false; 418 | 419 | Blend.New "UIPadding" { 420 | PaddingBottom = UDim.new(0, 5); 421 | PaddingLeft = UDim.new(0, 10); 422 | PaddingRight = UDim.new(0, 10); 423 | PaddingTop = UDim.new(0, 5); 424 | }; 425 | 426 | Blend.New "UICorner" { 427 | CornerRadius = UDim.new(0, 5); 428 | }; 429 | 430 | Blend.New "UIStroke" { 431 | Color = Color3.fromRGB(35, 35, 35); 432 | Transparency = transparency; 433 | }; 434 | 435 | Blend.New "TextLabel" { 436 | Name = "label"; 437 | AnchorPoint = Vector2.new(0.5, 0.5); 438 | AutomaticSize = Enum.AutomaticSize.X; 439 | BackgroundTransparency = 1; 440 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 441 | Position = UDim2.fromScale(0.5, 0.5); 442 | Size = UDim2.fromOffset(0, 14); 443 | Text = "Space"; 444 | TextColor3 = Color3.fromRGB(110, 110, 110); 445 | TextTransparency = transparency; 446 | }; 447 | }; 448 | } 449 | end 450 | 451 | return CommandEntry -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/CommandGroup.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local BasicPane = require("BasicPane") 4 | local Blend = require("Blend") 5 | local CommandEntry = require("CommandEntry") 6 | local ObservableList = require("ObservableList") 7 | local Rx = require("Rx") 8 | local RxBrioUtils = require("RxBrioUtils") 9 | local Signal = require("Signal") 10 | local ValueObject = require("ValueObject") 11 | 12 | local CommandGroup = setmetatable({}, BasicPane) 13 | CommandGroup.ClassName = "CommandGroup" 14 | CommandGroup.__index = CommandGroup 15 | 16 | function CommandGroup.new() 17 | local self = setmetatable(BasicPane.new(), CommandGroup) 18 | 19 | self._entries = self._maid:Add(ObservableList.new()) 20 | self._groupCollapsed = self._maid:Add(ValueObject.new(false)) 21 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 22 | 23 | self.Activated = self._maid:Add(Signal.new()) 24 | self.LayoutOrder = self._maid:Add(ValueObject.new(0)) 25 | 26 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 27 | self._percentVisibleTarget.Value = isVisible and 1 or 0 28 | end)) 29 | 30 | return self 31 | end 32 | 33 | function CommandGroup:SetIsCollapsed(isCollapsed: boolean) 34 | self._groupCollapsed.Value = isCollapsed 35 | end 36 | 37 | function CommandGroup:SetHeader(name: string, icon: string) 38 | if not name or not icon then 39 | return 40 | end 41 | 42 | local entry = self._maid:Add(CommandEntry.new()) 43 | entry:SetIsGroupHeader(true) 44 | entry:SetGroupName(name) 45 | entry:SetGroupIcon(icon) 46 | 47 | self._maid:GiveTask(self._entries:Add(entry)) 48 | 49 | self._maid:GiveTask(entry.Activated:Connect(function() 50 | self._groupCollapsed.Value = not self._groupCollapsed.Value 51 | end)) 52 | end 53 | 54 | function CommandGroup:AddEntry(macroData) 55 | if not macroData then 56 | return 57 | end 58 | 59 | local entry = self._maid:Add(CommandEntry.new()) 60 | entry:SetName(macroData.Name) 61 | entry:SetDescription(macroData.Description) 62 | 63 | self._maid:GiveTask(self._entries:Add(entry)) 64 | 65 | return entry 66 | end 67 | 68 | function CommandGroup:GetEntries() 69 | return self._entries:GetList() 70 | end 71 | 72 | function CommandGroup:IsCollapsed() 73 | return self._groupCollapsed.Value 74 | end 75 | 76 | function CommandGroup:Render(props) 77 | local target = self._percentVisibleTarget:Observe() 78 | 79 | local percentVisible = Blend.Spring(target, 30, 0.7) 80 | local percentAlpha = Blend.AccelTween(target, target:Pipe({ 81 | Rx.map(function(value) 82 | return value == 1 and 400 or 1000 83 | end) 84 | })) 85 | 86 | self._maid:GiveTask(Blend.Computed(percentAlpha, function(percent) 87 | local entries = self._entries:GetList() 88 | local entryCount = math.max(1, #entries) 89 | 90 | for index, entry in entries do 91 | local progress = (index - 1) / entryCount + 1e-2 92 | entry:SetVisible(progress <= percent) 93 | end 94 | end):Subscribe()) 95 | 96 | local transparency = Blend.Computed(percentAlpha, function(percent) 97 | return 1 - percent 98 | end) 99 | 100 | local targetParent = self._maid:Add(Blend.State(nil)) 101 | 102 | self._maid:GiveTask(props.TargetParent:Subscribe(function(target) 103 | if target == nil then 104 | targetParent.Value = self._groupFrame 105 | else 106 | targetParent.Value = target 107 | end 108 | end)) 109 | 110 | return Blend.New "Frame" { 111 | Name = "CommandGroup"; 112 | AutomaticSize = Enum.AutomaticSize.Y; 113 | BackgroundTransparency = 1; 114 | LayoutOrder = self.LayoutOrder; 115 | Size = UDim2.fromScale(1, 0); 116 | 117 | Visible = Blend.Computed(props.TargetParent, props.CustomResult, props.Enabled, function(target, customResult, isEnabled) 118 | if customResult then 119 | return isEnabled 120 | elseif not target then 121 | return true 122 | end 123 | 124 | return false 125 | end); 126 | 127 | Blend.New "UIListLayout" { 128 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 129 | Padding = UDim.new(0, 3); 130 | VerticalAlignment = Enum.VerticalAlignment.Center; 131 | }; 132 | 133 | [Blend.Instance] = function(groupFrame) 134 | self._groupFrame = groupFrame 135 | end; 136 | 137 | self._entries:ObserveItemsBrio():Pipe({ 138 | RxBrioUtils.map(function(entry) 139 | return entry:Render({ 140 | CustomResult = props.CustomResult; 141 | Enabled = props.Enabled; 142 | EntryHeight = props.EntryHeight; 143 | FzyConfig = props.FzyConfig; 144 | GroupCollapsed = self._groupCollapsed; 145 | SearchQuery = props.SearchQuery; 146 | TargetParent = targetParent; 147 | Transparency = transparency; 148 | }) 149 | end); 150 | }); 151 | } 152 | end 153 | 154 | return CommandGroup -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/CommandPalette.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local ChangeHistoryService = game:GetService("ChangeHistoryService") 4 | local RunService = game:GetService("RunService") 5 | local TweenService = game:GetService("TweenService") 6 | local UserInputService = game:GetService("UserInputService") 7 | 8 | local BasicPane = require("BasicPane") 9 | local Blend = require("Blend") 10 | local ButtonHighlightModel = require("ButtonHighlightModel") 11 | local ColorEntry = require("ColorEntry") 12 | local ColorPickerPane = require("ColorPickerPane") 13 | local CommandGroup = require("CommandGroup") 14 | local FontData = require("FontData") 15 | local FontEntry = require("FontEntry") 16 | local FontStyleEntry = require("FontStyleEntry") 17 | local Fzy = require("Fzy") 18 | local LipsumUtils = require("LipsumUtils") 19 | local ObservableList = require("ObservableList") 20 | local ObservableSortedList = require("ObservableSortedList") 21 | local Rx = require("Rx") 22 | local RxBrioUtils = require("RxBrioUtils") 23 | local RxInstanceUtils = require("RxInstanceUtils") 24 | local SearchUtils = require("SearchUtils") 25 | local Signal = require("Signal") 26 | local ValueObject = require("ValueObject") 27 | 28 | local FZY_CONFIG = Fzy.createConfig() 29 | local TWEEN_INFO = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out) 30 | 31 | local CommandPalette = setmetatable({}, BasicPane) 32 | CommandPalette.ClassName = "CommandPalette" 33 | CommandPalette.__index = CommandPalette 34 | 35 | function CommandPalette.new() 36 | local self = setmetatable(BasicPane.new(), CommandPalette) 37 | 38 | self._groupMap = {} 39 | 40 | self._actionText = self._maid:Add(ValueObject.new("select a command...")) 41 | self._currentCustomResults = self._maid:Add(ValueObject.new(nil)) 42 | self._customResultsEnabled = self._maid:Add(ValueObject.new(false)) 43 | self._entries = self._maid:Add(ObservableList.new()) 44 | self._entriesOffset = self._maid:Add(ValueObject.new(0)) 45 | self._entryHeight = self._maid:Add(ValueObject.new(45)) 46 | self._fontPreviewText = self._maid:Add(ValueObject.new("")) 47 | self._inputFocused = self._maid:Add(ValueObject.new(false)) 48 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 49 | self._primaryIcon = self._maid:Add(ValueObject.new("rbxassetid://6031154871")) 50 | self._primaryModel = self._maid:Add(ButtonHighlightModel.new()) 51 | self._searchEntries = self._maid:Add(ObservableSortedList.new(true)) 52 | self._searchQuery = self._maid:Add(ValueObject.new("")) 53 | self._secondaryModel = self._maid:Add(ButtonHighlightModel.new()) 54 | self._selectedEntry = self._maid:Add(ValueObject.new(nil)) 55 | self._selectedEntryIndex = self._maid:Add(ValueObject.new(0)) 56 | self._selectedGroupEntryIndex = self._maid:Add(ValueObject.new(0)) 57 | self._tabbed = self._maid:Add(Signal.new()) 58 | self._targetEntryParent = self._maid:Add(ValueObject.new(nil)) 59 | self._targetFont = self._maid:Add(ValueObject.new(nil)) 60 | 61 | self.CustomResultActivated = self._maid:Add(Signal.new()) 62 | self.CustomResultsReset = self._maid:Add(Signal.new()) 63 | self.TargetProperty = self._maid:Add(ValueObject.new(nil)) 64 | self.TargetSelection = self._maid:Add(ValueObject.new(nil)) 65 | 66 | self:_handleInput() 67 | self:_setupSearch() 68 | 69 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 70 | if self._input then 71 | if isVisible then 72 | self:CaptureFocus() 73 | 74 | if #self._entries:GetList() > 0 then 75 | if self._searchQuery.Value ~= "" then 76 | self._selectedGroupEntryIndex.Value = 0 77 | self._selectedEntryIndex.Value = 1 78 | else 79 | self._selectedGroupEntryIndex.Value = 1 80 | self._selectedEntryIndex.Value = 0 81 | end 82 | end 83 | else 84 | self._input.Text = "" 85 | self.TargetSelection.Value = nil 86 | self:SetCustomResults(nil) 87 | end 88 | end 89 | 90 | self._primaryModel:SetInteractionEnabled(isVisible) 91 | self._secondaryModel:SetInteractionEnabled(isVisible) 92 | self._percentVisibleTarget.Value = isVisible and 1 or 0 93 | end)) 94 | 95 | return self 96 | end 97 | 98 | function CommandPalette:AddGroup(groupData) 99 | if not groupData then 100 | return 101 | end 102 | 103 | local groupEntry = self._maid:Add(CommandGroup.new()) 104 | groupEntry:SetHeader(groupData.Name, groupData.Icon) 105 | 106 | table.insert(self._groupMap, groupEntry) 107 | 108 | self._maid:GiveTask(self._entries:Add(groupEntry)) 109 | self._maid:GiveTask(groupEntry._entries:ObserveItemsBrio():Subscribe(function(entryBrio) 110 | if entryBrio:IsDead() then 111 | return 112 | end 113 | 114 | local maid, entry = entryBrio:ToMaidAndValue() 115 | if not entry then 116 | return 117 | end 118 | 119 | entry:SetGroupIcon(groupData.Icon) 120 | entry:SetGroupName(groupData.Name) 121 | 122 | maid:GiveTask(entry.SearchScore:ObserveBrio():Subscribe(function(scoreBrio) 123 | if scoreBrio:IsDead() then 124 | return 125 | end 126 | 127 | if self._customResultsEnabled.Value and not entry.CustomResultType then 128 | return 129 | end 130 | 131 | local score = scoreBrio:GetValue() 132 | if score == nil or score < 0 then 133 | return 134 | end 135 | 136 | scoreBrio:ToMaid():GiveTask(self._searchEntries:Add(entry, entry.SearchScore:Observe())) 137 | end)) 138 | end)) 139 | 140 | return groupEntry 141 | end 142 | 143 | function CommandPalette:SetCustomResults(customResults: string) 144 | if not customResults then 145 | self._actionText.Value = "select a command..." 146 | self._currentCustomResults.Value = nil 147 | self._customResultsEnabled.Value = false 148 | self._entriesOffset.Value = 0 149 | self._fontPreviewText.Value = "" 150 | self._primaryIcon.Value = "rbxassetid://6031154871" 151 | self._targetFont.Value = false 152 | self._primaryModel:SetIsChoosen(false) 153 | 154 | if self._lastSelectedEntryIndex then 155 | self._selectedEntryIndex.Value = self._lastSelectedEntryIndex 156 | self._lastSelectedEntryIndex = nil 157 | end 158 | 159 | if self:IsVisible() then 160 | task.defer(function() 161 | RunService.RenderStepped:Wait() 162 | self._input.Text = self._lastSearchQuery or "" 163 | self:CaptureFocus() 164 | end) 165 | end 166 | 167 | self.CustomResultsReset:Fire() 168 | 169 | return 170 | end 171 | 172 | self._lastSelectedEntryIndex = self._selectedEntryIndex.Value 173 | self._currentCustomResults.Value = customResults 174 | self._customResultsEnabled.Value = true 175 | self._input.Text = "" 176 | self._primaryIcon.Value = "rbxassetid://6031091000" 177 | self._primaryModel:SetIsChoosen(true) 178 | 179 | self:CaptureFocus() 180 | 181 | local selection = self.TargetSelection.Value 182 | 183 | if customResults == "Color" then 184 | if not self._colorEntries then 185 | self:_setupColorResults() 186 | end 187 | 188 | self._actionText.Value = "type any color (rgb, hex, color name)..." 189 | elseif customResults == "Font" then 190 | if not self._fontEntries or not self._fontStyleEntries then 191 | self:_setupFontResults() 192 | end 193 | 194 | self._actionText.Value = "type any font name..." 195 | 196 | if selection then 197 | local instance = selection[1] 198 | local text = "" 199 | 200 | if instance then 201 | if instance:IsA("TextLabel") or instance:IsA("TextButton") or instance:IsA("TextBox") then 202 | text = instance.Text 203 | end 204 | end 205 | 206 | if text == "" then 207 | text = LipsumUtils.sentence(7) 208 | end 209 | 210 | self._fontPreviewText.Value = string.sub(text, 0, 90) 211 | end 212 | elseif customResults == "FontStyle" then 213 | if not self._fontEntries or not self._fontStyleEntries then 214 | self:_setupFontResults() 215 | end 216 | 217 | self._actionText.Value = "type any font style..." 218 | 219 | if selection then 220 | local instance = selection[1] 221 | 222 | if instance then 223 | if instance:IsA("TextLabel") or instance:IsA("TextButton") or instance:IsA("TextBox") then 224 | self._targetFont.Value = instance.FontFace 225 | end 226 | end 227 | end 228 | end 229 | 230 | task.defer(function() 231 | self._selectedEntryIndex.Value = 0 232 | end) 233 | end 234 | 235 | function CommandPalette:_handleInput() 236 | self._maid:GiveTask(UserInputService.InputBegan:Connect(function(input, gpe) 237 | local keysPressed = UserInputService:GetKeysPressed() 238 | local upPressed, downPressed, ctrlPressed = false, false, false 239 | local shiftPressed = false 240 | local tabPressed = false 241 | 242 | if input.KeyCode == Enum.KeyCode.Up then 243 | upPressed = true 244 | elseif input.KeyCode == Enum.KeyCode.Down then 245 | downPressed = true 246 | end 247 | 248 | for _, inputObject in keysPressed do 249 | if inputObject.KeyCode == Enum.KeyCode.LeftControl or inputObject.KeyCode == Enum.KeyCode.RightControl then 250 | ctrlPressed = true 251 | elseif inputObject.KeyCode == Enum.KeyCode.LeftShift then 252 | shiftPressed = true 253 | elseif inputObject.KeyCode == Enum.KeyCode.Tab then 254 | tabPressed = true 255 | end 256 | end 257 | 258 | if tabPressed then 259 | if self._inputFocused.Value then 260 | self._tabbed:Fire() 261 | task.spawn(function() 262 | RunService.RenderStepped:Wait() 263 | local inputText = self._input.Text 264 | self._input.Text = string.sub(inputText, 0, #inputText - 1) 265 | end) 266 | end 267 | end 268 | 269 | if ctrlPressed then 270 | for _, inputObject in keysPressed do 271 | if inputObject.KeyCode == Enum.KeyCode.J then 272 | downPressed = true 273 | elseif inputObject.KeyCode == Enum.KeyCode.K then 274 | upPressed = true 275 | elseif inputObject.KeyCode == Enum.KeyCode.D then 276 | self._entryHeight.Value = self._entryHeight.Value == 45 and 30 or 45 277 | self._secondaryModel:SetKeyDown(true) 278 | 279 | self._maid:GiveTask(task.delay(0.1, function() 280 | self._secondaryModel:SetKeyDown(false) 281 | end)) 282 | end 283 | end 284 | end 285 | 286 | if input.KeyCode == Enum.KeyCode.Escape then 287 | if self._customResultsEnabled.Value then 288 | if self._input.Text == "" then 289 | if shiftPressed then 290 | self._input:ReleaseFocus() 291 | else 292 | self._primaryModel:SetKeyDown(true) 293 | self._maid:GiveTask(task.delay(0.1, function() 294 | self._primaryModel:SetKeyDown(false) 295 | end)) 296 | 297 | self:SetCustomResults(nil) 298 | end 299 | else 300 | if shiftPressed then 301 | self._input:ReleaseFocus() 302 | else 303 | self._input.Text = "" 304 | self:CaptureFocus() 305 | end 306 | end 307 | else 308 | if shiftPressed then 309 | self._input:ReleaseFocus() 310 | else 311 | if self._input.Text == "" then 312 | self:Hide() 313 | else 314 | self._input.Text = "" 315 | self:CaptureFocus() 316 | end 317 | end 318 | end 319 | elseif upPressed or downPressed then 320 | if self._searchQuery.Value ~= "" then 321 | self._selectedGroupEntryIndex.Value = 0 322 | 323 | local selectedEntryIndex = self._selectedEntryIndex.Value 324 | if upPressed and selectedEntryIndex - 1 > 0 then 325 | self._selectedEntryIndex.Value -= 1 326 | elseif downPressed and selectedEntryIndex + 1 <= #self._searchEntries:GetList() then 327 | self._selectedEntryIndex.Value += 1 328 | end 329 | 330 | return 331 | end 332 | 333 | local customResultsEnabled = self._customResultsEnabled.Value 334 | 335 | local selectedEntry = self._selectedEntryIndex.Value 336 | local selectedGroup = self._selectedGroupEntryIndex.Value 337 | local groupEntries = self._groupMap 338 | 339 | if not customResultsEnabled and selectedGroup == 0 and selectedEntry == 0 then 340 | if upPressed then 341 | -- self._selectedGroupEntryIndex.Value = #groupEntries 342 | else 343 | self._selectedGroupEntryIndex.Value += 1 344 | end 345 | return 346 | end 347 | 348 | local currentGroup = groupEntries[selectedGroup] 349 | if not currentGroup then 350 | return 351 | end 352 | 353 | local currentGroupEntries 354 | if not customResultsEnabled then 355 | currentGroupEntries = #currentGroup:GetEntries() - 1 356 | else 357 | local customResultType = self._currentCustomResults.Value 358 | if customResultType == "Color" then 359 | currentGroupEntries = #self._colorEntries - 1 360 | elseif customResultType == "Font" then 361 | currentGroupEntries = #self._fontEntries - 1 362 | elseif customResultType == "FontStyle" then 363 | currentGroupEntries = #self._fontStyleEntries - 1 364 | end 365 | end 366 | 367 | if upPressed then 368 | if not customResultsEnabled then 369 | if selectedEntry - 1 >= 0 and not currentGroup:IsCollapsed() then 370 | self._selectedEntryIndex.Value -= 1 371 | elseif selectedEntry == 0 then 372 | if selectedGroup - 1 <= 0 then 373 | -- self._selectedGroupEntryIndex.Value = #groupEntries 374 | return 375 | else 376 | self._selectedGroupEntryIndex.Value -= 1 377 | end 378 | 379 | local newGroup = groupEntries[self._selectedGroupEntryIndex.Value] 380 | if newGroup:IsCollapsed() then 381 | self._selectedEntryIndex.Value = 0 382 | else 383 | self._selectedEntryIndex.Value = #newGroup:GetEntries() - 1 384 | end 385 | end 386 | else 387 | if selectedEntry - 1 >= 0 then 388 | self._selectedEntryIndex.Value -= 1 389 | end 390 | end 391 | else 392 | if not customResultsEnabled then 393 | if selectedEntry + 1 <= currentGroupEntries and not currentGroup:IsCollapsed() then 394 | self._selectedEntryIndex.Value += 1 395 | elseif ((selectedEntry + 1 > currentGroupEntries) or currentGroup:IsCollapsed()) and selectedGroup + 1 <= #groupEntries then 396 | self._selectedEntryIndex.Value = 0 397 | self._selectedGroupEntryIndex.Value += 1 398 | end 399 | else 400 | if selectedEntry + 1 <= currentGroupEntries then 401 | self._selectedEntryIndex.Value += 1 402 | end 403 | end 404 | end 405 | end 406 | end)) 407 | end 408 | 409 | function CommandPalette:_setupSearch() 410 | self._maid:GiveTask(Rx.combineLatest({ 411 | CurrentEntry = self._selectedEntryIndex:Observe(); 412 | CurrentGroup = self._selectedGroupEntryIndex:Observe(); 413 | CustomResultType = self._currentCustomResults:Observe(); 414 | CustomResultsEnabled = self._customResultsEnabled:Observe(); 415 | SearchQuery = self._searchQuery:Observe(); 416 | }):Subscribe(function(data) 417 | if data.SearchQuery ~= "" then 418 | self._selectedEntry.Value = self._searchEntries:GetList()[data.CurrentEntry] 419 | else 420 | local currentEntries 421 | 422 | if not data.CustomResultsEnabled then 423 | if data.CurrentGroup == 0 then 424 | return 425 | end 426 | 427 | local groupEntries = self._entries:GetList() 428 | local currentGroup = groupEntries[data.CurrentGroup] 429 | 430 | currentEntries = currentGroup:GetEntries() 431 | else 432 | if data.CustomResultType == "Color" then 433 | currentEntries = self._colorEntries 434 | elseif data.CustomResultType == "Font" then 435 | currentEntries = self._fontEntries 436 | elseif data.CustomResultType == "FontStyle" then 437 | currentEntries = self._fontStyleEntries 438 | end 439 | end 440 | 441 | if not currentEntries then 442 | return 443 | end 444 | 445 | if data.CurrentEntry == 0 then 446 | self._selectedEntry.Value = currentEntries[1] 447 | else 448 | self._selectedEntry.Value = currentEntries[data.CurrentEntry + 1] 449 | end 450 | end 451 | end)) 452 | 453 | self._maid:GiveTask(self._selectedEntry:Observe():Subscribe(function(entry) 454 | if self._previousEntry then 455 | self._previousEntry:SetIsSelected(false) 456 | self._previousEntry = nil 457 | end 458 | 459 | if not entry then 460 | return 461 | end 462 | 463 | entry:SetIsSelected(true) 464 | self._previousEntry = entry 465 | end)) 466 | end 467 | 468 | function CommandPalette:_setupColorResults() 469 | if self._colorEntries then 470 | return 471 | end 472 | 473 | self._colorEntries = {} 474 | 475 | local sortedEntries = {} 476 | local colorPickerPane = self._maid:Add(ColorPickerPane.new()) 477 | 478 | self._colorPicker = colorPickerPane 479 | if self._bodyFrame then 480 | self._colorPicker:SetTargetParent(self._bodyFrame) 481 | end 482 | 483 | self._maid:GiveTask(colorPickerPane.TabReturned:Connect(function() 484 | self:CaptureFocus() 485 | end)) 486 | 487 | self._maid:GiveTask(self._currentCustomResults:ObserveBrio():Subscribe(function(customResultsBrio) 488 | if customResultsBrio:IsDead() then 489 | return 490 | end 491 | 492 | local maid, customResults = customResultsBrio:ToMaidAndValue() 493 | if customResults ~= "Color" then 494 | return 495 | end 496 | 497 | local initialSelection = self.TargetSelection.Value 498 | if not initialSelection or #initialSelection == 0 then 499 | return 500 | end 501 | 502 | local targetProperty = self.TargetProperty.Value 503 | if not targetProperty then 504 | return 505 | end 506 | 507 | if not initialSelection[1][targetProperty] then 508 | return 509 | end 510 | 511 | colorPickerPane:SetColor(initialSelection[1][targetProperty]) 512 | 513 | -- Undo/redo support 514 | maid:GiveTask(ChangeHistoryService.OnUndo:Connect(function() 515 | colorPickerPane:SetColor(initialSelection[1][targetProperty]) 516 | end)) 517 | 518 | maid:GiveTask(ChangeHistoryService.OnRedo:Connect(function() 519 | colorPickerPane:SetColor(initialSelection[1][targetProperty]) 520 | end)) 521 | 522 | maid:GiveTask(colorPickerPane:ObserveColor():Subscribe(function(newColor) 523 | if not newColor or initialSelection[1][targetProperty] == newColor then 524 | return 525 | end 526 | 527 | for _, instance in initialSelection do 528 | instance[targetProperty] = newColor 529 | end 530 | end)) 531 | 532 | -- Since we don't want to spam the undo stack as they're picking a 533 | -- color, we only want to fire the macro when the user stops dragging 534 | maid:GiveTask(colorPickerPane.ColorUpdated:Connect(function(newColor) 535 | self.CustomResultActivated:Fire("Color", true, newColor) 536 | end)) 537 | end)) 538 | 539 | local function createColorEntry(i) 540 | local brickColor = BrickColor.new(i) 541 | if brickColor.Name == "Medium stone grey" and i ~= 194 then 542 | return 543 | end 544 | 545 | local entry = self._maid:Add(ColorEntry.new()) 546 | entry:SetColorName(brickColor.Name) 547 | entry:SetColor(brickColor.Color) 548 | 549 | table.insert(sortedEntries, entry) 550 | 551 | self._maid:GiveTask(entry.Activated:Connect(function(shiftPressed) 552 | colorPickerPane:SetColor(brickColor.Color) 553 | self.CustomResultActivated:Fire("Color", shiftPressed, brickColor.Color) 554 | end)) 555 | end 556 | 557 | -- from https://create.roblox.com/docs/reference/engine/datatypes/BrickColor 558 | for i = 1, 50 do 559 | createColorEntry(i) 560 | end 561 | 562 | for i = 100, 365 do 563 | createColorEntry(i) 564 | end 565 | 566 | for i = 1001, 1032 do 567 | createColorEntry(i) 568 | end 569 | 570 | table.sort(sortedEntries, function(a, b) 571 | local aH, aS, aV = a.Color.Value:ToHSV() 572 | local bH, bS, bV = b.Color.Value:ToHSV() 573 | 574 | if aH ~= bH then 575 | return aH < bH 576 | elseif aS ~= bS then 577 | return aS < bS 578 | else 579 | return aV < bV 580 | end 581 | end) 582 | 583 | self._maid:GiveTask(self._entries:Add(colorPickerPane)) 584 | 585 | for index, entry in sortedEntries do 586 | entry:SetDefaultIndex(index) 587 | table.insert(self._colorEntries, entry) 588 | self._maid:GiveTask(self._entries:Add(entry)) 589 | 590 | self._maid:GiveTask(entry.SearchScore:ObserveBrio():Subscribe(function(scoreBrio) 591 | if scoreBrio:IsDead() then 592 | return 593 | end 594 | 595 | if not self._customResultsEnabled.Value then 596 | return 597 | elseif entry.CustomResultType ~= self._currentCustomResults.Value then 598 | return 599 | end 600 | 601 | local score = scoreBrio:GetValue() 602 | if score == nil or score < 0 then 603 | return 604 | end 605 | 606 | scoreBrio:ToMaid():GiveTask(self._searchEntries:Add(entry, entry.SearchScore:Observe())) 607 | end)) 608 | end 609 | 610 | self._maid:GiveTask(Blend.Computed(self._customResultsEnabled, self._currentCustomResults, function(isEnabled, resultsType) 611 | local isVisible = false 612 | if isEnabled and resultsType == "Color" then 613 | isVisible = true 614 | end 615 | 616 | colorPickerPane:SetVisible(isVisible) 617 | for _, entry in self._colorEntries do 618 | entry:SetVisible(isVisible) 619 | end 620 | end):Subscribe()) 621 | end 622 | 623 | function CommandPalette:_setupFontResults() 624 | if self._fontEntries then 625 | return 626 | end 627 | 628 | self._fontEntries = {} 629 | self._fontStyleEntries = {} 630 | 631 | local sortedFontEntries = {} 632 | local sortedFontStyleEntries = {} 633 | local styleEntryMap = {} 634 | 635 | for fontId, data in FontData do 636 | local newFont 637 | if typeof(fontId) == "number" then 638 | newFont = Font.fromId(fontId) 639 | elseif typeof(fontId) == "string" then 640 | newFont = Font.new(fontId) 641 | end 642 | 643 | local fontNameEntry = self._maid:Add(FontEntry.new()) 644 | fontNameEntry:SetFontName(data.Name) 645 | fontNameEntry:SetStyleCount(#data.Styles) 646 | fontNameEntry.Font.Value = newFont 647 | 648 | for _, style in data.Styles do 649 | local isItalic = string.find(style, "Italic") and true or false 650 | local styleEntry = styleEntryMap[style] 651 | 652 | if not styleEntry then 653 | styleEntry = self._maid:Add(FontStyleEntry.new()) 654 | styleEntry:SetIsItalic(isItalic) 655 | styleEntry:SetStyleName(style) 656 | 657 | table.insert(sortedFontStyleEntries, styleEntry) 658 | styleEntryMap[style] = styleEntry 659 | 660 | self._maid:GiveTask(styleEntry.Activated:Connect(function(shiftPressed) 661 | self.CustomResultActivated:Fire("FontStyle", shiftPressed, styleEntry:GetData()) 662 | end)) 663 | end 664 | 665 | styleEntry:SetFontCompatible(newFont) 666 | end 667 | 668 | self._maid:GiveTask(fontNameEntry.Activated:Connect(function(shiftPressed) 669 | self.CustomResultActivated:Fire("Font", shiftPressed, fontNameEntry.Font.Value) 670 | end)) 671 | 672 | table.insert(sortedFontEntries, fontNameEntry) 673 | end 674 | 675 | table.sort(sortedFontEntries, function(a, b) 676 | local aFontName = a:GetFontName() 677 | local bFontName = b:GetFontName() 678 | 679 | return aFontName < bFontName 680 | end) 681 | 682 | table.sort(sortedFontStyleEntries, function(a, b) 683 | local aFontWeight = a:GetFontWeight() 684 | local bFontWeight = b:GetFontWeight() 685 | 686 | return aFontWeight < bFontWeight 687 | end) 688 | 689 | for index, entry in sortedFontEntries do 690 | entry:SetDefaultIndex(index) 691 | table.insert(self._fontEntries, entry) 692 | self._maid:GiveTask(self._entries:Add(entry)) 693 | 694 | self._maid:GiveTask(entry.SearchScore:ObserveBrio():Subscribe(function(scoreBrio) 695 | if scoreBrio:IsDead() then 696 | return 697 | end 698 | 699 | if not self._customResultsEnabled.Value then 700 | return 701 | elseif entry.CustomResultType ~= self._currentCustomResults.Value then 702 | return 703 | end 704 | 705 | local score = scoreBrio:GetValue() 706 | if score == nil or score < 0 then 707 | return 708 | end 709 | 710 | scoreBrio:ToMaid():GiveTask(self._searchEntries:Add(entry, entry.SearchScore:Observe())) 711 | end)) 712 | end 713 | 714 | for index, entry in sortedFontStyleEntries do 715 | entry:SetDefaultIndex(index) 716 | table.insert(self._fontStyleEntries, entry) 717 | self._maid:GiveTask(self._entries:Add(entry)) 718 | 719 | self._maid:GiveTask(entry.SearchScore:ObserveBrio():Subscribe(function(scoreBrio) 720 | if scoreBrio:IsDead() then 721 | return 722 | end 723 | 724 | if not self._customResultsEnabled.Value then 725 | return 726 | elseif entry.CustomResultType ~= self._currentCustomResults.Value then 727 | return 728 | end 729 | 730 | local targetFont = self._targetFont.Value 731 | if not targetFont or not entry:IsFontCompatible(targetFont) then 732 | return 733 | end 734 | 735 | local score = scoreBrio:GetValue() 736 | if score == nil or score < 0 then 737 | return 738 | end 739 | 740 | scoreBrio:ToMaid():GiveTask(self._searchEntries:Add(entry, entry.SearchScore:Observe())) 741 | end)) 742 | end 743 | 744 | self._maid:GiveTask(Blend.Computed(self._customResultsEnabled, self._currentCustomResults, function(isEnabled, resultsType) 745 | local isVisible = false 746 | if isEnabled then 747 | isVisible = true 748 | end 749 | 750 | if resultsType == "Font" then 751 | for _, entry in self._fontEntries do 752 | entry:SetVisible(isVisible) 753 | end 754 | elseif resultsType == "FontStyle" then 755 | for _, entry in self._fontStyleEntries do 756 | entry:SetVisible(isVisible) 757 | end 758 | end 759 | end):Subscribe()) 760 | end 761 | 762 | function CommandPalette:CaptureFocus() 763 | if not self._input then 764 | return 765 | end 766 | 767 | task.defer(function() 768 | RunService.RenderStepped:Wait() 769 | self._input:CaptureFocus() 770 | end) 771 | end 772 | 773 | function CommandPalette:Render(props) 774 | local target = self._percentVisibleTarget:Observe() 775 | 776 | local percentVisible = Blend.Spring(target, 50, 0.9) 777 | local percentAlpha = Blend.AccelTween(target, target:Pipe({ 778 | Rx.map(function(value) 779 | return value == 1 and 400 or 2000 780 | end) 781 | })) 782 | 783 | self._maid:GiveTask(Blend.Computed(percentAlpha, function(percent) 784 | local entries = self._entries:GetList() 785 | local entryCount = math.max(1, #entries) 786 | 787 | for index, entry in entries do 788 | local progress = (index - 1) / entryCount + 1e-1 789 | entry:SetVisible(progress <= percent) 790 | end 791 | end):Subscribe()) 792 | 793 | local transparency = Blend.Computed(percentAlpha, function(percent) 794 | return 1 - percent 795 | end) 796 | 797 | local contentHeight = self._maid:Add(Blend.State(0)) 798 | local windowHeight = self._maid:Add(Blend.State(0)) 799 | 800 | local scrollThicknessTarget = Blend.Computed(contentHeight, windowHeight, function(content, window) 801 | if not self:IsVisible() then 802 | return 803 | end 804 | 805 | return content > window and 1 or 0 806 | end) 807 | 808 | local percentScrollThickness = Blend.AccelTween(scrollThicknessTarget, 500) 809 | 810 | local containerPercent = Blend.Spring(Blend.Computed(contentHeight, windowHeight, function(content, window) 811 | return math.min(content + 92, window) 812 | end), 30, 1); 813 | 814 | self._maid:GiveTask(Blend.Computed(self._selectedEntry, function(entry) 815 | if not self._entriesFrame then 816 | return 817 | end 818 | 819 | if entry then 820 | local canvasPosition = self._entriesFrame.CanvasPosition.Y 821 | local framePosition = self._entriesFrame.AbsolutePosition.Y 822 | local frameSize = self._entriesFrame.AbsoluteSize.Y 823 | 824 | local entryPosition = entry:GetYPosition() - framePosition 825 | local entrySize = self._entryHeight.Value 826 | 827 | local customResults = self._currentCustomResults.Value 828 | if customResults == "Font" or customResults == "FontStyle" then 829 | entrySize = entrySize == 45 and 60 or 40 830 | end 831 | 832 | local entryBottom = entryPosition + entrySize 833 | local newCanvasPosition = canvasPosition 834 | 835 | if entryBottom > frameSize then 836 | newCanvasPosition += entryBottom - frameSize + 1 837 | elseif entryPosition < framePosition then 838 | newCanvasPosition -= entryPosition 839 | end 840 | 841 | if newCanvasPosition ~= canvasPosition then 842 | TweenService:Create(self._entriesFrame, TWEEN_INFO, { 843 | CanvasPosition = Vector2.new(0, newCanvasPosition) 844 | }):Play() 845 | end 846 | end 847 | end):Subscribe()) 848 | 849 | local buttonVisible = Blend.Computed(percentVisible, function(percent) 850 | return percent > 0.01 851 | end) 852 | 853 | local primaryPercentHighlighted = Blend.AccelTween(self._primaryModel:ObservePercentHighlightedTarget(), 400) 854 | local primaryPercentPressed = Blend.Spring(self._primaryModel:ObservePercentPressedTarget(), 40, 0.8) 855 | local primaryPercentSelected = Blend.Spring(self._primaryModel:ObservePercentChoosenTarget(), 40, 0.8) 856 | local secondaryPercentHighlighted = Blend.AccelTween(self._secondaryModel:ObservePercentHighlightedTarget(), 400) 857 | local secondaryPercentPressed = Blend.Spring(self._secondaryModel:ObservePercentPressedTarget(), 40, 0.8) 858 | 859 | return Blend.New "Frame" { 860 | Name = "CommandPalette"; 861 | AnchorPoint = Vector2.new(0.5, 0.5); 862 | BackgroundTransparency = 1; 863 | Position = UDim2.fromScale(0.5, 0.5); 864 | Size = UDim2.fromScale(0.4, 0.75); 865 | 866 | [Blend.OnChange "AbsoluteSize"] = function(windowSize) 867 | windowHeight.Value = math.round(windowSize.Y) - 90 868 | end; 869 | 870 | Blend.New "UISizeConstraint" { 871 | MinSize = Vector2.new(650, 0); 872 | }; 873 | 874 | Blend.New "Frame" { 875 | Name = "wrapper"; 876 | AnchorPoint = Vector2.new(0.5, 0); 877 | BackgroundTransparency = 1; 878 | Position = UDim2.fromScale(0.5, 0); 879 | 880 | Size = Blend.Computed(containerPercent, contentHeight, windowHeight, function(percent, content, window) 881 | if content > window then 882 | return UDim2.fromScale(1, 1) 883 | end 884 | 885 | return UDim2.new(1, 0, 0, math.min(percent, window)); 886 | end); 887 | 888 | Blend.New "UIScale" { 889 | Scale = Blend.Computed(percentVisible, function(percent) 890 | if percent >= 0.98 then 891 | percent = 1 892 | end 893 | 894 | return 0.9 + (percent * 0.1) 895 | end); 896 | }; 897 | 898 | Blend.New "ImageLabel" { 899 | Name = "shadow"; 900 | AnchorPoint = Vector2.new(0.5, 0.5); 901 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 902 | BackgroundTransparency = 1; 903 | Image = "rbxassetid://6150493168"; 904 | ImageColor3 = Color3.fromRGB(0, 0, 0); 905 | Position = UDim2.fromScale(0.5, 0.5); 906 | ScaleType = Enum.ScaleType.Slice; 907 | Size = UDim2.new(1, 50, 1, 50); 908 | SliceCenter = Rect.new(Vector2.new(100, 100), Vector2.new(100, 100)); 909 | SliceScale = 0.4; 910 | ZIndex = -10; 911 | 912 | ImageTransparency = Blend.Computed(transparency, function(percent) 913 | return 0.9 + (percent * 0.1) 914 | end); 915 | }; 916 | 917 | Blend.New "Frame" { 918 | Name = "container"; 919 | BackgroundColor3 = Color3.fromRGB(30, 30, 30); 920 | BackgroundTransparency = transparency; 921 | Size = UDim2.fromScale(1, 1); 922 | 923 | Blend.New "UICorner" { 924 | CornerRadius = UDim.new(0, 10); 925 | }; 926 | 927 | Blend.New "UIStroke" { 928 | Color = Color3.fromRGB(40, 40, 40); 929 | Transparency = transparency; 930 | }; 931 | 932 | Blend.New "Frame" { 933 | Name = "search"; 934 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 935 | BackgroundTransparency = transparency; 936 | Size = UDim2.new(1, 0, 0, 40); 937 | 938 | Blend.New "UICorner" { 939 | CornerRadius = UDim.new(0, 10); 940 | }; 941 | 942 | Blend.New "Frame" { 943 | Name = "cover"; 944 | AnchorPoint = Vector2.new(0, 1); 945 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 946 | BackgroundTransparency = transparency; 947 | Position = UDim2.fromScale(0, 1); 948 | Size = UDim2.new(1, 0, 0, 10); 949 | ZIndex = -5; 950 | }; 951 | 952 | Blend.New "Frame" { 953 | Name = "container"; 954 | BackgroundTransparency = 1; 955 | Size = UDim2.fromScale(1, 1); 956 | 957 | Blend.New "UIPadding" { 958 | PaddingBottom = UDim.new(0, 10); 959 | PaddingLeft = UDim.new(0, 10); 960 | PaddingRight = UDim.new(0, 10); 961 | PaddingTop = UDim.new(0, 10); 962 | }; 963 | 964 | Blend.New "UIListLayout" { 965 | FillDirection = Enum.FillDirection.Horizontal; 966 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 967 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 968 | Padding = UDim.new(0, 5); 969 | VerticalAlignment = Enum.VerticalAlignment.Center; 970 | }; 971 | 972 | Blend.New "TextButton" { 973 | Name = "primaryAction"; 974 | AnchorPoint = Vector2.new(1, 0.5); 975 | BackgroundColor3 = Color3.fromRGB(40, 40, 40); 976 | Interactable = self._customResultsEnabled; 977 | LayoutOrder = 1; 978 | Position = UDim2.fromScale(1, 0); 979 | Size = UDim2.new(1, 5, 1, 5); 980 | Text = ""; 981 | Visible = buttonVisible; 982 | ZIndex = 5; 983 | 984 | BackgroundTransparency = Blend.Computed(transparency, primaryPercentHighlighted, primaryPercentSelected, function(percent, percentHighlight, percentSelected) 985 | return 1 - percentHighlight + percent - percentSelected 986 | end); 987 | 988 | [Blend.OnEvent "Activated"] = function() 989 | if self._customResultsEnabled.Value then 990 | self:SetCustomResults(nil) 991 | end 992 | end; 993 | 994 | [Blend.Instance] = function(button) 995 | self._primaryModel:SetButton(button) 996 | end; 997 | 998 | Blend.New "UIAspectRatioConstraint" { 999 | AspectRatio = 1; 1000 | }; 1001 | 1002 | Blend.New "UICorner" { 1003 | CornerRadius = UDim.new(0, 5); 1004 | }; 1005 | 1006 | Blend.New "ImageLabel" { 1007 | Name = "icon"; 1008 | AnchorPoint = Vector2.new(0.5, 0.5); 1009 | BackgroundTransparency = 1; 1010 | Image = self._primaryIcon; 1011 | ImageTransparency = transparency; 1012 | Position = UDim2.fromScale(0.5, 0.5); 1013 | Size = UDim2.fromScale(1, 1); 1014 | 1015 | ImageColor3 = Blend.Computed(primaryPercentHighlighted, function(percentHighlight) 1016 | return Color3.fromRGB(75, 75, 75):Lerp(Color3.fromRGB(150, 150, 150), percentHighlight) 1017 | end); 1018 | 1019 | Blend.New "UIScale" { 1020 | Scale = Blend.Computed(primaryPercentPressed, function(percent) 1021 | return 1 - (percent * 0.15) 1022 | end); 1023 | }; 1024 | }; 1025 | 1026 | Blend.New "UIPadding" { 1027 | PaddingBottom = UDim.new(0, 3); 1028 | PaddingLeft = UDim.new(0, 3); 1029 | PaddingRight = UDim.new(0, 3); 1030 | PaddingTop = UDim.new(0, 3); 1031 | }; 1032 | }; 1033 | Blend.New "TextBox" { 1034 | Name = "input"; 1035 | BackgroundTransparency = 1; 1036 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1037 | LayoutOrder = 2; 1038 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 1039 | PlaceholderText = self._actionText; 1040 | Position = UDim2.fromScale(0.04, 0); 1041 | Size = UDim2.fromScale(0.9, 1); 1042 | TextColor3 = Color3.fromRGB(220, 220, 220); 1043 | TextSize = 15; 1044 | TextTransparency = transparency; 1045 | TextXAlignment = Enum.TextXAlignment.Left; 1046 | Visible = buttonVisible; 1047 | 1048 | Blend.New "UITextSizeConstraint" { 1049 | MaxTextSize = 16; 1050 | }; 1051 | 1052 | [Blend.Instance] = function(input) 1053 | self._input = input 1054 | end; 1055 | 1056 | [Blend.OnChange "Text"] = function(text) 1057 | local safeText = string.gsub(text, "%s+", " ") 1058 | 1059 | if safeText == " " then 1060 | safeText = "" 1061 | end 1062 | 1063 | if self._customResultsEnabled.Value then 1064 | if not self._lastSearchQuery then 1065 | self._lastSearchQuery = self._searchQuery.Value 1066 | end 1067 | else 1068 | self._lastSearchQuery = nil 1069 | end 1070 | 1071 | self._searchQuery.Value = safeText 1072 | 1073 | if #self._entries:GetList() > 0 then 1074 | if self._searchQuery.Value ~= "" then 1075 | self._targetEntryParent.Value = self._entriesFrame 1076 | self._selectedGroupEntryIndex.Value = 0 1077 | self._selectedEntryIndex.Value = 1 1078 | else 1079 | self._targetEntryParent.Value = nil 1080 | self._selectedGroupEntryIndex.Value = 1 1081 | self._selectedEntryIndex.Value = 0 1082 | end 1083 | end 1084 | end; 1085 | 1086 | [Blend.OnEvent "Focused"] = function() 1087 | self._inputFocused.Value = true 1088 | end; 1089 | 1090 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 1091 | local selectedEntry = self._selectedEntry.Value 1092 | local refocusInput = false 1093 | local escapePressed, shiftPressed, tabPressed 1094 | 1095 | -- Hold shift to trigger without closing the palette 1096 | for _, inputObject in UserInputService:GetKeysPressed() do 1097 | if inputObject.KeyCode == Enum.KeyCode.LeftShift then 1098 | shiftPressed = true 1099 | elseif inputObject.KeyCode == Enum.KeyCode.Escape then 1100 | escapePressed = true 1101 | elseif inputObject.KeyCode == Enum.KeyCode.Tab then 1102 | tabPressed = true 1103 | end 1104 | end 1105 | 1106 | if tabPressed then 1107 | return 1108 | end 1109 | 1110 | if shiftPressed and not escapePressed then 1111 | refocusInput = true 1112 | end 1113 | 1114 | if enterPressed and selectedEntry then 1115 | if not selectedEntry.CustomResultType and selectedEntry:IsGroupHeader() then 1116 | local selectedGroup = self._selectedGroupEntryIndex.Value 1117 | local groupEntry = self._entries:GetList()[selectedGroup] 1118 | 1119 | if not groupEntry then 1120 | return 1121 | end 1122 | 1123 | groupEntry:SetIsCollapsed(not groupEntry:IsCollapsed()) 1124 | refocusInput = true 1125 | else 1126 | selectedEntry.Activated:Fire(shiftPressed) 1127 | end 1128 | elseif enterPressed then 1129 | if self._customResultsEnabled.Value then 1130 | local customResultsType = self._currentCustomResults.Value 1131 | if customResultsType == "Color" then 1132 | local currentText = self._input.Text 1133 | if SearchUtils.stringIsRGB(currentText) then 1134 | local _, r, g, b = SearchUtils.stringIsRGB(currentText) 1135 | local newColor = Color3.fromRGB(r, g, b) 1136 | if self._colorPicker then 1137 | self._colorPicker:SetColor(newColor) 1138 | end 1139 | self.CustomResultActivated:Fire("Color", shiftPressed, newColor) 1140 | elseif SearchUtils.stringIsHex(currentText) then 1141 | local _, hex = SearchUtils.stringIsHex(currentText) 1142 | local newColor = Color3.fromHex(hex) 1143 | if self._colorPicker then 1144 | self._colorPicker:SetColor(newColor) 1145 | end 1146 | self.CustomResultActivated:Fire("Color", shiftPressed, newColor) 1147 | end 1148 | end 1149 | end 1150 | end 1151 | 1152 | if refocusInput then 1153 | self:CaptureFocus() 1154 | else 1155 | self._inputFocused.Value = false 1156 | end 1157 | end; 1158 | }; 1159 | 1160 | Blend.New "TextButton" { 1161 | Name = "secondaryAction"; 1162 | AnchorPoint = Vector2.new(1, 0.5); 1163 | BackgroundColor3 = Color3.fromRGB(40, 40, 40); 1164 | LayoutOrder = 3; 1165 | Position = UDim2.fromScale(1, 0); 1166 | Size = UDim2.new(1, 5, 1, 5); 1167 | Text = ""; 1168 | Visible = buttonVisible; 1169 | ZIndex = 5; 1170 | 1171 | BackgroundTransparency = Blend.Computed(transparency, secondaryPercentHighlighted, function(percent, percentHighlight) 1172 | return 1 - percentHighlight + percent 1173 | end); 1174 | 1175 | [Blend.OnEvent "Activated"] = function() 1176 | self._entryHeight.Value = self._entryHeight.Value == 45 and 30 or 45 1177 | end; 1178 | 1179 | [Blend.Instance] = function(button) 1180 | self._secondaryModel:SetButton(button) 1181 | end; 1182 | 1183 | Blend.New "UIAspectRatioConstraint" { 1184 | AspectRatio = 1; 1185 | }; 1186 | 1187 | Blend.New "UICorner" { 1188 | CornerRadius = UDim.new(0, 5); 1189 | }; 1190 | 1191 | Blend.New "ImageLabel" { 1192 | Name = "icon"; 1193 | AnchorPoint = Vector2.new(0.5, 0.5); 1194 | BackgroundTransparency = 1; 1195 | Image = "rbxassetid://6022668888"; 1196 | ImageTransparency = transparency; 1197 | Position = UDim2.fromScale(0.5, 0.5); 1198 | Size = UDim2.fromScale(1, 1); 1199 | 1200 | ImageColor3 = Blend.Computed(secondaryPercentHighlighted, function(percentHighlight) 1201 | return Color3.fromRGB(75, 75, 75):Lerp(Color3.fromRGB(150, 150, 150), percentHighlight) 1202 | end); 1203 | 1204 | Blend.New "UIScale" { 1205 | Scale = Blend.Computed(secondaryPercentPressed, function(percent) 1206 | return 1 - (percent * 0.15) 1207 | end); 1208 | }; 1209 | }; 1210 | 1211 | Blend.New "UIPadding" { 1212 | PaddingBottom = UDim.new(0, 3); 1213 | PaddingLeft = UDim.new(0, 3); 1214 | PaddingRight = UDim.new(0, 3); 1215 | PaddingTop = UDim.new(0, 3); 1216 | }; 1217 | }; 1218 | }; 1219 | 1220 | Blend.New "TextButton" { 1221 | Name = "button"; 1222 | BackgroundTransparency = 1; 1223 | ZIndex = 5; 1224 | 1225 | Position = Blend.Computed(self._inputFocused, self._customResultsEnabled, function(isFocused, customResults) 1226 | if isFocused or customResults then 1227 | return UDim2.new(0, 35, 0, 0) 1228 | else 1229 | return UDim2.fromOffset(0, 0) 1230 | end 1231 | end); 1232 | 1233 | Size = Blend.Computed(self._inputFocused, self._customResultsEnabled, function(isFocused, customResults) 1234 | if isFocused then 1235 | return UDim2.new(1, -70, 1, 0) 1236 | else 1237 | return UDim2.new(1, -35, 1, 0) 1238 | end 1239 | end); 1240 | 1241 | Visible = Blend.Computed(self:ObserveVisible(), self._inputFocused, function(isVisible, isFocused) 1242 | if not isVisible then 1243 | return false 1244 | else 1245 | return not isFocused 1246 | end 1247 | end); 1248 | 1249 | [Blend.OnEvent "Activated"] = function() 1250 | self:CaptureFocus() 1251 | end; 1252 | }; 1253 | }; 1254 | 1255 | Blend.New "Frame" { 1256 | Name = "body"; 1257 | BackgroundTransparency = 1; 1258 | Position = UDim2.fromOffset(0, 40); 1259 | Size = UDim2.new(1, 0, 1, -80); 1260 | 1261 | Blend.New "UIPadding" { 1262 | PaddingBottom = UDim.new(0, 5); 1263 | PaddingLeft = UDim.new(0, 5); 1264 | PaddingRight = UDim.new(0, 5); 1265 | PaddingTop = UDim.new(0, 5); 1266 | }; 1267 | 1268 | [Blend.Instance] = function(frame) 1269 | self._bodyFrame = frame 1270 | end; 1271 | 1272 | Blend.New "ScrollingFrame" { 1273 | Name = "entries"; 1274 | Active = true; 1275 | BackgroundTransparency = 1; 1276 | ScrollBarImageColor3 = Color3.fromRGB(50, 50, 50); 1277 | ScrollBarImageTransparency = transparency; 1278 | ScrollingDirection = Enum.ScrollingDirection.Y; 1279 | ScrollingEnabled = self:ObserveVisible(); 1280 | VerticalScrollBarInset = Enum.ScrollBarInset.Always; 1281 | Visible = buttonVisible; 1282 | 1283 | CanvasSize = Blend.Computed(contentHeight, self._entriesOffset, function(contentHeight, offset) 1284 | return UDim2.fromOffset(0, contentHeight + 2 - offset); 1285 | end); 1286 | 1287 | Position = Blend.Computed(self._entriesOffset, function(offset) 1288 | return UDim2.fromOffset(0, offset) 1289 | end); 1290 | 1291 | Size = Blend.Computed(self._entriesOffset, function(offset) 1292 | return UDim2.new(1, 0, 1, -offset) 1293 | end); 1294 | 1295 | ScrollBarThickness = Blend.Computed(percentScrollThickness, function(percent) 1296 | return 10 * percent 1297 | end); 1298 | 1299 | [Blend.Instance] = function(scrollingFrame) 1300 | self._entriesFrame = scrollingFrame 1301 | end; 1302 | 1303 | Blend.New "UIPadding" { 1304 | PaddingBottom = UDim.new(0, 4); 1305 | PaddingLeft = UDim.new(0, 1); 1306 | PaddingTop = UDim.new(0, 1); 1307 | 1308 | PaddingRight = Blend.Computed(contentHeight, windowHeight, function(content, window) 1309 | return UDim.new(0, content > window and 5 or 1) 1310 | end); 1311 | }; 1312 | 1313 | Blend.New "UIListLayout" { 1314 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 1315 | 1316 | [Blend.OnChange "AbsoluteContentSize"] = function(size) 1317 | if not self:IsVisible() then 1318 | return 1319 | end 1320 | 1321 | contentHeight.Value = math.round(size.Y) + self._entriesOffset.Value 1322 | end; 1323 | }; 1324 | 1325 | self._entries:ObserveItemsBrio():Pipe({ 1326 | RxBrioUtils.map(function(groupEntry) 1327 | return groupEntry:Render({ 1328 | CustomResult = self._currentCustomResults; 1329 | EntryHeight = self._entryHeight; 1330 | FontPreviewText = self._fontPreviewText; 1331 | FzyConfig = FZY_CONFIG; 1332 | SearchQuery = self._searchQuery; 1333 | Tabbed = self._tabbed; 1334 | Transparency = transparency; 1335 | TargetFont = self._targetFont; 1336 | 1337 | Enabled = Blend.Computed(self._customResultsEnabled, self._currentCustomResults, function(isEnabled, customResults) 1338 | if customResults and groupEntry.CustomResultType == customResults then 1339 | return isEnabled 1340 | else 1341 | return not isEnabled 1342 | end 1343 | end); 1344 | 1345 | TargetParent = Blend.Computed(self._targetEntryParent, self._customResultsEnabled, self._currentCustomResults, function(targetParent, isEnabled, customResultType) 1346 | if groupEntry.CustomResultType then 1347 | if isEnabled then 1348 | if customResultType == groupEntry.CustomResultType then 1349 | if groupEntry.Pinned then 1350 | self._entriesOffset.Value = groupEntry.Height or 0 1351 | groupEntry:SetTargetParent(self._bodyFrame) 1352 | return self._bodyFrame 1353 | else 1354 | return self._entriesFrame 1355 | end 1356 | else 1357 | return nil 1358 | end 1359 | else 1360 | return nil 1361 | end 1362 | else 1363 | return not isEnabled and targetParent or nil 1364 | end 1365 | end); 1366 | }) 1367 | end); 1368 | }); 1369 | }; 1370 | }; 1371 | 1372 | Blend.New "Frame" { 1373 | Name = "footer"; 1374 | AnchorPoint = Vector2.new(0, 1); 1375 | BackgroundTransparency = 1; 1376 | Position = UDim2.fromScale(0, 1); 1377 | Size = UDim2.new(1, 0, 0, 40); 1378 | 1379 | Blend.New "Frame" { 1380 | Name = "container"; 1381 | BackgroundTransparency = 1; 1382 | Size = UDim2.fromScale(1, 1); 1383 | 1384 | Blend.New "UIPadding" { 1385 | PaddingBottom = UDim.new(0, 10); 1386 | PaddingLeft = UDim.new(0, 5); 1387 | PaddingRight = UDim.new(0, 5); 1388 | PaddingTop = UDim.new(0, 10); 1389 | }; 1390 | 1391 | Blend.New "UIListLayout" { 1392 | FillDirection = Enum.FillDirection.Horizontal; 1393 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 1394 | Padding = UDim.new(0, 5); 1395 | VerticalAlignment = Enum.VerticalAlignment.Center; 1396 | }; 1397 | 1398 | Blend.New "Frame" { 1399 | Name = "shortcut"; 1400 | AutomaticSize = Enum.AutomaticSize.X; 1401 | BackgroundColor3 = Color3.fromRGB(25, 25, 25); 1402 | BackgroundTransparency = transparency; 1403 | LayoutOrder = 1; 1404 | Size = UDim2.fromScale(0, 1); 1405 | 1406 | Blend.New "UIPadding" { 1407 | PaddingBottom = UDim.new(0, 5); 1408 | PaddingLeft = UDim.new(0, 5); 1409 | PaddingRight = UDim.new(0, 5); 1410 | PaddingTop = UDim.new(0, 5); 1411 | }; 1412 | 1413 | Blend.New "UICorner" { 1414 | CornerRadius = UDim.new(0, 5); 1415 | }; 1416 | 1417 | Blend.New "UIStroke" { 1418 | Color = Color3.fromRGB(35, 35, 35); 1419 | Transparency = transparency; 1420 | }; 1421 | 1422 | Blend.New "TextLabel" { 1423 | Name = "label"; 1424 | AutomaticSize = Enum.AutomaticSize.X; 1425 | BackgroundTransparency = 1; 1426 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1427 | Size = UDim2.fromScale(0, 1); 1428 | Text = "Up"; 1429 | TextColor3 = Color3.fromRGB(90, 90, 90); 1430 | TextTransparency = transparency; 1431 | }; 1432 | }; 1433 | 1434 | Blend.New "Frame" { 1435 | Name = "shortcut"; 1436 | AutomaticSize = Enum.AutomaticSize.X; 1437 | BackgroundColor3 = Color3.fromRGB(25, 25, 25); 1438 | BackgroundTransparency = transparency; 1439 | LayoutOrder = 2; 1440 | Size = UDim2.fromScale(0, 1); 1441 | 1442 | Blend.New "UIPadding" { 1443 | PaddingBottom = UDim.new(0, 5); 1444 | PaddingLeft = UDim.new(0, 5); 1445 | PaddingRight = UDim.new(0, 5); 1446 | PaddingTop = UDim.new(0, 5); 1447 | }; 1448 | 1449 | Blend.New "UICorner" { 1450 | CornerRadius = UDim.new(0, 5); 1451 | }; 1452 | 1453 | Blend.New "UIStroke" { 1454 | Color = Color3.fromRGB(35, 35, 35); 1455 | Transparency = transparency; 1456 | }; 1457 | 1458 | Blend.New "TextLabel" { 1459 | Name = "label"; 1460 | AutomaticSize = Enum.AutomaticSize.X; 1461 | BackgroundTransparency = 1; 1462 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1463 | Size = UDim2.fromScale(0, 1); 1464 | Text = "Down"; 1465 | TextColor3 = Color3.fromRGB(90, 90, 90); 1466 | TextTransparency = transparency; 1467 | }; 1468 | }; 1469 | 1470 | Blend.New "TextLabel" { 1471 | Name = "label"; 1472 | AutomaticSize = Enum.AutomaticSize.X; 1473 | BackgroundTransparency = 1; 1474 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1475 | LayoutOrder = 3; 1476 | Size = UDim2.fromScale(0, 1); 1477 | Text = "to navigate"; 1478 | TextColor3 = Color3.fromRGB(110, 110, 110); 1479 | TextTransparency = transparency; 1480 | }; 1481 | 1482 | Blend.New "Frame" { 1483 | Name = "buffer"; 1484 | BackgroundTransparency = 1; 1485 | LayoutOrder = 4; 1486 | Size = UDim2.new(0, 5, 1, 0); 1487 | }; 1488 | 1489 | Blend.New "Frame" { 1490 | Name = "shortcut"; 1491 | AutomaticSize = Enum.AutomaticSize.X; 1492 | BackgroundColor3 = Color3.fromRGB(25, 25, 25); 1493 | BackgroundTransparency = transparency; 1494 | LayoutOrder = 5; 1495 | Size = UDim2.fromScale(0, 1); 1496 | 1497 | Blend.New "UIPadding" { 1498 | PaddingBottom = UDim.new(0, 5); 1499 | PaddingLeft = UDim.new(0, 5); 1500 | PaddingRight = UDim.new(0, 5); 1501 | PaddingTop = UDim.new(0, 5); 1502 | }; 1503 | 1504 | Blend.New "UICorner" { 1505 | CornerRadius = UDim.new(0, 5); 1506 | }; 1507 | 1508 | Blend.New "UIStroke" { 1509 | Color = Color3.fromRGB(35, 35, 35); 1510 | Transparency = transparency; 1511 | }; 1512 | 1513 | Blend.New "TextLabel" { 1514 | Name = "label"; 1515 | AutomaticSize = Enum.AutomaticSize.X; 1516 | BackgroundTransparency = 1; 1517 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1518 | Size = UDim2.fromScale(0, 1); 1519 | Text = "Enter"; 1520 | TextColor3 = Color3.fromRGB(90, 90, 90); 1521 | TextTransparency = transparency; 1522 | }; 1523 | }; 1524 | 1525 | Blend.New "TextLabel" { 1526 | Name = "label"; 1527 | AutomaticSize = Enum.AutomaticSize.X; 1528 | BackgroundTransparency = 1; 1529 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1530 | LayoutOrder = 6; 1531 | Size = UDim2.fromScale(0, 1); 1532 | Text = "to use"; 1533 | TextColor3 = Color3.fromRGB(110, 110, 110); 1534 | TextTransparency = transparency; 1535 | }; 1536 | 1537 | Blend.New "Frame" { 1538 | Name = "buffer"; 1539 | BackgroundTransparency = 1; 1540 | LayoutOrder = 7; 1541 | Size = UDim2.new(0, 5, 1, 0); 1542 | }; 1543 | 1544 | Blend.New "Frame" { 1545 | Name = "shortcut"; 1546 | AutomaticSize = Enum.AutomaticSize.X; 1547 | BackgroundColor3 = Color3.fromRGB(25, 25, 25); 1548 | BackgroundTransparency = transparency; 1549 | LayoutOrder = 8; 1550 | Size = UDim2.fromScale(0, 1); 1551 | 1552 | Blend.New "UIPadding" { 1553 | PaddingBottom = UDim.new(0, 5); 1554 | PaddingLeft = UDim.new(0, 5); 1555 | PaddingRight = UDim.new(0, 5); 1556 | PaddingTop = UDim.new(0, 5); 1557 | }; 1558 | 1559 | Blend.New "UICorner" { 1560 | CornerRadius = UDim.new(0, 5); 1561 | }; 1562 | 1563 | Blend.New "UIStroke" { 1564 | Color = Color3.fromRGB(35, 35, 35); 1565 | Transparency = transparency; 1566 | }; 1567 | 1568 | Blend.New "TextLabel" { 1569 | Name = "label"; 1570 | AutomaticSize = Enum.AutomaticSize.X; 1571 | BackgroundTransparency = 1; 1572 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1573 | Size = UDim2.fromScale(0, 1); 1574 | Text = "Escape"; 1575 | TextColor3 = Color3.fromRGB(90, 90, 90); 1576 | TextTransparency = transparency; 1577 | }; 1578 | }; 1579 | 1580 | Blend.New "TextLabel" { 1581 | Name = "label"; 1582 | AutomaticSize = Enum.AutomaticSize.X; 1583 | BackgroundTransparency = 1; 1584 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 1585 | LayoutOrder = 9; 1586 | Size = UDim2.fromScale(0, 1); 1587 | Text = "to dismiss"; 1588 | TextColor3 = Color3.fromRGB(110, 110, 110); 1589 | TextTransparency = transparency; 1590 | }; 1591 | }; 1592 | 1593 | Blend.New "Frame" { 1594 | Name = "divider"; 1595 | BackgroundColor3 = Color3.fromRGB(40, 40, 40); 1596 | BackgroundTransparency = transparency; 1597 | Size = UDim2.new(1, 0, 0, 1); 1598 | }; 1599 | }; 1600 | }; 1601 | }; 1602 | } 1603 | end 1604 | 1605 | return CommandPalette -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/Input/Color/ColorEntry.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local UserInputService = game:GetService("UserInputService") 4 | 5 | local BasicPane = require("BasicPane") 6 | local Blend = require("Blend") 7 | local ButtonHighlightModel = require("ButtonHighlightModel") 8 | local Fzy = require("Fzy") 9 | local SearchUtils = require("SearchUtils") 10 | local Signal = require("Signal") 11 | local ValueObject = require("ValueObject") 12 | 13 | local ColorEntry = setmetatable({}, BasicPane) 14 | ColorEntry.ClassName = "ColorEntry" 15 | ColorEntry.CustomResultType = "Color" 16 | ColorEntry.__index = ColorEntry 17 | 18 | function ColorEntry.new() 19 | local self = setmetatable(BasicPane.new(), ColorEntry) 20 | 21 | self._colorName = self._maid:Add(ValueObject.new("")) 22 | self._defaultIndex = self._maid:Add(ValueObject.new(0)) 23 | self._model = self._maid:Add(ButtonHighlightModel.new()) 24 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 25 | self._yPosition = self._maid:Add(ValueObject.new(0)) 26 | 27 | self.Activated = self._maid:Add(Signal.new()) 28 | self.Color = self._maid:Add(ValueObject.new(Color3.new(1, 1, 1))) 29 | self.SearchScore = self._maid:Add(ValueObject.new(0)) 30 | 31 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 32 | self._model:SetInteractionEnabled(isVisible) 33 | self._percentVisibleTarget.Value = isVisible and 1 or 0 34 | end)) 35 | 36 | return self 37 | end 38 | 39 | function ColorEntry:SetColorName(colorName: string) 40 | self._colorName.Value = colorName 41 | end 42 | 43 | function ColorEntry:SetColor(color: Color3) 44 | self.Color.Value = color 45 | end 46 | 47 | function ColorEntry:GetYPosition() 48 | return self._yPosition.Value 49 | end 50 | 51 | function ColorEntry:SetDefaultIndex(index: number) 52 | self._defaultIndex.Value = index 53 | end 54 | 55 | function ColorEntry:SetIsSelected(isSelected: boolean) 56 | self._model:SetIsChoosen(isSelected) 57 | self._model._isMouseOver.Value = isSelected 58 | end 59 | 60 | function ColorEntry:GetData() 61 | return self.Color.Value 62 | end 63 | 64 | function ColorEntry:Render(props) 65 | local target = self._percentVisibleTarget:Observe() 66 | 67 | local percentHighlighted = Blend.Spring(self._model:ObservePercentHighlightedTarget(), 35, 0.7) 68 | local percentPressed = Blend.Spring(self._model:ObservePercentPressedTarget(), 35, 0.55) 69 | local percentSelected = Blend.AccelTween(self._model:ObservePercentChoosenTarget(), 400) 70 | 71 | local foregroundColor = Blend.Computed(percentSelected, function(percent) 72 | return Color3.fromRGB(150, 150, 150):Lerp(Color3.fromRGB(255, 255, 255), percent) 73 | end); 74 | 75 | local fzyConfig = props.FzyConfig 76 | local transparency = props.Transparency 77 | 78 | self._maid:GiveTask(Blend.Computed(self._colorName, props.SearchQuery, function(colorName, searchQuery) 79 | if searchQuery == "" then 80 | self.SearchScore.Value = nil 81 | return 82 | end 83 | 84 | self.SearchScore.Value = Fzy.score(fzyConfig, searchQuery, string.lower(colorName)) 85 | end):Subscribe()) 86 | 87 | local verticalPadding = Blend.Computed(props.EntryHeight, function(height) 88 | return UDim.new(0, height == 45 and 14 or 10) 89 | end); 90 | 91 | return Blend.New "Frame" { 92 | Name = "ColorEntry"; 93 | BackgroundTransparency = 1; 94 | Parent = props.TargetParent; 95 | 96 | LayoutOrder = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 97 | return score and -(score * 10000) or defaultIndex 98 | end); 99 | 100 | Size = Blend.Computed(props.EntryHeight, function(height) 101 | return UDim2.new(1, 0, 0, height) 102 | end); 103 | 104 | Visible = Blend.Computed(props.CustomResult, props.Enabled, props.SearchQuery, self.SearchScore, function(customResult, isEnabled, searchQuery, searchScore) 105 | if not customResult then 106 | return false 107 | elseif isEnabled then 108 | if searchQuery ~= "" and searchScore then 109 | return searchScore > 0 110 | else 111 | return true 112 | end 113 | end 114 | 115 | return false 116 | end); 117 | 118 | ZIndex = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 119 | return score and -(score * 10000) or defaultIndex 120 | end); 121 | 122 | [Blend.OnChange "AbsolutePosition"] = function(position) 123 | if not self:IsVisible() then 124 | return 125 | end 126 | 127 | self._yPosition.Value = math.round(position.Y) 128 | end; 129 | 130 | Blend.New "Frame" { 131 | Name = "wrapper"; 132 | BackgroundColor3 = Color3.fromRGB(50, 50, 50); 133 | Size = UDim2.fromScale(1, 1); 134 | 135 | BackgroundTransparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 136 | return 1 - percentHighlight + percent 137 | end); 138 | 139 | Blend.New "UIListLayout" { 140 | FillDirection = Enum.FillDirection.Horizontal; 141 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 142 | Padding = UDim.new(0, 10); 143 | VerticalAlignment = Enum.VerticalAlignment.Center; 144 | }; 145 | 146 | Blend.New "UIPadding" { 147 | PaddingBottom = verticalPadding; 148 | PaddingLeft = UDim.new(0, 7); 149 | PaddingRight = UDim.new(0, 7); 150 | PaddingTop = verticalPadding; 151 | }; 152 | 153 | Blend.New "UIStroke" { 154 | Color = Color3.fromRGB(85, 85, 85); 155 | Transparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 156 | return math.clamp(1 - percentHighlight + percent, 0, 1) 157 | end); 158 | }; 159 | 160 | Blend.New "UICorner" { 161 | CornerRadius = UDim.new(0, 5); 162 | }; 163 | 164 | Blend.New "Frame" { 165 | Name = "colorPreview"; 166 | BackgroundColor3 = self.Color; 167 | BackgroundTransparency = transparency; 168 | Size = UDim2.fromScale(1.5, 1.5); 169 | 170 | Blend.New "UIAspectRatioConstraint" { 171 | AspectRatio = 1; 172 | }; 173 | 174 | Blend.New "UICorner" { 175 | CornerRadius = UDim.new(0, 5); 176 | }; 177 | }; 178 | 179 | Blend.New "Frame" { 180 | Name = "container"; 181 | BackgroundTransparency = 1; 182 | LayoutOrder = 3; 183 | Size = UDim2.fromScale(0.85, 1); 184 | 185 | Blend.New "TextLabel" { 186 | Name = "colorName"; 187 | AnchorPoint = Vector2.new(0, 0.5); 188 | BackgroundTransparency = 1; 189 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 190 | LayoutOrder = 2; 191 | Position = UDim2.fromScale(0, 0.5); 192 | RichText = true; 193 | Size = UDim2.new(1, 0, 0, 15); 194 | TextColor3 = Color3.fromRGB(255, 255, 255); 195 | TextSize = 15; 196 | TextTransparency = transparency; 197 | TextWrapped = true; 198 | TextXAlignment = Enum.TextXAlignment.Left; 199 | 200 | Text = Blend.Computed(self._colorName, props.SearchQuery, function(colorName, searchQuery) 201 | if searchQuery ~= "" then 202 | return SearchUtils.getMatchedString(fzyConfig, colorName, searchQuery) 203 | else 204 | return colorName 205 | end 206 | end); 207 | }; 208 | 209 | Blend.New "TextLabel" { 210 | Name = "colorValue"; 211 | AnchorPoint = Vector2.new(1, 0.5); 212 | BackgroundTransparency = 1; 213 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 214 | LayoutOrder = 2; 215 | Position = UDim2.fromScale(1, 0.5); 216 | Size = UDim2.new(1, 0, 0, 15); 217 | TextSize = 13; 218 | TextTransparency = transparency; 219 | TextWrapped = true; 220 | TextXAlignment = Enum.TextXAlignment.Right; 221 | 222 | Text = Blend.Computed(self.Color, function(color) 223 | if not color then 224 | return "" 225 | end 226 | 227 | return `{math.round(color.R * 255)}, {math.round(color.G * 255)}, {math.round(color.B * 255)}` 228 | end); 229 | 230 | TextColor3 = Blend.Computed(percentSelected, function(percent) 231 | return Color3.fromRGB(110, 110, 110):Lerp(Color3.fromRGB(255, 255, 255), percent) 232 | end); 233 | }; 234 | }; 235 | }; 236 | 237 | Blend.New "Frame" { 238 | Name = "divider"; 239 | AnchorPoint = Vector2.new(0, 1); 240 | BackgroundColor3 = Color3.fromRGB(35, 35, 35); 241 | BackgroundTransparency = transparency; 242 | Position = UDim2.new(0, 0, 1, 2); 243 | Size = UDim2.new(1, 0, 0, 1); 244 | }; 245 | 246 | Blend.New "TextButton" { 247 | Name = "button"; 248 | BackgroundTransparency = 1; 249 | Size = UDim2.fromScale(1, 1); 250 | Visible = props.Enabled; 251 | ZIndex = 5; 252 | 253 | [Blend.OnEvent "Activated"] = function() 254 | local shiftPressed = false 255 | for _, inputObject in UserInputService:GetKeysPressed() do 256 | if inputObject.KeyCode == Enum.KeyCode.LeftShift then 257 | shiftPressed = true 258 | break 259 | end 260 | end 261 | 262 | self.Activated:Fire(shiftPressed) 263 | end; 264 | 265 | [Blend.Instance] = function(button) 266 | self._model:SetButton(button) 267 | end; 268 | }; 269 | } 270 | end 271 | 272 | return ColorEntry -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/Input/Color/ColorPickerPane.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local RunService = game:GetService("RunService") 4 | local TweenService = game:GetService("TweenService") 5 | local UserInputService = game:GetService("UserInputService") 6 | 7 | local BasicPane = require("BasicPane") 8 | local Blend = require("Blend") 9 | local Rx = require("Rx") 10 | local SearchUtils = require("SearchUtils") 11 | local Signal = require("Signal") 12 | local SpringObject = require("SpringObject") 13 | local ValueObject = require("ValueObject") 14 | 15 | local TWEEN_INFO = TweenInfo.new(0.2, Enum.EasingStyle.Quart, Enum.EasingDirection.Out) 16 | 17 | local ColorPickerPane = setmetatable({}, BasicPane) 18 | ColorPickerPane.ClassName = "ColorPickerPane" 19 | ColorPickerPane.CustomResultType = "Color" 20 | ColorPickerPane.Height = 160 21 | ColorPickerPane.Pinned = true 22 | ColorPickerPane.__index = ColorPickerPane 23 | 24 | function ColorPickerPane.new() 25 | local self = setmetatable(BasicPane.new(), ColorPickerPane) 26 | 27 | self._tabInputMap = {} 28 | 29 | self._currentBrightness = self._maid:Add(ValueObject.new(nil)) 30 | self._currentHue = self._maid:Add(ValueObject.new(nil)) 31 | self._currentSaturation = self._maid:Add(ValueObject.new(nil)) 32 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 33 | self._tabInputIndex = self._maid:Add(ValueObject.new(nil)) 34 | self._targetParent = self._maid:Add(ValueObject.new(nil)) 35 | self._yPosition = self._maid:Add(ValueObject.new(0)) 36 | 37 | self.ColorUpdated = self._maid:Add(Signal.new()) 38 | self.CurrentColor = self._maid:Add(ValueObject.new(Color3.new(1, 1, 1))) 39 | self.TabReturned = self._maid:Add(Signal.new()) 40 | 41 | self._maid:GiveTask(self._tabInputIndex:Observe():Subscribe(function(index) 42 | if not index then 43 | return 44 | end 45 | 46 | if index == 0 then 47 | self.TabReturned:Fire() 48 | return 49 | end 50 | 51 | local input = self._tabInputMap[index] 52 | if input then 53 | task.spawn(function() 54 | RunService.RenderStepped:Wait() 55 | input:CaptureFocus() 56 | end) 57 | end 58 | end)) 59 | 60 | self._maid:GiveTask(UserInputService.InputBegan:Connect(function(input) 61 | if input.UserInputType == Enum.UserInputType.Keyboard then 62 | local shiftPressed = self:_shiftPressed() 63 | 64 | if input.KeyCode == Enum.KeyCode.Tab then 65 | if shiftPressed then 66 | self._tabInputIndex.Value = math.clamp(self._tabInputIndex.Value - 1, 0, #self._tabInputMap) 67 | end 68 | end 69 | end 70 | end)) 71 | 72 | self._maid:GiveTask(UserInputService.InputChanged:Connect(function(input) 73 | if not self:IsVisible() then 74 | return 75 | end 76 | 77 | if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then 78 | if self.DraggingHue then 79 | self:_updateHuePosition(input) 80 | elseif self.DraggingBrightness then 81 | self:_updateBrightnessPosition(input) 82 | elseif self.DraggingCursor then 83 | self:_updateCursorPosition(input) 84 | end 85 | end 86 | end)) 87 | 88 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 89 | self._percentVisibleTarget.Value = isVisible and 1 or 0 90 | end)) 91 | 92 | return self 93 | end 94 | 95 | function ColorPickerPane:ObserveColor() 96 | return self.CurrentColor:Observe() 97 | end 98 | 99 | function ColorPickerPane:SetColor(color) 100 | self.CurrentColor.Value = color 101 | end 102 | 103 | function ColorPickerPane:GetYPosition() 104 | return self._yPosition.Value 105 | end 106 | 107 | function ColorPickerPane:_updateCursorPosition(inputObject) 108 | if not self._color then 109 | return 110 | end 111 | 112 | local relativeX = inputObject.Position.X - self._color.AbsolutePosition.X 113 | local relativeY = inputObject.Position.Y - self._color.AbsolutePosition.Y 114 | 115 | local saturation = math.abs(math.clamp(relativeX / self._color.AbsoluteSize.X, 0, 1) - 1) 116 | local value = math.abs(math.clamp(relativeY / self._color.AbsoluteSize.Y, 0, 1) - 1) 117 | 118 | self._currentSaturation.Value = saturation 119 | self._currentBrightness.Value = value 120 | end 121 | 122 | function ColorPickerPane:_updateHuePosition(inputObject) 123 | if not self._hue then 124 | return 125 | end 126 | 127 | local relativeY = inputObject.Position.Y - self._hue.AbsolutePosition.Y 128 | local hue = math.clamp(relativeY / self._hue.AbsoluteSize.Y, 0, 1) 129 | 130 | self._currentHue.Value = hue 131 | end 132 | 133 | function ColorPickerPane:_updateBrightnessPosition(inputObject) 134 | if not self._brightness then 135 | return 136 | end 137 | 138 | local relativeY = inputObject.Position.Y - self._brightness.AbsolutePosition.Y 139 | local value = math.clamp(relativeY / self._brightness.AbsoluteSize.Y, 0, 1) 140 | 141 | self._currentBrightness.Value = math.abs(1 - value) 142 | end 143 | 144 | function ColorPickerPane:SetTargetParent(targetParent) 145 | self._targetParent.Value = targetParent 146 | end 147 | 148 | function ColorPickerPane:Render(props) 149 | local target = self._percentVisibleTarget:Observe() 150 | 151 | local percentVisible = Blend.Spring(target, 30, 0.7) 152 | local transparency = props.Transparency 153 | 154 | local hue = self._maid:Add(Blend.State(0)) 155 | 156 | local oldColor = self._maid:Add(Blend.State(self.CurrentColor.Value)) 157 | local targetColor = self._maid:Add(Instance.new("Color3Value")) 158 | 159 | self._maid:GiveTask(Blend.Computed(self._currentHue, self._currentSaturation, self._currentBrightness, function(hue, saturation, brightness) 160 | if not hue or not saturation or not brightness then 161 | local currentColor = self.CurrentColor.Value 162 | if not currentColor then 163 | return 164 | end 165 | 166 | hue, saturation, brightness = currentColor:ToHSV() 167 | end 168 | 169 | self.CurrentColor.Value = Color3.fromHSV(hue, saturation, brightness) 170 | end):Subscribe()) 171 | 172 | self._maid:GiveTask(self.CurrentColor:Observe():Subscribe(function(newColor) 173 | if not newColor then 174 | return 175 | end 176 | 177 | local h, s, v = newColor:ToHSV() 178 | -- Fill default hsv values 179 | local currentHue = self._currentHue.Value 180 | if not currentHue or h ~= self._currentHue.Value then 181 | self._currentHue.Value = h 182 | end 183 | local currentSaturation = self._currentSaturation.Value 184 | if not currentSaturation or s ~= self._currentSaturation.Value then 185 | self._currentSaturation.Value = s 186 | end 187 | local currentBrightness = self._currentBrightness.Value 188 | if not currentBrightness or v ~= self._currentBrightness.Value then 189 | self._currentBrightness.Value = v 190 | end 191 | 192 | TweenService:Create(targetColor, TWEEN_INFO, { 193 | Value = newColor 194 | }):Play() 195 | 196 | oldColor.Value = newColor 197 | end)) 198 | 199 | local percentHue = Blend.Spring(self._currentHue, 35, 0.9) 200 | local percentSaturation = Blend.Spring(self._currentSaturation, 35, 0.9) 201 | local percentBrightness = Blend.Spring(self._currentBrightness, 35, 0.9) 202 | 203 | local pickerCursorX = Blend.Spring(percentSaturation:Pipe({ 204 | Rx.map(function(percent) 205 | return math.abs(percent - 1) 206 | end); 207 | }), 50, 0.9) 208 | 209 | local pickerCursorY = Blend.Spring(percentBrightness:Pipe({ 210 | Rx.map(function(percent) 211 | return math.abs(percent - 1) 212 | end); 213 | }), 50, 0.9) 214 | 215 | self._maid._tabbed = props.Tabbed:Connect(function() 216 | if self:_shiftPressed() then 217 | return 218 | end 219 | 220 | if not self._tabInputIndex.Value then 221 | self._tabInputIndex.Value = 1 222 | return 223 | end 224 | 225 | self._tabInputIndex.Value = math.clamp(self._tabInputIndex.Value + 1, 0, #self._tabInputMap) 226 | end) 227 | 228 | return Blend.New "Frame" { 229 | Name = "ColorPickerPane"; 230 | BackgroundTransparency = 1; 231 | LayoutOrder = -1; 232 | LayoutOrder = -9e9; 233 | Size = UDim2.new(1, 0, 0, self.Height); 234 | Parent = self._targetParent; 235 | 236 | Visible = Blend.Computed(props.CustomResult, props.Enabled, function(customResult, isEnabled) 237 | if customResult then 238 | return isEnabled 239 | else 240 | return false 241 | end 242 | end); 243 | 244 | [Blend.OnChange "AbsolutePosition"] = function(position) 245 | if not self:IsVisible() then 246 | return 247 | end 248 | 249 | self._yPosition.Value = math.round(position.Y) 250 | end; 251 | 252 | Blend.New "Frame" { 253 | Name = "wrapper"; 254 | BackgroundTransparency = 1; 255 | Size = UDim2.new(1, 0, 1, -10); 256 | 257 | Blend.New "UIListLayout" { 258 | FillDirection = Enum.FillDirection.Horizontal; 259 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 260 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 261 | Padding = UDim.new(0, 6); 262 | VerticalAlignment = Enum.VerticalAlignment.Center; 263 | }; 264 | 265 | Blend.New "Frame" { 266 | Name = "currentColor"; 267 | BackgroundColor3 = targetColor; 268 | BackgroundTransparency = transparency; 269 | LayoutOrder = 1; 270 | Size = UDim2.new(0, 40, 1, 0); 271 | 272 | Blend.New "UIFlexItem" { }; 273 | 274 | Blend.New "UICorner" { 275 | CornerRadius = UDim.new(0, 5); 276 | }; 277 | 278 | Blend.New "UIStroke" { 279 | Color = Color3.fromRGB(40, 40, 40); 280 | Transparency = transparency; 281 | }; 282 | }; 283 | 284 | Blend.New "Frame" { 285 | Name = "selection"; 286 | BackgroundTransparency = 1; 287 | LayoutOrder = 2; 288 | Size = UDim2.fromScale(0.5, 1); 289 | ZIndex = 3; 290 | 291 | Blend.New "Frame" { 292 | Name = "background"; 293 | BackgroundTransparency = 1; 294 | Size = UDim2.fromScale(1, 1); 295 | 296 | Blend.New "Frame" { 297 | Name = "white"; 298 | BackgroundTransparency = transparency; 299 | Size = UDim2.fromScale(1, 1); 300 | ZIndex = 2; 301 | 302 | Blend.New "UIGradient" { 303 | Transparency = NumberSequence.new(1, 0); 304 | }; 305 | 306 | Blend.New "UICorner" { 307 | CornerRadius = UDim.new(0, 5); 308 | }; 309 | }; 310 | 311 | Blend.New "Frame" { 312 | Name = "black"; 313 | BackgroundTransparency = transparency; 314 | Size = UDim2.fromScale(1, 1); 315 | ZIndex = 3; 316 | 317 | Blend.New "UIGradient" { 318 | Color = ColorSequence.new(Color3.fromRGB(0, 0, 0), Color3.fromRGB(0, 0, 0)); 319 | Rotation = 90; 320 | Transparency = NumberSequence.new(1, 0); 321 | }; 322 | 323 | Blend.New "UICorner" { 324 | CornerRadius = UDim.new(0, 4); 325 | }; 326 | 327 | Blend.New "UIStroke" { 328 | Color = Color3.fromRGB(40, 40, 40); 329 | Transparency = transparency; 330 | }; 331 | }; 332 | 333 | Blend.New "Frame" { 334 | Name = "color"; 335 | BackgroundColor3 = targetColor; 336 | BackgroundTransparency = props.Transparency; 337 | Size = UDim2.fromScale(1, 1); 338 | 339 | Blend.New "UICorner" { 340 | CornerRadius = UDim.new(0, 5); 341 | }; 342 | }; 343 | }; 344 | 345 | Blend.New "TextButton" { 346 | Name = "button"; 347 | Size = UDim2.fromScale(1, 1); 348 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 349 | BackgroundTransparency = 1; 350 | TextColor3 = Color3.fromRGB(27, 42, 53); 351 | TextSize = 8; 352 | ZIndex = 5; 353 | 354 | [Blend.Instance] = function(instance) 355 | self._color = instance 356 | end; 357 | 358 | [Blend.OnEvent "InputBegan"] = function(input) 359 | if self:_isValidInput(input) then 360 | self.DraggingCursor = true 361 | self:_updateCursorPosition(input) 362 | end 363 | end; 364 | 365 | [Blend.OnEvent "InputEnded"] = function(input) 366 | if self:_isValidInput(input) then 367 | self.DraggingCursor = false 368 | self.ColorUpdated:Fire(self.CurrentColor.Value) 369 | end 370 | end; 371 | }; 372 | 373 | Blend.New "Frame" { 374 | Name = "cursor"; 375 | AnchorPoint = Vector2.new(0.5, 0.5); 376 | BackgroundTransparency = 1; 377 | Size = UDim2.fromScale(0.1, 0.1); 378 | ZIndex = 5; 379 | 380 | Position = Blend.Computed(pickerCursorX, pickerCursorY, function(x, y) 381 | return UDim2.fromScale(x, y); 382 | end); 383 | 384 | Blend.New "UIAspectRatioConstraint" { 385 | AspectRatio = 1; 386 | }; 387 | 388 | Blend.New "UISizeConstraint" { 389 | MaxSize = Vector2.new(16, 16); 390 | }; 391 | 392 | Blend.New "Frame" { 393 | Name = "container"; 394 | AnchorPoint = Vector2.new(0.5, 0.5); 395 | BackgroundTransparency = 1; 396 | Position = UDim2.fromScale(0.5, 0.5); 397 | Size = UDim2.fromScale(1, 1); 398 | 399 | Blend.New "Frame" { 400 | Name = "dot"; 401 | AnchorPoint = Vector2.new(0.5, 0.5); 402 | BackgroundTransparency = 1; 403 | Position = UDim2.fromScale(0.5, 0.5); 404 | Size = UDim2.fromScale(1, 1); 405 | 406 | Blend.New "UICorner" { 407 | CornerRadius = UDim.new(1, 0); 408 | }; 409 | 410 | Blend.New "UIPadding" { 411 | PaddingBottom = UDim.new(0, 2); 412 | PaddingLeft = UDim.new(0, 2); 413 | PaddingRight = UDim.new(0, 2); 414 | PaddingTop = UDim.new(0, 2); 415 | }; 416 | 417 | Blend.New "UIStroke" { 418 | Color = Color3.fromRGB(255, 255, 255); 419 | Transparency = transparency; 420 | }; 421 | 422 | Blend.New "Frame" { 423 | Name = "inner"; 424 | AnchorPoint = Vector2.new(0.5, 0.5); 425 | BackgroundTransparency = transparency; 426 | Position = UDim2.fromScale(0.5, 0.5); 427 | Size = UDim2.fromScale(1, 1); 428 | 429 | Blend.New "UICorner" { 430 | CornerRadius = UDim.new(1, 0); 431 | }; 432 | }; 433 | }; 434 | 435 | Blend.New "ImageLabel" { 436 | Name = "shadow"; 437 | Position = UDim2.fromScale(0.5, 0.5); 438 | AnchorPoint = Vector2.new(0.5, 0.5); 439 | Size = UDim2.fromScale(2, 2); 440 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 441 | BackgroundTransparency = 1; 442 | Image = "rbxassetid://6150493168"; 443 | ImageColor3 = Color3.fromRGB(0, 0, 0); 444 | ImageTransparency = 0.8; 445 | ZIndex = -10; 446 | }; 447 | }; 448 | }; 449 | }; 450 | 451 | Blend.New "Frame" { 452 | Name = "sliders"; 453 | BackgroundTransparency = 1; 454 | LayoutOrder = 3; 455 | Position = UDim2.fromScale(0.5, 0); 456 | Size = UDim2.new(0, 40, 1, 0); 457 | ZIndex = 2; 458 | 459 | Blend.New "UIFlexItem" { }; 460 | 461 | Blend.New "Frame" { 462 | Name = "hue"; 463 | AnchorPoint = Vector2.new(0, 1); 464 | BackgroundTransparency = 1; 465 | Position = UDim2.fromScale(0, 1); 466 | Size = UDim2.new(0, 16, 1, 0); 467 | 468 | Blend.New "Frame" { 469 | Name = "container"; 470 | BackgroundTransparency = transparency; 471 | Size = UDim2.fromScale(1, 1); 472 | ZIndex = 2; 473 | 474 | Blend.New "UIStroke" { 475 | Color = Color3.fromRGB(40, 40, 40); 476 | Transparency = transparency; 477 | }; 478 | 479 | Blend.New "UICorner" { 480 | CornerRadius = UDim.new(0, 3); 481 | }; 482 | 483 | Blend.New "UIGradient" { 484 | Color = ColorSequence.new({ 485 | ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 0)), 486 | ColorSequenceKeypoint.new(0.05571, Color3.fromRGB(255, 85, 0)), 487 | ColorSequenceKeypoint.new(0.111421, Color3.fromRGB(255, 170, 0)), 488 | ColorSequenceKeypoint.new(0.167131, Color3.fromRGB(254, 255, 0)), 489 | ColorSequenceKeypoint.new(0.222841, Color3.fromRGB(169, 255, 0)), 490 | ColorSequenceKeypoint.new(0.278552, Color3.fromRGB(83, 255, 0)), 491 | ColorSequenceKeypoint.new(0.334262, Color3.fromRGB(0, 255, 1)), 492 | ColorSequenceKeypoint.new(0.389972, Color3.fromRGB(0, 255, 86)), 493 | ColorSequenceKeypoint.new(0.445682, Color3.fromRGB(0, 255, 171)), 494 | ColorSequenceKeypoint.new(0.501393, Color3.fromRGB(0, 252, 255)), 495 | ColorSequenceKeypoint.new(0.557103, Color3.fromRGB(0, 167, 255)), 496 | ColorSequenceKeypoint.new(0.612813, Color3.fromRGB(0, 82, 255)), 497 | ColorSequenceKeypoint.new(0.668524, Color3.fromRGB(2, 0, 255)), 498 | ColorSequenceKeypoint.new(0.724234, Color3.fromRGB(88, 0, 255)), 499 | ColorSequenceKeypoint.new(0.779944, Color3.fromRGB(173, 0, 255)), 500 | ColorSequenceKeypoint.new(0.835655, Color3.fromRGB(255, 0, 251)), 501 | ColorSequenceKeypoint.new(0.891365, Color3.fromRGB(255, 0, 166)), 502 | ColorSequenceKeypoint.new(0.947075, Color3.fromRGB(255, 0, 80)), 503 | ColorSequenceKeypoint.new(1, Color3.fromRGB(255, 0, 0)) 504 | }); 505 | Rotation = 90; 506 | }; 507 | 508 | Blend.New "Frame" { 509 | Name = "cursor"; 510 | AnchorPoint = Vector2.new(0.5, 0.5); 511 | BackgroundTransparency = transparency; 512 | Size = UDim2.fromScale(1.2, 1); 513 | 514 | Position = Blend.Computed(percentHue, function(percent) 515 | return UDim2.fromScale(0.5, percent) 516 | end); 517 | 518 | Blend.New "UIAspectRatioConstraint" { 519 | AspectRatio = 2.5; 520 | }; 521 | 522 | Blend.New "UICorner" { 523 | CornerRadius = UDim.new(0, 2); 524 | }; 525 | 526 | Blend.New "UIStroke" { 527 | Color = Color3.fromRGB(230, 230, 230); 528 | Transparency = transparency; 529 | }; 530 | }; 531 | }; 532 | 533 | Blend.New "TextButton" { 534 | Name = "button"; 535 | Size = UDim2.fromScale(1, 1); 536 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 537 | BackgroundTransparency = 1; 538 | TextColor3 = Color3.fromRGB(27, 42, 53); 539 | TextSize = 8; 540 | ZIndex = 5; 541 | 542 | [Blend.Instance] = function(instance) 543 | self._hue = instance 544 | end; 545 | 546 | [Blend.OnEvent "InputBegan"] = function(input) 547 | if self:_isValidInput(input) then 548 | self.DraggingHue = true 549 | self:_updateHuePosition(input) 550 | end 551 | end; 552 | 553 | [Blend.OnEvent "InputEnded"] = function(input) 554 | if self:_isValidInput(input) then 555 | self.DraggingHue = false 556 | self.ColorUpdated:Fire(self.CurrentColor.Value) 557 | end 558 | end; 559 | }; 560 | }; 561 | 562 | Blend.New "Frame" { 563 | Name = "brightness"; 564 | Position = UDim2.fromScale(1, 1); 565 | AnchorPoint = Vector2.new(1, 1); 566 | Size = UDim2.new(0, 16, 1, 0); 567 | BackgroundTransparency = 1; 568 | 569 | Blend.New "Frame" { 570 | Name = "container"; 571 | BackgroundTransparency = transparency; 572 | Size = UDim2.fromScale(1, 1); 573 | ZIndex = 2; 574 | 575 | Blend.New "UIStroke" { 576 | Color = Color3.fromRGB(40, 40, 40); 577 | Transparency = transparency; 578 | }; 579 | 580 | Blend.New "UICorner" { 581 | CornerRadius = UDim.new(0, 3); 582 | }; 583 | 584 | Blend.New "UIGradient" { 585 | Rotation = 90; 586 | 587 | Color = Blend.Computed(targetColor, function(color) 588 | return ColorSequence.new(color, Color3.fromRGB(0, 0, 0)); 589 | end); 590 | }; 591 | 592 | Blend.New "Frame" { 593 | Name = "cursor"; 594 | AnchorPoint = Vector2.new(0.5, 0.5); 595 | BackgroundTransparency = transparency; 596 | Size = UDim2.fromScale(1.2, 1); 597 | 598 | Position = Blend.Computed(percentBrightness, function(percent) 599 | return UDim2.fromScale(0.5, 1 - percent) 600 | end); 601 | 602 | Blend.New "UIAspectRatioConstraint" { 603 | AspectRatio = 2.5; 604 | }; 605 | 606 | Blend.New "UICorner" { 607 | CornerRadius = UDim.new(0, 2); 608 | }; 609 | 610 | Blend.New "UIStroke" { 611 | Color = Color3.fromRGB(230, 230, 230); 612 | Transparency = transparency; 613 | }; 614 | }; 615 | }; 616 | 617 | Blend.New "TextButton" { 618 | Name = "button"; 619 | Size = UDim2.fromScale(1, 1); 620 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 621 | BackgroundTransparency = 1; 622 | TextColor3 = Color3.fromRGB(27, 42, 53); 623 | TextSize = 8; 624 | ZIndex = 5; 625 | 626 | [Blend.Instance] = function(instance) 627 | self._brightness = instance 628 | end; 629 | 630 | [Blend.OnEvent "InputBegan"] = function(input) 631 | if self:_isValidInput(input) then 632 | self.DraggingBrightness = true 633 | self:_updateBrightnessPosition(input) 634 | end 635 | end; 636 | 637 | [Blend.OnEvent "InputEnded"] = function(input) 638 | if self:_isValidInput(input) then 639 | self.DraggingBrightness = false 640 | self.ColorUpdated:Fire(self.CurrentColor.Value) 641 | end 642 | end; 643 | }; 644 | }; 645 | }; 646 | 647 | Blend.New "Frame" { 648 | Name = "inputs"; 649 | BackgroundTransparency = 1; 650 | LayoutOrder = 4; 651 | Position = UDim2.fromScale(0.780371, 0); 652 | Size = UDim2.fromScale(0.2, 1); 653 | ZIndex = 2; 654 | 655 | Blend.New "UIListLayout" { 656 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 657 | Padding = UDim.new(0, 5); 658 | VerticalAlignment = Enum.VerticalAlignment.Center; 659 | VerticalFlex = Enum.UIFlexAlignment.Fill; 660 | }; 661 | 662 | Blend.New "Frame" { 663 | Name = "rgb"; 664 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 665 | BackgroundTransparency = transparency; 666 | LayoutOrder = 1; 667 | Size = UDim2.fromScale(1, 1); 668 | 669 | Blend.New "UICorner" { 670 | CornerRadius = UDim.new(0, 5); 671 | }; 672 | 673 | Blend.New "UIStroke" { 674 | Color = Color3.fromRGB(40, 40, 40); 675 | Transparency = transparency; 676 | }; 677 | 678 | Blend.New "UIListLayout" { 679 | FillDirection = Enum.FillDirection.Horizontal; 680 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 681 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 682 | Padding = UDim.new(0, 5); 683 | VerticalAlignment = Enum.VerticalAlignment.Center; 684 | }; 685 | 686 | Blend.New "UIPadding" { 687 | PaddingBottom = UDim.new(0, 10); 688 | PaddingLeft = UDim.new(0, 10); 689 | PaddingRight = UDim.new(0, 10); 690 | PaddingTop = UDim.new(0, 10); 691 | }; 692 | 693 | Blend.New "ImageLabel" { 694 | Name = "icon"; 695 | LayoutOrder = 1; 696 | Size = UDim2.fromScale(1, 1); 697 | BackgroundTransparency = 1; 698 | Image = "rbxassetid://6034316009"; 699 | ImageColor3 = Color3.fromRGB(75, 75, 75); 700 | 701 | Blend.New "UIAspectRatioConstraint" { 702 | AspectRatio = 1; 703 | }; 704 | }; 705 | 706 | Blend.New "TextBox" { 707 | Name = "input"; 708 | LayoutOrder = 2; 709 | Size = UDim2.fromScale(1, 1); 710 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 711 | BackgroundTransparency = 1; 712 | FontFace = Font.new("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal); 713 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 714 | TextColor3 = Color3.fromRGB(255, 255, 255); 715 | TextSize = 18; 716 | TextXAlignment = Enum.TextXAlignment.Left; 717 | ZIndex = 5; 718 | 719 | PlaceholderText = Blend.Computed(targetColor, function(color) 720 | return `{math.round(color.R * 255)}, {math.round(color.G * 255)}, {math.round(color.B * 255)}` 721 | end); 722 | 723 | [Blend.Instance] = function(textBox) 724 | self._rgbInput = textBox 725 | table.insert(self._tabInputMap, 1, textBox) 726 | end; 727 | 728 | [Blend.OnChange "Text"] = function(newText) 729 | if not self._rgbInput then 730 | return 731 | end 732 | 733 | if newText == self._rgbInput.PlaceholderText then 734 | return 735 | end 736 | 737 | if SearchUtils.stringIsRGB(self._rgbInput.Text) then 738 | local _, r, g, b = SearchUtils.stringIsRGB(self._rgbInput.Text) 739 | self:SetColor(Color3.fromRGB(r, g, b)) 740 | end 741 | end; 742 | 743 | [Blend.OnEvent "Focused"] = function() 744 | self:_handleFocused(self._rgbInput) 745 | end; 746 | 747 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 748 | if not self._rgbInput then 749 | return 750 | end 751 | 752 | if enterPressed then 753 | if self._rgbInput.Text ~= self._rgbInput.PlaceholderText then 754 | if SearchUtils.stringIsRGB(self._rgbInput.Text) then 755 | local _, r, g, b = SearchUtils.stringIsRGB(self._rgbInput.Text) 756 | self:SetColor(Color3.fromRGB(r, g, b)) 757 | end 758 | 759 | self._rgbInput.Text = "" 760 | else 761 | self._rgbInput.Text = "" 762 | end 763 | else 764 | self._rgbInput.Text = "" 765 | end 766 | end; 767 | 768 | self:_handleFocusLost() 769 | }; 770 | }; 771 | 772 | Blend.New "Frame" { 773 | Name = "hex"; 774 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 775 | BackgroundTransparency = transparency; 776 | LayoutOrder = 2; 777 | Size = UDim2.fromScale(1, 1); 778 | 779 | Blend.New "UICorner" { 780 | CornerRadius = UDim.new(0, 5); 781 | }; 782 | 783 | Blend.New "UIStroke" { 784 | Color = Color3.fromRGB(40, 40, 40); 785 | Transparency = transparency; 786 | }; 787 | 788 | Blend.New "UIListLayout" { 789 | FillDirection = Enum.FillDirection.Horizontal; 790 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 791 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 792 | Padding = UDim.new(0, 5); 793 | VerticalAlignment = Enum.VerticalAlignment.Center; 794 | }; 795 | 796 | Blend.New "UIPadding" { 797 | PaddingBottom = UDim.new(0, 10); 798 | PaddingLeft = UDim.new(0, 10); 799 | PaddingRight = UDim.new(0, 10); 800 | PaddingTop = UDim.new(0, 10); 801 | }; 802 | 803 | Blend.New "ImageLabel" { 804 | Name = "icon"; 805 | BackgroundTransparency = 1; 806 | Image = "rbxassetid://6035078895"; 807 | ImageColor3 = Color3.fromRGB(75, 75, 75); 808 | ImageTransparency = transparency; 809 | LayoutOrder = 1; 810 | Size = UDim2.fromScale(1, 1); 811 | 812 | Blend.New "UIAspectRatioConstraint" { 813 | AspectRatio = 1; 814 | }; 815 | }; 816 | 817 | Blend.New "TextBox" { 818 | Name = "input"; 819 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 820 | BackgroundTransparency = 1; 821 | FontFace = Font.new("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal); 822 | LayoutOrder = 2; 823 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 824 | Size = UDim2.fromScale(1, 1); 825 | TextColor3 = Color3.fromRGB(255, 255, 255); 826 | TextSize = 18; 827 | TextTransparency = transparency; 828 | TextXAlignment = Enum.TextXAlignment.Left; 829 | ZIndex = 5; 830 | 831 | PlaceholderText = Blend.Computed(targetColor, function(color) 832 | return color:ToHex() 833 | end); 834 | 835 | [Blend.Instance] = function(textBox) 836 | self._hexInput = textBox 837 | table.insert(self._tabInputMap, 2, textBox) 838 | end; 839 | 840 | [Blend.OnChange "Text"] = function(newText) 841 | if not self._hexInput then 842 | return 843 | end 844 | 845 | if newText == self._hexInput.PlaceholderText then 846 | return 847 | end 848 | 849 | if SearchUtils.stringIsHex(self._hexInput.Text) then 850 | local _, hex = SearchUtils.stringIsHex(self._hexInput.Text) 851 | self:SetColor(Color3.fromHex(hex)) 852 | end 853 | end; 854 | 855 | [Blend.OnEvent "Focused"] = function() 856 | self:_handleFocused(self._hexInput) 857 | end; 858 | 859 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 860 | if not self._hexInput then 861 | return 862 | end 863 | 864 | if enterPressed then 865 | if self._hexInput.Text ~= self._hexInput.PlaceholderText then 866 | if SearchUtils.stringIsHex(self._hexInput.Text) then 867 | local _, hex = SearchUtils.stringIsHex(self._hexInput.Text) 868 | self:SetColor(Color3.fromHex(hex)) 869 | end 870 | 871 | self._hexInput.Text = "" 872 | else 873 | self._hexInput.Text = "" 874 | end 875 | else 876 | self._hexInput.Text = "" 877 | end 878 | 879 | self:_handleFocusLost() 880 | end; 881 | }; 882 | }; 883 | 884 | Blend.New "Frame" { 885 | Name = "rgbValues"; 886 | LayoutOrder = 3; 887 | Size = UDim2.fromScale(1, 1); 888 | BackgroundTransparency = 1; 889 | 890 | Blend.New "UIListLayout" { 891 | FillDirection = Enum.FillDirection.Horizontal; 892 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 893 | HorizontalFlex = Enum.UIFlexAlignment.Fill; 894 | Padding = UDim.new(0, 5); 895 | VerticalAlignment = Enum.VerticalAlignment.Center; 896 | }; 897 | 898 | Blend.New "Frame" { 899 | Name = "r"; 900 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 901 | BackgroundTransparency = transparency; 902 | LayoutOrder = 1; 903 | Size = UDim2.fromScale(1, 1); 904 | 905 | Blend.New "UICorner" { 906 | CornerRadius = UDim.new(0, 5); 907 | }; 908 | 909 | Blend.New "UIStroke" { 910 | Color = Color3.fromRGB(40, 40, 40); 911 | Transparency = transparency; 912 | }; 913 | 914 | Blend.New "UIPadding" { 915 | PaddingBottom = UDim.new(0, 10); 916 | PaddingLeft = UDim.new(0, 10); 917 | PaddingRight = UDim.new(0, 10); 918 | PaddingTop = UDim.new(0, 10); 919 | }; 920 | 921 | Blend.New "TextBox" { 922 | Name = "input"; 923 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 924 | BackgroundTransparency = 1; 925 | FontFace = Font.new("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal); 926 | LayoutOrder = 2; 927 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 928 | Size = UDim2.fromScale(1, 1); 929 | TextColor3 = Color3.fromRGB(255, 255, 255); 930 | TextSize = 18; 931 | TextTransparency = transparency; 932 | ZIndex = 5; 933 | 934 | PlaceholderText = Blend.Computed(targetColor, function(color) 935 | return `{math.round(color.R * 255)}` 936 | end); 937 | 938 | [Blend.Instance] = function(textBox) 939 | self._rInput = textBox 940 | table.insert(self._tabInputMap, 3, textBox) 941 | end; 942 | 943 | [Blend.OnChange "Text"] = function(newText) 944 | if not self._rInput or not self._rInput:IsFocused() then 945 | return 946 | end 947 | 948 | if newText == self._rInput.PlaceholderText then 949 | return 950 | end 951 | 952 | if tonumber(newText) then 953 | local currentColor = self.CurrentColor.Value 954 | local r, g, b = math.clamp(tonumber(newText), 0, 255), 955 | math.clamp(math.round(currentColor.G * 255), 0, 255), 956 | math.clamp(math.round(currentColor.B * 255), 0, 255) 957 | 958 | self:SetColor(Color3.fromRGB(r, g, b)) 959 | end 960 | end; 961 | 962 | [Blend.OnEvent "Focused"] = function() 963 | self:_handleFocused(self._rInput) 964 | end; 965 | 966 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 967 | if not self._rInput then 968 | return 969 | end 970 | 971 | local text = self._rInput.Text 972 | if enterPressed then 973 | if text ~= self._rInput.PlaceholderText then 974 | if tonumber(text) then 975 | local currentColor = self.CurrentColor.Value 976 | local r, g, b = math.clamp(tonumber(text), 0, 255), 977 | math.clamp(math.round(currentColor.G * 255), 0, 255), 978 | math.clamp(math.round(currentColor.B * 255), 0, 255) 979 | 980 | self:SetColor(Color3.fromRGB(r, g, b)) 981 | end 982 | 983 | self._rInput.Text = "" 984 | else 985 | self._rInput.Text = "" 986 | end 987 | else 988 | self._rInput.Text = "" 989 | end 990 | 991 | self:_handleFocusLost() 992 | end; 993 | }; 994 | }; 995 | 996 | Blend.New "Frame" { 997 | Name = "g"; 998 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 999 | BackgroundTransparency = transparency; 1000 | LayoutOrder = 2; 1001 | Size = UDim2.fromScale(1, 1); 1002 | 1003 | Blend.New "UICorner" { 1004 | CornerRadius = UDim.new(0, 5); 1005 | }; 1006 | 1007 | Blend.New "UIStroke" { 1008 | Color = Color3.fromRGB(40, 40, 40); 1009 | Transparency = transparency; 1010 | }; 1011 | 1012 | Blend.New "UIPadding" { 1013 | PaddingBottom = UDim.new(0, 10); 1014 | PaddingLeft = UDim.new(0, 10); 1015 | PaddingRight = UDim.new(0, 10); 1016 | PaddingTop = UDim.new(0, 10); 1017 | }; 1018 | 1019 | Blend.New "TextBox" { 1020 | Name = "input"; 1021 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 1022 | BackgroundTransparency = 1; 1023 | FontFace = Font.new("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal); 1024 | LayoutOrder = 2; 1025 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 1026 | Size = UDim2.fromScale(1, 1); 1027 | TextColor3 = Color3.fromRGB(255, 255, 255); 1028 | TextSize = 18; 1029 | TextTransparency = transparency; 1030 | ZIndex = 5; 1031 | 1032 | PlaceholderText = Blend.Computed(targetColor, function(color) 1033 | return `{math.round(color.G * 255)}` 1034 | end); 1035 | 1036 | [Blend.Instance] = function(textBox) 1037 | self._gInput = textBox 1038 | table.insert(self._tabInputMap, 4, textBox) 1039 | end; 1040 | 1041 | [Blend.OnChange "Text"] = function(newText) 1042 | if not self._gInput or not self._gInput:IsFocused() then 1043 | return 1044 | end 1045 | 1046 | if newText == self._gInput.PlaceholderText then 1047 | return 1048 | end 1049 | 1050 | if tonumber(newText) then 1051 | local currentColor = self.CurrentColor.Value 1052 | local r, g, b = math.clamp(math.round(currentColor.R * 255), 0, 255), 1053 | math.clamp(tonumber(newText), 0, 255), 1054 | math.clamp(math.round(currentColor.B * 255), 0, 255) 1055 | 1056 | self:SetColor(Color3.fromRGB(r, g, b)) 1057 | end 1058 | end; 1059 | 1060 | [Blend.OnEvent "Focused"] = function() 1061 | self:_handleFocused(self._gInput) 1062 | end; 1063 | 1064 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 1065 | if not self._gInput then 1066 | return 1067 | end 1068 | 1069 | local text = self._gInput.Text 1070 | if enterPressed then 1071 | if text ~= self._gInput.PlaceholderText then 1072 | if tonumber(text) then 1073 | local currentColor = self.CurrentColor.Value 1074 | local r, g, b = math.clamp(math.round(currentColor.R * 255), 0, 255), 1075 | math.clamp(tonumber(text), 0, 255), 1076 | math.clamp(math.round(currentColor.B * 255), 0, 255) 1077 | 1078 | self:SetColor(Color3.fromRGB(r, g, b)) 1079 | end 1080 | 1081 | self._gInput.Text = "" 1082 | else 1083 | self._gInput.Text = "" 1084 | end 1085 | else 1086 | self._gInput.Text = "" 1087 | end 1088 | 1089 | self:_handleFocusLost() 1090 | end; 1091 | }; 1092 | }; 1093 | 1094 | Blend.New "Frame" { 1095 | Name = "b"; 1096 | BackgroundColor3 = Color3.fromRGB(15, 15, 15); 1097 | BackgroundTransparency = transparency; 1098 | LayoutOrder = 3; 1099 | Size = UDim2.fromScale(1, 1); 1100 | 1101 | Blend.New "UICorner" { 1102 | CornerRadius = UDim.new(0, 5); 1103 | }; 1104 | 1105 | Blend.New "UIStroke" { 1106 | Color = Color3.fromRGB(40, 40, 40); 1107 | Transparency = transparency; 1108 | }; 1109 | 1110 | Blend.New "UIPadding" { 1111 | PaddingBottom = UDim.new(0, 10); 1112 | PaddingLeft = UDim.new(0, 10); 1113 | PaddingRight = UDim.new(0, 10); 1114 | PaddingTop = UDim.new(0, 10); 1115 | }; 1116 | 1117 | Blend.New "TextBox" { 1118 | Name = "input"; 1119 | BackgroundColor3 = Color3.fromRGB(163, 162, 165); 1120 | BackgroundTransparency = 1; 1121 | FontFace = Font.new("rbxasset://fonts/families/RobotoMono.json", Enum.FontWeight.SemiBold, Enum.FontStyle.Normal); 1122 | LayoutOrder = 2; 1123 | PlaceholderColor3 = Color3.fromRGB(80, 80, 80); 1124 | Size = UDim2.fromScale(1, 1); 1125 | TextColor3 = Color3.fromRGB(255, 255, 255); 1126 | TextSize = 18; 1127 | TextTransparency = transparency; 1128 | ZIndex = 5; 1129 | 1130 | PlaceholderText = Blend.Computed(targetColor, function(color) 1131 | return `{math.round(color.B * 255)}` 1132 | end); 1133 | 1134 | [Blend.Instance] = function(textBox) 1135 | self._bInput = textBox 1136 | table.insert(self._tabInputMap, 5, textBox) 1137 | end; 1138 | 1139 | [Blend.OnChange "Text"] = function(newText) 1140 | if not self._bInput or not self._bInput:IsFocused() then 1141 | return 1142 | end 1143 | 1144 | if newText == self._bInput.PlaceholderText then 1145 | return 1146 | end 1147 | 1148 | if tonumber(newText) then 1149 | local currentColor = self.CurrentColor.Value 1150 | local r, g, b = math.clamp(math.round(currentColor.R * 255), 0, 255), 1151 | math.clamp(math.round(currentColor.G * 255), 0, 255), 1152 | math.clamp(tonumber(newText), 0, 255) 1153 | 1154 | self:SetColor(Color3.fromRGB(r, g, b)) 1155 | end 1156 | end; 1157 | 1158 | [Blend.OnEvent "Focused"] = function() 1159 | self:_handleFocused(self._bInput) 1160 | end; 1161 | 1162 | [Blend.OnEvent "FocusLost"] = function(enterPressed) 1163 | if not self._bInput then 1164 | return 1165 | end 1166 | 1167 | local text = self._bInput.Text 1168 | if enterPressed then 1169 | if text ~= self._bInput.PlaceholderText then 1170 | if tonumber(text) then 1171 | local currentColor = self.CurrentColor.Value 1172 | local r, g, b = math.clamp(math.round(currentColor.R * 255), 0, 255), 1173 | math.clamp(math.round(currentColor.G * 255), 0, 255), 1174 | math.clamp(tonumber(text), 0, 255) 1175 | 1176 | self:SetColor(Color3.fromRGB(r, g, b)) 1177 | end 1178 | 1179 | self._bInput.Text = "" 1180 | else 1181 | self._bInput.Text = "" 1182 | end 1183 | else 1184 | self._bInput.Text = "" 1185 | end 1186 | 1187 | self:_handleFocusLost() 1188 | end; 1189 | }; 1190 | }; 1191 | }; 1192 | }; 1193 | }; 1194 | 1195 | Blend.New "Frame" { 1196 | Name = "divider"; 1197 | AnchorPoint = Vector2.new(0, 1); 1198 | BackgroundColor3 = Color3.fromRGB(35, 35, 35); 1199 | BackgroundTransparency = transparency; 1200 | Position = UDim2.new(0, 0, 1, -4); 1201 | Size = UDim2.new(1, 0, 0, 1); 1202 | ZIndex = 0; 1203 | }; 1204 | }; 1205 | end 1206 | 1207 | function ColorPickerPane:_handleFocusLost() 1208 | if not UserInputService:GetFocusedTextBox() then 1209 | self._tabInputIndex.Value = nil 1210 | end 1211 | end 1212 | 1213 | function ColorPickerPane:_handleFocused(input) 1214 | if not input then 1215 | return 1216 | end 1217 | 1218 | if not self._tabInputIndex.Value then 1219 | local index = table.find(self._tabInputMap, input) 1220 | self._tabInputIndex.Value = index 1221 | end 1222 | 1223 | local placeholder = input.PlaceholderText 1224 | 1225 | input.Text = placeholder 1226 | input.SelectionStart = 0 1227 | input.CursorPosition = #placeholder + 1 1228 | end 1229 | 1230 | function ColorPickerPane:_isValidInput(inputObject) 1231 | return inputObject.UserInputType == Enum.UserInputType.MouseButton1 or inputObject.UserInputType == Enum.UserInputType.Touch 1232 | end 1233 | 1234 | function ColorPickerPane:_shiftPressed() 1235 | local shiftPressed = false 1236 | for _, inputObject in UserInputService:GetKeysPressed() do 1237 | if inputObject.KeyCode == Enum.KeyCode.LeftShift then 1238 | shiftPressed = true 1239 | break 1240 | end 1241 | end 1242 | return shiftPressed 1243 | end 1244 | 1245 | return ColorPickerPane -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/Input/Font/FontEntry.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local BasicPane = require("BasicPane") 4 | local Blend = require("Blend") 5 | local ButtonHighlightModel = require("ButtonHighlightModel") 6 | local Fzy = require("Fzy") 7 | local SearchUtils = require("SearchUtils") 8 | local Signal = require("Signal") 9 | local ValueObject = require("ValueObject") 10 | 11 | local FontEntry = setmetatable({}, BasicPane) 12 | FontEntry.ClassName = "FontEntry" 13 | FontEntry.CustomResultType = "Font" 14 | FontEntry.__index = FontEntry 15 | 16 | function FontEntry.new() 17 | local self = setmetatable(BasicPane.new(), FontEntry) 18 | 19 | self._defaultIndex = self._maid:Add(ValueObject.new(0)) 20 | self._fontName = self._maid:Add(ValueObject.new("")) 21 | self._model = self._maid:Add(ButtonHighlightModel.new()) 22 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 23 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 24 | self._styleCount = self._maid:Add(ValueObject.new(0)) 25 | self._yPosition = self._maid:Add(ValueObject.new(0)) 26 | 27 | self.Activated = self._maid:Add(Signal.new()) 28 | self.Font = self._maid:Add(ValueObject.new(nil)) 29 | self.SearchScore = self._maid:Add(ValueObject.new(0)) 30 | 31 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 32 | self._percentVisibleTarget.Value = isVisible and 1 or 0 33 | end)) 34 | 35 | return self 36 | end 37 | 38 | function FontEntry:SetFontName(fontName: string) 39 | self._fontName.Value = fontName 40 | end 41 | 42 | function FontEntry:SetStyleCount(count: number) 43 | self._styleCount.Value = count 44 | end 45 | 46 | function FontEntry:GetYPosition() 47 | return self._yPosition.Value 48 | end 49 | 50 | function FontEntry:SetDefaultIndex(index: number) 51 | self._defaultIndex.Value = index 52 | end 53 | 54 | function FontEntry:SetIsSelected(isSelected: boolean) 55 | self._model:SetIsChoosen(isSelected) 56 | self._model._isMouseOver.Value = isSelected 57 | end 58 | 59 | function FontEntry:GetData() 60 | return self.Font.Value 61 | end 62 | 63 | function FontEntry:GetFontName() 64 | return self._fontName.Value 65 | end 66 | 67 | function FontEntry:Render(props) 68 | local target = self._percentVisibleTarget:Observe() 69 | 70 | local percentHighlighted = Blend.Spring(self._model:ObservePercentHighlightedTarget(), 35, 0.7) 71 | local percentPressed = Blend.Spring(self._model:ObservePercentPressedTarget(), 35, 0.55) 72 | local percentSelected = Blend.AccelTween(self._model:ObservePercentChoosenTarget(), 400) 73 | 74 | local foregroundColor = Blend.Computed(percentSelected, function(percent) 75 | return Color3.fromRGB(150, 150, 150):Lerp(Color3.fromRGB(255, 255, 255), percent) 76 | end); 77 | 78 | local fzyConfig = props.FzyConfig 79 | local transparency = props.Transparency 80 | 81 | self._maid:GiveTask(Blend.Computed(self._fontName, props.SearchQuery, function(fontName, searchQuery) 82 | if searchQuery == "" then 83 | self.SearchScore.Value = nil 84 | return 85 | end 86 | 87 | self.SearchScore.Value = Fzy.score(fzyConfig, searchQuery, string.lower(fontName)) 88 | end):Subscribe()) 89 | 90 | return Blend.New "Frame" { 91 | Name = "FontEntry"; 92 | BackgroundTransparency = 1; 93 | Parent = props.TargetParent; 94 | 95 | LayoutOrder = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 96 | return score and -(score * 10000) or defaultIndex 97 | end); 98 | 99 | Size = Blend.Computed(props.EntryHeight, function(height) 100 | if height == 45 then 101 | return UDim2.new(1, 0, 0, 60) 102 | else 103 | return UDim2.new(1, 0, 0, 40) 104 | end 105 | end); 106 | 107 | Visible = Blend.Computed(props.CustomResult, props.Enabled, props.SearchQuery, self.SearchScore, function(customResult, isEnabled, searchQuery, searchScore) 108 | if not customResult then 109 | return false 110 | elseif isEnabled then 111 | if searchQuery ~= "" and searchScore then 112 | return searchScore > 0 113 | else 114 | return true 115 | end 116 | end 117 | 118 | return false 119 | end); 120 | 121 | ZIndex = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 122 | return score and -(score * 10000) or defaultIndex 123 | end); 124 | 125 | [Blend.OnChange "AbsolutePosition"] = function(position) 126 | if not self:IsVisible() then 127 | return 128 | end 129 | 130 | self._yPosition.Value = math.round(position.Y) 131 | end; 132 | 133 | Blend.New "Frame" { 134 | Name = "wrapper"; 135 | BackgroundColor3 = Color3.fromRGB(50, 50, 50); 136 | Size = UDim2.fromScale(1, 1); 137 | 138 | BackgroundTransparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 139 | return 1 - percentHighlight + percent 140 | end); 141 | 142 | Blend.New "UIPadding" { 143 | PaddingBottom = UDim.new(0, 14); 144 | PaddingLeft = UDim.new(0, 7); 145 | PaddingRight = UDim.new(0, 7); 146 | PaddingTop = UDim.new(0, 14); 147 | }; 148 | 149 | Blend.New "UIStroke" { 150 | Color = Color3.fromRGB(85, 85, 85); 151 | Transparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 152 | return math.clamp(1 - percentHighlight + percent, 0, 1) 153 | end); 154 | }; 155 | 156 | Blend.New "UICorner" { 157 | CornerRadius = UDim.new(0, 5); 158 | }; 159 | 160 | Blend.New "TextLabel" { 161 | Name = "styles"; 162 | LayoutOrder = 2; 163 | Position = UDim2.fromScale(1, 0.5); 164 | AnchorPoint = Vector2.new(1, 0.5); 165 | Size = UDim2.new(1, 0, 0, 15); 166 | BackgroundTransparency = 1; 167 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 168 | TextColor3 = Color3.fromRGB(85, 85, 85); 169 | TextWrapped = true; 170 | TextXAlignment = Enum.TextXAlignment.Right; 171 | 172 | Text = Blend.Computed(self._styleCount, function(count) 173 | return `{count} {count == 1 and "style" or "styles"}` 174 | end); 175 | }; 176 | 177 | Blend.New "Frame" { 178 | Name = "container"; 179 | LayoutOrder = 9; 180 | Size = UDim2.fromScale(0.9, 1); 181 | BackgroundTransparency = 1; 182 | 183 | Blend.New "UIListLayout" { 184 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 185 | VerticalAlignment = Enum.VerticalAlignment.Center; 186 | }; 187 | 188 | Blend.New "TextLabel" { 189 | Name = "textPreview"; 190 | LayoutOrder = 2; 191 | Position = UDim2.fromScale(0, 0.5); 192 | AnchorPoint = Vector2.new(0, 0.5); 193 | Size = UDim2.new(1, 0, 0, 20); 194 | BackgroundTransparency = 1; 195 | RichText = true; 196 | TextColor3 = Color3.fromRGB(255, 255, 255); 197 | TextSize = 20; 198 | TextTruncate = Enum.TextTruncate.AtEnd; 199 | TextXAlignment = Enum.TextXAlignment.Left; 200 | 201 | Text = Blend.Computed(props.EntryHeight, props.FontPreviewText, self._fontName, props.SearchQuery, function(height, fontPreviewText, fontName, searchQuery) 202 | if height == 45 then 203 | return SearchUtils.getMatchedString(fzyConfig, fontPreviewText, searchQuery) 204 | else 205 | return SearchUtils.getMatchedString(fzyConfig, fontName, searchQuery) 206 | end 207 | end); 208 | 209 | FontFace = Blend.Computed(self.Font, function(font) 210 | if not font then 211 | return Font.new("rbxasset://fonts/families/SourceSansPro.json") 212 | end 213 | 214 | return font 215 | end); 216 | }; 217 | 218 | Blend.New "TextLabel" { 219 | Name = "fontName"; 220 | AnchorPoint = Vector2.new(1, 0.5); 221 | BackgroundTransparency = 1; 222 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 223 | LayoutOrder = 2; 224 | Position = UDim2.fromScale(1, 0.5); 225 | RichText = true; 226 | Size = UDim2.new(1, 0, 0, 15); 227 | TextColor3 = Color3.fromRGB(110, 110, 110); 228 | TextWrapped = true; 229 | TextXAlignment = Enum.TextXAlignment.Left; 230 | 231 | Text = Blend.Computed(self._fontName, props.SearchQuery, function(fontName, searchQuery) 232 | if searchQuery ~= "" then 233 | return SearchUtils.getMatchedString(fzyConfig, fontName, searchQuery) 234 | else 235 | return fontName 236 | end 237 | end); 238 | 239 | Visible = Blend.Computed(props.EntryHeight, function(height) 240 | return height == 45 241 | end); 242 | }; 243 | }; 244 | }; 245 | 246 | Blend.New "Frame" { 247 | Name = "divider"; 248 | AnchorPoint = Vector2.new(0, 1); 249 | BackgroundColor3 = Color3.fromRGB(35, 35, 35); 250 | BackgroundTransparency = transparency; 251 | Position = UDim2.new(0, 0, 1, 2); 252 | Size = UDim2.new(1, 0, 0, 1); 253 | }; 254 | 255 | Blend.New "TextButton" { 256 | Name = "button"; 257 | BackgroundTransparency = 1; 258 | Size = UDim2.fromScale(1, 1); 259 | Visible = props.Enabled; 260 | ZIndex = 5; 261 | 262 | [Blend.OnEvent "Activated"] = function() 263 | self.Activated:Fire() 264 | end; 265 | 266 | [Blend.Instance] = function(button) 267 | self._model:SetButton(button) 268 | end; 269 | }; 270 | } 271 | end 272 | 273 | return FontEntry -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/CommandPalette/Input/Font/FontStyleEntry.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local BasicPane = require("BasicPane") 4 | local Blend = require("Blend") 5 | local ButtonHighlightModel = require("ButtonHighlightModel") 6 | local Fzy = require("Fzy") 7 | local SearchUtils = require("SearchUtils") 8 | local Signal = require("Signal") 9 | local String = require("String") 10 | local ValueObject = require("ValueObject") 11 | 12 | local FontStyleEntry = setmetatable({}, BasicPane) 13 | FontStyleEntry.ClassName = "FontStyleEntry" 14 | FontStyleEntry.CustomResultType = "FontStyle" 15 | FontStyleEntry.__index = FontStyleEntry 16 | 17 | function FontStyleEntry.new() 18 | local self = setmetatable(BasicPane.new(), FontStyleEntry) 19 | 20 | self._compatibleFonts = {} 21 | 22 | self._defaultIndex = self._maid:Add(ValueObject.new(0)) 23 | self._enumWeight = self._maid:Add(ValueObject.new(nil)) 24 | self._fontStyleName = self._maid:Add(ValueObject.new("")) 25 | self._fontWeight = self._maid:Add(ValueObject.new(nil)) 26 | self._fontWeightName = self._maid:Add(ValueObject.new("")) 27 | self._isItalic = self._maid:Add(ValueObject.new(false)) 28 | self._model = self._maid:Add(ButtonHighlightModel.new()) 29 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 30 | self._percentVisibleTarget = self._maid:Add(ValueObject.new(0)) 31 | self._styleCount = self._maid:Add(ValueObject.new(0)) 32 | self._yPosition = self._maid:Add(ValueObject.new(0)) 33 | 34 | self.Activated = self._maid:Add(Signal.new()) 35 | self.SearchScore = self._maid:Add(ValueObject.new(0)) 36 | 37 | self._maid:GiveTask(self.VisibleChanged:Connect(function(isVisible) 38 | self._percentVisibleTarget.Value = isVisible and 1 or 0 39 | end)) 40 | 41 | return self 42 | end 43 | 44 | function FontStyleEntry:SetFontCompatible(font: Font) 45 | self._compatibleFonts[font.Family] = true 46 | end 47 | 48 | function FontStyleEntry:IsFontCompatible(font: Font) 49 | return self._compatibleFonts[font.Family] 50 | end 51 | 52 | function FontStyleEntry:SetStyleName(styleName: string) 53 | if not styleName then 54 | return 55 | end 56 | 57 | self._fontStyleName.Value = styleName 58 | 59 | local formattedName = String.toCamelCase(styleName) 60 | local cleanStyleName = formattedName:gsub("Italic", "") 61 | 62 | local weights = Enum.FontWeight:GetEnumItems() 63 | local weightNames = {} 64 | for _, weight in weights do 65 | weightNames[weight.Name] = true 66 | end 67 | 68 | local isValidWeight = weightNames[cleanStyleName] 69 | local weight 70 | 71 | if not isValidWeight then 72 | if formattedName == "Italic" then 73 | weight = Enum.FontWeight.Regular 74 | isValidWeight = true 75 | elseif cleanStyleName == "Black" then 76 | weight = Enum.FontWeight.Heavy 77 | isValidWeight = true 78 | end 79 | end 80 | 81 | if isValidWeight then 82 | weight = weight or Enum.FontWeight[cleanStyleName] 83 | self._fontWeight.Value = weight.Value 84 | self._fontWeightName.Value = weight.Name 85 | self._enumWeight.Value = weight 86 | end 87 | end 88 | 89 | function FontStyleEntry:GetFontWeight() 90 | return self._fontWeight.Value or 100 91 | end 92 | 93 | function FontStyleEntry:SetIsItalic(isItalic: boolean) 94 | self._isItalic.Value = isItalic 95 | end 96 | 97 | function FontStyleEntry:SetStyleCount(count: number) 98 | self._styleCount.Value = count 99 | end 100 | 101 | function FontStyleEntry:GetYPosition() 102 | return self._yPosition.Value 103 | end 104 | 105 | function FontStyleEntry:SetDefaultIndex(index: number) 106 | self._defaultIndex.Value = index 107 | end 108 | 109 | function FontStyleEntry:SetIsSelected(isSelected: boolean) 110 | self._model:SetIsChoosen(isSelected) 111 | self._model._isMouseOver.Value = isSelected 112 | end 113 | 114 | function FontStyleEntry:GetData() 115 | return self._enumWeight.Value, self._isItalic.Value 116 | end 117 | 118 | function FontStyleEntry:Render(props) 119 | local target = self._percentVisibleTarget:Observe() 120 | 121 | local percentHighlighted = Blend.Spring(self._model:ObservePercentHighlightedTarget(), 35, 0.7) 122 | local percentPressed = Blend.Spring(self._model:ObservePercentPressedTarget(), 35, 0.55) 123 | local percentSelected = Blend.AccelTween(self._model:ObservePercentChoosenTarget(), 400) 124 | 125 | local foregroundColor = Blend.Computed(percentSelected, function(percent) 126 | return Color3.fromRGB(150, 150, 150):Lerp(Color3.fromRGB(255, 255, 255), percent) 127 | end); 128 | 129 | local fzyConfig = props.FzyConfig 130 | local transparency = props.Transparency 131 | 132 | self._maid:GiveTask(Blend.Computed(self._fontStyleName, self._fontWeightName, props.SearchQuery, function(styleName, weightName, searchQuery) 133 | if searchQuery == "" then 134 | self.SearchScore.Value = nil 135 | return 136 | end 137 | 138 | self.SearchScore.Value = Fzy.score(fzyConfig, searchQuery, `{string.lower(styleName)} {string.lower(weightName)}`) 139 | end):Subscribe()) 140 | 141 | return Blend.New "Frame" { 142 | Name = "FontStyleEntry"; 143 | BackgroundTransparency = 1; 144 | Parent = props.TargetParent; 145 | 146 | LayoutOrder = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 147 | return score and -(score * 10000) or defaultIndex 148 | end); 149 | 150 | Size = Blend.Computed(props.EntryHeight, function(height) 151 | if height == 45 then 152 | return UDim2.new(1, 0, 0, 60) 153 | else 154 | return UDim2.new(1, 0, 0, 40) 155 | end 156 | end); 157 | 158 | Visible = Blend.Computed(props.CustomResult, props.Enabled, props.SearchQuery, self.SearchScore, function(customResult, isEnabled, searchQuery, searchScore) 159 | if not customResult then 160 | return false 161 | elseif isEnabled then 162 | if searchQuery ~= "" and searchScore then 163 | return searchScore > 0 164 | else 165 | return true 166 | end 167 | end 168 | 169 | return false 170 | end); 171 | 172 | ZIndex = Blend.Computed(self.SearchScore, self._defaultIndex, function(score, defaultIndex) 173 | return score and -(score * 10000) or defaultIndex 174 | end); 175 | 176 | [Blend.OnChange "AbsolutePosition"] = function(position) 177 | if not self:IsVisible() then 178 | return 179 | end 180 | 181 | self._yPosition.Value = math.round(position.Y) 182 | end; 183 | 184 | Blend.New "Frame" { 185 | Name = "wrapper"; 186 | BackgroundColor3 = Color3.fromRGB(50, 50, 50); 187 | Size = UDim2.fromScale(1, 1); 188 | 189 | BackgroundTransparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 190 | return 1 - percentHighlight + percent 191 | end); 192 | 193 | Blend.New "UIPadding" { 194 | PaddingBottom = UDim.new(0, 14); 195 | PaddingLeft = UDim.new(0, 7); 196 | PaddingRight = UDim.new(0, 7); 197 | PaddingTop = UDim.new(0, 14); 198 | }; 199 | 200 | Blend.New "UIStroke" { 201 | Color = Color3.fromRGB(85, 85, 85); 202 | Transparency = Blend.Computed(transparency, percentHighlighted, function(percent, percentHighlight) 203 | return math.clamp(1 - percentHighlight + percent, 0, 1) 204 | end); 205 | }; 206 | 207 | Blend.New "UICorner" { 208 | CornerRadius = UDim.new(0, 5); 209 | }; 210 | 211 | Blend.New "TextLabel" { 212 | Name = "styles"; 213 | LayoutOrder = 2; 214 | Position = UDim2.fromScale(1, 0.5); 215 | AnchorPoint = Vector2.new(1, 0.5); 216 | Size = UDim2.new(1, 0, 0, 15); 217 | BackgroundTransparency = 1; 218 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 219 | TextColor3 = Color3.fromRGB(85, 85, 85); 220 | TextWrapped = true; 221 | TextXAlignment = Enum.TextXAlignment.Right; 222 | 223 | Text = Blend.Computed(self._fontWeight, function(weight) 224 | return weight or "" 225 | end); 226 | }; 227 | 228 | Blend.New "Frame" { 229 | Name = "container"; 230 | LayoutOrder = 9; 231 | Size = UDim2.fromScale(0.9, 1); 232 | BackgroundTransparency = 1; 233 | 234 | Blend.New "UIListLayout" { 235 | HorizontalAlignment = Enum.HorizontalAlignment.Center; 236 | VerticalAlignment = Enum.VerticalAlignment.Center; 237 | }; 238 | 239 | Blend.New "TextLabel" { 240 | Name = "textPreview"; 241 | AnchorPoint = Vector2.new(0, 0.5); 242 | BackgroundTransparency = 1; 243 | LayoutOrder = 2; 244 | Position = UDim2.fromScale(0, 0.5); 245 | RichText = true; 246 | Size = UDim2.new(1, 0, 0, 20); 247 | TextColor3 = Color3.fromRGB(255, 255, 255); 248 | TextSize = 20; 249 | TextTruncate = Enum.TextTruncate.AtEnd; 250 | TextXAlignment = Enum.TextXAlignment.Left; 251 | 252 | FontFace = Blend.Computed(props.TargetFont, self._enumWeight, self._isItalic, function(font, fontWeight, isItalic) 253 | if not font then 254 | return Font.new("rbxasset://fonts/families/SourceSansPro.json") 255 | end 256 | 257 | local family = font.Family 258 | local style = isItalic and Enum.FontStyle.Italic or Enum.FontStyle.Normal 259 | 260 | return Font.new(family, fontWeight or Enum.FontWeight.Regular, style) 261 | end); 262 | 263 | Text = Blend.Computed(props.EntryHeight, self._fontStyleName, self._fontWeightName, props.SearchQuery, function(height, fontStyleName, fontWeightName, searchQuery) 264 | if height == 45 then 265 | return SearchUtils.getMatchedString(fzyConfig, fontStyleName, searchQuery) 266 | else 267 | return SearchUtils.getMatchedString(fzyConfig, fontWeightName, searchQuery) 268 | end 269 | end); 270 | }; 271 | 272 | Blend.New "TextLabel" { 273 | Name = "fontWeightName"; 274 | AnchorPoint = Vector2.new(1, 0.5); 275 | BackgroundTransparency = 1; 276 | FontFace = Font.new("rbxasset://fonts/families/SourceSansPro.json"); 277 | LayoutOrder = 2; 278 | Position = UDim2.fromScale(1, 0.5); 279 | RichText = true; 280 | Size = UDim2.new(1, 0, 0, 15); 281 | TextColor3 = Color3.fromRGB(110, 110, 110); 282 | TextWrapped = true; 283 | TextXAlignment = Enum.TextXAlignment.Left; 284 | 285 | Text = Blend.Computed(self._fontWeightName, props.SearchQuery, function(fontWeightName, searchQuery) 286 | if searchQuery ~= "" then 287 | return SearchUtils.getMatchedString(fzyConfig, fontWeightName, searchQuery) 288 | else 289 | return fontWeightName 290 | end 291 | end); 292 | 293 | Visible = Blend.Computed(props.EntryHeight, function(height) 294 | return height == 45 295 | end); 296 | }; 297 | }; 298 | }; 299 | 300 | Blend.New "Frame" { 301 | Name = "divider"; 302 | AnchorPoint = Vector2.new(0, 1); 303 | BackgroundColor3 = Color3.fromRGB(35, 35, 35); 304 | BackgroundTransparency = transparency; 305 | Position = UDim2.new(0, 0, 1, 2); 306 | Size = UDim2.new(1, 0, 0, 1); 307 | }; 308 | 309 | Blend.New "TextButton" { 310 | Name = "button"; 311 | BackgroundTransparency = 1; 312 | Size = UDim2.fromScale(1, 1); 313 | Visible = props.Enabled; 314 | ZIndex = 5; 315 | 316 | [Blend.OnEvent "Activated"] = function() 317 | self.Activated:Fire() 318 | end; 319 | 320 | [Blend.Instance] = function(button) 321 | self._model:SetButton(button) 322 | end; 323 | }; 324 | } 325 | end 326 | 327 | return FontStyleEntry -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/Fonts/FontData.luau: -------------------------------------------------------------------------------- 1 | return { 2 | [12187364842] = { 3 | Name = "Unica One"; 4 | Styles = { "Regular" }; 5 | }; 6 | 7 | [12187366475] = { 8 | Name = "Rubik Maze"; 9 | Styles = { "Regular" }; 10 | }; 11 | 12 | [12187364147] = { 13 | Name = "IBM Plex Sans JP"; 14 | Styles = { 15 | "Thin", 16 | "Extra Light", 17 | "Light", 18 | "Regular", 19 | "Medium", 20 | "Semi Bold", 21 | "Bold" 22 | }; 23 | }; 24 | 25 | [12187369639] = { 26 | Name = "Noto Serif JP"; 27 | Styles = { "Extra Light", "Light", "Regular", "Medium", "Semi Bold", "Bold", "Black" }; 28 | }; 29 | 30 | [12187361943] = { 31 | Name = "Parisienne"; 32 | Styles = { "Regular" }; 33 | }; 34 | 35 | [12187367666] = { 36 | Name = "Bungee Shade"; 37 | Styles = { "Regular" }; 38 | }; 39 | 40 | [11702779240] = { 41 | Name = "Raleway"; 42 | Styles = { 43 | "Thin", 44 | "Thin Italic", 45 | "Extra Light", 46 | "Extra Light Italic", 47 | "Light", 48 | "Light Italic", 49 | "Regular", 50 | "Italic", 51 | "Medium", 52 | "Medium Italic", 53 | "Semi Bold", 54 | "Semi Bold Italic", 55 | "Bold", 56 | "Bold Italic", 57 | "Extra Bold", 58 | "Extra Bold Italic", 59 | "Black", 60 | "Black Italic" 61 | }; 62 | }; 63 | 64 | [11322590111] = { 65 | Name = "Fuzzy Bubbles"; 66 | Styles = { "Regular", "Bold" }; 67 | }; 68 | 69 | [12187366846] = { 70 | Name = "Noto Serif HK"; 71 | Styles = { 72 | "Extra Light", 73 | "Light", 74 | "Regular", 75 | "Medium", 76 | "Semi Bold", 77 | "Bold", 78 | "Extra Bold", 79 | "Black" 80 | }; 81 | }; 82 | 83 | [12187363148] = { 84 | Name = "Rubik Burned"; 85 | Styles = { "Regular" }; 86 | }; 87 | 88 | [12187371622] = { 89 | Name = "Kings"; 90 | Styles = { "Regular" }; 91 | }; 92 | 93 | [12187363616] = { 94 | Name = "Are You Serious"; 95 | Styles = { "Regular" }; 96 | }; 97 | 98 | [12187370000] = { 99 | Name = "Bungee Inline"; 100 | Styles = { "Regular" }; 101 | }; 102 | 103 | [12187374537] = { 104 | Name = "Sono"; 105 | Styles = { 106 | "Extra Light", 107 | "Light", 108 | "Regular", 109 | "Medium", 110 | "Semi Bold", 111 | "Bold", 112 | "Extra Bold" 113 | }; 114 | }; 115 | 116 | [12187375422] = { 117 | Name = "Rajdhani"; 118 | Styles = { 119 | "Light", 120 | "Regular", 121 | "Medium", 122 | "Semi Bold", 123 | "Bold" 124 | }; 125 | }; 126 | 127 | [12187365977] = { 128 | Name = "Rubik"; 129 | Styles = { 130 | "Light", 131 | "Light Italic", 132 | "Regular", 133 | "Italic", 134 | "Medium", 135 | "Medium Italic", 136 | "Semi Bold", 137 | "Semi Bold Italic", 138 | "Bold", 139 | "Bold Italic", 140 | "Extra Bold", 141 | "Extra Bold Italic", 142 | "Black", 143 | "Black Italic" 144 | }; 145 | }; 146 | 147 | [12187367066] = { 148 | Name = "Rubik Marker Hatch"; 149 | Styles = { "Regular" }; 150 | }; 151 | 152 | [12187607287] = { 153 | Name = "Prompt"; 154 | Styles = { 155 | "Thin", 156 | "Thin Italic", 157 | "Extra Light", 158 | "Extra Light Italic", 159 | "Light", 160 | "Light Italic", 161 | "Regular", 162 | "Italic", 163 | "Medium", 164 | "Medium Italic", 165 | "Semi Bold", 166 | "Semi Bold Italic", 167 | "Bold", 168 | "Bold Italic", 169 | "Extra Bold", 170 | "Extra Bold Italic", 171 | "Black", 172 | "Black Italic" 173 | }; 174 | }; 175 | 176 | [12187377588] = { 177 | Name = "Tajawal"; 178 | Styles = { 179 | "Extra Light", 180 | "Light", 181 | "Regular", 182 | "Medium", 183 | "Bold", 184 | "Extra Bold", 185 | "Black" 186 | }; 187 | }; 188 | 189 | [12187361116] = { 190 | Name = "Hind"; 191 | Styles = { 192 | "Light", 193 | "Regular", 194 | "Medium", 195 | "Semi Bold", 196 | "Bold" 197 | }; 198 | }; 199 | 200 | [12187370747] = { 201 | Name = "Noto Sans"; 202 | Styles = { 203 | "Thin", 204 | "Thin Italic", 205 | "Extra Light", 206 | "Extra Light Italic", 207 | "Light", 208 | "Light Italic", 209 | "Regular", 210 | "Italic", 211 | "Medium", 212 | "Medium Italic", 213 | "Semi Bold", 214 | "Semi Bold Italic", 215 | "Bold", 216 | "Bold Italic", 217 | "Extra Bold", 218 | "Extra Bold Italic", 219 | "Black", 220 | "Black Italic" 221 | }; 222 | }; 223 | 224 | [12187372847] = { 225 | Name = "Barlow"; 226 | Styles = { 227 | "Thin", 228 | "Thin Italic", 229 | "Extra Light", 230 | "Extra Light Italic", 231 | "Light", 232 | "Light Italic", 233 | "Regular", 234 | "Italic", 235 | "Medium", 236 | "Medium Italic", 237 | "Semi Bold", 238 | "Semi Bold Italic", 239 | "Bold", 240 | "Bold Italic", 241 | "Extra Bold", 242 | "Extra Bold Italic", 243 | "Black", 244 | "Black Italic" 245 | }; 246 | }; 247 | 248 | [12187368625] = { 249 | Name = "Roboto Slab"; 250 | Styles = { 251 | "Thin", 252 | "Extra Light", 253 | "Light", 254 | "Regular", 255 | "Medium", 256 | "Semi Bold", 257 | "Bold", 258 | "Extra Bold", 259 | "Black" 260 | }; 261 | }; 262 | 263 | [12187376545] = { 264 | Name = "Tangerine"; 265 | Styles = { "Regular", "Bold" }; 266 | }; 267 | 268 | [12187376739] = { 269 | Name = "Noto Serif SC"; 270 | Styles = { 271 | "Extra Light", 272 | "Light", 273 | "Regular", 274 | "Medium", 275 | "Semi Bold", 276 | "Bold", 277 | "Black" 278 | }; 279 | }; 280 | 281 | [11702779517] = { 282 | Name = "Montserrat"; 283 | Styles = { 284 | "Thin", 285 | "Thin Italic", 286 | "Extra Light", 287 | "Extra Light Italic", 288 | "Light", 289 | "Light Italic", 290 | "Regular", 291 | "Italic", 292 | "Medium", 293 | "Medium Italic", 294 | "Semi Bold", 295 | "Semi Bold Italic", 296 | "Bold", 297 | "Bold Italic", 298 | "Extra Bold", 299 | "Extra Bold Italic", 300 | "Black", 301 | "Black Italic" 302 | }; 303 | }; 304 | 305 | [12187372629] = { 306 | Name = "Mulish"; 307 | Styles = { 308 | "Extra Light", 309 | "Extra Light Italic", 310 | "Light", 311 | "Light Italic", 312 | "Regular", 313 | "Italic", 314 | "Medium", 315 | "Medium Italic", 316 | "Semi Bold", 317 | "Semi Bold Italic", 318 | "Bold", 319 | "Bold Italic", 320 | "Extra Bold", 321 | "Extra Bold Italic", 322 | "Black", 323 | "Black Italic" 324 | }; 325 | }; 326 | 327 | [12187607722] = { 328 | Name = "Damion"; 329 | Styles = { "Regular" }; 330 | }; 331 | 332 | [12187377099] = { 333 | Name = "Cairo"; 334 | Styles = { 335 | "Extra Light", 336 | "Light", 337 | "Regular", 338 | "Medium", 339 | "Semi Bold", 340 | "Bold", 341 | "Extra Bold", 342 | "Black" 343 | }; 344 | }; 345 | 346 | [12187365559] = { 347 | Name = "Mukta"; 348 | Styles = { 349 | "Extra Light", 350 | "Light", 351 | "Regular", 352 | "Medium", 353 | "Semi Bold", 354 | "Bold", 355 | "Extra Bold" 356 | }; 357 | }; 358 | 359 | [12187606783] = { 360 | Name = "Monofett"; 361 | Styles = { "Regular" }; 362 | }; 363 | 364 | [12187371991] = { 365 | Name = "Barrio"; 366 | Styles = { "Regular" }; 367 | }; 368 | 369 | [11598121416] = { 370 | Name = "Open Sans"; 371 | Styles = { 372 | "Light", 373 | "Light Italic", 374 | "Regular", 375 | "Italic", 376 | "Medium", 377 | "Medium Italic", 378 | "Semi Bold", 379 | "Semi Bold Italic", 380 | "Bold", 381 | "Bold Italic", 382 | "Extra Bold", 383 | "Extra Bold Italic" 384 | }; 385 | }; 386 | 387 | [12187376357] = { 388 | Name = "Sedgwick Ave Display"; 389 | Styles = { "Regular" }; 390 | }; 391 | 392 | [12187375716] = { 393 | Name = "Finger Paint"; 394 | Styles = { "Regular" }; 395 | }; 396 | 397 | [12187372382] = { 398 | Name = "Eater"; 399 | Styles = { "Regular" }; 400 | }; 401 | 402 | [12187373327] = { 403 | Name = "Work Sans"; 404 | Styles = { 405 | "Thin", 406 | "Thin Italic", 407 | "Extra Light", 408 | "Extra Light Italic", 409 | "Light", 410 | "Light Italic", 411 | "Regular", 412 | "Italic", 413 | "Medium", 414 | "Medium Italic", 415 | "Semi Bold", 416 | "Semi Bold Italic", 417 | "Bold", 418 | "Bold Italic", 419 | "Extra Bold", 420 | "Extra Bold Italic", 421 | "Black", 422 | "Black Italic" 423 | }; 424 | }; 425 | 426 | [12187368843] = { 427 | Name = "Caesar Dressing"; 428 | Styles = { "Regular" }; 429 | }; 430 | 431 | [12187373592] = { 432 | Name = "Kanit"; 433 | Styles = { 434 | "Thin", 435 | "Thin Italic", 436 | "Extra Light", 437 | "Extra Light Italic", 438 | "Light", 439 | "Light Italic", 440 | "Regular", 441 | "Italic", 442 | "Medium", 443 | "Medium Italic", 444 | "Semi Bold", 445 | "Semi Bold Italic", 446 | "Bold", 447 | "Bold Italic", 448 | "Extra Bold", 449 | "Extra Bold Italic", 450 | "Black", 451 | "Black Italic" 452 | }; 453 | }; 454 | 455 | [16658254058] = { 456 | Name = "Arimo"; 457 | Styles = { 458 | "Regular", 459 | "Medium", 460 | "Semi Bold", 461 | "Bold", 462 | "Italic", 463 | "Medium Italic", 464 | "Semi Bold Italic", 465 | "Bold Italic" 466 | }; 467 | }; 468 | 469 | [12187361378] = { 470 | Name = "Hind Siliguri"; 471 | Styles = { 472 | "Light", 473 | "Regular", 474 | "Medium", 475 | "Semi Bold", 476 | "Bold" 477 | }; 478 | }; 479 | 480 | [12187362120] = { 481 | Name = "Rubik Iso"; 482 | Styles = { "Regular" }; 483 | }; 484 | 485 | [8836875837] = { 486 | Name = "Lobster"; 487 | Styles = { "Regular" }; 488 | }; 489 | 490 | [12187374765] = { 491 | Name = "Playfair Display"; 492 | Styles = { 493 | "Regular", 494 | "Italic", 495 | "Medium", 496 | "Medium Italic", 497 | "Semi Bold", 498 | "Semi Bold Italic", 499 | "Bold", 500 | "Bold Italic", 501 | "Extra Bold", 502 | "Extra Bold Italic", 503 | "Black", 504 | "Black Italic" 505 | }; 506 | }; 507 | 508 | [12187374273] = { 509 | Name = "Italianno"; 510 | Styles = { "Regular" }; 511 | }; 512 | 513 | [12187606624] = { 514 | Name = "PT Serif"; 515 | Styles = { 516 | "Regular", 517 | "Italic", 518 | "Bold", 519 | "Bold Italic" 520 | }; 521 | }; 522 | 523 | [12187371840] = { 524 | Name = "Silkscreen"; 525 | Styles = { "Regular", "Bold" }; 526 | }; 527 | 528 | [12187607493] = { 529 | Name = "Shadows Into Light"; 530 | Styles = { "Regular" }; 531 | }; 532 | 533 | [12187365104] = { 534 | Name = "Blaka"; 535 | Styles = { "Regular" }; 536 | }; 537 | 538 | [12187377325] = { 539 | Name = "Nosifer"; 540 | Styles = { "Regular" }; 541 | }; 542 | 543 | [12187368093] = { 544 | Name = "Noto Serif TC"; 545 | Styles = { 546 | "Extra Light", 547 | "Light", 548 | "Regular", 549 | "Medium", 550 | "Semi Bold", 551 | "Bold", 552 | "Black" 553 | }; 554 | }; 555 | 556 | [12187373881] = { 557 | Name = "Yellowtail"; 558 | Styles = { "Regular" }; 559 | }; 560 | 561 | [12187367362] = { 562 | Name = "Pacifico"; 563 | Styles = { "Regular" }; 564 | }; 565 | 566 | [12187363887] = { 567 | Name = "Codystar"; 568 | Styles = { "Light", "Regular" }; 569 | }; 570 | 571 | [12187369802] = { 572 | Name = "Caveat"; 573 | Styles = { 574 | "Regular", 575 | "Medium", 576 | "Semi Bold", 577 | "Bold" 578 | }; 579 | }; 580 | 581 | [12187364648] = { 582 | Name = "Marhey"; 583 | Styles = { 584 | "Light", 585 | "Regular", 586 | "Medium", 587 | "Semi Bold", 588 | "Bold" 589 | }; 590 | }; 591 | 592 | [12187607116] = { 593 | Name = "La Belle Aurore"; 594 | Styles = { "Regular" }; 595 | }; 596 | 597 | [12187362578] = { 598 | Name = "Sono Monospace"; 599 | Styles = { 600 | "Extra Light", 601 | "Light", 602 | "Regular", 603 | "Medium", 604 | "Semi Bold", 605 | "Bold", 606 | "Extra Bold" 607 | }; 608 | }; 609 | 610 | [16658237174] = { 611 | Name = "Builder Extended"; 612 | Styles = { 613 | "Light", 614 | "Regular", 615 | "Semi Bold", 616 | "Bold", 617 | "Extra Bold" 618 | }; 619 | }; 620 | 621 | [12188570269] = { 622 | Name = "M PLUS Rounded 1c"; 623 | Styles = { 624 | "Thin", 625 | "Light", 626 | "Regular", 627 | "Medium", 628 | "Bold", 629 | "Extra Bold", 630 | "Black" 631 | }; 632 | }; 633 | 634 | [12187363368] = { 635 | Name = "Nunito Sans"; 636 | Styles = { 637 | "Extra Light", 638 | "Extra Light Italic", 639 | "Light", 640 | "Light Italic", 641 | "Regular", 642 | "Italic", 643 | "Semi Bold", 644 | "Semi Bold Italic", 645 | "Bold", 646 | "Bold Italic", 647 | "Extra Bold", 648 | "Extra Bold Italic", 649 | "Black", 650 | "Black Italic" 651 | }; 652 | }; 653 | 654 | [12187374098] = { 655 | Name = "Monoton"; 656 | Styles = { "Regular" }; 657 | }; 658 | 659 | [11598289817] = { 660 | Name = "Lato"; 661 | Styles = { 662 | "Thin", 663 | "Thin Italic", 664 | "Light", 665 | "Light Italic", 666 | "Regular", 667 | "Italic", 668 | "Bold", 669 | "Bold Italic", 670 | "Black", 671 | "Black Italic" 672 | }; 673 | }; 674 | 675 | [16658246179] = { 676 | Name = "Builder Mono"; 677 | Styles = { "Light", "Regular", "Bold" }; 678 | }; 679 | 680 | [12187376174] = { 681 | Name = "Teko"; 682 | Styles = { 683 | "Light", 684 | "Regular", 685 | "Medium", 686 | "Semi Bold", 687 | "Bold" 688 | }; 689 | }; 690 | 691 | [8764312106] = { 692 | Name = "Dancing Script"; 693 | Styles = { 694 | "Regular", 695 | "Medium", 696 | "Semi Bold", 697 | "Bold" 698 | }; 699 | }; 700 | 701 | [12187372175] = { 702 | Name = "Rye"; 703 | Styles = { "Regular" }; 704 | }; 705 | 706 | [12187360881] = { 707 | Name = "Audiowide"; 708 | Styles = { "Regular" }; 709 | }; 710 | 711 | [12187367901] = { 712 | Name = "Nothing You Could Do"; 713 | Styles = { "Regular" }; 714 | }; 715 | 716 | [12187376910] = { 717 | Name = "Irish Grover"; 718 | Styles = { "Regular" }; 719 | }; 720 | 721 | [12187368317] = { 722 | Name = "Akronim"; 723 | Styles = { "Regular" }; 724 | }; 725 | 726 | [12187374954] = { 727 | Name = "Fira Sans"; 728 | Styles = { 729 | "Thin", 730 | "Thin Italic", 731 | "Extra Light", 732 | "Extra Light Italic", 733 | "Light", 734 | "Light Italic", 735 | "Regular", 736 | "Italic", 737 | "Medium", 738 | "Medium Italic", 739 | "Semi Bold", 740 | "Semi Bold Italic", 741 | "Bold", 742 | "Bold Italic", 743 | "Extra Bold", 744 | "Extra Bold Italic", 745 | "Black", 746 | "Black Italic" 747 | }; 748 | }; 749 | 750 | [12187370928] = { 751 | Name = "Faster One"; 752 | Styles = { "Regular" }; 753 | }; 754 | 755 | [12187361718] = { 756 | Name = "Nanum Gothic"; 757 | Styles = { "Regular", "Bold", "Extra Bold" }; 758 | }; 759 | 760 | [12187369046] = { 761 | Name = "Rubik Wet Paint"; 762 | Styles = { "Regular" }; 763 | }; 764 | 765 | [11702779409] = { 766 | Name = "Poppins"; 767 | Styles = { 768 | "Thin", 769 | "Thin Italic", 770 | "Extra Light", 771 | "Extra Light Italic", 772 | "Light", 773 | "Light Italic", 774 | "Regular", 775 | "Italic", 776 | "Medium", 777 | "Medium Italic", 778 | "Semi Bold", 779 | "Semi Bold Italic", 780 | "Bold", 781 | "Bold Italic", 782 | "Extra Bold", 783 | "Extra Bold Italic", 784 | "Black", 785 | "Black Italic" 786 | }; 787 | }; 788 | 789 | [12187366657] = { 790 | Name = "Lora"; 791 | Styles = { 792 | "Regular", 793 | "Italic", 794 | "Medium", 795 | "Medium Italic", 796 | "Semi Bold", 797 | "Semi Bold Italic", 798 | "Bold", 799 | "Bold Italic" 800 | }; 801 | }; 802 | 803 | [12187365769] = { 804 | Name = "Libre Baskerville"; 805 | Styles = { "Regular", "Italic", "Bold" }; 806 | }; 807 | 808 | [12187606934] = { 809 | Name = "PT Sans"; 810 | Styles = { 811 | "Regular", 812 | "Italic", 813 | "Bold", 814 | "Bold Italic" 815 | }; 816 | }; 817 | 818 | [16658221428] = { 819 | Name = "Builder Sans"; 820 | Styles = { 821 | "Thin", 822 | "Light", 823 | "Regular", 824 | "Medium", 825 | "Semi Bold", 826 | "Bold", 827 | "Extra Bold" 828 | }; 829 | }; 830 | 831 | [12187362892] = { 832 | Name = "Noto Sans HK"; 833 | Styles = { 834 | "Thin", 835 | "Light", 836 | "Regular", 837 | "Medium", 838 | "Bold", 839 | "Black" 840 | }; 841 | }; 842 | 843 | [12187365364] = { 844 | Name = "Inter"; 845 | Styles = { 846 | "Thin", 847 | "Extra Light", 848 | "Light", 849 | "Regular", 850 | "Medium", 851 | "Semi Bold", 852 | "Bold", 853 | "Extra Bold", 854 | "Black" 855 | }; 856 | }; 857 | 858 | [12187371324] = { 859 | Name = "Quicksand"; 860 | Styles = { "Light", "Regular", "Medium", "Semi Bold", "Bold" }; 861 | }; 862 | 863 | [12187375194] = { 864 | Name = "Frijole"; 865 | Styles = { "Regular" }; 866 | }; 867 | 868 | [12187375958] = { 869 | Name = "Great Vibes"; 870 | Styles = { "Regular" }; 871 | }; 872 | 873 | ["rbxasset://fonts/families/AccanthisADFStd.json"] = { 874 | Name = "Accanthis ADF Std"; 875 | Styles = { "Regular" }; 876 | }; 877 | 878 | ["rbxasset://fonts/families/AmaticSC.json"] = { 879 | Name = "Amatic SC"; 880 | Styles = { "Regular", "Bold" }; 881 | }; 882 | 883 | ["rbxasset://fonts/families/Arial.json"] = { 884 | Name = "Arial"; 885 | Styles = { "Regular", "Bold" }; 886 | }; 887 | 888 | ["rbxasset://fonts/families/LegacyArial.json"] = { 889 | Name = "Arial (Legacy)"; 890 | Styles = { "Regular", "Bold" }; 891 | }; 892 | 893 | ["rbxasset://fonts/families/Balthazar.json"] = { 894 | Name = "Balthazar"; 895 | Styles = { "Regular" }; 896 | }; 897 | 898 | ["rbxasset://fonts/families/Bangers.json"] = { 899 | Name = "Bangers"; 900 | Styles = { "Regular" }; 901 | }; 902 | 903 | ["rbxasset://fonts/families/ComicNeueAngular.json"] = { 904 | Name = "Comic Neue Angular"; 905 | Styles = { "Bold" }; 906 | }; 907 | 908 | ["rbxasset://fonts/families/Creepster.json"] = { 909 | Name = "Creepster"; 910 | Styles = { "Regular" }; 911 | }; 912 | 913 | ["rbxasset://fonts/families/DenkOne.json"] = { 914 | Name = "Denk One"; 915 | Styles = { "Regular" }; 916 | }; 917 | 918 | ["rbxasset://fonts/families/Fondamento.json"] = { 919 | Name = "Fondamento"; 920 | Styles = { "Regular" }; 921 | }; 922 | 923 | ["rbxasset://fonts/families/FredokaOne.json"] = { 924 | Name = "Fredoka One"; 925 | Styles = { "Regular" }; 926 | }; 927 | 928 | ["rbxasset://fonts/families/GothamSSm.json"] = { 929 | Name = "Gotham SSm"; 930 | Styles = { 931 | "Regular", 932 | "Medium", 933 | "Bold", 934 | "Black" 935 | }; 936 | }; 937 | 938 | ["rbxasset://fonts/families/GrenzeGotisch.json"] = { 939 | Name = "Grenze Gotisch"; 940 | Styles = { 941 | "Thin", 942 | "Extra Light", 943 | "Light", 944 | "Regular", 945 | "Medium", 946 | "Semi Bold", 947 | "Bold", 948 | "Extra Bold", 949 | "Black" 950 | }; 951 | }; 952 | 953 | ["rbxasset://fonts/families/Guru.json"] = { 954 | Name = "Guru"; 955 | Styles = { "Regular" }; 956 | }; 957 | 958 | ["rbxasset://fonts/families/HighwayGothic.json"] = { 959 | Name = "Highway Gothic"; 960 | Styles = { "Regular" }; 961 | }; 962 | 963 | ["rbxasset://fonts/families/Inconsolata.json"] = { 964 | Name = "Inconsolata"; 965 | Styles = { 966 | "Extra Light", 967 | "Light", 968 | "Regular", 969 | "Medium", 970 | "Semi Bold", 971 | "Bold", 972 | "Extra Bold", 973 | "Black" 974 | }; 975 | }; 976 | 977 | ["rbxasset://fonts/families/IndieFlower.json"] = { 978 | Name = "Indie Flower"; 979 | Styles = { "Regular" }; 980 | }; 981 | 982 | ["rbxasset://fonts/families/JosefinSans.json"] = { 983 | Name = "Josefin Sans"; 984 | Styles = { 985 | "Thin", 986 | "Extra Light", 987 | "Light", 988 | "Regular", 989 | "Medium", 990 | "Semi Bold", 991 | "Bold" 992 | }; 993 | }; 994 | 995 | ["rbxasset://fonts/families/Jura.json"] = { 996 | Name = "Jura"; 997 | Styles = { 998 | "Light", 999 | "Regular", 1000 | "Medium", 1001 | "Semi Bold", 1002 | "Bold" 1003 | }; 1004 | }; 1005 | 1006 | ["rbxasset://fonts/families/Kalam.json"] = { 1007 | Name = "Kalam"; 1008 | Styles = { 1009 | "Light", 1010 | "Regular", 1011 | "Bold" 1012 | }; 1013 | }; 1014 | 1015 | ["rbxasset://fonts/families/LuckiestGuy.json"] = { 1016 | Name = "Luckiest Guy"; 1017 | Styles = { "Regular" }; 1018 | }; 1019 | 1020 | ["rbxasset://fonts/families/Merriweather.json"] = { 1021 | Name = "Merriweather"; 1022 | Styles = { 1023 | "Light", 1024 | "Regular", 1025 | "Bold", 1026 | "Black" 1027 | }; 1028 | }; 1029 | 1030 | ["rbxasset://fonts/families/Michroma.json"] = { 1031 | Name = "Michroma"; 1032 | Styles = { "Regular" }; 1033 | }; 1034 | 1035 | ["rbxasset://fonts/families/Oswald.json"] = { 1036 | Name = "Oswald"; 1037 | Styles = { 1038 | "Extra Light", 1039 | "Light", 1040 | "Regular", 1041 | "Medium", 1042 | "Semi Bold", 1043 | "Bold" 1044 | }; 1045 | }; 1046 | 1047 | ["rbxasset://fonts/families/PatrickHand.json"] = { 1048 | Name = "Patrick Hand"; 1049 | Styles = { "Regular" }; 1050 | }; 1051 | 1052 | ["rbxasset://fonts/families/PermanentMarker.json"] = { 1053 | Name = "Permanent Marker"; 1054 | Styles = { "Regular" }; 1055 | }; 1056 | 1057 | ["rbxasset://fonts/families/PressStart2P.json"] = { 1058 | Name = "Press Start 2P"; 1059 | Styles = { "Regular" }; 1060 | }; 1061 | 1062 | ["rbxasset://fonts/families/Roboto.json"] = { 1063 | Name = "Roboto"; 1064 | Styles = { 1065 | "Thin", 1066 | "Light", 1067 | "Regular", 1068 | "Medium", 1069 | "Bold", 1070 | "Black" 1071 | }; 1072 | }; 1073 | 1074 | ["rbxasset://fonts/families/RobotoCondensed.json"] = { 1075 | Name = "Roboto Condensed"; 1076 | Styles = { "Light", "Regular", "Bold" }; 1077 | }; 1078 | 1079 | ["rbxasset://fonts/families/RobotoMono.json"] = { 1080 | Name = "Roboto Mono"; 1081 | Styles = { 1082 | "Thin", 1083 | "Extra Light", 1084 | "Light", 1085 | "Regular", 1086 | "Medium", 1087 | "Semi Bold", 1088 | "Bold" 1089 | }; 1090 | }; 1091 | 1092 | ["rbxasset://fonts/families/RomanAntique.json"] = { 1093 | Name = "Roman Antique"; 1094 | Styles = { "Regular" }; 1095 | }; 1096 | 1097 | ["rbxasset://fonts/families/Sarpanch.json"] = { 1098 | Name = "Sarpanch"; 1099 | Styles = { 1100 | "Regular", 1101 | "Medium", 1102 | "Semi Bold", 1103 | "Bold", 1104 | "Extra Bold", 1105 | "Black" 1106 | }; 1107 | }; 1108 | 1109 | ["rbxasset://fonts/families/SourceSansPro.json"] = { 1110 | Name = "Source Sans Pro"; 1111 | Styles = { 1112 | "Extra Light", 1113 | "Light", 1114 | "Regular", 1115 | "Semi Bold", 1116 | "Bold", 1117 | "Black" 1118 | }; 1119 | }; 1120 | 1121 | ["rbxasset://fonts/families/SpecialElite.json"] = { 1122 | Name = "Special Elite"; 1123 | Styles = { "Regular" }; 1124 | }; 1125 | 1126 | ["rbxasset://fonts/families/TitilliumWeb.json"] = { 1127 | Name = "Titillium Web"; 1128 | Styles = { 1129 | "Extra Light", 1130 | "Light", 1131 | "Regular", 1132 | "Semi Bold", 1133 | "Bold", 1134 | "Black" 1135 | }; 1136 | }; 1137 | 1138 | ["rbxasset://fonts/families/Ubuntu.json"] = { 1139 | Name = "Ubuntu"; 1140 | Styles = { 1141 | "Light", 1142 | "Regular", 1143 | "Medium", 1144 | "Bold" 1145 | }; 1146 | }; 1147 | 1148 | ["rbxasset://fonts/families/Zekton.json"] = { 1149 | Name = "Zekton"; 1150 | Styles = { "Regular" }; 1151 | }; 1152 | 1153 | --[[ 1154 | [0] = { 1155 | Name = ""; 1156 | Styles = {}; 1157 | }; 1158 | --]] 1159 | } -------------------------------------------------------------------------------- /src/StudioMacros/modules/Shared/SearchUtils.luau: -------------------------------------------------------------------------------- 1 | local require = require(script.Parent.loader).load(script) 2 | 3 | local Fzy = require("Fzy") 4 | 5 | local SearchUtils = {} 6 | 7 | function SearchUtils.getMatchedString(fzyConfig, targetString, searchQuery) 8 | if not fzyConfig or not targetString or not searchQuery then 9 | return 10 | end 11 | 12 | searchQuery = string.lower(searchQuery) 13 | 14 | local positions = Fzy.positions(fzyConfig, searchQuery, string.lower(targetString)) 15 | if not positions or #positions == 0 then 16 | return targetString 17 | end 18 | 19 | local ranges = {} 20 | local startPos, endPos = positions[1], positions[1] 21 | 22 | for i = 2, #positions do 23 | if positions[i] == endPos + 1 then 24 | endPos = positions[i] 25 | 26 | if i == #positions then 27 | table.insert(ranges, { startPos, endPos }) 28 | end 29 | else 30 | table.insert(ranges, { startPos, endPos }) 31 | startPos = positions[i] 32 | endPos = positions[i] 33 | end 34 | end 35 | 36 | if #ranges == 0 then 37 | return targetString 38 | end 39 | 40 | local finalString = "" 41 | 42 | for index, range in ranges do 43 | local start = string.sub(targetString, index == 1 and 0 or ranges[index - 1][2] + 1, range[1] - 1) 44 | local matched = string.sub(targetString, range[1], range[2]) 45 | 46 | finalString ..= `{start}{matched}` 47 | 48 | if index == #ranges then 49 | finalString ..= string.sub(targetString, range[2] + 1, string.len(targetString)) 50 | end 51 | end 52 | 53 | return finalString 54 | end 55 | 56 | function SearchUtils.stringIsRGB(str) 57 | if not str then 58 | return 59 | end 60 | 61 | local pattern = "^%s*(%d+)%s*,%s*(%d+)%s*,%s*(%d+)%s*$" 62 | local r, g, b = string.match(str, pattern) 63 | if r and g and b then 64 | r, g, b = tonumber(r), tonumber(g), tonumber(b) 65 | local isRGB = r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 66 | return isRGB, r, g, b 67 | end 68 | return false 69 | end 70 | 71 | function SearchUtils.stringIsHex(str) 72 | if not str then 73 | return 74 | end 75 | 76 | str = string.lower(str) 77 | 78 | local pattern = "^%s*#?([0-9a-f]+)%s*$" 79 | local hex = string.match(str, pattern) 80 | if hex then 81 | local len = #hex 82 | return (len == 6 or len == 3), hex 83 | end 84 | return false 85 | end 86 | 87 | return SearchUtils -------------------------------------------------------------------------------- /src/StudioMacros/modules/node_modules.project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node_modules", 3 | "globIgnorePaths": [ "**/.package-lock.json" ], 4 | "tree": { 5 | "$path": { "optional": "../../../node_modules" } 6 | } 7 | } --------------------------------------------------------------------------------