azul-sync 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. package/README.md +142 -0
  5. package/dist/build.d.ts +19 -0
  6. package/dist/build.d.ts.map +1 -0
  7. package/dist/build.js +92 -0
  8. package/dist/build.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +397 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/config.d.ts +26 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +105 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/fs/fileWriter.d.ts +100 -0
  18. package/dist/fs/fileWriter.d.ts.map +1 -0
  19. package/dist/fs/fileWriter.js +342 -0
  20. package/dist/fs/fileWriter.js.map +1 -0
  21. package/dist/fs/treeManager.d.ts +84 -0
  22. package/dist/fs/treeManager.d.ts.map +1 -0
  23. package/dist/fs/treeManager.js +365 -0
  24. package/dist/fs/treeManager.js.map +1 -0
  25. package/dist/fs/watcher.d.ts +39 -0
  26. package/dist/fs/watcher.d.ts.map +1 -0
  27. package/dist/fs/watcher.js +120 -0
  28. package/dist/fs/watcher.js.map +1 -0
  29. package/dist/index.d.ts +61 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +349 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/ipc/httpPolling.d.ts +56 -0
  34. package/dist/ipc/httpPolling.d.ts.map +1 -0
  35. package/dist/ipc/httpPolling.js +171 -0
  36. package/dist/ipc/httpPolling.js.map +1 -0
  37. package/dist/ipc/messages.d.ts +112 -0
  38. package/dist/ipc/messages.d.ts.map +1 -0
  39. package/dist/ipc/messages.js +5 -0
  40. package/dist/ipc/messages.js.map +1 -0
  41. package/dist/ipc/server.d.ts +50 -0
  42. package/dist/ipc/server.d.ts.map +1 -0
  43. package/dist/ipc/server.js +168 -0
  44. package/dist/ipc/server.js.map +1 -0
  45. package/dist/pack.d.ts +19 -0
  46. package/dist/pack.d.ts.map +1 -0
  47. package/dist/pack.js +225 -0
  48. package/dist/pack.js.map +1 -0
  49. package/dist/push.d.ts +43 -0
  50. package/dist/push.d.ts.map +1 -0
  51. package/dist/push.js +532 -0
  52. package/dist/push.js.map +1 -0
  53. package/dist/rojo.d.ts +9 -0
  54. package/dist/rojo.d.ts.map +1 -0
  55. package/dist/rojo.js +114 -0
  56. package/dist/rojo.js.map +1 -0
  57. package/dist/snapshot/rojo.d.ts +39 -0
  58. package/dist/snapshot/rojo.d.ts.map +1 -0
  59. package/dist/snapshot/rojo.js +364 -0
  60. package/dist/snapshot/rojo.js.map +1 -0
  61. package/dist/snapshot.d.ts +23 -0
  62. package/dist/snapshot.d.ts.map +1 -0
  63. package/dist/snapshot.js +132 -0
  64. package/dist/snapshot.js.map +1 -0
  65. package/dist/sourcemap/generator.d.ts +78 -0
  66. package/dist/sourcemap/generator.d.ts.map +1 -0
  67. package/dist/sourcemap/generator.js +351 -0
  68. package/dist/sourcemap/generator.js.map +1 -0
  69. package/dist/sourcemap/propertyLoader.d.ts +19 -0
  70. package/dist/sourcemap/propertyLoader.d.ts.map +1 -0
  71. package/dist/sourcemap/propertyLoader.js +131 -0
  72. package/dist/sourcemap/propertyLoader.js.map +1 -0
  73. package/dist/util/id.d.ts +9 -0
  74. package/dist/util/id.d.ts.map +1 -0
  75. package/dist/util/id.js +14 -0
  76. package/dist/util/id.js.map +1 -0
  77. package/dist/util/log.d.ts +13 -0
  78. package/dist/util/log.d.ts.map +1 -0
  79. package/dist/util/log.js +51 -0
  80. package/dist/util/log.js.map +1 -0
  81. package/docs/assets/azul-logo.pdn +0 -0
  82. package/docs/assets/logo-200px.png +0 -0
  83. package/docs/assets/logo.png +0 -0
  84. package/docs/assets/plugin/toolbox.png +0 -0
  85. package/docs/assets/synced.png +0 -0
  86. package/package.json +41 -0
  87. package/plugin/README.md +54 -0
  88. package/plugin/sourcemap.json +264 -0
  89. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Actor/AzulSync.server.luau +905 -0
  90. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/AzulService.luau +1010 -0
  91. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Config.luau +29 -0
  92. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/Enums.luau +11 -0
  93. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CollapsibleTitledSection.luau +214 -0
  94. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ColorPicker.luau +360 -0
  95. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/CustomTextButton.luau +170 -0
  96. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/DropdownMenu.luau +363 -0
  97. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/HorizontalLine.luau +43 -0
  98. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/ImageButtonWithText.luau +181 -0
  99. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledCheckbox.luau +295 -0
  100. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledColorInputPicker.luau +294 -0
  101. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledMultiChoice.luau +163 -0
  102. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledNumberInput.luau +312 -0
  103. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledRadioButton.luau +55 -0
  104. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledSlider.luau +151 -0
  105. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledTextInput.luau +222 -0
  106. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/LabeledToggleButton.luau +73 -0
  107. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/StatefulImageButton.luau +125 -0
  108. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalScrollingFrame.luau +100 -0
  109. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticalSpacer.luau +35 -0
  110. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/Components/VerticallyScalingListFrame.luau +107 -0
  111. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/GuiUtilities.luau +429 -0
  112. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/StudioWidgets/RbxGui.luau +4363 -0
  113. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/UI.luau +425 -0
  114. package/plugin/sync/ReplicatedFirst/AzulCompanionPlugin/WebSocketClient.luau +161 -0
  115. package/src/build.ts +120 -0
  116. package/src/cli.ts +496 -0
  117. package/src/config.ts +170 -0
  118. package/src/fs/fileWriter.ts +414 -0
  119. package/src/fs/treeManager.ts +458 -0
  120. package/src/fs/watcher.ts +142 -0
  121. package/src/index.ts +450 -0
  122. package/src/ipc/httpPolling.ts +214 -0
  123. package/src/ipc/messages.ts +159 -0
  124. package/src/ipc/server.ts +196 -0
  125. package/src/pack.ts +309 -0
  126. package/src/push.ts +726 -0
  127. package/src/snapshot/rojo.ts +467 -0
  128. package/src/snapshot.ts +161 -0
  129. package/src/sourcemap/generator.ts +504 -0
  130. package/src/sourcemap/propertyLoader.ts +195 -0
  131. package/src/util/id.ts +15 -0
  132. package/src/util/log.ts +94 -0
  133. package/tsconfig.json +24 -0
