first commit
This commit is contained in:
27
.eslintrc
Normal file
27
.eslintrc
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"jsx": true,
|
||||
"useJSXTextNode": true,
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"/out"
|
||||
],
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"roblox-ts",
|
||||
"prettier"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:roblox-ts/recommended-legacy",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "warn"
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/node_modules
|
||||
/out
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"useTabs": true
|
||||
}
|
||||
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"roblox-ts.vscode-roblox-ts",
|
||||
"dbaeumer.vscode-eslint"
|
||||
]
|
||||
}
|
||||
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"files.eol": "\n",
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"eslint.run": "onType",
|
||||
"eslint.format.enable": true,
|
||||
"eslint.useFlatConfig": false
|
||||
}
|
||||
19
default.project.json
Normal file
19
default.project.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "roblox-ts-plugin",
|
||||
"globIgnorePaths": [
|
||||
"**/package.json",
|
||||
"**/tsconfig.json"
|
||||
],
|
||||
"tree": {
|
||||
"$path": "out",
|
||||
"include": {
|
||||
"$path": "include",
|
||||
"node_modules": {
|
||||
"$className": "Folder",
|
||||
"@rbxts": {
|
||||
"$path": "node_modules/@rbxts"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2068
include/Promise.lua
Normal file
2068
include/Promise.lua
Normal file
File diff suppressed because it is too large
Load Diff
260
include/RuntimeLib.lua
Normal file
260
include/RuntimeLib.lua
Normal file
@@ -0,0 +1,260 @@
|
||||
local Promise = require(script.Parent.Promise)
|
||||
|
||||
local RunService = game:GetService("RunService")
|
||||
|
||||
local OUTPUT_PREFIX = "roblox-ts: "
|
||||
local NODE_MODULES = "node_modules"
|
||||
local DEFAULT_SCOPE = "@rbxts"
|
||||
|
||||
local TS = {}
|
||||
|
||||
TS.Promise = Promise
|
||||
|
||||
local function isPlugin(context)
|
||||
return RunService:IsStudio() and context:FindFirstAncestorWhichIsA("Plugin") ~= nil
|
||||
end
|
||||
|
||||
function TS.getModule(context, scope, moduleName)
|
||||
-- legacy call signature
|
||||
if moduleName == nil then
|
||||
moduleName = scope
|
||||
scope = DEFAULT_SCOPE
|
||||
end
|
||||
|
||||
-- ensure modules have fully replicated
|
||||
if RunService:IsRunning() and RunService:IsClient() and not isPlugin(context) and not game:IsLoaded() then
|
||||
game.Loaded:Wait()
|
||||
end
|
||||
|
||||
local object = context
|
||||
repeat
|
||||
local nodeModulesFolder = object:FindFirstChild(NODE_MODULES)
|
||||
if nodeModulesFolder then
|
||||
local scopeFolder = nodeModulesFolder:FindFirstChild(scope)
|
||||
if scopeFolder then
|
||||
local module = scopeFolder:FindFirstChild(moduleName)
|
||||
if module then
|
||||
return module
|
||||
end
|
||||
end
|
||||
end
|
||||
object = object.Parent
|
||||
until object == nil
|
||||
|
||||
error(OUTPUT_PREFIX .. "Could not find module: " .. moduleName, 2)
|
||||
end
|
||||
|
||||
-- This is a hash which TS.import uses as a kind of linked-list-like history of [Script who Loaded] -> Library
|
||||
local currentlyLoading = {}
|
||||
local registeredLibraries = {}
|
||||
|
||||
function TS.import(context, module, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
module = module:WaitForChild((select(i, ...)))
|
||||
end
|
||||
|
||||
if module.ClassName ~= "ModuleScript" then
|
||||
error(OUTPUT_PREFIX .. "Failed to import! Expected ModuleScript, got " .. module.ClassName, 2)
|
||||
end
|
||||
|
||||
currentlyLoading[context] = module
|
||||
|
||||
-- Check to see if a case like this occurs:
|
||||
-- module -> Module1 -> Module2 -> module
|
||||
|
||||
-- WHERE currentlyLoading[module] is Module1
|
||||
-- and currentlyLoading[Module1] is Module2
|
||||
-- and currentlyLoading[Module2] is module
|
||||
|
||||
local currentModule = module
|
||||
local depth = 0
|
||||
|
||||
while currentModule do
|
||||
depth = depth + 1
|
||||
currentModule = currentlyLoading[currentModule]
|
||||
|
||||
if currentModule == module then
|
||||
local str = currentModule.Name -- Get the string traceback
|
||||
|
||||
for _ = 1, depth do
|
||||
currentModule = currentlyLoading[currentModule]
|
||||
str = str .. " ⇒ " .. currentModule.Name
|
||||
end
|
||||
|
||||
error(OUTPUT_PREFIX .. "Failed to import! Detected a circular dependency chain: " .. str, 2)
|
||||
end
|
||||
end
|
||||
|
||||
if not registeredLibraries[module] then
|
||||
if _G[module] then
|
||||
error(
|
||||
OUTPUT_PREFIX
|
||||
.. "Invalid module access! Do you have multiple TS runtimes trying to import this? "
|
||||
.. module:GetFullName(),
|
||||
2
|
||||
)
|
||||
end
|
||||
|
||||
_G[module] = TS
|
||||
registeredLibraries[module] = true -- register as already loaded for subsequent calls
|
||||
end
|
||||
|
||||
local data = require(module)
|
||||
|
||||
if currentlyLoading[context] == module then -- Thread-safe cleanup!
|
||||
currentlyLoading[context] = nil
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
function TS.instanceof(obj, class)
|
||||
-- custom Class.instanceof() check
|
||||
if type(class) == "table" and type(class.instanceof) == "function" then
|
||||
return class.instanceof(obj)
|
||||
end
|
||||
|
||||
-- metatable check
|
||||
if type(obj) == "table" then
|
||||
obj = getmetatable(obj)
|
||||
while obj ~= nil do
|
||||
if obj == class then
|
||||
return true
|
||||
end
|
||||
local mt = getmetatable(obj)
|
||||
if mt then
|
||||
obj = mt.__index
|
||||
else
|
||||
obj = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function TS.async(callback)
|
||||
return function(...)
|
||||
local n = select("#", ...)
|
||||
local args = { ... }
|
||||
return Promise.new(function(resolve, reject)
|
||||
coroutine.wrap(function()
|
||||
local ok, result = pcall(callback, unpack(args, 1, n))
|
||||
if ok then
|
||||
resolve(result)
|
||||
else
|
||||
reject(result)
|
||||
end
|
||||
end)()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function TS.await(promise)
|
||||
if not Promise.is(promise) then
|
||||
return promise
|
||||
end
|
||||
|
||||
local status, value = promise:awaitStatus()
|
||||
if status == Promise.Status.Resolved then
|
||||
return value
|
||||
elseif status == Promise.Status.Rejected then
|
||||
error(value, 2)
|
||||
else
|
||||
error("The awaited Promise was cancelled", 2)
|
||||
end
|
||||
end
|
||||
|
||||
local SIGN = 2 ^ 31
|
||||
local COMPLEMENT = 2 ^ 32
|
||||
local function bit_sign(num)
|
||||
-- Restores the sign after an unsigned conversion according to 2s complement.
|
||||
if bit32.btest(num, SIGN) then
|
||||
return num - COMPLEMENT
|
||||
else
|
||||
return num
|
||||
end
|
||||
end
|
||||
|
||||
function TS.bit_lrsh(a, b)
|
||||
return bit_sign(bit32.arshift(a, b))
|
||||
end
|
||||
|
||||
TS.TRY_RETURN = 1
|
||||
TS.TRY_BREAK = 2
|
||||
TS.TRY_CONTINUE = 3
|
||||
|
||||
function TS.try(try, catch, finally)
|
||||
-- execute try
|
||||
local trySuccess, exitTypeOrTryError, returns = pcall(try)
|
||||
local exitType, tryError
|
||||
if trySuccess then
|
||||
exitType = exitTypeOrTryError
|
||||
else
|
||||
tryError = exitTypeOrTryError
|
||||
end
|
||||
|
||||
local catchSuccess = true
|
||||
local catchError
|
||||
|
||||
-- if try block failed, and catch block exists, execute catch
|
||||
if not trySuccess and catch then
|
||||
local newExitTypeOrCatchError, newReturns
|
||||
catchSuccess, newExitTypeOrCatchError, newReturns = pcall(catch, tryError)
|
||||
local newExitType
|
||||
if catchSuccess then
|
||||
newExitType = newExitTypeOrCatchError
|
||||
else
|
||||
catchError = newExitTypeOrCatchError
|
||||
end
|
||||
|
||||
if newExitType then
|
||||
exitType, returns = newExitType, newReturns
|
||||
end
|
||||
end
|
||||
|
||||
-- execute finally
|
||||
if finally then
|
||||
local newExitType, newReturns = finally()
|
||||
if newExitType then
|
||||
exitType, returns = newExitType, newReturns
|
||||
end
|
||||
end
|
||||
|
||||
-- if exit type is a control flow, do not rethrow errors
|
||||
if exitType ~= TS.TRY_RETURN and exitType ~= TS.TRY_BREAK and exitType ~= TS.TRY_CONTINUE then
|
||||
-- if catch block threw an error, rethrow it
|
||||
if not catchSuccess then
|
||||
error(catchError, 2)
|
||||
end
|
||||
|
||||
-- if try block threw an error and there was no catch block, rethrow it
|
||||
if not trySuccess and not catch then
|
||||
error(tryError, 2)
|
||||
end
|
||||
end
|
||||
|
||||
return exitType, returns
|
||||
end
|
||||
|
||||
function TS.generator(callback)
|
||||
local co = coroutine.create(callback)
|
||||
return {
|
||||
next = function(...)
|
||||
if coroutine.status(co) == "dead" then
|
||||
return { done = true }
|
||||
else
|
||||
local success, value = coroutine.resume(co, ...)
|
||||
if success == false then
|
||||
error(value, 2)
|
||||
end
|
||||
return {
|
||||
value = value,
|
||||
done = coroutine.status(co) == "dead",
|
||||
}
|
||||
end
|
||||
end,
|
||||
}
|
||||
end
|
||||
|
||||
return TS
|
||||
2562
package-lock.json
generated
Normal file
2562
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "next_station_plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "rbxtsc",
|
||||
"watch": "rbxtsc -w"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"devDependencies": {
|
||||
"@rbxts/compiler-types": "^3.0.0-types.0",
|
||||
"@rbxts/types": "^1.0.881",
|
||||
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
||||
"@typescript-eslint/parser": "^8.43.0",
|
||||
"eslint": "^9.35.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.5.4",
|
||||
"eslint-plugin-roblox-ts": "^1.1.0",
|
||||
"prettier": "^3.6.2",
|
||||
"roblox-ts": "^3.0.0",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
}
|
||||
16
serve.project.json
Normal file
16
serve.project.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "roblox-ts-plugin-serve",
|
||||
"globIgnorePaths": [
|
||||
"**/package.json",
|
||||
"**/tsconfig.json"
|
||||
],
|
||||
"tree": {
|
||||
"$className": "DataModel",
|
||||
"ServerScriptService": {
|
||||
"$className": "ServerScriptService",
|
||||
"Plugin": {
|
||||
"$path": "./default.project.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
src/Adornment/Area.ts
Normal file
65
src/Adornment/Area.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { createAnchor } from "Utils/Gui";
|
||||
import { Handles } from "./Handles";
|
||||
|
||||
function areaSphereAdornment(model: Folder, anchor: Part) {
|
||||
const sphereAdornment = new Instance("SphereHandleAdornment");
|
||||
sphereAdornment.Adornee = anchor;
|
||||
sphereAdornment.AlwaysOnTop = true;
|
||||
sphereAdornment.ZIndex = 5;
|
||||
sphereAdornment.Color3 = new Color3(1, 0, 0);
|
||||
sphereAdornment.Transparency = 0.8;
|
||||
sphereAdornment.Radius = 0.4;
|
||||
sphereAdornment.Parent = model;
|
||||
return sphereAdornment;
|
||||
}
|
||||
|
||||
export class HandlesArea {
|
||||
originHandles: Handles;
|
||||
originAdornment: SphereHandleAdornment;
|
||||
originAnchor: Part;
|
||||
originValueChanged;
|
||||
|
||||
tipHandles: Handles;
|
||||
tipAdornment: SphereHandleAdornment;
|
||||
tipAnchor: Part;
|
||||
tipValueChanged;
|
||||
|
||||
box: BoxHandleAdornment;
|
||||
boxAnchor: Part;
|
||||
constructor(model: Folder, firstPos: Vector3, secondPos: Vector3) {
|
||||
this.originHandles = new Handles(model, firstPos);
|
||||
this.originAnchor = this.originHandles.anchor;
|
||||
this.originAdornment = areaSphereAdornment(model, this.originHandles.anchor);
|
||||
this.tipHandles = new Handles(model, secondPos);
|
||||
this.tipAnchor = this.tipHandles.anchor;
|
||||
this.tipAdornment = areaSphereAdornment(model, this.tipHandles.anchor);
|
||||
|
||||
// create the box
|
||||
const boxSize = secondPos.sub(firstPos);
|
||||
const boxCenter = firstPos.add(boxSize.div(2));
|
||||
this.boxAnchor = createAnchor(model, boxCenter);
|
||||
const box = new Instance("BoxHandleAdornment");
|
||||
box.Adornee = this.boxAnchor;
|
||||
box.AlwaysOnTop = true;
|
||||
box.ZIndex = 5;
|
||||
box.Color3 = new Color3(1, 0, 0);
|
||||
box.Transparency = 0.8;
|
||||
box.Size = new Vector3(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z));
|
||||
box.Parent = model;
|
||||
this.box = box;
|
||||
|
||||
// Function
|
||||
this.originAnchor.GetPropertyChangedSignal("Position").Connect(() => this.updateBox());
|
||||
this.tipAnchor.GetPropertyChangedSignal("Position").Connect(() => this.updateBox());
|
||||
this.originValueChanged = this.originHandles.valueChanged;
|
||||
this.tipValueChanged = this.tipHandles.valueChanged;
|
||||
}
|
||||
updateBox() {
|
||||
const origin = this.originAnchor.Position;
|
||||
const tip = this.tipAnchor.Position;
|
||||
const boxSize = tip.sub(origin);
|
||||
const boxCenter = origin.add(boxSize.div(2));
|
||||
this.boxAnchor.Position = boxCenter;
|
||||
this.box.Size = new Vector3(math.abs(boxSize.X), math.abs(boxSize.Y), math.abs(boxSize.Z));
|
||||
}
|
||||
}
|
||||
51
src/Adornment/Handles.ts
Normal file
51
src/Adornment/Handles.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import Signal from "@rbxts/signal";
|
||||
import { createAnchor } from "Utils/Gui";
|
||||
|
||||
export class Handles {
|
||||
currentVector: Vector3 = new Vector3();
|
||||
anchor: Part;
|
||||
valueChanged = new Signal<(newValue: Vector3) => void>();
|
||||
|
||||
constructor(model: Folder, startPosition: Vector3 = Vector3.zero, anchor = createAnchor(model, startPosition)) {
|
||||
this.anchor = anchor;
|
||||
|
||||
const handles = new Instance("Handles");
|
||||
handles.Adornee = this.anchor;
|
||||
handles.Parent = model;
|
||||
handles.MouseButton1Down.Connect(() => {
|
||||
this.currentVector = this.anchor.Position;
|
||||
});
|
||||
handles.MouseDrag.Connect((face, distance) => {
|
||||
// Apply the changes to the anchor's position
|
||||
let offset = Vector3.zero;
|
||||
|
||||
switch (face) {
|
||||
case Enum.NormalId.Right:
|
||||
offset = new Vector3(distance, 0, 0);
|
||||
break;
|
||||
case Enum.NormalId.Left:
|
||||
offset = new Vector3(-distance, 0, 0);
|
||||
break;
|
||||
case Enum.NormalId.Top:
|
||||
offset = new Vector3(0, distance, 0);
|
||||
break;
|
||||
case Enum.NormalId.Bottom:
|
||||
offset = new Vector3(0, -distance, 0);
|
||||
break;
|
||||
case Enum.NormalId.Front:
|
||||
offset = new Vector3(0, 0, -distance);
|
||||
break;
|
||||
case Enum.NormalId.Back:
|
||||
offset = new Vector3(0, 0, distance);
|
||||
break;
|
||||
}
|
||||
|
||||
this.anchor.Position = this.currentVector.add(offset);
|
||||
});
|
||||
|
||||
handles.MouseButton1Up.Connect(() => {
|
||||
const newpos = this.anchor.Position;
|
||||
this.valueChanged.Fire(newpos);
|
||||
});
|
||||
}
|
||||
}
|
||||
13
src/Lualibs/CollapsibleTitledSection.d.ts
vendored
Normal file
13
src/Lualibs/CollapsibleTitledSection.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
declare class CollapsibleTitledSection {
|
||||
constructor(
|
||||
nameSuffix: string,
|
||||
titleText: string,
|
||||
autoScalingList?: boolean,
|
||||
minimizable?: boolean,
|
||||
minimizedByDefault?: boolean,
|
||||
);
|
||||
GetSectionFrame(): Instance;
|
||||
GetContentsFrame(): Instance;
|
||||
SetCollapsedState(minimized: boolean): void;
|
||||
}
|
||||
export = CollapsibleTitledSection;
|
||||
168
src/Lualibs/CollapsibleTitledSection.lua
Normal file
168
src/Lualibs/CollapsibleTitledSection.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- CollapsibleTitledSectionClass
|
||||
--
|
||||
-- Creates a section with a title label:
|
||||
--
|
||||
-- "SectionXXX"
|
||||
-- "TitleBarVisual"
|
||||
-- "Contents"
|
||||
--
|
||||
-- Requires "parent" and "sectionName" parameters and returns the section and its contentsFrame
|
||||
-- The entire frame will resize dynamically as contents frame changes size.
|
||||
--
|
||||
-- "autoScalingList" is a boolean that defines wheter or not the content frame automatically resizes when children are added.
|
||||
-- This is important for cases when you want minimize button to push or contract what is below it.
|
||||
--
|
||||
-- Both "minimizeable" and "minimizedByDefault" are false by default
|
||||
-- These parameters define if the section will have an arrow button infront of the title label,
|
||||
-- which the user may use to hide the section's contents
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
local kRightButtonAsset = "rbxasset://textures/TerrainTools/button_arrow.png"
|
||||
local kDownButtonAsset = "rbxasset://textures/TerrainTools/button_arrow_down.png"
|
||||
|
||||
local kArrowSize = 9
|
||||
local kDoubleClickTimeSec = 0.5
|
||||
|
||||
CollapsibleTitledSectionClass = {}
|
||||
CollapsibleTitledSectionClass.__index = CollapsibleTitledSectionClass
|
||||
|
||||
|
||||
function CollapsibleTitledSectionClass.new(nameSuffix, titleText, autoScalingList, minimizable, minimizedByDefault)
|
||||
local self = {}
|
||||
setmetatable(self, CollapsibleTitledSectionClass)
|
||||
|
||||
self._minimized = minimizedByDefault
|
||||
self._minimizable = minimizable
|
||||
|
||||
self._titleBarHeight = GuiUtilities.kTitleBarHeight
|
||||
|
||||
local frame = Instance.new('Frame')
|
||||
frame.Name = 'CTSection' .. nameSuffix
|
||||
frame.BackgroundTransparency = 1
|
||||
self._frame = frame
|
||||
|
||||
local uiListLayout = Instance.new('UIListLayout')
|
||||
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
uiListLayout.Parent = frame
|
||||
self._uiListLayout = uiListLayout
|
||||
|
||||
local contentsFrame = Instance.new('Frame')
|
||||
contentsFrame.Name = 'Contents'
|
||||
contentsFrame.BackgroundTransparency = 1
|
||||
contentsFrame.Size = UDim2.new(1, 0, 0, 1)
|
||||
contentsFrame.Position = UDim2.new(0, 0, 0, titleBarSize)
|
||||
contentsFrame.Parent = frame
|
||||
contentsFrame.LayoutOrder = 2
|
||||
GuiUtilities.syncGuiElementBackgroundColor(contentsFrame)
|
||||
|
||||
self._contentsFrame = contentsFrame
|
||||
|
||||
uiListLayout:GetPropertyChangedSignal('AbsoluteContentSize'):connect(function()
|
||||
self:_UpdateSize()
|
||||
end)
|
||||
self:_UpdateSize()
|
||||
|
||||
self:_CreateTitleBar(titleText)
|
||||
self:SetCollapsedState(self._minimized)
|
||||
|
||||
if (autoScalingList) then
|
||||
GuiUtilities.MakeFrameAutoScalingList(self:GetContentsFrame())
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function CollapsibleTitledSectionClass:GetSectionFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:GetContentsFrame()
|
||||
return self._contentsFrame
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:_UpdateSize()
|
||||
local totalSize = self._uiListLayout.AbsoluteContentSize.Y
|
||||
self._frame.Size = UDim2.new(1, 0, 0, totalSize)
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:_UpdateMinimizeButton()
|
||||
-- We can't rotate it because rotated images don't get clipped by parents.
|
||||
-- This is all in a scroll widget.
|
||||
-- :(
|
||||
if (self._minimized) then
|
||||
self._minimizeButton.Image = kRightButtonAsset
|
||||
else
|
||||
self._minimizeButton.Image = kDownButtonAsset
|
||||
end
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:SetCollapsedState(bool)
|
||||
self._minimized = bool
|
||||
self._contentsFrame.Visible = not bool
|
||||
self:_UpdateMinimizeButton()
|
||||
self:_UpdateSize()
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:_ToggleCollapsedState()
|
||||
self:SetCollapsedState(not self._minimized)
|
||||
end
|
||||
|
||||
function CollapsibleTitledSectionClass:_CreateTitleBar(titleText)
|
||||
local titleTextOffset = self._titleBarHeight
|
||||
|
||||
local titleBar = Instance.new('ImageButton')
|
||||
titleBar.AutoButtonColor = false
|
||||
titleBar.Name = 'TitleBarVisual'
|
||||
titleBar.BorderSizePixel = 0
|
||||
titleBar.Position = UDim2.new(0, 0, 0, 0)
|
||||
titleBar.Size = UDim2.new(1, 0, 0, self._titleBarHeight)
|
||||
titleBar.Parent = self._frame
|
||||
titleBar.LayoutOrder = 1
|
||||
GuiUtilities.syncGuiElementTitleColor(titleBar)
|
||||
|
||||
local titleLabel = Instance.new('TextLabel')
|
||||
titleLabel.Name = 'TitleLabel'
|
||||
titleLabel.BackgroundTransparency = 1
|
||||
titleLabel.Font = Enum.Font.SourceSansBold --todo: input spec font
|
||||
titleLabel.TextSize = 15 --todo: input spec font size
|
||||
titleLabel.TextXAlignment = Enum.TextXAlignment.Left
|
||||
titleLabel.Text = titleText
|
||||
titleLabel.Position = UDim2.new(0, titleTextOffset, 0, 0)
|
||||
titleLabel.Size = UDim2.new(1, -titleTextOffset, 1, GuiUtilities.kTextVerticalFudge)
|
||||
titleLabel.Parent = titleBar
|
||||
GuiUtilities.syncGuiElementFontColor(titleLabel)
|
||||
|
||||
self._minimizeButton = Instance.new('ImageButton')
|
||||
self._minimizeButton.Name = 'MinimizeSectionButton'
|
||||
self._minimizeButton.Image = kRightButtonAsset --todo: input arrow image from spec
|
||||
self._minimizeButton.Size = UDim2.new(0, kArrowSize, 0, kArrowSize)
|
||||
self._minimizeButton.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
self._minimizeButton.Position = UDim2.new(0, self._titleBarHeight*.5,
|
||||
0, self._titleBarHeight*.5)
|
||||
self._minimizeButton.BackgroundTransparency = 1
|
||||
self._minimizeButton.Visible = self._minimizable -- only show when minimizable
|
||||
|
||||
self._minimizeButton.MouseButton1Down:connect(function()
|
||||
self:_ToggleCollapsedState()
|
||||
end)
|
||||
self:_UpdateMinimizeButton()
|
||||
self._minimizeButton.Parent = titleBar
|
||||
|
||||
self._latestClickTime = 0
|
||||
titleBar.MouseButton1Down:connect(function()
|
||||
local now = tick()
|
||||
if (now - self._latestClickTime < kDoubleClickTimeSec) then
|
||||
self:_ToggleCollapsedState()
|
||||
self._latestClickTime = 0
|
||||
else
|
||||
self._latestClickTime = now
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
return CollapsibleTitledSectionClass
|
||||
5
src/Lualibs/CustomTextButton.d.ts
vendored
Normal file
5
src/Lualibs/CustomTextButton.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare class CustomTextButtonClass {
|
||||
constructor(buttonName: string, labelText: string);
|
||||
GetButton(): Instance;
|
||||
}
|
||||
export = CustomTextButtonClass;
|
||||
94
src/Lualibs/CustomTextButton.lua
Normal file
94
src/Lualibs/CustomTextButton.lua
Normal file
@@ -0,0 +1,94 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- CustomTextButton.lua
|
||||
--
|
||||
-- Creates text button with custom look & feel, hover/click effects.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
local kButtonImageIdDefault = "rbxasset://textures/TerrainTools/button_default.png"
|
||||
local kButtonImageIdHovered = "rbxasset://textures/TerrainTools/button_hover.png"
|
||||
local kButtonImageIdPressed = "rbxasset://textures/TerrainTools/button_pressed.png"
|
||||
|
||||
CustomTextButtonClass = {}
|
||||
CustomTextButtonClass.__index = CustomTextButtonClass
|
||||
|
||||
function CustomTextButtonClass.new(buttonName, labelText)
|
||||
local self = {}
|
||||
setmetatable(self, CustomTextButtonClass)
|
||||
|
||||
local button = Instance.new('ImageButton')
|
||||
button.Name = buttonName
|
||||
button.Image = kButtonImageIdDefault
|
||||
button.BackgroundTransparency = 1
|
||||
button.ScaleType = Enum.ScaleType.Slice
|
||||
button.SliceCenter = Rect.new(7, 7, 156, 36)
|
||||
button.AutoButtonColor = false
|
||||
|
||||
local label = Instance.new('TextLabel')
|
||||
label.Text = labelText
|
||||
label.BackgroundTransparency = 1
|
||||
label.Size = UDim2.new(1, 0, 1, GuiUtilities.kButtonVerticalFudge)
|
||||
label.Font = Enum.Font.SourceSans
|
||||
label.TextSize = 15
|
||||
label.Parent = button
|
||||
|
||||
self._label = label
|
||||
self._button = button
|
||||
|
||||
self._clicked = false
|
||||
self._hovered = false
|
||||
|
||||
button.InputBegan:connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = true
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
button.InputEnded:connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = false
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
button.MouseButton1Down:connect(function()
|
||||
self._clicked = true
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
button.MouseButton1Up:connect(function()
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
self:_updateButtonVisual()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function CustomTextButtonClass:_updateButtonVisual()
|
||||
if (self._clicked) then
|
||||
self._button.Image = kButtonImageIdPressed
|
||||
self._label.TextColor3 = GuiUtilities.kPressedButtonTextColor
|
||||
elseif (self._hovered) then
|
||||
self._button.Image = kButtonImageIdHovered
|
||||
self._label.TextColor3 = GuiUtilities.kStandardButtonTextColor
|
||||
else
|
||||
self._button.Image = kButtonImageIdDefault
|
||||
self._label.TextColor3 = GuiUtilities.kStandardButtonTextColor
|
||||
end
|
||||
end
|
||||
|
||||
-- Backwards compatibility (should be removed in the future)
|
||||
-- CustomTextButtonClass.getButton = CustomTextButtonClass.GetButton
|
||||
|
||||
function CustomTextButtonClass:GetButton()
|
||||
return self._button
|
||||
end
|
||||
|
||||
return CustomTextButtonClass
|
||||
247
src/Lualibs/GuiUtilities.lua
Normal file
247
src/Lualibs/GuiUtilities.lua
Normal file
@@ -0,0 +1,247 @@
|
||||
local module = {}
|
||||
|
||||
|
||||
module.kTitleBarHeight = 27
|
||||
module.kInlineTitleBarHeight = 24
|
||||
|
||||
module.kStandardContentAreaWidth = 180
|
||||
|
||||
module.kStandardPropertyHeight = 30
|
||||
module.kSubSectionLabelHeight = 30
|
||||
|
||||
module.kStandardVMargin = 7
|
||||
module.kStandardHMargin = 16
|
||||
|
||||
module.kCheckboxMinLabelWidth = 52
|
||||
module.kCheckboxMinMargin = 12
|
||||
module.kCheckboxWidth = 12
|
||||
|
||||
module.kRadioButtonsHPadding = 24
|
||||
|
||||
module.StandardLineLabelLeftMargin = module.kTitleBarHeight
|
||||
module.StandardLineElementLeftMargin = (module.StandardLineLabelLeftMargin + module.kCheckboxMinLabelWidth
|
||||
+ module.kCheckboxMinMargin + module.kCheckboxWidth + module.kRadioButtonsHPadding)
|
||||
module.StandardLineLabelWidth = (module.StandardLineElementLeftMargin - module.StandardLineLabelLeftMargin - 10 )
|
||||
|
||||
module.kDropDownHeight = 55
|
||||
|
||||
module.kBottomButtonsFrameHeight = 50
|
||||
module.kBottomButtonsHeight = 28
|
||||
|
||||
module.kShapeButtonSize = 32
|
||||
module.kTextVerticalFudge = -3
|
||||
module.kButtonVerticalFudge = -5
|
||||
|
||||
module.kBottomButtonsWidth = 100
|
||||
|
||||
module.kDisabledTextColor = Color3.new(.4, .4, .4) --todo: input spec disabled text color
|
||||
module.kStandardButtonTextColor = Color3.new(0, 0, 0) --todo: input spec disabled text color
|
||||
module.kPressedButtonTextColor = Color3.new(1, 1, 1) --todo: input spec disabled text color
|
||||
|
||||
module.kButtonStandardBackgroundColor = Color3.new(1, 1, 1) --todo: sync with spec
|
||||
module.kButtonStandardBorderColor = Color3.new(.4,.4,.4) --todo: sync with spec
|
||||
module.kButtonDisabledBackgroundColor = Color3.new(.7,.7,.7) --todo: sync with spec
|
||||
module.kButtonDisabledBorderColor = Color3.new(.6,.6,.6) --todo: sync with spec
|
||||
|
||||
module.kButtonBackgroundTransparency = 0.5
|
||||
module.kButtonBackgroundIntenseTransparency = 0.4
|
||||
|
||||
module.kMainFrame = nil
|
||||
|
||||
function module.ShouldUseIconsForDarkerBackgrounds()
|
||||
local mainColor = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground)
|
||||
return (mainColor.r + mainColor.g + mainColor.b) / 3 < 0.5
|
||||
end
|
||||
|
||||
function module.SetMainFrame(frame)
|
||||
module.kMainFrame = frame
|
||||
end
|
||||
|
||||
function module.syncGuiElementTitleColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Titlebar)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementInputFieldColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.InputFieldBackground)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementBackgroundColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementStripeColor(guiElement)
|
||||
local function setColors()
|
||||
if ((guiElement.LayoutOrder + 1) % 2 == 0) then
|
||||
guiElement.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainBackground)
|
||||
else
|
||||
guiElement.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.CategoryItem)
|
||||
end
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementBorderColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.BorderColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Border)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementFontColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.TextColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainText)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
function module.syncGuiElementScrollColor(guiElement)
|
||||
local function setColors()
|
||||
guiElement.ScrollBarImageColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.ScrollBar)
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(setColors)
|
||||
setColors()
|
||||
end
|
||||
|
||||
-- A frame with standard styling.
|
||||
function module.MakeFrame(name)
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Name = name
|
||||
frame.BackgroundTransparency = 0
|
||||
frame.BorderSizePixel = 0
|
||||
|
||||
module.syncGuiElementBackgroundColor(frame)
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
|
||||
-- A frame that is a whole line, containing some arbitrary sized widget.
|
||||
function module.MakeFixedHeightFrame(name, height)
|
||||
local frame = module.MakeFrame(name)
|
||||
frame.Size = UDim2.new(1, 0, 0, height)
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
-- A frame that is one standard-sized line, containing some standard-sized widget (label, edit box, dropdown,
|
||||
-- checkbox)
|
||||
function module.MakeStandardFixedHeightFrame(name)
|
||||
return module.MakeFixedHeightFrame(name, module.kStandardPropertyHeight)
|
||||
end
|
||||
|
||||
function module.AdjustHeightDynamicallyToLayout(frame, uiLayout, optPadding)
|
||||
if (not optPadding) then
|
||||
optPadding = 0
|
||||
end
|
||||
|
||||
local function updateSizes()
|
||||
frame.Size = UDim2.new(1, 0, 0, uiLayout.AbsoluteContentSize.Y + optPadding)
|
||||
end
|
||||
uiLayout:GetPropertyChangedSignal("AbsoluteContentSize"):connect(updateSizes)
|
||||
updateSizes()
|
||||
end
|
||||
|
||||
-- Assumes input frame has a List layout with sort order layout order.
|
||||
-- Add frames in order as siblings of list layout, they will be laid out in order.
|
||||
-- Color frame background accordingly.
|
||||
function module.AddStripedChildrenToListFrame(listFrame, frames)
|
||||
for index, frame in ipairs(frames) do
|
||||
frame.Parent = listFrame
|
||||
frame.LayoutOrder = index
|
||||
frame.BackgroundTransparency = 0
|
||||
frame.BorderSizePixel = 1
|
||||
module.syncGuiElementStripeColor(frame)
|
||||
module.syncGuiElementBorderColor(frame)
|
||||
end
|
||||
end
|
||||
|
||||
local function MakeSectionInternal(parentGui, name, title, contentHeight)
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Name = name
|
||||
frame.BackgroundTransparency = 1
|
||||
frame.Parent = parentGui
|
||||
frame.BackgroundTransparency = 1
|
||||
frame.BorderSizePixel = 0
|
||||
|
||||
-- If title is "nil', no title bar.
|
||||
local contentYOffset = 0
|
||||
local titleBar = nil
|
||||
if (title ~= nil) then
|
||||
local titleBarFrame = Instance.new("Frame")
|
||||
titleBarFrame.Name = "TitleBarFrame"
|
||||
titleBarFrame.Parent = frame
|
||||
titleBarFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
titleBarFrame.LayoutOrder = 0
|
||||
|
||||
local titleBar = Instance.new("TextLabel")
|
||||
titleBar.Name = "TitleBarLabel"
|
||||
titleBar.Text = title
|
||||
titleBar.Parent = titleBarFrame
|
||||
titleBar.BackgroundTransparency = 1
|
||||
titleBar.Position = UDim2.new(0, module.kStandardHMargin, 0, 0)
|
||||
|
||||
module.syncGuiElementFontColor(titleBar)
|
||||
|
||||
contentYOffset = contentYOffset + module.kTitleBarHeight
|
||||
end
|
||||
|
||||
frame.Size = UDim2.new(1, 0, 0, contentYOffset + contentHeight)
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
function module.MakeStandardPropertyLabel(text, opt_ignoreThemeUpdates)
|
||||
local label = Instance.new('TextLabel')
|
||||
label.Name = 'Label'
|
||||
label.BackgroundTransparency = 1
|
||||
label.Font = Enum.Font.SourceSans --todo: input spec font
|
||||
label.TextSize = 15 --todo: input spec font size
|
||||
label.TextXAlignment = Enum.TextXAlignment.Left
|
||||
label.Text = text
|
||||
label.AnchorPoint = Vector2.new(0, 0.5)
|
||||
label.Position = UDim2.new(0, module.StandardLineLabelLeftMargin, 0.5, module.kTextVerticalFudge)
|
||||
label.Size = UDim2.new(0, module.StandardLineLabelWidth, 1, 0)
|
||||
|
||||
if (not opt_ignoreThemeUpdates) then
|
||||
module.syncGuiElementFontColor(label)
|
||||
end
|
||||
|
||||
return label
|
||||
end
|
||||
|
||||
function module.MakeFrameWithSubSectionLabel(name, text)
|
||||
local row = module.MakeFixedHeightFrame(name, module.kSubSectionLabelHeight)
|
||||
row.BackgroundTransparency = 1
|
||||
|
||||
local label = module.MakeStandardPropertyLabel(text)
|
||||
label.BackgroundTransparency = 1
|
||||
label.Parent = row
|
||||
|
||||
return row
|
||||
end
|
||||
|
||||
function module.MakeFrameAutoScalingList(frame)
|
||||
local uiListLayout = Instance.new("UIListLayout")
|
||||
uiListLayout.Parent = frame
|
||||
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
|
||||
module.AdjustHeightDynamicallyToLayout(frame, uiListLayout)
|
||||
end
|
||||
|
||||
|
||||
return module
|
||||
17
src/Lualibs/ImageButtonWithText.d.ts
vendored
Normal file
17
src/Lualibs/ImageButtonWithText.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
declare class ImageButtonWithTextClass {
|
||||
constructor(
|
||||
name: string,
|
||||
layoutOrder: number,
|
||||
icon: string,
|
||||
text: string,
|
||||
buttonSize: UDim2,
|
||||
imageSize: UDim2,
|
||||
imagePos: UDim2,
|
||||
textSize: UDim2,
|
||||
textPos: UDim2,
|
||||
);
|
||||
GetButton(): Instance;
|
||||
SetSelected(selected: boolean): void;
|
||||
GetSelected(): boolean;
|
||||
}
|
||||
export = ImageButtonWithTextClass;
|
||||
158
src/Lualibs/ImageButtonWithText.lua
Normal file
158
src/Lualibs/ImageButtonWithText.lua
Normal file
@@ -0,0 +1,158 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- ImageButtonWithText.lua
|
||||
--
|
||||
-- An image button with text underneath. Standardized hover, clicked, and
|
||||
-- selected states.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
ImageButtonWithTextClass = {}
|
||||
ImageButtonWithTextClass.__index = ImageButtonWithTextClass
|
||||
|
||||
local kSelectedBaseTransparency = 0.2
|
||||
local kAdditionalTransparency = 0.1
|
||||
|
||||
function ImageButtonWithTextClass.new(name,
|
||||
layoutOrder,
|
||||
icon,
|
||||
text,
|
||||
buttonSize,
|
||||
imageSize,
|
||||
imagePos,
|
||||
textSize,
|
||||
textPos)
|
||||
local self = {}
|
||||
setmetatable(self, ImageButtonWithTextClass)
|
||||
|
||||
local button = Instance.new("ImageButton")
|
||||
button.Name = name
|
||||
button.AutoButtonColor = false
|
||||
button.Size = buttonSize
|
||||
button.BorderSizePixel = 1
|
||||
-- Image-with-text button has translucent background and "selected" background color.
|
||||
-- When selected we set transluency to not-zero so we see selected color.
|
||||
button.BackgroundTransparency = 1
|
||||
|
||||
button.LayoutOrder = layoutOrder
|
||||
|
||||
local buttonIcon = Instance.new("ImageLabel")
|
||||
buttonIcon.BackgroundTransparency = 1
|
||||
buttonIcon.Image = icon or ""
|
||||
buttonIcon.Size = imageSize
|
||||
buttonIcon.Position = imagePos
|
||||
buttonIcon.Parent = button
|
||||
|
||||
local textLabel = Instance.new("TextLabel")
|
||||
textLabel.BackgroundTransparency = 1
|
||||
textLabel.Text = text
|
||||
textLabel.Size = textSize
|
||||
textLabel.Position = textPos
|
||||
textLabel.TextScaled = true
|
||||
textLabel.Font = Enum.Font.SourceSans
|
||||
textLabel.Parent = button
|
||||
|
||||
GuiUtilities.syncGuiElementFontColor(textLabel)
|
||||
|
||||
local uiTextSizeConstraint = Instance.new("UITextSizeConstraint")
|
||||
-- Spec asks for fontsize of 12 pixels, but in Roblox the text font sizes look smaller than the mock
|
||||
--Note: For this font the Roblox text size is 25.7% larger than the design spec.
|
||||
uiTextSizeConstraint.MaxTextSize = 15
|
||||
uiTextSizeConstraint.Parent = textLabel
|
||||
|
||||
self._button = button
|
||||
self._clicked = false
|
||||
self._hovered = false
|
||||
self._selected = false
|
||||
|
||||
button.InputBegan:Connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = true
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
button.InputEnded:Connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = false
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
button.MouseButton1Down:Connect(function()
|
||||
self._clicked = true
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
button.MouseButton1Up:Connect(function()
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
function updateButtonVisual()
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(updateButtonVisual)
|
||||
|
||||
self:_updateButtonVisual()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function ImageButtonWithTextClass:_updateButtonVisual()
|
||||
-- Possibilties:
|
||||
if (self._clicked) then
|
||||
-- This covers 'clicked and selected' or 'clicked'
|
||||
self._button.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Button,
|
||||
Enum.StudioStyleGuideModifier.Selected)
|
||||
self._button.BorderColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Border,
|
||||
Enum.StudioStyleGuideModifier.Selected)
|
||||
if (self._selected) then
|
||||
self._button.BackgroundTransparency = GuiUtilities.kButtonBackgroundIntenseTransparency
|
||||
else
|
||||
self._button.BackgroundTransparency = GuiUtilities.kButtonBackgroundTransparency
|
||||
end
|
||||
elseif (self._hovered) then
|
||||
-- This covers 'hovered and selected' or 'hovered'
|
||||
self._button.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Button,
|
||||
Enum.StudioStyleGuideModifier.Hover)
|
||||
self._button.BorderColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Border,
|
||||
Enum.StudioStyleGuideModifier.Hover)
|
||||
if (self._selected) then
|
||||
self._button.BackgroundTransparency = GuiUtilities.kButtonBackgroundIntenseTransparency
|
||||
else
|
||||
self._button.BackgroundTransparency = GuiUtilities.kButtonBackgroundTransparency
|
||||
end
|
||||
elseif (self._selected) then
|
||||
-- This covers 'selected'
|
||||
self._button.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Button,
|
||||
Enum.StudioStyleGuideModifier.Selected)
|
||||
self._button.BorderColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Border,
|
||||
Enum.StudioStyleGuideModifier.Selected)
|
||||
self._button.BackgroundTransparency = GuiUtilities.kButtonBackgroundTransparency
|
||||
else
|
||||
-- This covers 'no special state'
|
||||
self._button.BackgroundColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Button)
|
||||
self._button.BorderColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.Border)
|
||||
self._button.BackgroundTransparency = 1
|
||||
end
|
||||
end
|
||||
|
||||
function ImageButtonWithTextClass:GetButton()
|
||||
return self._button
|
||||
end
|
||||
|
||||
function ImageButtonWithTextClass:SetSelected(selected)
|
||||
self._selected = selected
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
|
||||
function ImageButtonWithTextClass:GetSelected()
|
||||
return self._selected
|
||||
end
|
||||
|
||||
|
||||
return ImageButtonWithTextClass
|
||||
14
src/Lualibs/LabeledCheckbox.d.ts
vendored
Normal file
14
src/Lualibs/LabeledCheckbox.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
declare class LabeledCheckboxClass {
|
||||
constructor(nameSuffix: string, labelText: string, initValue?: boolean, initDisabled?: boolean);
|
||||
GetFrame(): Instance;
|
||||
GetValue(): boolean;
|
||||
GetLabel(): Instance;
|
||||
GetButton(): Instance;
|
||||
SetValueChangedFunction(fn: (value: boolean) => void): void;
|
||||
SetDisabled(disabled: boolean): void;
|
||||
GetDisabled(): boolean;
|
||||
SetValue(value: boolean): void;
|
||||
UseSmallSize(): void;
|
||||
DisableWithOverrideValue(overrideValue: boolean): void;
|
||||
}
|
||||
export = LabeledCheckboxClass;
|
||||
243
src/Lualibs/LabeledCheckbox.lua
Normal file
243
src/Lualibs/LabeledCheckbox.lua
Normal file
@@ -0,0 +1,243 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- LabeledCheckbox.lua
|
||||
--
|
||||
-- Creates a frame containing a label and a checkbox.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
local kCheckboxWidth = GuiUtilities.kCheckboxWidth
|
||||
|
||||
local kMinTextSize = 14
|
||||
local kMinHeight = 24
|
||||
local kMinLabelWidth = GuiUtilities.kCheckboxMinLabelWidth
|
||||
local kMinMargin = GuiUtilities.kCheckboxMinMargin
|
||||
local kMinButtonWidth = kCheckboxWidth;
|
||||
|
||||
local kMinLabelSize = UDim2.new(0, kMinLabelWidth, 0, kMinHeight)
|
||||
local kMinLabelPos = UDim2.new(0, kMinButtonWidth + kMinMargin, 0, kMinHeight/2)
|
||||
|
||||
local kMinButtonSize = UDim2.new(0, kMinButtonWidth, 0, kMinButtonWidth)
|
||||
local kMinButtonPos = UDim2.new(0, 0, 0, kMinHeight/2)
|
||||
|
||||
local kCheckImageWidth = 8
|
||||
local kMinCheckImageWidth = kCheckImageWidth
|
||||
|
||||
local kCheckImageSize = UDim2.new(0, kCheckImageWidth, 0, kCheckImageWidth)
|
||||
local kMinCheckImageSize = UDim2.new(0, kMinCheckImageWidth, 0, kMinCheckImageWidth)
|
||||
|
||||
local kEnabledCheckImage = "rbxasset://textures/TerrainTools/icon_tick.png"
|
||||
local kDisabledCheckImage = "rbxasset://textures/TerrainTools/icon_tick_grey.png"
|
||||
local kCheckboxFrameImage = "rbxasset://textures/TerrainTools/checkbox_square.png"
|
||||
LabeledCheckboxClass = {}
|
||||
LabeledCheckboxClass.__index = LabeledCheckboxClass
|
||||
|
||||
LabeledCheckboxClass.kMinFrameSize = UDim2.new(0, kMinLabelWidth + kMinMargin + kMinButtonWidth, 0, kMinHeight)
|
||||
|
||||
|
||||
function LabeledCheckboxClass.new(nameSuffix, labelText, initValue, initDisabled)
|
||||
local self = {}
|
||||
setmetatable(self, LabeledCheckboxClass)
|
||||
|
||||
local initValue = not not initValue
|
||||
local initDisabled = not not initDisabled
|
||||
|
||||
local frame = GuiUtilities.MakeStandardFixedHeightFrame("CBF" .. nameSuffix)
|
||||
|
||||
local fullBackgroundButton = Instance.new("TextButton")
|
||||
fullBackgroundButton.Name = "FullBackground"
|
||||
fullBackgroundButton.Parent = frame
|
||||
fullBackgroundButton.BackgroundTransparency = 1
|
||||
fullBackgroundButton.Size = UDim2.new(1, 0, 1, 0)
|
||||
fullBackgroundButton.Position = UDim2.new(0, 0, 0, 0)
|
||||
fullBackgroundButton.Text = ""
|
||||
|
||||
local label = GuiUtilities.MakeStandardPropertyLabel(labelText, true)
|
||||
label.Parent = fullBackgroundButton
|
||||
|
||||
local button = Instance.new('ImageButton')
|
||||
button.Name = 'Button'
|
||||
button.Size = UDim2.new(0, kCheckboxWidth, 0, kCheckboxWidth)
|
||||
button.AnchorPoint = Vector2.new(0, .5)
|
||||
button.BackgroundTransparency = 0
|
||||
button.Position = UDim2.new(0, GuiUtilities.StandardLineElementLeftMargin, .5, 0)
|
||||
button.Parent = fullBackgroundButton
|
||||
button.Image = kCheckboxFrameImage
|
||||
button.BorderSizePixel = 0
|
||||
button.AutoButtonColor = false
|
||||
|
||||
local checkImage = Instance.new("ImageLabel")
|
||||
checkImage.Name = "CheckImage"
|
||||
checkImage.Parent = button
|
||||
checkImage.Image = kEnabledCheckImage
|
||||
checkImage.Visible = false
|
||||
checkImage.Size = kCheckImageSize
|
||||
checkImage.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
checkImage.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
checkImage.BackgroundTransparency = 1
|
||||
checkImage.BorderSizePixel = 0
|
||||
|
||||
self._frame = frame
|
||||
self._button = button
|
||||
self._label = label
|
||||
self._checkImage = checkImage
|
||||
self._fullBackgroundButton = fullBackgroundButton
|
||||
self._useDisabledOverride = false
|
||||
self._disabledOverride = false
|
||||
self:SetDisabled(initDisabled)
|
||||
|
||||
self._value = not initValue
|
||||
self:SetValue(initValue)
|
||||
|
||||
self:_SetupMouseClickHandling()
|
||||
|
||||
local function updateFontColors()
|
||||
self:UpdateFontColors()
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(updateFontColors)
|
||||
updateFontColors()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
function LabeledCheckboxClass:_MaybeToggleState()
|
||||
if not self._disabled then
|
||||
self:SetValue(not self._value)
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:_SetupMouseClickHandling()
|
||||
self._button.MouseButton1Down:connect(function()
|
||||
self:_MaybeToggleState()
|
||||
end)
|
||||
|
||||
self._fullBackgroundButton.MouseButton1Down:connect(function()
|
||||
self:_MaybeToggleState()
|
||||
end)
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:_HandleUpdatedValue()
|
||||
self._checkImage.Visible = self:GetValue()
|
||||
|
||||
if (self._valueChangedFunction) then
|
||||
self._valueChangedFunction(self:GetValue())
|
||||
end
|
||||
end
|
||||
|
||||
-- Small checkboxes are a different entity.
|
||||
-- All the bits are smaller.
|
||||
-- Fixed width instead of flood-fill.
|
||||
-- Box comes first, then label.
|
||||
function LabeledCheckboxClass:UseSmallSize()
|
||||
self._label.TextSize = kMinTextSize
|
||||
self._label.Size = kMinLabelSize
|
||||
self._label.Position = kMinLabelPos
|
||||
self._label.TextXAlignment = Enum.TextXAlignment.Left
|
||||
|
||||
self._button.Size = kMinButtonSize
|
||||
self._button.Position = kMinButtonPos
|
||||
|
||||
self._checkImage.Size = kMinCheckImageSize
|
||||
|
||||
self._frame.Size = LabeledCheckboxClass.kMinFrameSize
|
||||
self._frame.BackgroundTransparency = 1
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:GetValue()
|
||||
-- If button is disabled, and we should be using a disabled override,
|
||||
-- use the disabled override.
|
||||
if (self._disabled and self._useDisabledOverride) then
|
||||
return self._disabledOverride
|
||||
else
|
||||
return self._value
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:GetLabel()
|
||||
return self._label
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:GetButton()
|
||||
return self._button
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:SetValueChangedFunction(vcFunction)
|
||||
self._valueChangedFunction = vcFunction
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:SetDisabled(newDisabled)
|
||||
local newDisabled = not not newDisabled
|
||||
|
||||
local originalValue = self:GetValue()
|
||||
|
||||
if newDisabled ~= self._disabled then
|
||||
self._disabled = newDisabled
|
||||
|
||||
-- if we are no longer disabled, then we don't need or want
|
||||
-- the override any more. Forget it.
|
||||
if (not self._disabled) then
|
||||
self._useDisabledOverride = false
|
||||
end
|
||||
|
||||
if (newDisabled) then
|
||||
self._checkImage.Image = kDisabledCheckImage
|
||||
else
|
||||
self._checkImage.Image = kEnabledCheckImage
|
||||
end
|
||||
|
||||
self:UpdateFontColors()
|
||||
self._button.BackgroundColor3 = self._disabled and GuiUtilities.kButtonDisabledBackgroundColor or GuiUtilities.kButtonStandardBackgroundColor
|
||||
self._button.BorderColor3 = self._disabled and GuiUtilities.kButtonDisabledBorderColor or GuiUtilities.kButtonStandardBorderColor
|
||||
if self._disabledChangedFunction then
|
||||
self._disabledChangedFunction(self._disabled)
|
||||
end
|
||||
end
|
||||
|
||||
local newValue = self:GetValue()
|
||||
if (newValue ~= originalValue) then
|
||||
self:_HandleUpdatedValue()
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:UpdateFontColors()
|
||||
if self._disabled then
|
||||
self._label.TextColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.DimmedText)
|
||||
else
|
||||
self._label.TextColor3 = settings().Studio.Theme:GetColor(Enum.StudioStyleGuideColor.MainText)
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:DisableWithOverrideValue(overrideValue)
|
||||
-- Disable this checkbox. While disabled, force value to override
|
||||
-- value.
|
||||
local oldValue = self:GetValue()
|
||||
self._useDisabledOverride = true
|
||||
self._disabledOverride = overrideValue
|
||||
self:SetDisabled(true)
|
||||
local newValue = self:GetValue()
|
||||
if (oldValue ~= newValue) then
|
||||
self:_HandleUpdatedValue()
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:GetDisabled()
|
||||
return self._disabled
|
||||
end
|
||||
|
||||
function LabeledCheckboxClass:SetValue(newValue)
|
||||
local newValue = not not newValue
|
||||
|
||||
if newValue ~= self._value then
|
||||
self._value = newValue
|
||||
|
||||
self:_HandleUpdatedValue()
|
||||
end
|
||||
end
|
||||
|
||||
return LabeledCheckboxClass
|
||||
12
src/Lualibs/LabeledMultiChoice.d.ts
vendored
Normal file
12
src/Lualibs/LabeledMultiChoice.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
interface LabeledMultiChoiceChoice {
|
||||
Id: string;
|
||||
Text: string;
|
||||
}
|
||||
declare class LabeledMultiChoiceClass {
|
||||
constructor(nameSuffix: string, labelText: string, choices: LabeledMultiChoiceChoice[], initChoiceIndex?: number);
|
||||
SetSelectedIndex(index: number): void;
|
||||
GetSelectedIndex(): number;
|
||||
SetValueChangedFunction(fn: (index: number) => void): void;
|
||||
GetFrame(): Instance;
|
||||
}
|
||||
export = LabeledMultiChoiceClass;
|
||||
132
src/Lualibs/LabeledMultiChoice.lua
Normal file
132
src/Lualibs/LabeledMultiChoice.lua
Normal file
@@ -0,0 +1,132 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- LabeledMultiChoice.lua
|
||||
--
|
||||
-- Creates a frame containing a label and list of choices, of which exactly one
|
||||
-- is always selected.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
LabeledRadioButton = require(script.Parent.LabeledRadioButton)
|
||||
LabeledCheckbox = require(script.Parent.LabeledCheckbox)
|
||||
VerticallyScalingListFrame = require(script.Parent.VerticallyScalingListFrame)
|
||||
|
||||
local kRadioButtonsHPadding = GuiUtilities.kRadioButtonsHPadding
|
||||
|
||||
LabeledMultiChoiceClass = {}
|
||||
LabeledMultiChoiceClass.__index = LabeledMultiChoiceClass
|
||||
|
||||
|
||||
-- Note:
|
||||
-- "choices" is an array of entries.
|
||||
-- each entry must have at least 2 fields:
|
||||
-- "Id" - a unique (in the scope of choices) string id. Not visible to user.
|
||||
-- "Text" - user-facing string: the label for the choice.
|
||||
function LabeledMultiChoiceClass.new(nameSuffix, labelText, choices, initChoiceIndex)
|
||||
local self = {}
|
||||
setmetatable(self, LabeledMultiChoiceClass)
|
||||
|
||||
self._buttonObjsByIndex = {}
|
||||
|
||||
if (not initChoiceIndex ) then
|
||||
initChoiceIndex = 1
|
||||
end
|
||||
if (initChoiceIndex > #choices) then
|
||||
initChoiceIndex = #choices
|
||||
end
|
||||
|
||||
|
||||
local vsl = VerticallyScalingListFrame.new("MCC_" .. nameSuffix)
|
||||
vsl:AddBottomPadding()
|
||||
|
||||
local titleLabel = GuiUtilities.MakeFrameWithSubSectionLabel("Title", labelText)
|
||||
vsl:AddChild(titleLabel)
|
||||
|
||||
-- Container for cells.
|
||||
local cellFrame = self:_MakeRadioButtons(choices)
|
||||
vsl:AddChild(cellFrame)
|
||||
|
||||
self._vsl = vsl
|
||||
|
||||
self:SetSelectedIndex(initChoiceIndex)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function LabeledMultiChoiceClass:SetSelectedIndex(selectedIndex)
|
||||
self._selectedIndex = selectedIndex
|
||||
for i = 1, #self._buttonObjsByIndex do
|
||||
self._buttonObjsByIndex[i]:SetValue(i == selectedIndex)
|
||||
end
|
||||
|
||||
if (self._valueChangedFunction) then
|
||||
self._valueChangedFunction(self._selectedIndex)
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledMultiChoiceClass:GetSelectedIndex()
|
||||
return self._selectedIndex
|
||||
end
|
||||
|
||||
function LabeledMultiChoiceClass:SetValueChangedFunction(vcf)
|
||||
self._valueChangedFunction = vcf
|
||||
end
|
||||
|
||||
function LabeledMultiChoiceClass:GetFrame()
|
||||
return self._vsl:GetFrame()
|
||||
end
|
||||
|
||||
|
||||
-- Small checkboxes are a different entity.
|
||||
-- All the bits are smaller.
|
||||
-- Fixed width instead of flood-fill.
|
||||
-- Box comes first, then label.
|
||||
function LabeledMultiChoiceClass:_MakeRadioButtons(choices)
|
||||
local frame = GuiUtilities.MakeFrame("RadioButtons")
|
||||
frame.BackgroundTransparency = 1
|
||||
|
||||
local padding = Instance.new("UIPadding")
|
||||
padding.PaddingLeft = UDim.new(0, GuiUtilities.StandardLineLabelLeftMargin)
|
||||
padding.PaddingRight = UDim.new(0, GuiUtilities.StandardLineLabelLeftMargin)
|
||||
padding.Parent = frame
|
||||
|
||||
-- Make a grid to put checkboxes in.
|
||||
local uiGridLayout = Instance.new("UIGridLayout")
|
||||
uiGridLayout.CellSize = LabeledCheckbox.kMinFrameSize
|
||||
uiGridLayout.CellPadding = UDim2.new(0,
|
||||
kRadioButtonsHPadding,
|
||||
0,
|
||||
GuiUtilities.kStandardVMargin)
|
||||
uiGridLayout.HorizontalAlignment = Enum.HorizontalAlignment.Left
|
||||
uiGridLayout.VerticalAlignment = Enum.VerticalAlignment.Top
|
||||
uiGridLayout.Parent = frame
|
||||
uiGridLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
|
||||
for i, choiceData in ipairs(choices) do
|
||||
self:_AddRadioButton(frame, i, choiceData)
|
||||
end
|
||||
|
||||
-- Sync size with content size.
|
||||
GuiUtilities.AdjustHeightDynamicallyToLayout(frame, uiGridLayout)
|
||||
|
||||
return frame
|
||||
end
|
||||
|
||||
function LabeledMultiChoiceClass:_AddRadioButton(parentFrame, index, choiceData)
|
||||
local radioButtonObj = LabeledRadioButton.new(choiceData.Id, choiceData.Text)
|
||||
self._buttonObjsByIndex[index] = radioButtonObj
|
||||
|
||||
radioButtonObj:SetValueChangedFunction(function(value)
|
||||
-- If we notice the button going from off to on, and it disagrees with
|
||||
-- our current notion of selection, update selection.
|
||||
if (value and self._selectedIndex ~= index) then
|
||||
self:SetSelectedIndex(index)
|
||||
end
|
||||
end)
|
||||
|
||||
radioButtonObj:GetFrame().LayoutOrder = index
|
||||
radioButtonObj:GetFrame().Parent = parentFrame
|
||||
end
|
||||
|
||||
|
||||
return LabeledMultiChoiceClass
|
||||
8
src/Lualibs/LabeledRadioButton.d.ts
vendored
Normal file
8
src/Lualibs/LabeledRadioButton.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare class LabeledRadioButtonClass {
|
||||
constructor(nameSuffix: string, labelText: string);
|
||||
GetFrame(): Instance;
|
||||
GetValue(): boolean;
|
||||
SetValueChangedFunction(fn: (value: boolean) => void): void;
|
||||
SetValue(value: boolean): void;
|
||||
}
|
||||
export = LabeledRadioButtonClass;
|
||||
60
src/Lualibs/LabeledRadioButton.lua
Normal file
60
src/Lualibs/LabeledRadioButton.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- LabeledRadioButton.lua
|
||||
--
|
||||
-- Creates a frame containing a label and a radio button.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
LabeledCheckbox = require(script.Parent.LabeledCheckbox)
|
||||
|
||||
local kButtonImage = "rbxasset://textures/TerrainTools/radio_button_frame.png"
|
||||
local kBulletImage = "rbxasset://textures/TerrainTools/radio_button_bullet.png"
|
||||
|
||||
local kButtonImageDark = "rbxasset://textures/TerrainTools/radio_button_frame_dark.png"
|
||||
local kBulletImageDark = "rbxasset://textures/TerrainTools/radio_button_bullet_dark.png"
|
||||
|
||||
local kFrameSize = 12
|
||||
local kBulletSize = 14
|
||||
|
||||
LabeledRadioButtonClass = {}
|
||||
LabeledRadioButtonClass.__index = LabeledRadioButtonClass
|
||||
setmetatable(LabeledRadioButtonClass, LabeledCheckbox)
|
||||
|
||||
function LabeledRadioButtonClass.new(nameSuffix, labelText)
|
||||
local newButton = LabeledCheckbox.new(nameSuffix, labelText, false)
|
||||
setmetatable(newButton, LabeledRadioButtonClass)
|
||||
|
||||
newButton:UseSmallSize()
|
||||
newButton._checkImage.Position = UDim2.new(0.5, 0, 0.5, 0)
|
||||
newButton._checkImage.Image = kBulletImage
|
||||
newButton._checkImage.Size = UDim2.new(0, kBulletSize, 0, kBulletSize)
|
||||
|
||||
newButton._button.Image = kButtonImage
|
||||
newButton._button.Size = UDim2.new(0, kFrameSize, 0, kFrameSize)
|
||||
newButton._button.BackgroundTransparency = 1
|
||||
|
||||
local function updateImages()
|
||||
if (GuiUtilities:ShouldUseIconsForDarkerBackgrounds()) then
|
||||
newButton._checkImage.Image = kBulletImageDark
|
||||
newButton._button.Image = kButtonImageDark
|
||||
else
|
||||
newButton._checkImage.Image = kBulletImage
|
||||
newButton._button.Image = kButtonImage
|
||||
end
|
||||
end
|
||||
settings().Studio.ThemeChanged:connect(updateImages)
|
||||
updateImages()
|
||||
|
||||
return newButton
|
||||
end
|
||||
|
||||
function LabeledRadioButtonClass:_MaybeToggleState()
|
||||
-- A checkbox can never be toggled off.
|
||||
-- Only turns off because another one turns on.
|
||||
if (not self._disabled and not self._value) then
|
||||
self:SetValue(not self._value)
|
||||
end
|
||||
end
|
||||
|
||||
return LabeledRadioButtonClass
|
||||
8
src/Lualibs/LabeledSlider.d.ts
vendored
Normal file
8
src/Lualibs/LabeledSlider.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare class LabeledSliderClass {
|
||||
constructor(nameSuffix: string, labelText: string, sliderIntervals?: number, defaultValue?: number);
|
||||
SetValueChangedFunction(fn: (value: number) => void): void;
|
||||
GetFrame(): Instance;
|
||||
SetValue(value: number): void;
|
||||
GetValue(): number;
|
||||
}
|
||||
export = LabeledSliderClass;
|
||||
122
src/Lualibs/LabeledSlider.lua
Normal file
122
src/Lualibs/LabeledSlider.lua
Normal file
@@ -0,0 +1,122 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- LabeledSlider.lua
|
||||
--
|
||||
-- Creates a frame containing a label and a slider control.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
rbxGuiLibrary = require(script.Parent.RbxGui)
|
||||
|
||||
local kSliderWidth = 100
|
||||
|
||||
local kSliderThumbImage = "rbxasset://textures/TerrainTools/sliderbar_button.png"
|
||||
local kPreThumbImage = "rbxasset://textures/TerrainTools/sliderbar_blue.png"
|
||||
local kPostThumbImage = "rbxasset://textures/TerrainTools/sliderbar_grey.png"
|
||||
|
||||
local kThumbSize = 13
|
||||
|
||||
local kSteps = 100
|
||||
|
||||
LabeledSliderClass = {}
|
||||
LabeledSliderClass.__index = LabeledSliderClass
|
||||
|
||||
function LabeledSliderClass.new(nameSuffix, labelText, sliderIntervals, defaultValue)
|
||||
local self = {}
|
||||
setmetatable(self, LabeledSliderClass)
|
||||
|
||||
self._valueChangedFunction = nil
|
||||
|
||||
local sliderIntervals = sliderIntervals or 100
|
||||
local defaultValue = defaultValue or 1
|
||||
|
||||
local frame = GuiUtilities.MakeStandardFixedHeightFrame('Slider' .. nameSuffix)
|
||||
self._frame = frame
|
||||
|
||||
local label = GuiUtilities.MakeStandardPropertyLabel(labelText)
|
||||
label.Parent = frame
|
||||
self._label = label
|
||||
|
||||
self._value = defaultValue
|
||||
|
||||
--steps, width, position
|
||||
local slider, sliderValue = rbxGuiLibrary.CreateSlider(sliderIntervals,
|
||||
kSteps,
|
||||
UDim2.new(0, 0, .5, -3))
|
||||
self._slider = slider
|
||||
self._sliderValue = sliderValue
|
||||
-- Some tweaks to make slider look nice.
|
||||
-- Hide the existing bar.
|
||||
slider.Bar.BackgroundTransparency = 1
|
||||
-- Replace slider thumb image.
|
||||
self._thumb = slider.Bar.Slider
|
||||
self._thumb.Image = kSliderThumbImage
|
||||
self._thumb.AnchorPoint = Vector2.new(0.5, 0.5)
|
||||
self._thumb.Size = UDim2.new(0, kThumbSize, 0, kThumbSize)
|
||||
|
||||
-- Add images on bar.
|
||||
self._preThumbImage = Instance.new("ImageLabel")
|
||||
self._preThumbImage.Name = "PreThumb"
|
||||
self._preThumbImage.Parent = slider.Bar
|
||||
self._preThumbImage.Size = UDim2.new(1, 0, 1, 0)
|
||||
self._preThumbImage.Position = UDim2.new(0, 0, 0, 0)
|
||||
self._preThumbImage.Image = kPreThumbImage
|
||||
self._preThumbImage.BorderSizePixel = 0
|
||||
|
||||
self._postThumbImage = Instance.new("ImageLabel")
|
||||
self._postThumbImage.Name = "PostThumb"
|
||||
self._postThumbImage.Parent = slider.Bar
|
||||
self._postThumbImage.Size = UDim2.new(1, 0, 1, 0)
|
||||
self._postThumbImage.Position = UDim2.new(0, 0, 0, 0)
|
||||
self._postThumbImage.Image = kPostThumbImage
|
||||
self._postThumbImage.BorderSizePixel = 0
|
||||
|
||||
sliderValue.Changed:connect(function()
|
||||
self._value = sliderValue.Value
|
||||
|
||||
-- Min value is 1.
|
||||
-- Max value is sliderIntervals.
|
||||
-- So scale is...
|
||||
local scale = (self._value - 1)/(sliderIntervals-1)
|
||||
|
||||
self._preThumbImage.Size = UDim2.new(scale, 0, 1, 0)
|
||||
self._postThumbImage.Size = UDim2.new(1 - scale, 0, 1, 0)
|
||||
self._postThumbImage.Position = UDim2.new(scale, 0, 0, 0)
|
||||
|
||||
self._thumb.Position = UDim2.new(scale, 0,
|
||||
0.5, 0)
|
||||
|
||||
if self._valueChangedFunction then
|
||||
self._valueChangedFunction(self._value)
|
||||
end
|
||||
end)
|
||||
|
||||
self:SetValue(defaultValue)
|
||||
slider.AnchorPoint = Vector2.new(0, 0.5)
|
||||
slider.Size = UDim2.new(0, kSliderWidth, 1, 0)
|
||||
slider.Position = UDim2.new(0, GuiUtilities.StandardLineElementLeftMargin, 0, GuiUtilities.kStandardPropertyHeight/2)
|
||||
slider.Parent = frame
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function LabeledSliderClass:SetValueChangedFunction(vcf)
|
||||
self._valueChangedFunction = vcf
|
||||
end
|
||||
|
||||
function LabeledSliderClass:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function LabeledSliderClass:SetValue(newValue)
|
||||
if self._sliderValue.Value ~= newValue then
|
||||
self._sliderValue.Value = newValue
|
||||
end
|
||||
end
|
||||
|
||||
function LabeledSliderClass:GetValue()
|
||||
return self._sliderValue.Value
|
||||
end
|
||||
|
||||
|
||||
return LabeledSliderClass
|
||||
10
src/Lualibs/LabeledTextInput.d.ts
vendored
Normal file
10
src/Lualibs/LabeledTextInput.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare class LabeledTextInput {
|
||||
constructor(nameSuffix: string, labelText: string, defaultValue?: string);
|
||||
SetValueChangedFunction(fn: (value: string) => void): void;
|
||||
GetFrame(): Instance;
|
||||
GetValue(): string;
|
||||
GetMaxGraphemes(): number;
|
||||
SetMaxGraphemes(value: number): void;
|
||||
SetValue(value: string): void;
|
||||
}
|
||||
export = LabeledTextInput;
|
||||
122
src/Lualibs/LabeledTextInput.lua
Normal file
122
src/Lualibs/LabeledTextInput.lua
Normal file
@@ -0,0 +1,122 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- LabeledTextInput.lua
|
||||
--
|
||||
-- Creates a frame containing a label and a text input control.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
local kTextInputWidth = 100
|
||||
local kTextBoxInternalPadding = 4
|
||||
|
||||
LabeledTextInputClass = {}
|
||||
LabeledTextInputClass.__index = LabeledTextInputClass
|
||||
|
||||
function LabeledTextInputClass.new(nameSuffix, labelText, defaultValue)
|
||||
local self = {}
|
||||
setmetatable(self, LabeledTextInputClass)
|
||||
|
||||
-- Note: we are using "graphemes" instead of characters.
|
||||
-- In modern text-manipulation-fu, what with internationalization,
|
||||
-- emojis, etc, it's not enough to count characters, particularly when
|
||||
-- concerned with "how many <things> am I rendering?".
|
||||
-- We are using the
|
||||
self._MaxGraphemes = 10
|
||||
|
||||
self._valueChangedFunction = nil
|
||||
|
||||
local defaultValue = defaultValue or ""
|
||||
|
||||
local frame = GuiUtilities.MakeStandardFixedHeightFrame('TextInput ' .. nameSuffix)
|
||||
self._frame = frame
|
||||
|
||||
local label = GuiUtilities.MakeStandardPropertyLabel(labelText)
|
||||
label.Parent = frame
|
||||
self._label = label
|
||||
|
||||
self._value = defaultValue
|
||||
|
||||
-- Dumb hack to add padding to text box,
|
||||
local textBoxWrapperFrame = Instance.new("Frame")
|
||||
textBoxWrapperFrame.Name = "Wrapper"
|
||||
textBoxWrapperFrame.Size = UDim2.new(0, kTextInputWidth, 0.6, 0)
|
||||
textBoxWrapperFrame.Position = UDim2.new(0, GuiUtilities.StandardLineElementLeftMargin, .5, 0)
|
||||
textBoxWrapperFrame.AnchorPoint = Vector2.new(0, .5)
|
||||
textBoxWrapperFrame.Parent = frame
|
||||
GuiUtilities.syncGuiElementInputFieldColor(textBoxWrapperFrame)
|
||||
GuiUtilities.syncGuiElementBorderColor(textBoxWrapperFrame)
|
||||
|
||||
local textBox = Instance.new("TextBox")
|
||||
textBox.Parent = textBoxWrapperFrame
|
||||
textBox.Name = "TextBox"
|
||||
textBox.Text = defaultValue
|
||||
textBox.Font = Enum.Font.SourceSans
|
||||
textBox.TextSize = 15
|
||||
textBox.BackgroundTransparency = 1
|
||||
textBox.TextXAlignment = Enum.TextXAlignment.Left
|
||||
textBox.Size = UDim2.new(1, -kTextBoxInternalPadding, 1, GuiUtilities.kTextVerticalFudge)
|
||||
textBox.Position = UDim2.new(0, kTextBoxInternalPadding, 0, 0)
|
||||
textBox.ClipsDescendants = true
|
||||
|
||||
GuiUtilities.syncGuiElementFontColor(textBox)
|
||||
|
||||
textBox:GetPropertyChangedSignal("Text"):connect(function()
|
||||
-- Never let the text be too long.
|
||||
-- Careful here: we want to measure number of graphemes, not characters,
|
||||
-- in the text, and we want to clamp on graphemes as well.
|
||||
if (utf8.len(self._textBox.Text) > self._MaxGraphemes) then
|
||||
local count = 0
|
||||
for start, stop in utf8.graphemes(self._textBox.Text) do
|
||||
count = count + 1
|
||||
if (count > self._MaxGraphemes) then
|
||||
-- We have gone one too far.
|
||||
-- clamp just before the beginning of this grapheme.
|
||||
self._textBox.Text = string.sub(self._textBox.Text, 1, start-1)
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Don't continue with rest of function: the resetting of "Text" field
|
||||
-- above will trigger re-entry. We don't need to trigger value
|
||||
-- changed function twice.
|
||||
return
|
||||
end
|
||||
|
||||
self._value = self._textBox.Text
|
||||
if (self._valueChangedFunction) then
|
||||
self._valueChangedFunction(self._value)
|
||||
end
|
||||
end)
|
||||
|
||||
self._textBox = textBox
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:SetValueChangedFunction(vcf)
|
||||
self._valueChangedFunction = vcf
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:GetValue()
|
||||
return self._value
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:GetMaxGraphemes()
|
||||
return self._MaxGraphemes
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:SetMaxGraphemes(newValue)
|
||||
self._MaxGraphemes = newValue
|
||||
end
|
||||
|
||||
function LabeledTextInputClass:SetValue(newValue)
|
||||
if self._value ~= newValue then
|
||||
self._textBox.Text = newValue
|
||||
end
|
||||
end
|
||||
|
||||
return LabeledTextInputClass
|
||||
4214
src/Lualibs/RbxGui.lua
Normal file
4214
src/Lualibs/RbxGui.lua
Normal file
File diff suppressed because it is too large
Load Diff
7
src/Lualibs/StatefulImageButton.d.ts
vendored
Normal file
7
src/Lualibs/StatefulImageButton.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
declare class StatefulImageButtonClass {
|
||||
constructor(buttonName: string, imageAsset: string, buttonSize: UDim2);
|
||||
setSelected(selected: boolean): void;
|
||||
getSelected(): boolean;
|
||||
getButton(): Instance;
|
||||
}
|
||||
export = StatefulImageButtonClass;
|
||||
95
src/Lualibs/StatefulImageButton.lua
Normal file
95
src/Lualibs/StatefulImageButton.lua
Normal file
@@ -0,0 +1,95 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- StatefulImageButton.lua
|
||||
--
|
||||
-- Image button.
|
||||
-- Has custom image for when "selected"
|
||||
-- Uses shading to indicate hover and click states.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
StatefulImageButtonClass = {}
|
||||
StatefulImageButtonClass.__index = StatefulImageButtonClass
|
||||
|
||||
function StatefulImageButtonClass.new(buttonName, imageAsset, buttonSize)
|
||||
local self = {}
|
||||
setmetatable(self, StatefulImageButtonClass)
|
||||
|
||||
local button = Instance.new("ImageButton")
|
||||
button.Parent = parent
|
||||
button.Image = imageAsset
|
||||
button.BackgroundTransparency = 1
|
||||
button.BorderSizePixel = 0
|
||||
button.Size = buttonSize
|
||||
button.Name = buttonName
|
||||
|
||||
self._button = button
|
||||
|
||||
self._hovered = false
|
||||
self._clicked = false
|
||||
self._selected = false
|
||||
|
||||
button.InputBegan:connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = true
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
button.InputEnded:connect(function(input)
|
||||
if (input.UserInputType == Enum.UserInputType.MouseMovement) then
|
||||
self._hovered = false
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
end)
|
||||
|
||||
button.MouseButton1Down:connect(function()
|
||||
self._clicked = true
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
button.MouseButton1Up:connect(function()
|
||||
self._clicked = false
|
||||
self:_updateButtonVisual()
|
||||
end)
|
||||
|
||||
self:_updateButtonVisual()
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function StatefulImageButtonClass:_updateButtonVisual()
|
||||
if (self._selected) then
|
||||
self._button.ImageTransparency = 0
|
||||
self._button.ImageColor3 = Color3.new(1,1,1)
|
||||
else
|
||||
self._button.ImageTransparency = 0.5
|
||||
self._button.ImageColor3 = Color3.new(.5,.5,.5)
|
||||
end
|
||||
|
||||
if (self._clicked) then
|
||||
self._button.BackgroundTransparency = 0.8
|
||||
elseif (self._hovered) then
|
||||
self._button.BackgroundTransparency = 0.9
|
||||
else
|
||||
self._button.BackgroundTransparency = 1
|
||||
end
|
||||
end
|
||||
|
||||
function StatefulImageButtonClass:setSelected(selected)
|
||||
self._selected = selected
|
||||
self:_updateButtonVisual()
|
||||
end
|
||||
|
||||
function StatefulImageButtonClass:getSelected()
|
||||
return self._selected
|
||||
end
|
||||
|
||||
function StatefulImageButtonClass:getButton()
|
||||
return self._button
|
||||
end
|
||||
|
||||
return StatefulImageButtonClass
|
||||
6
src/Lualibs/VerticalScrollingFrame.d.ts
vendored
Normal file
6
src/Lualibs/VerticalScrollingFrame.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare class VerticalScrollingFrame {
|
||||
constructor(suffix: string);
|
||||
GetContentsFrame(): Instance;
|
||||
GetSectionFrame(): Instance;
|
||||
}
|
||||
export = VerticalScrollingFrame;
|
||||
87
src/Lualibs/VerticalScrollingFrame.lua
Normal file
87
src/Lualibs/VerticalScrollingFrame.lua
Normal file
@@ -0,0 +1,87 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- VerticalScrollingFrame.lua
|
||||
--
|
||||
-- Creates a scrolling frame that automatically updates canvas size
|
||||
--
|
||||
----------------------------------------
|
||||
|
||||
local GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
local VerticalScrollingFrame = {}
|
||||
VerticalScrollingFrame.__index = VerticalScrollingFrame
|
||||
|
||||
function VerticalScrollingFrame.new(suffix)
|
||||
local self = {}
|
||||
setmetatable(self, VerticalScrollingFrame)
|
||||
|
||||
local section = Instance.new("Frame")
|
||||
section.BorderSizePixel = 0
|
||||
section.Size = UDim2.new(1, 0, 1, 0)
|
||||
section.Position = UDim2.new(0, 0, 0, 0)
|
||||
section.BackgroundTransparency = 1
|
||||
section.Name = "VerticalScrollFrame" .. suffix
|
||||
|
||||
local scrollBackground = Instance.new("Frame")
|
||||
scrollBackground.Name = "ScrollbarBackground"
|
||||
scrollBackground.BackgroundColor3 = Color3.fromRGB(238, 238, 238)
|
||||
scrollBackground.BorderColor3 = Color3.fromRGB(182, 182, 182)
|
||||
scrollBackground.Size = UDim2.new(0, 15, 1, -2)
|
||||
scrollBackground.Position = UDim2.new(1, -16, 0, 1)
|
||||
scrollBackground.Parent = section
|
||||
scrollBackground.ZIndex = 2;
|
||||
|
||||
local scrollFrame = Instance.new("ScrollingFrame")
|
||||
scrollFrame.Name = "ScrollFrame" .. suffix
|
||||
scrollFrame.VerticalScrollBarPosition = Enum.VerticalScrollBarPosition.Right
|
||||
scrollFrame.VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar
|
||||
scrollFrame.ElasticBehavior = Enum.ElasticBehavior.Never
|
||||
scrollFrame.ScrollBarThickness = 17
|
||||
scrollFrame.BorderSizePixel = 0
|
||||
scrollFrame.BackgroundTransparency = 1
|
||||
scrollFrame.ZIndex = 2
|
||||
scrollFrame.TopImage = "http://www.roblox.com/asset/?id=1533255544"
|
||||
scrollFrame.MidImage = "http://www.roblox.com/asset/?id=1535685612"
|
||||
scrollFrame.BottomImage = "http://www.roblox.com/asset/?id=1533256504"
|
||||
scrollFrame.Size = UDim2.new(1, 0, 1, 0)
|
||||
scrollFrame.Position = UDim2.new(0, 0, 0, 0)
|
||||
scrollFrame.Parent = section
|
||||
|
||||
local uiListLayout = Instance.new("UIListLayout")
|
||||
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
uiListLayout.Parent = scrollFrame
|
||||
|
||||
self._section = section
|
||||
self._scrollFrame = scrollFrame
|
||||
self._scrollBackground = scrollBackground
|
||||
self._uiListLayout = uiListLayout
|
||||
|
||||
scrollFrame:GetPropertyChangedSignal("AbsoluteSize"):connect(function() self:_updateScrollingFrameCanvas() end)
|
||||
uiListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):connect(function() self:_updateScrollingFrameCanvas() end)
|
||||
self:_updateScrollingFrameCanvas()
|
||||
|
||||
GuiUtilities.syncGuiElementScrollColor(scrollFrame)
|
||||
GuiUtilities.syncGuiElementBorderColor(scrollBackground)
|
||||
GuiUtilities.syncGuiElementTitleColor(scrollBackground)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function VerticalScrollingFrame:_updateScrollbarBackingVisibility()
|
||||
self._scrollBackground.Visible = self._scrollFrame.AbsoluteSize.y < self._uiListLayout.AbsoluteContentSize.y
|
||||
end
|
||||
|
||||
function VerticalScrollingFrame:_updateScrollingFrameCanvas()
|
||||
self._scrollFrame.CanvasSize = UDim2.new(0, 0, 0, self._uiListLayout.AbsoluteContentSize.Y)
|
||||
self:_updateScrollbarBackingVisibility()
|
||||
end
|
||||
|
||||
function VerticalScrollingFrame:GetContentsFrame()
|
||||
return self._scrollFrame
|
||||
end
|
||||
|
||||
function VerticalScrollingFrame:GetSectionFrame()
|
||||
return self._section
|
||||
end
|
||||
|
||||
return VerticalScrollingFrame
|
||||
8
src/Lualibs/VerticallyScalingListFrame.d.ts
vendored
Normal file
8
src/Lualibs/VerticallyScalingListFrame.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
declare class VerticallyScalingListFrame {
|
||||
constructor(nameSuffix: string);
|
||||
AddBottomPadding(): void;
|
||||
GetFrame(): Instance;
|
||||
AddChild(childFrame: Instance): void;
|
||||
SetCallbackOnResize(callback: () => void): void;
|
||||
}
|
||||
export = VerticallyScalingListFrame;
|
||||
73
src/Lualibs/VerticallyScalingListFrame.lua
Normal file
73
src/Lualibs/VerticallyScalingListFrame.lua
Normal file
@@ -0,0 +1,73 @@
|
||||
----------------------------------------
|
||||
--
|
||||
-- VerticallyScalingListFrame
|
||||
--
|
||||
-- Creates a frame that organizes children into a list layout.
|
||||
-- Will scale dynamically as children grow.
|
||||
--
|
||||
----------------------------------------
|
||||
GuiUtilities = require(script.Parent.GuiUtilities)
|
||||
|
||||
VerticallyScalingListFrame = {}
|
||||
VerticallyScalingListFrame.__index = VerticallyScalingListFrame
|
||||
|
||||
local kBottomPadding = 10
|
||||
|
||||
function VerticallyScalingListFrame.new(nameSuffix)
|
||||
local self = {}
|
||||
setmetatable(self, VerticallyScalingListFrame)
|
||||
|
||||
self._resizeCallback = nil
|
||||
|
||||
local frame = Instance.new('Frame')
|
||||
frame.Name = 'VSLFrame' .. nameSuffix
|
||||
frame.Size = UDim2.new(1, 0, 0, height)
|
||||
frame.BackgroundTransparency = 0
|
||||
frame.BorderSizePixel = 0
|
||||
GuiUtilities.syncGuiElementBackgroundColor(frame)
|
||||
|
||||
self._frame = frame
|
||||
|
||||
local uiListLayout = Instance.new('UIListLayout')
|
||||
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
|
||||
uiListLayout.Parent = frame
|
||||
self._uiListLayout = uiListLayout
|
||||
|
||||
local function updateSizes()
|
||||
self._frame.Size = UDim2.new(1, 0, 0, uiListLayout.AbsoluteContentSize.Y)
|
||||
if (self._resizeCallback) then
|
||||
self._resizeCallback()
|
||||
end
|
||||
end
|
||||
self._uiListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):connect(updateSizes)
|
||||
updateSizes()
|
||||
|
||||
self._childCount = 0
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function VerticallyScalingListFrame:AddBottomPadding()
|
||||
local frame = Instance.new("Frame")
|
||||
frame.Name = "BottomPadding"
|
||||
frame.BackgroundTransparency = 1
|
||||
frame.Size = UDim2.new(1, 0, 0, kBottomPadding)
|
||||
frame.LayoutOrder = 1000
|
||||
frame.Parent = self._frame
|
||||
end
|
||||
|
||||
function VerticallyScalingListFrame:GetFrame()
|
||||
return self._frame
|
||||
end
|
||||
|
||||
function VerticallyScalingListFrame:AddChild(childFrame)
|
||||
childFrame.LayoutOrder = self._childCount
|
||||
self._childCount = self._childCount + 1
|
||||
childFrame.Parent = self._frame
|
||||
end
|
||||
|
||||
function VerticallyScalingListFrame:SetCallbackOnResize(callback)
|
||||
self._resizeCallback = callback
|
||||
end
|
||||
|
||||
return VerticallyScalingListFrame
|
||||
9
src/Toolbar/WidgetButton.ts
Normal file
9
src/Toolbar/WidgetButton.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class WidgetButton {
|
||||
constructor(toolbar: PluginToolbar, widget: DockWidgetPluginGui, buttonText: string, icon: string) {
|
||||
const widgetButton = toolbar.CreateButton(buttonText, buttonText, icon);
|
||||
widgetButton.Click.Connect(() => {
|
||||
widgetButton.SetActive(!widget.Enabled);
|
||||
widget.Enabled = !widget.Enabled;
|
||||
});
|
||||
}
|
||||
}
|
||||
30
src/Utils/Gui.ts
Normal file
30
src/Utils/Gui.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export function createAnchor(model: Folder, position: Vector3) {
|
||||
// Create Anchor for Adornments
|
||||
const anchor = new Instance("Part");
|
||||
anchor.Anchored = true;
|
||||
anchor.CanCollide = false;
|
||||
anchor.Transparency = 1;
|
||||
anchor.Size = new Vector3(0.1, 0.1, 0.1);
|
||||
anchor.Position = position;
|
||||
anchor.Parent = model;
|
||||
anchor.Name = "_GizmoAnchor";
|
||||
return anchor;
|
||||
}
|
||||
|
||||
export function syncGuiColors(...objects: GuiObject[]) {
|
||||
function setColors() {
|
||||
for (const guiObject of objects) {
|
||||
const studio = settings().Studio as Studio & { Theme: StudioTheme };
|
||||
pcall(() => {
|
||||
guiObject.BackgroundColor3 = studio.Theme.GetColor(Enum.StudioStyleGuideColor.MainBackground);
|
||||
});
|
||||
pcall(() => {
|
||||
if (guiObject.IsA("TextLabel")) {
|
||||
guiObject.TextColor3 = studio.Theme.GetColor(Enum.StudioStyleGuideColor.MainText);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
setColors();
|
||||
settings().Studio.ThemeChanged.Connect(setColors);
|
||||
}
|
||||
18
src/Utils/Room.ts
Normal file
18
src/Utils/Room.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
type RoomConfig = Configuration & {
|
||||
RoomId: IntValue;
|
||||
RoomType: StringValue;
|
||||
Origin: Vector3Value;
|
||||
End: Vector3Value;
|
||||
[key: `Exit_${number}`]: CFrameValue;
|
||||
};
|
||||
|
||||
export function checkRoomConfig(obj: Instance): obj is RoomConfig {
|
||||
return (
|
||||
(obj.IsA("Configuration") &&
|
||||
obj.FindFirstChild("RoomId")?.IsA("IntValue") &&
|
||||
obj.FindFirstChild("RoomType")?.IsA("StringValue") &&
|
||||
obj.FindFirstChild("Origin")?.IsA("Vector3Value") &&
|
||||
obj.FindFirstChild("End")?.IsA("Vector3Value")) ??
|
||||
false
|
||||
);
|
||||
}
|
||||
209
src/Widget/RoomWidget.lua
Normal file
209
src/Widget/RoomWidget.lua
Normal file
@@ -0,0 +1,209 @@
|
||||
local RoomWidget = {}
|
||||
|
||||
local ScrollingFrame = require(script.Parent.Parent.Parent.StudioWidgets.VerticalScrollingFrame)
|
||||
local VerticallyScalingListFrame = require(script.Parent.Parent.Parent.StudioWidgets.VerticallyScalingListFrame)
|
||||
local CollapsibleTitledSection = require(script.Parent.Parent.Parent.StudioWidgets.CollapsibleTitledSection)
|
||||
local LabeledTextInput = require(script.Parent.Parent.Parent.StudioWidgets.LabeledTextInput)
|
||||
local CustomTextButton = require(script.Parent.Parent.Parent.StudioWidgets.CustomTextButton)
|
||||
|
||||
local guiModule = require(script.Parent.Parent.Parent.Tools.Gui)
|
||||
local UseLessModule = require(script.Parent.Parent.Parent.Tools.Useless)
|
||||
|
||||
|
||||
function RoomWidget:new(plugin:Plugin,adornmentContainer:Folder,pluginModel:Folder)
|
||||
self = setmetatable({}, {__index = RoomWidget})
|
||||
|
||||
self.adornmentContainer = adornmentContainer
|
||||
self.pluginModel = pluginModel
|
||||
|
||||
-- Create Widget
|
||||
self.info = DockWidgetPluginGuiInfo.new(
|
||||
Enum.InitialDockState.Left,
|
||||
false,
|
||||
false,
|
||||
200,
|
||||
300,
|
||||
150,
|
||||
150
|
||||
)
|
||||
|
||||
self.widget = plugin:CreateDockWidgetPluginGui(
|
||||
"RoomWidget",
|
||||
self.info
|
||||
)
|
||||
self.widget.Title = "Room Info"
|
||||
|
||||
-- Create Widget Components
|
||||
-- Create No Room Label
|
||||
self.noRoomLabel = Instance.new("TextLabel")
|
||||
self.noRoomLabel.Name = "NoSelect"
|
||||
self.noRoomLabel.Text = "Select a room to use this widget."
|
||||
self.noRoomLabel.Size = UDim2.new(1,0,1,0)
|
||||
guiModule.syncGuiColors({ self.noRoomLabel })
|
||||
|
||||
-- Create Scrolling Frame
|
||||
self.scrollFrame = ScrollingFrame.new("RoomScroll")
|
||||
|
||||
-- Create Vertical Scaling List Frame
|
||||
self.listFrame = VerticallyScalingListFrame.new("RoomWidget")
|
||||
|
||||
-- Create Room Collapse
|
||||
self.roomCollapse = CollapsibleTitledSection.new(
|
||||
"suffix", -- name suffix of the gui object
|
||||
"Room", -- the text displayed beside the collapsible arrow
|
||||
true, -- have the content frame auto-update its size?
|
||||
true, -- minimizable?
|
||||
false -- minimized by default?
|
||||
)
|
||||
|
||||
-- Create Exit Collapse
|
||||
self.exitCollapse = CollapsibleTitledSection.new(
|
||||
"ExitCollapse", -- name suffix of the gui object
|
||||
"Exit", -- title text of the collapsible arrow
|
||||
true, -- have the content frame auto-update its size?
|
||||
true, -- minimizable?
|
||||
false -- minimized by default?
|
||||
)
|
||||
|
||||
-- Create TextInput
|
||||
self.roomIdValue = Instance.new("IntValue")
|
||||
self.roomIdLabel = LabeledTextInput.new(
|
||||
"RoomId", -- name suffix of gui object
|
||||
"Room Id", -- title text of the multi choice
|
||||
"0" -- default value
|
||||
)
|
||||
self.roomIdLabel:SetValueChangedFunction(
|
||||
function (value)
|
||||
self.roomIdValue.Value = value
|
||||
end
|
||||
)
|
||||
|
||||
self.typeValue = Instance.new("StringValue")
|
||||
self.typeLabel = LabeledTextInput.new(
|
||||
"RoomType", -- name suffix of gui object
|
||||
"Room Type", -- title text of the multi choice
|
||||
"" -- default value
|
||||
)
|
||||
self.typeLabel:SetMaxGraphemes(255)
|
||||
self.typeLabel:SetValueChangedFunction(
|
||||
function (value)
|
||||
self.typeValue.Value = value
|
||||
end
|
||||
)
|
||||
|
||||
-- Setup Widget
|
||||
self.roomIdLabel:GetFrame().Parent = self.roomCollapse:GetContentsFrame()
|
||||
self.typeLabel:GetFrame().Parent = self.roomCollapse:GetContentsFrame()
|
||||
self.listFrame:AddChild(self.roomCollapse:GetSectionFrame())
|
||||
self.listFrame:AddChild(self.exitCollapse:GetSectionFrame())
|
||||
|
||||
self.listFrame:AddBottomPadding()
|
||||
|
||||
self.listFrame:GetFrame().Parent = self.scrollFrame:GetContentsFrame()
|
||||
self.scrollFrame:GetSectionFrame().Parent = self.widget
|
||||
self.noRoomLabel.Parent = self.widget
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function RoomWidget:UpdateValue(roomIdValue:IntValue,roomTypeValue:StringValue,roomExit:{CFrameValue})
|
||||
self.roomIdValue = roomIdValue
|
||||
self.roomIdLabel:SetValue(roomIdValue.Value)
|
||||
self.typeValue = roomTypeValue
|
||||
self.typeLabel:SetValue(roomTypeValue.Value)
|
||||
end
|
||||
|
||||
function RoomWidget:SetActive(active:boolean)
|
||||
self.noRoomLabel.Visible = not active
|
||||
self.scrollFrame.Visible = active
|
||||
end
|
||||
|
||||
function RoomWidget:_clearAdornment()
|
||||
self.adornmentContainer:ClearAllChildren()
|
||||
self.pluginModel:ClearAllChildren()
|
||||
end
|
||||
|
||||
function RoomWidget:ReloadExit(exits:{CFrameValue})
|
||||
self.exitCollapse:GetContentsFrame():ClearAllChildren()
|
||||
|
||||
for _,exit in exits do
|
||||
local exitCollapse = CollapsibleTitledSection.new(
|
||||
"ExitCollapse_"..exit.Name, -- name suffix of the gui object
|
||||
exit.Name, -- the text displayed beside the collapsible arrow
|
||||
true, -- have the content frame auto-update its size?
|
||||
true, -- minimizable?
|
||||
false -- minimized by default?
|
||||
)
|
||||
|
||||
local button = CustomTextButton.new(
|
||||
"edit_button", -- name of the gui object
|
||||
"Edit" -- the text displayed on the button
|
||||
)
|
||||
button:GetButton().Activated:Connect(
|
||||
function(inputObject: InputObject, clickCount: number)
|
||||
|
||||
end
|
||||
)
|
||||
|
||||
-- Handle widget
|
||||
exitCollapse:GetContentsFrame().Parent = self.exitCollapse:GetSectionFrame()
|
||||
end
|
||||
end
|
||||
|
||||
function RoomWidget:LoadExitMoveHandles(exit: CFrameValue)
|
||||
self:_clearAdornment()
|
||||
|
||||
local function createAnchor(position: Vector3)
|
||||
-- Create Anchor for Adornments
|
||||
local anchor = Instance.new("Part")
|
||||
anchor.Anchored = true
|
||||
anchor.CanCollide = false
|
||||
anchor.Transparency = 1
|
||||
anchor.Size = Vector3.new(0.1, 0.1, 0.1)
|
||||
anchor.Position = position
|
||||
anchor.Parent = self.pluginModel
|
||||
anchor.Name = "_GizmoAnchor"
|
||||
return anchor
|
||||
end
|
||||
|
||||
local anchor = createAnchor(exit.Value.Position)
|
||||
local handles = Instance.new("Handles")
|
||||
handles.Adornee = anchor
|
||||
handles.Parent = self.adornmentContainer
|
||||
|
||||
local currentVector = Vector3.new(0, 0, 0)
|
||||
|
||||
handles.MouseButton1Down:Connect(
|
||||
function(face: Enum.NormalId)
|
||||
currentVector = anchor.Position
|
||||
end
|
||||
)
|
||||
|
||||
handles.MouseDrag:Connect(
|
||||
function(face: Enum.NormalId, distance: number)
|
||||
-- Apply the changes to the anchor's position
|
||||
if face == Enum.NormalId.Top or face == Enum.NormalId.Right or face == Enum.NormalId.Back then
|
||||
anchor.Position = UseLessModule.roundVector3(currentVector + Vector3.new(
|
||||
UseLessModule.bton(face == Enum.NormalId.Right) * distance,
|
||||
UseLessModule.bton(face == Enum.NormalId.Top) * distance,
|
||||
UseLessModule.bton(face == Enum.NormalId.Back) * distance
|
||||
))
|
||||
else
|
||||
anchor.Position = UseLessModule.roundVector3(currentVector - Vector3.new(
|
||||
UseLessModule.bton(face == Enum.NormalId.Left) * distance,
|
||||
UseLessModule.bton(face == Enum.NormalId.Bottom) * distance,
|
||||
UseLessModule.bton(face == Enum.NormalId.Front) * distance
|
||||
))
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
handles.MouseButton1Up:Connect(
|
||||
function(face: Enum.NormalId)
|
||||
local newpos = anchor.Position
|
||||
position.Value = newpos
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
return RoomWidget
|
||||
83
src/Widget/RoomWidget.ts
Normal file
83
src/Widget/RoomWidget.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import CollapsibleTitledSection from "Lualibs/CollapsibleTitledSection";
|
||||
import LabeledTextInput from "Lualibs/LabeledTextInput";
|
||||
import VerticallyScalingListFrame from "Lualibs/VerticallyScalingListFrame";
|
||||
import VerticalScrollingFrame from "Lualibs/VerticalScrollingFrame";
|
||||
import { syncGuiColors } from "Utils/Gui";
|
||||
|
||||
export class RoomWidget {
|
||||
plugin: Plugin;
|
||||
model: Folder;
|
||||
info: DockWidgetPluginGuiInfo;
|
||||
widget: DockWidgetPluginGui;
|
||||
noRoomLabel: TextLabel;
|
||||
|
||||
scrollFrame = new VerticalScrollingFrame("RoomScroll");
|
||||
listFrame = new VerticallyScalingListFrame("RoomWidget");
|
||||
roomCollapse = new CollapsibleTitledSection(
|
||||
"roomCollapse", // name suffix of the gui object
|
||||
"Room", // the text displayed beside the collapsible arrow
|
||||
true, // have the content frame auto-update its size?
|
||||
true, // minimizable?
|
||||
false, // minimized by default?
|
||||
);
|
||||
exitCollapse = new CollapsibleTitledSection(
|
||||
"ExitCollapse", // name suffix of the gui object
|
||||
"Exit", // title text of the collapsible arrow
|
||||
true, // have the content frame auto-update its size?
|
||||
true, // minimizable?
|
||||
false, // minimized by default?
|
||||
);
|
||||
|
||||
roomIdValue = new Instance("IntValue");
|
||||
roomIdInput = new LabeledTextInput(
|
||||
"RoomId", // name suffix of gui object
|
||||
"Room Id", // title text of the multi choice
|
||||
"0", // default value
|
||||
);
|
||||
roomTypeValue = new Instance("StringValue");
|
||||
roomTypeInput = new LabeledTextInput(
|
||||
"RoomType", // name suffix of gui object
|
||||
"Room Type", // title text of the multi choice
|
||||
"", // default value
|
||||
);
|
||||
constructor(plugin: Plugin, model: Folder) {
|
||||
this.plugin = plugin;
|
||||
this.model = model;
|
||||
|
||||
this.info = new DockWidgetPluginGuiInfo(Enum.InitialDockState.Left, false, false, 200, 300, 150, 150);
|
||||
this.widget = plugin.CreateDockWidgetPluginGui("RoomWidget", this.info);
|
||||
(this.widget as unknown as { Title: string }).Title = "Room Info";
|
||||
|
||||
this.noRoomLabel = new Instance("TextLabel");
|
||||
this.noRoomLabel.Text = "Select a room to use this widget.";
|
||||
this.noRoomLabel.Size = new UDim2(1, 0, 1, 0);
|
||||
syncGuiColors(this.noRoomLabel);
|
||||
|
||||
this.roomIdInput.SetValueChangedFunction((value) => {
|
||||
this.roomIdValue.Value = tonumber(value) ?? 0;
|
||||
});
|
||||
|
||||
this.roomTypeInput.SetMaxGraphemes(255);
|
||||
this.roomTypeInput.SetValueChangedFunction((value) => {
|
||||
this.roomTypeValue.Value = value;
|
||||
});
|
||||
|
||||
// Setup Widget
|
||||
this.roomIdInput.GetFrame().Parent = this.roomCollapse.GetContentsFrame();
|
||||
this.roomTypeInput.GetFrame().Parent = this.roomCollapse.GetContentsFrame();
|
||||
this.listFrame.AddChild(this.roomCollapse.GetSectionFrame());
|
||||
this.listFrame.AddChild(this.exitCollapse.GetSectionFrame());
|
||||
|
||||
this.listFrame.AddBottomPadding();
|
||||
|
||||
this.listFrame.GetFrame().Parent = this.scrollFrame.GetContentsFrame();
|
||||
this.scrollFrame.GetSectionFrame().Parent = this.widget;
|
||||
this.noRoomLabel.Parent = this.widget;
|
||||
}
|
||||
UpdateValue() {
|
||||
|
||||
}
|
||||
SetActive(active: boolean) {
|
||||
this.noRoomLabel.Visible = !active;
|
||||
}
|
||||
}
|
||||
55
src/index.server.ts
Normal file
55
src/index.server.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import Signal from "@rbxts/signal";
|
||||
import { HandlesArea } from "Adornment/Area";
|
||||
import { WidgetButton } from "Toolbar/WidgetButton";
|
||||
import { checkRoomConfig } from "Utils/Room";
|
||||
|
||||
// Services
|
||||
const Selection = game.GetService("Selection");
|
||||
const CoreGui = game.GetService("CoreGui");
|
||||
|
||||
// Module
|
||||
// const UseLessModule = require(script.Parent.Tools.Useless)
|
||||
// const RoomWidget = require(script.Parent.Widget.Room.Main)
|
||||
// const WidgetButton = require(script.Parent.Widget.WidgetTogger)
|
||||
// const ToolsButtons = require(script.Parent.Buttons.ToolsButtons)
|
||||
|
||||
// Create Widget
|
||||
// const roomWidget = RoomWidget.new(plugin)
|
||||
|
||||
// Create Toolbar
|
||||
const toolbar = plugin.CreateToolbar("Next Station Plugin");
|
||||
|
||||
// Create Buttons
|
||||
// const roomWidgetButton = new WidgetButton(toolbar, roomWidget.widget, "Room Info", "rbxassetid.//14978048121");
|
||||
// const createRoomButton = ToolsButtons.createNewRoomButton(toolbar)
|
||||
|
||||
// Create CoreGui Folders
|
||||
const pluginModel = new Instance("Folder");
|
||||
pluginModel.Name = "_constGizmoContainer";
|
||||
pluginModel.Parent = CoreGui;
|
||||
|
||||
plugin.Unloading.Connect(() => {
|
||||
pluginModel.Destroy();
|
||||
});
|
||||
|
||||
// Selection Room Config Controller
|
||||
function clearAdornment() {
|
||||
pluginModel.ClearAllChildren();
|
||||
}
|
||||
|
||||
(Selection as unknown as { SelectionChanged: Signal }).SelectionChanged.Connect(() => {
|
||||
// Get Selection
|
||||
const selectedObjects = Selection.Get();
|
||||
// Reset Adornments
|
||||
clearAdornment();
|
||||
// roomWidget.SetActive(false)
|
||||
if (!selectedObjects.isEmpty()) {
|
||||
const selected = selectedObjects[0];
|
||||
const config = selected.FindFirstChild("RoomConfig");
|
||||
if (selected.IsA("Model") && config && checkRoomConfig(config)) {
|
||||
const roomArea = new HandlesArea(pluginModel, config.Origin.Value, config.End.Value);
|
||||
roomArea.originValueChanged.Connect((value) => (config.Origin.Value = value));
|
||||
roomArea.tipValueChanged.Connect((value) => (config.End.Value = value));
|
||||
}
|
||||
}
|
||||
});
|
||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// required
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"downlevelIteration": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "React.createElement",
|
||||
"jsxFragmentFactory": "React.Fragment",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "Node",
|
||||
"noLib": true,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"moduleDetection": "force",
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"typeRoots": ["node_modules/@rbxts"],
|
||||
|
||||
// configurable
|
||||
"types": ["types/plugin"],
|
||||
"rootDir": "src",
|
||||
"outDir": "out",
|
||||
"baseUrl": "src",
|
||||
"incremental": true,
|
||||
"tsBuildInfoFile": "out/tsconfig.tsbuildinfo"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user