@@ -0,0 +1,294 @@
1
+ ----------------------------------------
2
+ --
3
+ -- LabeledColorInput.lua
4
+ --
5
+ -- Creates a frame containing a label and a text input control.
6
+ --
7
+ ----------------------------------------
8
+ GuiUtilities = require("../GuiUtilities")
9
+
10
+ local ColorPicker = require("./ColorPicker")
11
+
12
+ local kTextInputWidth = 100
13
+ local kTextBoxInternalPadding = 4
14
+
15
+ LabeledColorInputPickerClass = {}
16
+ LabeledColorInputPickerClass.__index = LabeledColorInputPickerClass
17
+
18
+ function round(x)
19
+ return x + 0.5 - (x + 0.5) % 1
20
+ end
21
+
22
+ --- LabeledColorInputPickerClass constructor.
23
+ --- @param nameSuffix string -- Suffix to append to the element's name for uniqueness.
24
+ --- @param labelText string -- Text label displayed alongside the color input.
25
+ --- @param defaultValue Color3 -- The default color value to initialize the input with.
26
+ --- @return LabeledColorInputPickerClass -- A new instance of the labeled color input class.
27
+ function LabeledColorInputPickerClass.new(nameSuffix: string, labelText: string, defaultValue: Color3)
28
+ local self = {}
29
+ setmetatable(self, LabeledColorInputPickerClass)
30
+
31
+ -- Note: we are using "graphemes" instead of characters.
32
+ -- In modern text-manipulation-fu, what with internationalization,
33
+ -- emojis, etc, it's not enough to count characters, particularly when
34
+ -- concerned with "how many <things> am I rendering?".
35
+ -- We are using the
36
+ self._MaxGraphemes = 20
37
+
38
+ self._valueChangedFunction = nil
39
+ self._focusLostFunction = nil
40
+
41
+ defaultValue = defaultValue or Color3.new(1,1,1)
42
+
43
+ local frame = Instance.new("Frame")
44
+ frame.Name = "CTIF" .. nameSuffix
45
+ frame.Position = UDim2.new(0, 0, 1, 0)
46
+ frame.Size = UDim2.new(1, 0, 0, 0)
47
+ frame.BorderSizePixel = 0
48
+ frame.AutomaticSize = Enum.AutomaticSize.Y
49
+ GuiUtilities.syncGuiElementBackgroundColor(frame)
50
+
51
+ local inputFrame = GuiUtilities.MakeStandardFixedHeightFrame("InputFrame")
52
+ inputFrame.Parent = frame
53
+
54
+ local label = GuiUtilities.MakeStandardPropertyLabel(labelText)
55
+ label.Parent = inputFrame
56
+ self._label = label
57
+
58
+ -- Dumb hack to add padding to text box,
59
+ local colorFrame = Instance.new("ImageButton")
60
+ colorFrame.Name = "Color"
61
+ colorFrame.AutoButtonColor = true
62
+ colorFrame.Size = UDim2.new(0, 12, 0, 12)
63
+ colorFrame.Position = UDim2.new(0, GuiUtilities.StandardLineElementLeftMargin, .5, 0)
64
+ colorFrame.AnchorPoint = Vector2.new(0, .5)
65
+ colorFrame.Parent = inputFrame
66
+ colorFrame.BackgroundColor3 = defaultValue
67
+ colorFrame.Image = ""
68
+ GuiUtilities.syncGuiElementBorderColor(colorFrame)
69
+
70
+ local textBoxWrapperFrame = Instance.new("Frame")
71
+ textBoxWrapperFrame.Name = "Wrapper"
72
+ textBoxWrapperFrame.Size = UDim2.new(0, kTextInputWidth - 20, 0.6, 0)
73
+ textBoxWrapperFrame.Position = UDim2.new(0, GuiUtilities.StandardLineElementLeftMargin + 20, .5, 0)
74
+ textBoxWrapperFrame.AnchorPoint = Vector2.new(0, .5)
75
+ textBoxWrapperFrame.Parent = inputFrame
76
+ GuiUtilities.syncGuiElementInputFieldColor(textBoxWrapperFrame)
77
+ GuiUtilities.syncGuiElementBorderColor(textBoxWrapperFrame)
78
+
79
+ local textBox = Instance.new("TextBox")
80
+ textBox.Parent = textBoxWrapperFrame
81
+ textBox.Name = "TextBox"
82
+ textBox.Text = string.format("[%s, %s, %s]", tostring(round(defaultValue.R * 255)), tostring(round(defaultValue.G * 255)), tostring(round(defaultValue.B * 255)))
83
+ textBox.Font = Enum.Font.SourceSans
84
+ textBox.TextSize = 15
85
+ textBox.BackgroundTransparency = 1
86
+ textBox.TextXAlignment = Enum.TextXAlignment.Left
87
+ textBox.Size = UDim2.new(1, -kTextBoxInternalPadding, 1, GuiUtilities.kTextVerticalFudge)
88
+ textBox.Position = UDim2.new(0, kTextBoxInternalPadding, 0, 0)
89
+ textBox.ClipsDescendants = true
90
+ textBox.ClearTextOnFocus = false
91
+ GuiUtilities.syncGuiElementFontColor(textBox)
92
+
93
+ textBox:GetPropertyChangedSignal("Text"):Connect(function()
94
+ -- Never let the text be too long.
95
+ -- Careful here: we want to measure number of graphemes, not characters,
96
+ -- in the text, and we want to clamp on graphemes as well.
97
+ if (utf8.len(self._textBox.Text) > self._MaxGraphemes) then
98
+ local count = 0
99
+ for start, stop in utf8.graphemes(self._textBox.Text) do
100
+ count = count + 1
101
+ if (count > self._MaxGraphemes) then
102
+ -- We have gone one too far.
103
+ -- clamp just before the beginning of this grapheme.
104
+ self._textBox.Text = string.sub(self._textBox.Text, 1, start-1)
105
+ break
106
+ end
107
+ end
108
+ -- Don't continue with rest of function: the resetting of "Text" field
109
+ -- above will trigger re-entry. We don't need to trigger value
110
+ -- changed function twice.
111
+ return
112
+ end
113
+
114
+ self._value = self._textBox.Text
115
+ if (self._valueChangedFunction) then
116
+ self._valueChangedFunction(self._value)
117
+ end
118
+ end)
119
+
120
+ textBox.FocusLost:Connect(function (enterPressed: boolean)
121
+ self:_GuessColorFromInputValue()
122
+ if self._focusLostFunction then
123
+ self._focusLostFunction(enterPressed)
124
+ end
125
+ end)
126
+
127
+ local colorPickerComponent = ColorPicker.new("Picker")
128
+ colorPickerComponent:SetValue(defaultValue)
129
+ colorPickerComponent:GetFrame().Parent = frame
130
+ colorPickerComponent:GetFrame().Position = UDim2.new(0, 43, 0, 30)
131
+ colorPickerComponent:GetFrame().Visible = false
132
+ colorPickerComponent._buttonConfirm:GetFrame().Visible = false
133
+ colorPickerComponent._buttonCancel:GetButton().Text = "Close"
134
+ colorPickerComponent._colorPreviewBox.Visible = false
135
+ colorPickerComponent._colorCodeBoxRGB.Position = UDim2.new(0, 26, 0.5, 0)
136
+ colorPickerComponent._colorCodeBoxHex.Position = UDim2.new(0, 92, 0.5, 0)
137
+
138
+ colorPickerComponent:SetCancelFunction(function ()
139
+ colorPickerComponent:GetFrame().Visible = false
140
+ end)
141
+
142
+ colorPickerComponent:SetValueChangedFunction(function (newValue: Color3)
143
+ self._colorValue = newValue
144
+ self:_UpdateColorFrame()
145
+ self:_UpdateInputValue()
146
+ end)
147
+
148
+ colorFrame.MouseButton1Click:Connect(function ()
149
+ if not self._colorPickerEnabled then return end
150
+ colorPickerComponent:GetFrame().Visible = not colorPickerComponent:GetFrame().Visible
151
+ end)
152
+
153
+ self._frame = frame
154
+ self._textBox = textBox
155
+ self._colorFrame = colorFrame
156
+ self._value = self._textBox.Text
157
+ self._colorValue = defaultValue
158
+ self._colorPickerComponent = colorPickerComponent
159
+ self._colorPickerFrame = colorPickerComponent:GetFrame()
160
+ self._colorPickerEnabled = true
161
+
162
+ return self
163
+ end
164
+
165
+ function LabeledColorInputPickerClass:_UpdateColorFrame()
166
+ self._colorFrame.BackgroundColor3 = self._colorValue
167
+ end
168
+
169
+ function LabeledColorInputPickerClass:_UpdateInputValue()
170
+ self._value = if self._colorPickerEnabled then
171
+ `[{self._colorPickerComponent:GetRGBCode()}]`
172
+ else
173
+ string.format(
174
+ "[%s, %s, %s]",
175
+ tostring(round(self._colorValue.R * 255)),
176
+ tostring(round(self._colorValue.G * 255)),
177
+ tostring(round(self._colorValue.B * 255))
178
+ )
179
+ self._textBox.Text = self._value
180
+ end
181
+
182
+ function LabeledColorInputPickerClass:_GuessColorFromInputValue()
183
+ --- Attempts to parse a color value from the text input.
184
+ --- Supports two formats: `R, G, B` (e.g., "255, 128, 0") and hex code (e.g., "#FFA500").
185
+ --- If a valid color is found, it updates the current color value accordingly.
186
+ local text = self._textBox.Text
187
+
188
+ -- Try matching RGB format first
189
+ local R, G, B = string.match(text, "(%d+)%s*,%s*(%d+)%s*,%s*(%d+)")
190
+ R = tonumber(R)
191
+ G = tonumber(G)
192
+ B = tonumber(B)
193
+ if R and G and B then
194
+ self:SetColorValue(Color3.fromRGB(
195
+ math.clamp(R, 0, 255),
196
+ math.clamp(G, 0, 255),
197
+ math.clamp(B, 0, 255)
198
+ ))
199
+ return
200
+ end
201
+
202
+ -- Try matching HEX format like #2e2e2e or #ABCDEF
203
+ local hex = string.match(text, "#(%x%x%x%x%x%x)")
204
+ if hex then
205
+ local r = tonumber(string.sub(hex, 1, 2), 16)
206
+ local g = tonumber(string.sub(hex, 3, 4), 16)
207
+ local b = tonumber(string.sub(hex, 5, 6), 16)
208
+ if r and g and b then
209
+ self:SetColorValue(Color3.fromRGB(r, g, b))
210
+ end
211
+ end
212
+ end
213
+
214
+ --- Sets the function to be called when the input value changes.
215
+ --- @param vcf function -- The function to call when the value changes.
216
+ function LabeledColorInputPickerClass:SetValueChangedFunction(vcf)
217
+ self._valueChangedFunction = vcf
218
+ end
219
+
220
+ --- Sets the function to be called when the input loses focus.
221
+ --- @param flf function -- A function with signature (enterPressed: boolean) -> ().
222
+ function LabeledColorInputPickerClass:SetFocusLostFunction(flf: (enterPressed: boolean) -> ())
223
+ self._focusLostFunction = flf
224
+ end
225
+
226
+ --- Returns the main frame of the labeled color input component.
227
+ --- @return Frame -- The UI frame for the component.
228
+ function LabeledColorInputPickerClass:GetFrame()
229
+ return self._frame
230
+ end
231
+
232
+ --- Returns the current text value from the input field.
233
+ --- @return string -- The string entered in the input box.
234
+ function LabeledColorInputPickerClass:GetValue(): string
235
+ return self._textBox.Text
236
+ end
237
+
238
+ --- Returns the current color value.
239
+ --- @return Color3 -- The selected color value.
240
+ function LabeledColorInputPickerClass:GetColorValue(): Color3
241
+ return self._value
242
+ end
243
+
244
+ --- Returns the maximum number of graphemes allowed in the input field.
245
+ --- @return number -- The maximum graphemes setting.
246
+ function LabeledColorInputPickerClass:GetMaxGraphemes()
247
+ return self._MaxGraphemes
248
+ end
249
+
250
+ --- Sets the maximum number of graphemes allowed in the input field.
251
+ --- @param newValue number -- The maximum number of graphemes.
252
+ function LabeledColorInputPickerClass:SetMaxGraphemes(newValue)
253
+ self._MaxGraphemes = newValue
254
+ end
255
+
256
+ --- Sets the text value of the input field.
257
+ --- @param newValue string -- The new value to assign to the input.
258
+ function LabeledColorInputPickerClass:SetValue(newValue)
259
+ if self._value ~= newValue then
260
+ self._textBox.Text = newValue
261
+ end
262
+ end
263
+
264
+ --- Sets the color value for the color input and updates the display.
265
+ --- @param newValue Color3 -- The new color to assign.
266
+ function LabeledColorInputPickerClass:SetColorValue(newValue: Color3)
267
+ assert(typeof(newValue) == "Color3", "First parameter must be a Color3. Received " .. typeof(newValue))
268
+ self._colorValue = newValue
269
+ self:_UpdateColorFrame()
270
+ self:_UpdateInputValue()
271
+ if self._colorPickerEnabled then
272
+ self._colorPickerComponent:SetValue(newValue)
273
+ end
274
+ end
275
+
276
+ --- Enables or disables the color picker UI.
277
+ --- If disabled, the user can only manually input a RGB or a Hex color value.
278
+ ---
279
+ --- If enabled, the user can also open a color picker UI to choose a color.
280
+ --- @param state boolean -- Whether to enable (true) or disable (false) the color picker.
281
+ function LabeledColorInputPickerClass:SetPickerEnabled(state: boolean)
282
+ self._colorPickerEnabled = state
283
+ if not state then
284
+ self._colorPickerFrame.Visible = false
285
+ end
286
+ end
287
+
288
+ --- Returns whether the color picker is currently enabled.
289
+ --- @return boolean -- True if the color picker is enabled, false otherwise.
290
+ function LabeledColorInputPickerClass:GetPickerEnabled(state: boolean)
291
+ return self._colorPickerEnabled
292
+ end
293
+
294
+ return LabeledColorInputPickerClass
@@ -0,0 +1,163 @@
1
+ ----------------------------------------
2
+ --
3
+ -- LabeledMultiChoice.lua
4
+ --
5
+ -- Creates a frame containing a label and list of choices, of which exactly one
6
+ -- is always selected.
7
+ --
8
+ ----------------------------------------
9
+ GuiUtilities = require("../GuiUtilities")
10
+ LabeledRadioButton = require("./LabeledRadioButton")
11
+ LabeledCheckbox = require("./LabeledCheckbox")
12
+ VerticallyScalingListFrame = require("./VerticallyScalingListFrame")
13
+
14
+ local kRadioButtonsHPadding = GuiUtilities.kRadioButtonsHPadding
15
+
16
+ LabeledMultiChoiceClass = {}
17
+ LabeledMultiChoiceClass.__index = LabeledMultiChoiceClass
18
+
19
+
20
+ --- LabeledMultiChoiceClass constructor.
21
+ --- @param nameSuffix string -- Suffix to append to the name of the UI elements.
22
+ --- @param labelText string -- The label displayed next to the multi-choice selector.
23
+ --- @param choices {{Id: number | string, Text: string}} -- An array of choice entries. Each entry must include a .Id field and .Text field.
24
+ --- @param initChoiceIndex number? -- Optional index of the initially selected choice. Defaults to the first if not provided.
25
+ --- @return LabeledMultiChoiceClass -- A new instance of the labeled multi-choice selector.
26
+ function LabeledMultiChoiceClass.new(nameSuffix: string, labelText: string, choices: {{Id: number | string, Text: string}}, initChoiceIndex: number?)
27
+ local self = {}
28
+ setmetatable(self, LabeledMultiChoiceClass)
29
+
30
+ self._buttonObjsByIndex = {}
31
+
32
+ self._choices = choices
33
+
34
+ if (not initChoiceIndex ) then
35
+ initChoiceIndex = 1
36
+ end
37
+ if (initChoiceIndex > #choices) then
38
+ initChoiceIndex = #choices
39
+ end
40
+
41
+
42
+ local vsl = VerticallyScalingListFrame.new("MCC_" .. nameSuffix)
43
+ vsl:AddBottomPadding()
44
+
45
+ local titleLabel = GuiUtilities.MakeFrameWithSubSectionLabel("Title", labelText)
46
+ vsl:AddChild(titleLabel)
47
+
48
+ -- Container for cells.
49
+ local cellFrame = self:_MakeRadioButtons(choices)
50
+ vsl:AddChild(cellFrame)
51
+
52
+ self._vsl = vsl
53
+
54
+ self:SetSelectedIndex(initChoiceIndex)
55
+
56
+ return self
57
+ end
58
+
59
+ -- Small checkboxes are a different entity.
60
+ -- All the bits are smaller.
61
+ -- Fixed width instead of flood-fill.
62
+ -- Box comes first, then label.
63
+ function LabeledMultiChoiceClass:_MakeRadioButtons(choices)
64
+ local frame = GuiUtilities.MakeFrame("RadioButtons")
65
+ frame.BackgroundTransparency = 1
66
+
67
+ local padding = Instance.new("UIPadding")
68
+ padding.PaddingLeft = UDim.new(0, GuiUtilities.StandardLineLabelLeftMargin)
69
+ padding.PaddingRight = UDim.new(0, GuiUtilities.StandardLineLabelLeftMargin)
70
+ padding.Parent = frame
71
+
72
+ -- Make a grid to put checkboxes in.
73
+ local uiGridLayout = Instance.new("UIGridLayout")
74
+ uiGridLayout.CellSize = LabeledCheckbox.kMinFrameSize
75
+ uiGridLayout.CellPadding = UDim2.new(0,
76
+ kRadioButtonsHPadding,
77
+ 0,
78
+ GuiUtilities.kStandardVMargin)
79
+ uiGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left
80
+ uiGridLayout.VerticalAlignment = Enum.VerticalAlignment.Top
81
+ uiGridLayout.Parent = frame
82
+ uiGridLayout.SortOrder = Enum.SortOrder.LayoutOrder
83
+ self._uiGridLayout = uiGridLayout
84
+
85
+ for i, choiceData in ipairs(choices) do
86
+ self:_AddRadioButton(frame, i, choiceData)
87
+ end
88
+
89
+ -- Sync size with content size.
90
+ GuiUtilities.AdjustHeightDynamicallyToLayout(frame, uiGridLayout)
91
+
92
+ return frame
93
+ end
94
+
95
+ function LabeledMultiChoiceClass:_AddRadioButton(parentFrame, index, choiceData)
96
+ local radioButtonObj = LabeledRadioButton.new(choiceData.Id, choiceData.Text)
97
+ self._buttonObjsByIndex[index] = radioButtonObj
98
+
99
+ radioButtonObj:SetValueChangedFunction(function(value)
100
+ -- If we notice the button going from off to on, and it disagrees with
101
+ -- our current notion of selection, update selection.
102
+ if (value and self._selectedIndex ~= index) then
103
+ self:SetSelectedIndex(index)
104
+ end
105
+ end)
106
+
107
+ radioButtonObj:GetFrame().LayoutOrder = index
108
+ radioButtonObj:GetFrame().Parent = parentFrame
109
+ end
110
+
111
+ --- Sets the selected index in the multi-choice UI and updates the button states accordingly.
112
+ --- @param selectedIndex number -- The index of the choice to be selected.
113
+ function LabeledMultiChoiceClass:SetSelectedIndex(selectedIndex)
114
+ self._selectedIndex = selectedIndex
115
+ for i = 1, #self._buttonObjsByIndex do
116
+ self._buttonObjsByIndex[i]:SetValue(i == selectedIndex)
117
+ end
118
+
119
+ if (self._valueChangedFunction) then
120
+ self._valueChangedFunction(self._selectedIndex)
121
+ end
122
+ end
123
+
124
+ --- Gets the currently selected index in the multi-choice UI.
125
+ --- @return number -- The index of the selected choice.
126
+ function LabeledMultiChoiceClass:GetSelectedIndex()
127
+ return self._selectedIndex
128
+ end
129
+
130
+ --- Gets the ID value of the currently selected choice.
131
+ --- @return number | string -- The "Id" field of the selected choice.
132
+ function LabeledMultiChoiceClass:GetSelectedValue(): number | string
133
+ return self._choices[self._selectedIndex].Id
134
+ end
135
+
136
+ --- Sets a callback function to be invoked whenever the selected value changes.
137
+ --- @param vcf (newValue: number | string) -> () -- A function that receives the new selected index as a parameter.
138
+ function LabeledMultiChoiceClass:SetValueChangedFunction(vcf: (newValue: number | string) -> ())
139
+ self._valueChangedFunction = vcf
140
+ end
141
+
142
+ --- Retrieves the main frame containing the multi-choice UI elements.
143
+ --- @return Frame -- The UI frame of the multi-choice component.
144
+ function LabeledMultiChoiceClass:GetFrame(): Frame
145
+ return self._vsl:GetFrame()
146
+ end
147
+
148
+ --- Sets the horizontal size of each cell (button) in the multi-choice grid layout.
149
+ --- @param cellSize number -- The desired horizontal size in pixels.
150
+ function LabeledMultiChoiceClass:SetCellHorizontalSize(cellSize: number)
151
+ local size = self._uiGridLayout.CellSize :: UDim2
152
+ self._uiGridLayout.CellSize = UDim2.new(size.X.Scale, cellSize, size.Y.Scale, size.Y.Offset)
153
+ end
154
+
155
+ --- Sets the vertical size of each cell (button) in the multi-choice grid layout.
156
+ --- @param cellSize number -- The desired vertical size in pixels.
157
+ function LabeledMultiChoiceClass:SetCellVerticalSize(cellSize: number)
158
+ local size = self._uiGridLayout.CellSize :: UDim2
159
+ self._uiGridLayout.CellSize = UDim2.new(size.X.Scale, size.X.Offset, size.Y.Scale, cellSize)
160
+ end
161
+
162
+
163
+ return LabeledMultiChoiceClass