Run it
Copy the script and paste it into your executor.
success, err = (()
Players = (:())
ReplicatedFirst = (:())
ReplicatedStorage = (:())
ScriptContext = (:())
LocalPlayer = Players.LocalPlayer
ac_script = ReplicatedFirst:(, )
remotes_folder = ReplicatedStorage:(, )
analytics_folder = remotes_folder:(, )
ac_event = analytics_folder:(, )
TrustedFunctions = ({}, { __mode = })
(fn)
(fn) == TrustedFunctions[fn] =
fn
(fn) TrustedFunctions[fn] ==
SafeHook = (hookfn, ...)
args = { ... }
func, inst, metamethod, detour
hookfn ==
inst = args[]
metamethod = args[]
detour = args[]
func = args[]
detour = args[]
original_func
hookfn == (func) detour = (detour)
(detour) detour = (detour)
ok, hook_err = (()
(detour)
hookfn ==
original_func = (inst, metamethod, detour)
original_func = (func, detour)
)
original_func
oldfireserver
oldfireserver = (, ac_event.FireServer, (self, ...)
args = { ... }
(args[]) ==
(self, ...)
)
kKickNames = { , }
_, name (kKickNames)
func = LocalPlayer[name]
(func) ==
oldfunc
oldfunc = (, func, (self, ...)
self == LocalPlayer ()
(self, ...)
)
_, c ((ScriptContext.Error))
c.Function (c.Function) (, c.Function, (...) )
);
(()
()
candidates = {}
() ==
ok, env = ()
ok (env) == .(candidates, env)
(getfenv) ==
ok, env = (getfenv, )
ok (env) == .(candidates, env)
_, env (candidates)
wrote = (() env.__ZkxHubEnvProbe = )
seen = __ZkxHubEnvProbe ==
(() env.__ZkxHubEnvProbe = )
wrote seen env
__ZkhHub_genv = ()
__ZkhHub_genv.Executed ._ZkxHubExecuted
__ZkhHub_genv.Executed =
(() ._ZkxHubExecuted = )
_noop = ()
_genv = __ZkhHub_genv
_stubs = {
getconnections = () {} ,
= (obj, method, hook) ,
= (orig, hook) orig ,
= (f) f ,
getnamecallmethod = () ,
= _noop,
firesignal = _noop,
setclipboard = _noop,
cloneref = (o) o ,
gethui = _noop,
checkcaller = () ,
clonefunction = (f) f ,
= (o) (o) ,
setrawmetatable = (o, mt) (o, mt) ,
= _noop,
isreadonly = () ,
getgc = () {} ,
filtergc = () {} ,
replicatesignal = _noop,
= _noop,
= _noop,
getthreadidentity = () ,
setthreadidentity = _noop,
= () {} ,
getscriptfromthread = _noop,
getcallbackvalue = _noop,
getnilinstances = () {} ,
getinstances = () {} ,
isfunctionhooked = () ,
restorefunction = _noop,
}
_stubbed = {}
name, stub (_stubs)
(_genv, name) ==
_genv[name] = stub
_stubbed[name] =
.GameId ~= _genv.hookfunction ~= _stubs.hookfunction
ok = (_genv.hookfunction, () , () )
ok
_genv.hookfunction = _stubs.hookfunction
_stubbed.hookfunction =
_exName = (identifyexecutor) == ()
_exNameLower = .(_exName)
_exNameLower:() _exNameLower:()
_genv.firetouchinterest = _noop
_stubbed.firetouchinterest =
(_genv.queue_on_teleport) ~=
_qot = ((queue_on_teleport) == queue_on_teleport)
((queueonteleport) == queueonteleport)
(() == (.queue_on_teleport) == .queue_on_teleport)
_qot _genv.queue_on_teleport = _qot
_, name ({ , , , , })
(_genv[name]) ~= _stubbed[name] =
() ~=
_stubbed.getgenv =
ok, env = ()
(ok env == _genv) _stubbed.getgenv =
_debugExt = {}
_, name ({
,
,
,
,
,
,
,
,
,
,
})
ok, value = (() debug[name] )
_debugExt[ .. name] = (ok (value) == )
_genv.ZkxHubCaps = {
stubbed = _stubbed,
has = (name)
_stubbed[name]
probed = _debugExt[name]
probed ~= probed
target = _genv
part .(name, )
(target) ~=
ok, value = (() target[part] )
ok
target = value
(target) ==
,
}
Games = {
[] = { ID = , State = },
}
.()
:()
()
(setthreadidentity) == (setthreadidentity, )
()
__ZkhHub_shared = {}
__shared_module_cache = {}
(name)
(__ZkhHub_shared) ~= ()
cached = __shared_module_cache[name]
cached ~= cached
loader = __ZkhHub_shared[name]
(loader) ~= ( .. (name))
result = ()
__shared_module_cache[name] = result
result
Library =
ErrorReporter =
HttpService = :()
__ZkhHub_shared.obsidian_library = ()
cloneref = (cloneref clonereference (instance) instance )
(name) (:(name))
UserInputService = ()
Players = ()
RunService = ()
TweenService = ()
TextService = ()
HttpService = ()
GuiService = ()
CoreGui = ()
LocalPlayer = Players.LocalPlayer
Mouse = LocalPlayer LocalPlayer:()
rgb, hsv, dim2, dim, vec2 = .fromRGB, .fromHSV, .new, .new, .new
floor, clamp, max, min, abs, sin = .floor, .clamp, .max, .min, .abs, .sin
Library = {
Version = ,
Toggles = {},
Options = {},
Labels = {},
Buttons = {},
Tabs = {},
Signals = {},
UnloadSignals = {},
OnUnload = ,
Unloaded = ,
Toggled = ,
ShowCustomCursor = ,
NotifyOnError = ,
NotifySide = ,
CornerRadius = ,
ActiveLoading = ,
Window = ,
ScreenGui = ,
KeybindFrame = ,
ToggleKeybind = ,
Animations = { Enabled = , Time = },
ThemeFade = ,
DPIScale = ,
TextSize = ,
IsMobile = ,
DependencyBoxes = {},
FontListeners = {},
ColorMigrations = {},
Registry = ({}, { __mode = }),
}
Library.__index = Library
forced =
(() forced = ((() == ()) ).ZkxVaderForceMobile == )
Library.IsMobile = forced (UserInputService.TouchEnabled UserInputService.KeyboardEnabled)
Library.TextSize = Library.IsMobile
(size) Library.IsMobile (size * + ) size
Library.H = H
DefaultPalette = {
accent = (, , ),
text = (, , ),
a = (, , ),
b = (, , ),
c = (, , ),
d = (, , ),
e = (, , ),
f = (, , ),
g = (, , ),
risky = (, , ),
dim = (, , ),
}
Palette = {}
key, value (DefaultPalette) Palette[key] = value
Library.Palette = Palette
Library.Scheme = {
BackgroundColor = DefaultPalette.e,
MainColor = DefaultPalette.c,
AccentColor = DefaultPalette.accent,
OutlineColor = DefaultPalette.b,
FontColor = DefaultPalette.text,
DarkColor = DefaultPalette.d,
RedColor = DefaultPalette.risky,
WhiteColor = (, , ),
Font = ,
}
FONT_URL =
FONT_DIR =
Library.StorageDir = FONT_DIR
()
canFile = (isfile) == (writefile) == (getcustomasset) ==
canFile
okFont, font = (()
(isfolder) == (makefolder) ==
() ()
(FONT_DIR) (FONT_DIR)
ttf, family = FONT_DIR .. , FONT_DIR ..
(ttf)
data = :(FONT_URL)
(data) ~= #data < ()
(ttf, data)
(family)
(
family,
HttpService:({
name = ,
faces = { { name = , weight = , style = , assetId = (ttf) } },
})
)
Font.((family), .FontWeight.Regular)
)
okFont font
Library.PixelFont = ()
Library.Font = Library.PixelFont Font.(.Font.Code)
Library.Scheme.Font = Library.Font
TextObjects = ({}, { __mode = })
TEXT_CLASSES = { TextLabel = , TextButton = , TextBox = }
(instance, property, key)
entry = Library.Registry[instance]
entry
entry = {}
Library.Registry[instance] = entry
entry[property] = key
instance[property] = Palette[key]
Library.Theme = ThemeSet
() Library.Animations.Enabled Library.Unloaded
(instance, properties, time, style, direction)
() instance instance.Parent
property, value (properties) (() instance[property] = value )
tween = TweenService:(
instance,
TweenInfo.(time Library.Animations.Time, style .EasingStyle.Quad, direction .EasingDirection.Out),
properties
)
tween:()
tween
Library.Tween = Tween
(class, props, themed)
instance = .(class)
parent =
property, value (props {})
property ==
parent = value
instance[property] = value
TEXT_CLASSES[class]
TextObjects[instance] =
(props props.FontFace) instance.FontFace = Library.Font
(props props.TextSize) instance.TextSize = Library.TextSize
(props props.TextColor3) (themed themed.TextColor3) (instance, , )
instance.ClassName ~= instance.TextWrapped = props props.TextWrapped
class == class == class == class == class ==
(props props.BorderSizePixel) instance.BorderSizePixel =
themed
property, key (themed)
(instance, property, key)
parent instance.Parent = parent
instance
Library.New = New
Library:(signal)
.(Library.Signals, signal)
signal
(signal, callback)
connection = signal:(callback)
.(Library.Signals, connection)
connection
Library:(callback) .(Library.UnloadSignals, callback)
Library:(callback, ...)
(callback) ~=
results = .((callback, (err) debug.((err), ) , ...))
results[] .(results, , results.n)
Library.NotifyOnError Library.Notify (Library.Notify, Library, .. (results[]):(, ))
( .. (results[]))
SafeCallback = (...) Library:(...)
(text, size, font)
ok, bounds = (()
params = .()
params.Text = text
params.Font = font Library.Font
params.Size = size Library.TextSize
params.Width =
TextService:(params)
)
ok bounds bounds.X
#(text) *
(number, step)
step = step
step == number
inverse = / step
(number * inverse + ) / inverse
() UserInputService:() - GuiService:()
Library.MouseAbs = MouseAbs
(gui)
position = ()
corner, size = gui.AbsolutePosition, gui.AbsoluteSize
position.X >= corner.X position.X <= corner.X + size.X position.Y >= corner.Y position.Y <= corner.Y + size.Y
()
(gethui) ==
ok, holder = (gethui)
ok holder holder
CoreGui
Library.Popup =
Library:()
popup = Library.Popup
Library.Popup =
popup popup.Close popup.()
Library:(popup)
Library.Popup Library.Popup ~= popup Library:()
Library.Popup = popup
Classes = { Window = {}, Tab = {}, Groupbox = {}, Tabbox = {}, Container = {}, Dialog = {} }
_, class (Classes) class.__index = class
Library.Classes = Classes
(parent, keys, props)
props = props {}
outer, current = , parent
index, key (keys)
frame = (, {
Size = (index == props.Size) (props.AutoXY (, , , )) (props.Auto (, , , ) (, , , )),
Position = index == props.Position ,
AnchorPoint = index == props.AnchorPoint ,
AutomaticSize = props.AutoXY .AutomaticSize.XY (props.Auto .AutomaticSize.Y .AutomaticSize.None),
BackgroundTransparency = (index == #keys props.InnerTransparency) ,
ClipsDescendants = (index == #keys) props.Clip ,
Parent = current,
}, { BackgroundColor3 = key })
index < #keys (, { PaddingTop = (, ), PaddingBottom = (, ), PaddingLeft = (, ), PaddingRight = (, ), Parent = frame })
outer = outer frame
current = frame
outer, current
Library.Panel = Panel
Router = { Began = {}, Changed = {}, Ended = {}, Render = {} }
RouterDirty = {}
(kind, ...)
list = Router[kind]
count = #list
count ==
index = , count
handle = list[index]
fn = handle handle.fn
fn
ok, err = (fn, ...)
ok ( .. (err))
RouterDirty[kind]
RouterDirty[kind] =
kept = {}
_, handle (Router[kind])
handle.fn kept[#kept + ] = handle
Router[kind] = kept
.(Library.Signals, UserInputService.InputBegan:((...) (, ...) ))
.(Library.Signals, UserInputService.InputChanged:((...) (, ...) ))
.(Library.Signals, UserInputService.InputEnded:((...) (, ...) ))
.(Library.Signals, RunService.RenderStepped:((...) (, ...) ))
Library:(kind, fn)
handle = { fn = fn }
list = Router[kind]
list[#list + ] = handle
handle.()
handle.fn
handle.fn =
RouterDirty[kind] =
handle
FX = { Loops = {}, Count = }
Library.FX = FX
SCRAMBLE_GLYPHS =
loopConnection =
(label) label label.Parent ~= Library.Unloaded
(item)
label = item.label
(label)
item.always Library.Toggled
label.AbsoluteSize.X >
()
loopConnection
loopConnection = RunService.Heartbeat:((dt)
Library.Unloaded
now = .()
id, item (FX.Loops)
(item.label)
FX.Loops[id] =
FX.Count -=
() (item)
item.rate now - (item.last ) >= item.rate
item.last = now
item.(now, dt)
)
.(Library.Signals, loopConnection)
nextId =
(label, step, options)
options = options {}
nextId +=
id = nextId
FX.Loops[id] = { label = label, step = step, rate = options.rate, always = options.always }
FX.Count +=
()
handle = {}
handle.()
FX.Loops[id]
FX.Loops[id] =
FX.Count -=
options.restore (options.restore)
handle
(offset)
keys = {}
index = ,
.(keys, ColorSequenceKeypoint.(index / , ((offset + index / ) % , , )))
ColorSequence.(keys)
FX.(label, options)
options = options {}
speed = options.speed
gradient = (, { Color = (), Parent = label })
Library.Registry[label] =
label.TextColor3 = (, , )
gradient.Color = ()
(label, (now) gradient.Color = (now * speed) , {
always = options.always,
rate = / ,
restore = ()
gradient:()
(label, , )
,
})
FX.(label, options)
options = options {}
every, length = options.every , options.length
Library.Registry[label] =
label.TextColor3 = (, , )
()
accent, white = Palette.accent, (, , )
ColorSequence.({
ColorSequenceKeypoint.(, accent),
ColorSequenceKeypoint.(, accent),
ColorSequenceKeypoint.(, white),
ColorSequenceKeypoint.(, accent),
ColorSequenceKeypoint.(, accent),
})
gradient = (, { Color = (), Offset = (-, ), Parent = label })
(label, (now)
phase = (now % every) / length
gradient.Offset = (phase < (- + phase * ) , )
gradient.Color = ()
, {
always = options.always,
rate = / ,
restore = ()
gradient:()
(label, , )
,
})
FX.(label, options)
options = options {}
text = options.text label.Text
speed, spread = options.speed , options.spread
base = text
label.RichText =
(color)
.(, (color.R * ), (color.G * ), (color.B * ))
(label, (now)
parts = .(#base)
index = , #base
char = base:(index, index)
char:()
parts[index] = char
parts[index] = .. (((now * speed + index * spread) % , , )) .. .. char ..
label.Text = .(parts)
, {
always = options.always,
rate = / ,
restore = ()
label.RichText =
label.Text = base
,
})
FX.(label, options)
options = options {}
depth, speed = options.depth , options.speed
(label, (now) label.TextTransparency = ((now * speed) * + ) * depth , {
always = options.always,
rate = / ,
restore = () label.TextTransparency = ,
})
(label, text, duration, frame)
() duration duration <=
label.Text = text
token = (label:() ) +
label:(, token)
.(()
started = .()
(label) label:() == token
alpha = ((.() - started) / duration, , )
alpha >=
label.Text = (alpha)
RunService.Heartbeat:()
(label) label:() == token label.Text = text
)
FX.(label, text, duration)
text = text label.Text
(label, text, duration , (alpha)
settled = (alpha * #text)
out = .(#text)
index = , #text
char = text:(index, index)
index <= settled char:()
out[index] = char
pick = .(, #SCRAMBLE_GLYPHS)
out[index] = SCRAMBLE_GLYPHS:(pick, pick)
.(out)
)
FX.(label, text, charactersPerSecond)
text = text label.Text
rate = charactersPerSecond
(label, text, #text / rate, (alpha) text:(, (alpha * #text)) )
FX.(label, duration)
() (label)
duration = duration
ghosts = {}
index, tint ({ (, , ), (, , ) })
ghost = (, {
Text = label.Text,
TextColor3 = tint,
TextTransparency = ,
TextSize = label.TextSize,
FontFace = label.FontFace,
BackgroundTransparency = ,
TextXAlignment = label.TextXAlignment,
TextYAlignment = label.TextYAlignment,
Size = (, , , ),
ZIndex = label.ZIndex + (index == - ),
Parent = label,
})
ghost.TextColor3 = tint
.(ghosts, ghost)
.(()
started = .()
(label) .() - started < duration
index, ghost (ghosts)
ghost.Position = (, .(-, ) * (index == -), , .(-, ))
RunService.Heartbeat:()
_, ghost (ghosts) ghost:()
)
FX.(handle)
handle handle.Stop handle.()
Window, Tab = Classes.Window, Classes.Tab
Scalers = {}
() Library.DPIScale /
Library.GetScale = GetScale
Library.()
camera = .CurrentCamera
(camera camera.ViewportSize .(, )) / ()
(input) input.UserInputType == .UserInputType.MouseButton1 input.UserInputType == .UserInputType.Touch
(input) input.UserInputType == .UserInputType.MouseMovement input.UserInputType == .UserInputType.Touch
(frame, handle, onMoved, layoutId, withSize)
handle = handle frame
moveHandle, endHandle, renderHandle
startMouse, startPosition, pending
()
_, listener ({ moveHandle, endHandle, renderHandle }) listener.()
moveHandle, endHandle, renderHandle, pending = , , ,
layoutId Library:(layoutId, frame, withSize)
(handle.InputBegan, (input)
(input) moveHandle
startMouse, startPosition, pending = input.Position, frame.Position,
Library:()
moveHandle = Library:(, (changed)
(changed) pending = changed.Position
)
renderHandle = Library:(, ()
pending
delta = (pending - startMouse) / ()
pending =
frame.Position = .(startPosition.X.Scale, startPosition.X.Offset + delta.X, startPosition.Y.Scale, startPosition.Y.Offset + delta.Y)
onMoved ()
)
endHandle = Library:(, (ended)
(ended) ()
)
)
Library.MakeDraggable = MakeDraggable
(frame, grip, minimum, layoutId)
moveHandle, endHandle, renderHandle
startMouse, startSize, pending, wanted
ghost
()
_, listener ({ moveHandle, endHandle, renderHandle }) listener.()
moveHandle, endHandle, renderHandle, pending = , , ,
ghost ghost.Visible =
(grip.InputBegan, (input)
(input) moveHandle
startMouse, startSize, pending, wanted = input.Position, frame.AbsoluteSize / (), ,
Library:()
ghost
ghost = (, { Name = , BackgroundTransparency = , ZIndex = , Active = , Parent = Library.Main })
(, { Thickness = , Parent = ghost }, { Color = })
ghost.Position, ghost.Size, ghost.Visible = frame.Position, frame.Size,
moveHandle = Library:(, (changed)
(changed) pending = changed.Position
)
renderHandle = Library:(, ()
pending
delta = (pending - startMouse) / ()
pending =
wanted = .((minimum.X, startSize.X + delta.X), (minimum.Y, startSize.Y + delta.Y))
ghost.Size = .(wanted.X, wanted.Y)
)
endHandle = Library:(, (ended)
(ended)
wanted frame.Size = .(wanted.X, wanted.Y)
()
layoutId wanted Library:(layoutId, frame, )
)
)
THEME_KEYS = { , , , , , , , , , , }
themeTarget, themeConnection = ,
()
scheme = Library.Scheme
e, c, b = scheme.BackgroundColor, scheme.MainColor, scheme.OutlineColor
{
accent = scheme.AccentColor,
text = scheme.FontColor,
a = Palette.a,
b = b,
c = c,
d = .(e.R * , e.G * , e.B * ),
e = e,
f = b:((, , ), ),
g = c:(b, ),
risky = scheme.RedColor DefaultPalette.risky,
dim = scheme.FontColor:(e, ),
}
(one, two)
.(one.R - two.R) < .(one.G - two.G) < .(one.B - two.B) <
(changed)
instance, properties (Library.Registry)
instance.Parent
property, key (properties)
changed[key] instance[property] = Palette[key]
(dt)
themeTarget Library.Unloaded
blend = (Library.ThemeFade ()) ( - .(-dt * ))
changed, any, moving = {}, ,
_, key (THEME_KEYS)
current, wanted = Palette[key], themeTarget[key]
current ~= wanted
nextColor = wanted
blend < (current, wanted)
nextColor = current:(wanted, blend)
moving =
Palette[key] = nextColor
changed[key], any = ,
any (changed)
any Library.OnThemeChanged (Library.OnThemeChanged)
moving
Library:()
themeTarget = ()
themeConnection Library.Unloaded
themeConnection = RunService.Heartbeat:((dt)
(dt)
themeConnection:()
themeConnection =
)
.(Library.Signals, themeConnection)
Library:(font)
face = font
(font) == face = Font.(font)
(face) ~=
Library.Font = face
Library.Scheme.Font = face
instance (TextObjects)
(() instance.FontFace = face )
_, listener (Library.FontListeners) (listener)
Library:(percent)
percent = (percent)
Library.DPIScale = (percent, , )
_, scaler (Scalers)
scaler.Scale = ()
Library.Main Library.Main.Size = .( / (), / ())
Library.Window .(() Library:() )
Library:(image)
window = Library.Window
(window window.BackgroundImage)
image = (image )
window.BackgroundImage.Image = image
window.BackgroundImage.Visible = image ~=
Library:()
CURSOR_BINDING =
cursorFrame, cursorActive, oldMouseIcon = , ,
()
cursorFrame cursorFrame.Parent cursorFrame
cursorFrame = (, {
Name = , BackgroundTransparency = , AnchorPoint = (, ), Size = .(, ), Visible = , ZIndex = , Active = , Parent = Library.ScreenGui,
})
(size, color, zindex)
(, { AnchorPoint = (, ), Position = (, , , ), Size = size, BackgroundColor3 = color, ZIndex = zindex, Active = , Parent = cursorFrame })
dark, white = (, , ), (, , )
(.(, ), dark, )
(.(, ), dark, )
(.(, ), white, )
(.(, ), white, )
cursorFrame
()
cursorActive
cursorActive =
(() RunService:(CURSOR_BINDING) )
(() UserInputService.MouseIconEnabled = oldMouseIcon )
cursorFrame cursorFrame.Visible =
()
cursorActive Library.ScreenGui
UserInputService.TouchEnabled UserInputService.MouseEnabled
cursorActive =
oldMouseIcon = UserInputService.MouseIconEnabled
cursor = ()
(() RunService:(CURSOR_BINDING) )
RunService:(CURSOR_BINDING, .RenderPriority.Last.Value, ()
Library.Unloaded Library.Toggled (Library.ScreenGui Library.ScreenGui.Parent)
()
show = Library.ShowCustomCursor ~=
UserInputService.MouseIconEnabled = show
cursor.Visible = show
show
mouse = UserInputService:()
cursor.Position = .(mouse.X, mouse.Y)
)
Library.StartCursor, Library.StopCursor = startCursor, stopCursor
Library:(value)
window = Library.Window
window Library.Unloaded
target
(value) ==
target = value
target = Library.Toggled
target == Library.Toggled
Library.Toggled = target
root, curtain = window.Root, window.Curtain
window.OpenToken = (window.OpenToken ) +
token = window.OpenToken
target
home = window.HomePosition root.Position
window.HomePosition =
root.Visible =
()
root.Position = .(home.X.Scale, home.X.Offset, home.Y.Scale, home.Y.Offset + )
curtain.BackgroundTransparency =
(root, { Position = home }, , .EasingStyle.Quint)
(curtain, { BackgroundTransparency = }, , .EasingStyle.Quad)
root.Position, curtain.BackgroundTransparency = home,
window.Introduced
window.Introduced =
FX.(window.TitleLabel, window.TitleText, )
Library:()
home = root.Position
()
window.HomePosition = home
(root, { Position = .(home.X.Scale, home.X.Offset, home.Y.Scale, home.Y.Offset + ) }, , .EasingStyle.Quad)
(curtain, { BackgroundTransparency = }, , .EasingStyle.Quad)
.(, ()
window.OpenToken == token Library.Toggled
root.Visible =
root.Position = home
window.HomePosition =
)
root.Visible =
target () ()
Library.OnToggled (Library.OnToggled, target)
Library:()
Library.ScreenGui Library.ScreenGui.Parent Library.ScreenGui
gui = (, {
Name = ,
ResetOnSpawn = ,
ZIndexBehavior = .ZIndexBehavior.Sibling,
IgnoreGuiInset = ,
DisplayOrder = ,
})
gui.Parent = ()
Library.ScreenGui = gui
main = (, { Name = , BackgroundTransparency = , Size = .( / (), / ()), Parent = gui })
Library.Main = main
.(Scalers, (, { Scale = (), Parent = main }))
Library.Overlay = (, { Name = , BackgroundTransparency = , Size = (, , , ), ZIndex = , Parent = main })
gui
Library:()
window, main = Library.Window, Library.Main
(window window.Root main) main.AbsoluteSize.X <=
root = window.Root
screen, scale = Library.(), ((), )
size = root.AbsoluteSize / scale
width, height = (size.X, screen.X - ), (size.Y, screen.Y - )
width ~= size.X height ~= size.Y root.Size = .(width, height)
at = (root.AbsolutePosition - main.AbsolutePosition) / scale
dx = (at.X, , (, screen.X - width)) - at.X
dy = (at.Y, , (, screen.Y - height)) - at.Y
(dx) > (dy) >
position = root.Position
root.Position = .(position.X.Scale, position.X.Offset + dx, position.Y.Scale, position.Y.Offset + dy)
Library:(info)
info = info {}
Library.Window Library.Window
self = ({ Tabs = {}, Library = Library, Info = info }, Window)
Library.Window = self
Library.CornerRadius = info.CornerRadius Library.CornerRadius
Library.NotifySide = info.NotifySide Library.NotifySide
info.NotifySide Library.NotifyPlacement = (info.NotifySide):() ==
info.ShowCustomCursor ~= Library.ShowCustomCursor = info.ShowCustomCursor
info.Font Library:(info.Font)
Library:()
main = Library.Main
size = info.Size .(, )
screen = Library.()
size = .((size.X.Offset, screen.X - ), (size.Y.Offset, screen.Y - ))
position = info.Position
info.Center ~= (info.Center == position == )
position = .(, -size.X.Offset / , , -size.Y.Offset / )
root, body = (main, { , , , , , }, { Size = size, Position = position .(, ), InnerTransparency = })
root.Name =
root.Visible =
Library:(, root, )
self.Root, self.Body = root, body
self.BackgroundImage = (, {
BackgroundTransparency = , ImageTransparency = , Size = (, , , ), ScaleType = .ScaleType.Crop, Visible = , ZIndex = , Parent = body,
})
(, { Text = , Modal = , BackgroundTransparency = , Size = (, , , ), Parent = root })
header = (, { Name = , BackgroundTransparency = , Position = (, , , ), Size = (, -, , ()), Active = , Parent = body })
textLeft =
info.Icon
self.IconLabel = (, {
BackgroundTransparency = , Image = (info.Icon), Size = info.IconSize .(, ), Position = (, , , ), AnchorPoint = (, ), Parent = header,
})
textLeft = (info.IconSize info.IconSize.X.Offset ) +
self.TitleHolder = header
self.TitleText = (info.Title )
self.TitleLabel = (, {
Text = self.TitleText, BackgroundTransparency = , TextXAlignment = .TextXAlignment.Left, AutomaticSize = .AutomaticSize.X,
Size = (, , , ), Position = (, textLeft, , ), TextSize = , ZIndex = , Parent = header,
})
FX.(self.TitleLabel, { every = })
self.FooterLabel = (, {
Text = (info.Footer ), BackgroundTransparency = , TextXAlignment = .TextXAlignment.Right, Size = (, , , ),
Position = (, , , ), TextSize = Library.TextSize, ZIndex = , Parent = header,
}, { TextColor3 = })
size.X.Offset < self.FooterLabel.Visible =
(root, header, , , )
(root, root, , , )
bar = (, { Name = , BackgroundTransparency = , Position = (, , , + ()), Size = (, -, , ()), ClipsDescendants = , Parent = body })
(, { Name = , AnchorPoint = (, ), Position = (, , , ), Size = (, , , ), Parent = bar }, { BackgroundColor3 = })
strip = (, {
Name = , BackgroundTransparency = , Size = (, , , ), ScrollBarThickness = , ScrollingDirection = .ScrollingDirection.X,
AutomaticCanvasSize = .AutomaticSize.X, CanvasSize = (, , , ), Parent = bar,
})
(, { FillDirection = .FillDirection.Horizontal, Padding = (, ), SortOrder = .SortOrder.LayoutOrder, Parent = strip })
underline = (, { Name = , AnchorPoint = (, ), Position = (, , , ), Size = (, , , ), ZIndex = , Parent = bar }, { BackgroundColor3 = })
(, { Size = (, , , ), BackgroundColor3 = (, , ), BackgroundTransparency = , ZIndex = , Parent = underline })
self.TabBar, self.Strip, self.Underline = bar, strip, underline
(bar.InputChanged, (input)
input.UserInputType == .UserInputType.MouseWheel
strip.CanvasPosition = .((, strip.CanvasPosition.X - input.Position.Z * ), )
)
holder, pages = (body, { , , }, { Position = (, , , + () + ()), Size = (, -, , -( + () + ())), Clip = })
holder.Name =
self.PageHolder, self.Pages = holder, pages
self.Curtain = (, { Name = , BackgroundTransparency = , Size = (, , , ), ZIndex = , Active = , Parent = body }, { BackgroundColor3 = })
grip = (, { Text = , BackgroundTransparency = , AnchorPoint = (, ), Position = (, -, , -), Size = (, (), , ()), ZIndex = , Parent = body })
index = ,
(, { AnchorPoint = (, ), Position = (, -, , - - (index - ) * ), Size = (, index * , , ), BackgroundTransparency = , ZIndex = , Parent = grip }, { BackgroundColor3 = })
(root, grip, Library.IsMobile .(, ) .(, ), )
keybindFrame, keybindInner = (main, { , , }, { Position = (, , , ), AutoXY = })
keybindFrame.Name =
keybindFrame.Visible =
keybindFrame.ZIndex =
Library:(, keybindFrame)
keybindTitle = (, {
Text = , BackgroundTransparency = , Size = (, , , ), AutomaticSize = .AutomaticSize.X, TextSize = Library.TextSize, Active = , Parent = keybindInner,
}, { TextColor3 = })
(, { SortOrder = .SortOrder.LayoutOrder, Parent = keybindInner })
keybindList = (, { BackgroundTransparency = , Size = (, , , ), AutomaticSize = .AutomaticSize.XY, LayoutOrder = , Parent = keybindInner })
(, { SortOrder = .SortOrder.LayoutOrder, Parent = keybindList })
(, { PaddingLeft = (, ), PaddingRight = (, ), PaddingBottom = (, ), Parent = keybindList })
(keybindFrame, keybindTitle, , )
Library.KeybindFrame, Library.KeybindContainer = keybindFrame, keybindList
Library.ToggleKeybind = info.ToggleKeybind .KeyCode.RightControl
Library:(, (input, processed)
processed Library.Unloaded
bind = Library.ToggleKeybind
(bind) ==
input.KeyCode == bind Library:()
(bind) == bind.Type ==
)
info.DisableSearch ~= self.BuildSearch self:()
self.BuildMobileButton self:()
.(() Library:() )
camera = .CurrentCamera
camera (camera:(), () Library:() )
info.Watermark ~= Library.AddWatermark Library:({ Name = self.TitleText })
self:()
.(()
Library.Unloaded
info.AutoShow ~= info.AutoShow ~= Library:()
)
self
Window:(map, time, offset)
Library.Animations.Enabled =
(map) ==
any =
_, enabled (map) any = any enabled ==
Library.Animations.Enabled = any
(time) Library.Animations.Time = ((time), , )
(window, tab, instant)
button, bar, underline = tab.Button, window.TabBar, window.Underline
scale = ()
x = (button.AbsolutePosition.X - bar.AbsolutePosition.X) / scale
width = button.AbsoluteSize.X / scale
target = { Position = (, x, , ), Size = (, width, , ) }
instant () underline.Size.X.Offset ==
underline.Position, underline.Size = target.Position, target.Size
(underline, target, , .EasingStyle.Quint)
Window.PlaceUnderline = placeUnderline
Window:(name, icon, description)
name = (name)
tab = ({ Window = self, Library = Library, Name = name, Groupboxes = {}, Tabboxes = {}, Description = description }, Tab)
tab.Button = (, {
Text = name, AutoButtonColor = , BackgroundTransparency = , AutomaticSize = .AutomaticSize.X, Size = (, , , -),
LayoutOrder = #self.Tabs + , TextSize = Library.TextSize, Parent = self.Strip,
}, { TextColor3 = })
(, { PaddingLeft = (, ), PaddingRight = (, ), Parent = tab.Button })
(tab.Button:(), ()
self.ActiveTab == tab (self, tab, )
)
(tab.Button.MouseEnter, ()
self.ActiveTab ~= tab (tab.Button, { TextColor3 = Palette.text }, )
)
(tab.Button.MouseLeave, ()
self.ActiveTab ~= tab (tab.Button, { TextColor3 = Palette.dim }, )
)
tab.Page = (, { Name = name, BackgroundTransparency = , Size = (, , , ), Visible = , Parent = self.Pages })
(side)
scroller = (, {
Name = side, BackgroundTransparency = , ScrollBarThickness = , ScrollBarImageTransparency = , AutomaticCanvasSize = .AutomaticSize.Y,
CanvasSize = (, , , ), ScrollingDirection = .ScrollingDirection.Y, Active = ,
Position = side == (, , , ) (, , , ), Size = (, -, , -), Parent = tab.Page,
})
Library.(scroller, , )
(, { Padding = (, ), SortOrder = .SortOrder.LayoutOrder, Parent = scroller })
(, { PaddingTop = (, ), PaddingRight = (, ), PaddingBottom = (, ), Parent = scroller })
curtain = (, { BackgroundTransparency = , Position = scroller.Position, Size = scroller.Size, ZIndex = , Active = , Parent = tab.Page }, { BackgroundColor3 = })
scroller, curtain
tab.Left, tab.LeftCurtain = ()
tab.Right, tab.RightCurtain = ()
tab.Sides = { tab.Left, tab.Right }
(tab.Button.MouseButton1Click, () tab:() )
.(self.Tabs, tab)
Library.Tabs[name] = tab
.(()
self.ActiveTab == tab (self, tab, )
)
self.ActiveTab tab:()
tab
Tab:(instant)
window = self.Window
previous = window.ActiveTab
if previous == self and not instant then return end
Library:ClosePopup()
if previous and previous ~= self then
previous.Page.Visible = false
ThemeSet(previous.Button, 'TextColor3', 'dim')
end
self.Page.Visible = true
window.ActiveTab = self
ThemeSet(self.Button, 'TextColor3', 'text')
task.defer(function() placeUnderline(window, self, instant == true) end)
-- keep the active tab inside the visible part of the strip
task.defer(function()
local strip, button = window.Strip, self.Button
local left = button.AbsolutePosition.X - strip.AbsolutePosition.X + strip.CanvasPosition.X
local right = left + button.AbsoluteSize.X
if left < strip.CanvasPosition.X then
strip.CanvasPosition = Vector2.new(max(0, left - 20), 0)
elseif right > strip.CanvasPosition.X + strip.AbsoluteSize.X then
strip.CanvasPosition = Vector2.new(right - strip.AbsoluteSize.X + 20, 0)
end
end)
if instant ~= true and Animating() then
-- columns slide up 14px while a page-coloured curtain fades off; the right column lags by 60 ms
for index, pair in ipairs({ { self.Left, self.LeftCurtain }, { self.Right, self.RightCurtain } }) do
local scroller, curtain = pair[1], pair[2]
local home = scroller.Position
curtain.BackgroundTransparency = 0
scroller.Position = UDim2.new(home.X.Scale, home.X.Offset, home.Y.Scale, home.Y.Offset + 14)
task.delay((index - 1) * 0.06, function()
if not scroller.Parent then return end
Tween(scroller, { Position = home }, 0.3, Enum.EasingStyle.Quint)
Tween(curtain, { BackgroundTransparency = 1 }, 0.3, Enum.EasingStyle.Quad)
end)
end
FX.Scramble(self.Button, self.Name, 0.3)
else
self.LeftCurtain.BackgroundTransparency, self.RightCurtain.BackgroundTransparency = 1, 1
end
if Library.OnTabChanged then pcall(Library.OnTabChanged, self) end
end
-- tabs created later than the window's first layout need their underline re-aimed when the strip resizes
local lastSizeSignal = nil
function Window:WatchResize()
if lastSizeSignal then return end
lastSizeSignal = Connect(self.Root:GetPropertyChangedSignal('AbsoluteSize'), function()
local active = self.ActiveTab
if active then placeUnderline(self, active, true) end
end)
end
--// unload \\--
function Library:Unload()
if Library.Unloaded then return end
stopCursor()
Library.Unloaded = true
for index = #Library.Signals, 1, -1 do
local signal = table.remove(Library.Signals, index)
pcall(function()
if typeof(signal) == 'RBXScriptConnection' and signal.Connected then signal:Disconnect() end
end)
end
for _, callback in ipairs(Library.UnloadSignals) do
Library:SafeCallback(callback)
end
if Library.ScreenGui then
pcall(function() Library.ScreenGui:Destroy() end)
Library.ScreenGui = nil
end
if Library.ActiveLoading then
pcall(function() Library.ActiveLoading:Destroy() end)
Library.ActiveLoading = nil
end
Library.Window = nil
pcall(function()
local env = (type(getgenv) == 'function' and getgenv()) or _G
if env.Library == Library then env.Library = nil end
end)
end
end
-- ==== 30_container.lua ====
-- ZkxVader containers: groupbox (collapsible), tabbox, dependency box. Every container shares the Add* mixin (Classes.Container), which the
-- widget files extend. A container owns a `Container` frame (vertical list) that its element rows are parented to.
do
local Groupbox, Tabbox, Container, Tab = Classes.Groupbox, Classes.Tabbox, Classes.Container, Classes.Tab
for _, class in ipairs({ Groupbox, Tabbox, Classes.Dialog }) do
setmetatable(class, { __index = Container })
end
function Container:Resize() end -- layouts are automatic; kept for Obsidian API compatibility
function Container:NextOrder()
self.Order = (self.Order or 0) + 1
return self.Order
end
--// Reveal: a clipped wrapper whose height eases between 0 and its content (dependency boxes, collapsing groupboxes) \\--
local function Reveal(parent, layoutOrder, padding)
local wrapper = New('Frame', { BackgroundTransparency = 1, ClipsDescendants = true, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, LayoutOrder = layoutOrder or 0, Parent = parent })
local content = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, Parent = wrapper })
New('UIListLayout', { Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = content })
if padding then
New('UIPadding', { PaddingTop = dim(0, padding.Top or 0), PaddingBottom = dim(0, padding.Bottom or 0), PaddingLeft = dim(0, padding.Left or 0), PaddingRight = dim(0, padding.Right or 0), Parent = content })
end
local reveal = { Wrapper = wrapper, Content = content, Open = true }
local token = 0
function reveal.Set(open, instant)
if reveal.Open == open and not instant then return end
reveal.Open = open
token += 1
local mine = token
if instant or not Animating() then
wrapper.Visible = open
wrapper.AutomaticSize = open and Enum.AutomaticSize.Y or Enum.AutomaticSize.None
wrapper.Size = open and dim2(1, 0, 0, 0) or dim2(1, 0, 0, 0)
return
end
if open then
wrapper.AutomaticSize = Enum.AutomaticSize.None
wrapper.Size = dim2(1, 0, 0, 0)
wrapper.Visible = true
task.defer(function()
if token ~= mine then return end
RunService.Heartbeat:Wait() -- one frame so the content has been laid out
if token ~= mine then return end
local height = content.AbsoluteSize.Y / max(Library.GetScale(), 0.01)
Tween(wrapper, { Size = dim2(1, 0, 0, height) }, 0.2, Enum.EasingStyle.Quint)
task.delay(0.22, function()
if token == mine and reveal.Open then
wrapper.AutomaticSize = Enum.AutomaticSize.Y -- hand the height back to the layout
end
end)
end)
else
local height = wrapper.AbsoluteSize.Y / max(Library.GetScale(), 0.01)
wrapper.AutomaticSize = Enum.AutomaticSize.None
wrapper.Size = dim2(1, 0, 0, height)
Tween(wrapper, { Size = dim2(1, 0, 0, 0) }, 0.18, Enum.EasingStyle.Quint)
task.delay(0.2, function()
if token == mine and not reveal.Open then wrapper.Visible = false end
end)
end
end
return reveal
end
Library.Reveal = Reveal
--// groupbox: the Vaderhaxx section (title notched into the border), collapsible by clicking the title \\--
local function newGroupbox(tab, side, name)
local column = side == 'Left' and tab.Left or tab.Right
local outer, inner = Panel(column, { 'a', 'c', 'e' }, { Size = dim2(1, 0, 0, 0), Auto = true })
outer.LayoutOrder = #column:GetChildren()
local text = tostring(name)
-- the title and the collapse glyph both sit in the border: a page-coloured strip behind each one hides the two border lines, so neither touches the outline
local notch = New('Frame', { Size = dim2(0, TextWidth(text, Library.TextSize) + 12, 0, 3), Position = dim2(0, 5, 0, -2), ZIndex = 4, Parent = inner }, { BackgroundColor3 = 'e' })
local title = New('TextLabel', {
Text = text, BackgroundTransparency = 1, Size = dim2(0, TextWidth(text, Library.TextSize) + 12, 0, 16), Position = dim2(0, 5, 0, -8),
TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 5, Parent = inner,
})
New('UIPadding', { PaddingLeft = dim(0, 6), Parent = title })
New('Frame', { Size = dim2(0, 24, 0, 3), AnchorPoint = vec2(1, 0), Position = dim2(1, -8, 0, -2), ZIndex = 4, Parent = inner }, { BackgroundColor3 = 'e' })
local arrow = New('TextLabel', { Text = '-', BackgroundTransparency = 1, AnchorPoint = vec2(1, 0), Position = dim2(1, -8, 0, -8), Size = dim2(0, 24, 0, 16), TextSize = Library.TextSize, ZIndex = 5, Parent = inner }, { TextColor3 = 'dim' })
-- the whole header strip is the click target (title, glyph and the border between them), not just the title text
local header = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(18)), Position = dim2(0, 0, 0, -8), ZIndex = 6, Parent = inner })
table.insert(Library.FontListeners, function()
local width = TextWidth(text, Library.TextSize) + 12
notch.Size, title.Size = dim2(0, width, 0, 3), dim2(0, width, 0, 16)
end)
local reveal = Reveal(inner, 1, { Top = Library.IsMobile and 20 or 12, Bottom = 8, Left = 8, Right = 8 })
local box = setmetatable({
Tab = tab, Library = Library, Name = name, Side = side, Holder = outer, Container = reveal.Content, Elements = {}, Collapsed = false, Type = 'Groupbox', Reveal = reveal,
}, Groupbox)
box.Root = box
function box:SetCollapsed(collapsed)
collapsed = collapsed == true
if box.Collapsed == collapsed then return end
box.Collapsed = collapsed
Library:ClosePopup()
arrow.Text = collapsed and '+' or '-'
reveal.Set(not collapsed)
end
function box:SetVisible(visible) outer.Visible = visible end
Connect(header.MouseButton1Click, function() box:SetCollapsed(not box.Collapsed) end)
Connect(header.MouseEnter, function()
Tween(title, { TextColor3 = Palette.accent }, 0.12)
Tween(arrow, { TextColor3 = Palette.accent }, 0.12)
end)
Connect(header.MouseLeave, function()
Tween(title, { TextColor3 = Palette.text }, 0.12)
Tween(arrow, { TextColor3 = Palette.dim }, 0.12)
end)
tab.Groupboxes[name] = box
return box
end
function Tab:AddLeftGroupbox(name) return newGroupbox(self, 'Left', name) end
function Tab:AddRightGroupbox(name) return newGroupbox(self, 'Right', name) end
function Tab:AddGroupbox(info)
info = info or {}
return newGroupbox(self, info.Side == 2 and 'Right' or 'Left', info.Name or 'Groupbox')
end
--// tabbox: one panel with sub tabs \\--
local function newTabbox(tab, side, name)
local column = side == 'Left' and tab.Left or tab.Right
local outer, inner = Panel(column, { 'a', 'c', 'e' }, { Size = dim2(1, 0, 0, 0), Auto = true })
outer.LayoutOrder = #column:GetChildren()
New('UIListLayout', { SortOrder = Enum.SortOrder.LayoutOrder, Parent = inner })
local strip = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(22)), LayoutOrder = 0, Parent = inner })
New('UIListLayout', { FillDirection = Enum.FillDirection.Horizontal, SortOrder = Enum.SortOrder.LayoutOrder, Parent = strip })
local body = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, LayoutOrder = 1, Parent = inner })
local box = setmetatable({ Tab = tab, Library = Library, Name = name, Side = side, Holder = outer, Strip = strip, Body = body, Tabs = {}, Type = 'Tabbox' }, Tabbox)
function box:SetVisible(visible) outer.Visible = visible end
tab.Tabboxes[name or (#tab.Tabboxes + 1)] = box
return box
end
function Tab:AddLeftTabbox(name) return newTabbox(self, 'Left', name) end
function Tab:AddRightTabbox(name) return newTabbox(self, 'Right', name) end
function Tabbox:AddTab(name)
local parentBox = self
local page = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, Visible = false, Parent = self.Body })
New('UIListLayout', { Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = page })
New('UIPadding', { PaddingTop = dim(0, 8), PaddingBottom = dim(0, 8), PaddingLeft = dim(0, 8), PaddingRight = dim(0, 8), Parent = page })
local button = New('TextButton', {
Text = tostring(name), AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(0, 0, 1, 0), AutomaticSize = Enum.AutomaticSize.X,
TextSize = Library.TextSize, LayoutOrder = #self.Tabs + 1, Parent = self.Strip,
}, { TextColor3 = 'dim' })
New('UIPadding', { PaddingLeft = dim(0, 10), PaddingRight = dim(0, 10), Parent = button })
local bar = New('Frame', { AnchorPoint = vec2(0, 1), Position = dim2(0, 0, 1, 0), Size = dim2(1, 0, 0, 2), Visible = false, ZIndex = 3, Parent = button }, { BackgroundColor3 = 'accent' })
local sub = setmetatable({ Tab = self.Tab, Library = Library, Name = name, Container = page, Elements = {}, Page = page, Button = button, Bar = bar, Type = 'TabboxTab', Tabbox = parentBox }, Container)
sub.Root = sub
function sub:Show()
local previous = parentBox.Active
if previous == sub then return end
if previous then
previous.Page.Visible = false
previous.Bar.Visible = false
ThemeSet(previous.Button, 'TextColor3', 'dim')
end
parentBox.Active = sub
page.Visible = true
bar.Visible = true
ThemeSet(button, 'TextColor3', 'text')
Library:ClosePopup()
end
function sub:SetVisible(visible) button.Visible = visible end
Connect(button.MouseButton1Click, function() sub:Show() end)
table.insert(self.Tabs, sub)
if not self.Active then sub:Show() end
return sub
end
--// dependency box: its children show only while every condition holds \\--
function Container:AddDependencyBox()
local reveal = Reveal(self.Container, self:NextOrder())
local box = setmetatable({
Tab = self.Tab, Library = Library, Container = reveal.Content, Holder = reveal.Wrapper, Elements = {}, Dependencies = {}, Type = 'DependencyBox', Reveal = reveal,
}, Container)
reveal.Set(false, true)
function box:SetupDependencies(dependencies)
box.Dependencies = dependencies or {}
box:Update(true)
end
function box:Update(instant)
local show = #box.Dependencies > 0
for _, dependency in ipairs(box.Dependencies) do
local object, wanted = dependency[1], dependency[2]
if object == nil then
show = false
break
end
local value = object.Value
local matches
if object.Type == 'Dropdown' and object.Multi then
matches = type(value) == 'table' and value[wanted] == true
else
matches = value == wanted
end
if not matches then
show = false
break
end
end
reveal.Set(show, instant)
end
box.Root, box.Name = self.Root or self, (self.Root or self).Name
table.insert(self.Elements, box)
table.insert(Library.DependencyBoxes, box)
return box
end
function Library:UpdateDependencyBoxes()
for index = #Library.DependencyBoxes, 1, -1 do
local box = Library.DependencyBoxes[index]
if box.Holder and box.Holder.Parent then
box:Update(false)
else
table.remove(Library.DependencyBoxes, index)
end
end
end
end
-- ==== 40_elements.lua ====
-- ZkxVader elements (1/2): tooltip, label, divider, toggle, button (+sub buttons), slider, input.
-- Every element keeps Obsidian's object contract: Type, Value, SetValue, OnChanged/Changed, Callback, RunChanged, SetText, SetVisible, SetDisabled, Addons.
do
local Container = Classes.Container
local Addons = {}
Classes.Addons = Addons
local function noop() end
local Templates = {
Toggle = { Text = 'Toggle', Default = false, Callback = noop, Changed = noop, Risky = false, Disabled = false, Visible = true },
Input = {
Text = 'Input', Default = '', Finished = false, Numeric = false, ClearTextOnFocus = true, ClearTextOnBlur = false, Placeholder = '', AllowEmpty = true,
EmptyReset = '---', Callback = noop, Changed = noop, Disabled = false, Visible = true,
},
Slider = {
Text = 'Slider', Default = 0, Min = 0, Max = 100, Rounding = 0, Prefix = '', Suffix = '', Callback = noop, Changed = noop, Disabled = false, Visible = true,
AllowRightClickInput = true,
},
Dropdown = { Values = {}, DisabledValues = {}, Multi = false, MaxVisibleDropdownItems = 8, Callback = noop, Changed = noop, Disabled = false, Visible = true },
KeyPicker = {
Text = 'KeyPicker', Default = 'None', DefaultModifiers = {}, Blacklisted = {}, BlacklistedModifiers = {}, Whitelisted = {}, WhitelistedModifiers = {},
Mode = 'Toggle', Modes = { 'Always', 'Toggle', 'Hold' }, SyncToggleState = false, Callback = noop, ChangedCallback = noop, Changed = noop, Clicked = noop,
},
ColorPicker = { Default = Color3.new(1, 1, 1), Callback = noop, Changed = noop },
}
Library.Templates = Templates
local function Validate(info, template)
local out = {}
for key, value in pairs(template) do out[key] = value end
if type(info) == 'table' then
for key, value in pairs(info) do out[key] = value end
end
return out
end
Library.Validate = function(_, info, template) return Validate(info, template) end
--// tooltip: one shared panel, shown after a short hover \\--
local tooltipFrame, tooltipLabel
local tooltipToken = 0
local followHandle, followTooltip
local function showTooltip(text)
if not tooltipFrame then
local frame, inner = Panel(Library.Overlay, { 'a', 'b', 'e' }, { AutoXY = true })
tooltipFrame = frame
tooltipFrame.ZIndex = 90
tooltipFrame.Visible = false
tooltipFrame.Name = 'Tooltip'
tooltipLabel = New('TextLabel', {
Text = '', BackgroundTransparency = 1, AutomaticSize = Enum.AutomaticSize.XY, Size = dim2(0, 0, 0, 0), TextSize = 12, ZIndex = 91, TextWrapped = false, Parent = inner,
})
New('UIPadding', { PaddingLeft = dim(0, 6), PaddingRight = dim(0, 6), PaddingTop = dim(0, 3), PaddingBottom = dim(0, 4), Parent = inner })
end
tooltipLabel.Text = text
tooltipFrame.Visible = true
followHandle = followHandle or Library:OnInput('Render', function() followTooltip() end)
end
local function hideTooltip()
tooltipToken += 1
if tooltipFrame then tooltipFrame.Visible = false end
if followHandle then
followHandle.Disconnect()
followHandle = nil
end
end
Library.HideTooltip = hideTooltip
function followTooltip()
if tooltipFrame and tooltipFrame.Visible then
local mouse = UserInputService:GetMouseLocation() / Library.GetScale()
local size = tooltipFrame.AbsoluteSize / Library.GetScale()
local view = Library.Main.AbsoluteSize / Library.GetScale()
tooltipFrame.Position = dim2(0, clamp(mouse.X + 14, 0, max(0, view.X - size.X - 4)), 0, clamp(mouse.Y + 16, 0, max(0, view.Y - size.Y - 4)))
end
end
function Library:AddTooltip(info, disabledInfo, hover)
local tooltip = { Disabled = false, Info = info, DisabledInfo = disabledInfo }
if typeof(hover) ~= 'Instance' then return tooltip end
local function text() return tooltip.Disabled and (tooltip.DisabledInfo or tooltip.Info) or tooltip.Info end
Connect(hover.MouseEnter, function()
local current = text()
if type(current) ~= 'string' or current == '' then return end
tooltipToken += 1
local mine = tooltipToken
task.delay(0.45, function()
if mine == tooltipToken and not Library.Popup and Library.Toggled and hover.Parent and IsMouseOver(hover) then showTooltip(current) end
end)
end)
Connect(hover.MouseLeave, hideTooltip)
function tooltip:Destroy() end
return tooltip
end
local function attachTooltip(object, hover)
if type(object.Tooltip) == 'string' or type(object.DisabledTooltip) == 'string' then
object.TooltipTable = Library:AddTooltip(object.Tooltip, object.DisabledTooltip, hover)
object.TooltipTable.Disabled = object.Disabled == true
end
end
--// shared row pieces \\--
local function newRow(owner, height)
return New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, height), LayoutOrder = owner:NextOrder(), Parent = owner.Container })
end
local function newRightHolder(row)
local holder = New('Frame', { BackgroundTransparency = 1, AnchorPoint = vec2(1, 0), Position = dim2(1, 0, 0, 0), Size = dim2(0, 0, 1, 0), AutomaticSize = Enum.AutomaticSize.X, ZIndex = 3, Parent = row })
New('UIListLayout', { FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Right, VerticalAlignment = Enum.VerticalAlignment.Center, Padding = dim(0, 5), SortOrder = Enum.SortOrder.LayoutOrder, Parent = holder })
return holder
end
local function register(kind, idx, object)
object.Idx = idx
if idx ~= nil then Library[kind][idx] = object end
end
local function runChanged(object)
Library:SafeCallback(object.Callback, object.Value)
Library:SafeCallback(object.Changed, object.Value)
end
local function destroyable(owner, object, row, registry, idx)
function object:Destroy()
object.Destroyed = true
if row then row:Destroy() end
for index = #(object.Addons or {}), 1, -1 do
local addon = table.remove(object.Addons, index)
if addon and addon.Destroy then addon:Destroy() end
end
if registry and idx ~= nil and Library[registry][idx] == object then Library[registry][idx] = nil end
local at = table.find(owner.Elements, object)
if at then table.remove(owner.Elements, at) end
end
end
--// label \\--
function Container:AddLabel(...)
local first, second = select(1, ...), select(2, ...)
local data = {}
if type(first) == 'table' or type(second) == 'table' then
local params = type(first) == 'table' and first or second
data.Text, data.DoesWrap, data.Size = params.Text or '', params.DoesWrap or params.Wrap or false, params.Size or Library.TextSize
data.Visible = params.Visible ~= false
data.Idx = type(second) == 'table' and first or nil
else
data.Text, data.DoesWrap, data.Size, data.Visible, data.Idx = first or '', second or false, Library.TextSize, true, select(3, ...)
end
local label = { Text = data.Text, DoesWrap = data.DoesWrap, Addons = {}, Visible = data.Visible, Type = 'Label', Library = Library }
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(14)), AutomaticSize = data.DoesWrap and Enum.AutomaticSize.Y or Enum.AutomaticSize.None, LayoutOrder = self:NextOrder(), Visible = data.Visible, Parent = self.Container })
local text = New('TextLabel', {
Text = data.Text, BackgroundTransparency = 1, Size = data.DoesWrap and dim2(1, 0, 0, 0) or dim2(1, 0, 1, 0), AutomaticSize = data.DoesWrap and Enum.AutomaticSize.Y or Enum.AutomaticSize.None,
TextSize = data.Size, TextWrapped = data.DoesWrap, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, RichText = true, Parent = row,
})
label.TextLabel, label.Holder, label.Container = text, row, self.Container
label.RightHolder = (not data.DoesWrap) and newRightHolder(row) or nil
function label:SetText(value)
label.Text = tostring(value)
text.Text = label.Text
end
function label:SetVisible(visible)
label.Visible = visible
row.Visible = visible
end
destroyable(self, label, row, 'Labels', data.Idx)
if not data.DoesWrap then setmetatable(label, { __index = Addons }) end
table.insert(self.Elements, label)
if data.Idx ~= nil then Library.Labels[data.Idx] = label else table.insert(Library.Labels, label) end
return label
end
--// divider \\--
function Container:AddDivider(...)
local params = select(1, ...)
local text, top, bottom = nil, 0, 0
if type(params) == 'table' then
text, top, bottom = params.Text, params.MarginTop or params.Margin or 0, params.MarginBottom or params.Margin or 0
elseif type(params) == 'string' then
text = params
end
local divider = { Type = 'Divider', Visible = true }
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 10 + top + bottom), LayoutOrder = self:NextOrder(), Parent = self.Container })
if text and text ~= '' then
local width = TextWidth(text, 12) + 14
New('Frame', { AnchorPoint = vec2(0, 0.5), Position = dim2(0, 0, 0.5, (top - bottom) / 2), Size = dim2(0.5, -width / 2, 0, 1), Parent = row }, { BackgroundColor3 = 'b' })
New('Frame', { AnchorPoint = vec2(1, 0.5), Position = dim2(1, 0, 0.5, (top - bottom) / 2), Size = dim2(0.5, -width / 2, 0, 1), Parent = row }, { BackgroundColor3 = 'b' })
New('TextLabel', { Text = text, BackgroundTransparency = 1, AnchorPoint = vec2(0.5, 0.5), Position = dim2(0.5, 0, 0.5, (top - bottom) / 2), Size = dim2(0, width, 0, 12), TextSize = 12, Parent = row }, { TextColor3 = 'dim' })
else
New('Frame', { AnchorPoint = vec2(0, 0.5), Position = dim2(0, 0, 0.5, (top - bottom) / 2), Size = dim2(1, 0, 0, 1), Parent = row }, { BackgroundColor3 = 'b' })
end
function divider:SetVisible(visible) row.Visible = visible end
function divider:Destroy() row:Destroy() end
return divider
end
function Container:AddBlank(height)
New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, height or 8), LayoutOrder = self:NextOrder(), Parent = self.Container })
end
--// toggle (a Vaderhaxx checkbox) \\--
function Container:AddToggle(idx, info)
info = Validate(info, Templates.Toggle)
local toggle = {
Text = info.Text, Value = info.Default == true, Tooltip = info.Tooltip, DisabledTooltip = info.DisabledTooltip, Callback = info.Callback, Changed = info.Changed,
Risky = info.Risky, Disabled = info.Disabled, Visible = info.Visible, Addons = {}, AnyKeyPickerPicking = false, Variant = 'Checkbox', Type = 'Toggle', Library = Library,
}
local row = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(16)), LayoutOrder = self:NextOrder(), Visible = toggle.Visible, Active = not toggle.Disabled, Parent = self.Container })
local box, fill = Panel(row, { 'd', 'f' }, { Size = UDim2.fromOffset(H(11), H(11)), Position = dim2(0, 0, 0.5, 0), AnchorPoint = vec2(0, 0.5) })
local label = New('TextLabel', { Text = toggle.Text, BackgroundTransparency = 1, Position = dim2(0, 19, 0, 0), Size = dim2(1, -60, 1, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, RichText = true, Parent = row })
local right = newRightHolder(row)
toggle.Holder, toggle.TextLabel, toggle.Container, toggle.RightHolder = row, label, self.Container, right
attachTooltip(toggle, row)
local hovering = false
local function labelKey()
if toggle.Disabled then return 'dim' end
if toggle.Risky and toggle.Value then return 'risky' end
return (hovering and 'accent') or 'text'
end
function toggle:Display(animate)
if Library.Unloaded then return end
local fillKey = toggle.Value and (toggle.Risky and 'risky' or 'accent') or 'f'
Library.Registry[fill] = Library.Registry[fill] or {}
Library.Registry[fill].BackgroundColor3 = fillKey
Library.Registry[label] = Library.Registry[label] or {}
Library.Registry[label].TextColor3 = labelKey()
if animate ~= false and Animating() then
Tween(fill, { BackgroundColor3 = Palette[fillKey] }, 0.12)
Tween(label, { TextColor3 = Palette[labelKey()] }, 0.1)
if toggle.Value then
box.Size = UDim2.fromOffset(H(13), H(13))
Tween(box, { Size = UDim2.fromOffset(H(11), H(11)) }, 0.18, Enum.EasingStyle.Back)
end
else
fill.BackgroundColor3, label.TextColor3 = Palette[fillKey], Palette[labelKey()]
end
label.TextTransparency = toggle.Disabled and 0.5 or 0
box.BackgroundTransparency = toggle.Disabled and 0.6 or 0
end
function toggle:OnChanged(callback) toggle.Changed = callback end
function toggle:RunChanged() runChanged(toggle) end
function toggle:SetValue(value)
if toggle.Disabled then return end
toggle.Value = value == true
toggle:Display()
for _, addon in ipairs(toggle.Addons) do
if addon.Type == 'KeyPicker' and (addon.SyncToggleState or addon.Mode == 'Toggle') then
addon.Toggled = toggle.Value
addon:Update()
end
end
Library:UpdateDependencyBoxes()
if not toggle.AnyKeyPickerPicking then toggle:RunChanged() end
if toggle.Risky and toggle.Value and Animating() then FX.Glitch(label, 0.18) end
end
function toggle:SetDisabled(disabled)
toggle.Disabled = disabled
row.Active = not disabled
if toggle.TooltipTable then toggle.TooltipTable.Disabled = disabled end
for _, addon in ipairs(toggle.Addons) do
if addon.Type == 'KeyPicker' then addon:Update() end
end
toggle:Display(false)
end
function toggle:SetVisible(visible)
toggle.Visible = visible
row.Visible = visible
for _, addon in ipairs(toggle.Addons) do
if addon.Type == 'KeyPicker' then addon:Update() elseif addon.Refresh then addon:Refresh() end
end
end
function toggle:SetText(text)
toggle.Text = text
label.Text = text
end
Connect(row.MouseButton1Click, function()
if toggle.Disabled then return end
toggle:SetValue(not toggle.Value)
end)
Connect(row.MouseEnter, function()
hovering = true
if not toggle.Disabled then Tween(label, { TextColor3 = Palette[labelKey()] }, 0.08) end
end)
Connect(row.MouseLeave, function()
hovering = false
Tween(label, { TextColor3 = Palette[labelKey()] }, 0.1)
end)
toggle:Display(false)
toggle.Default = toggle.Value
destroyable(self, toggle, row, 'Toggles', idx)
setmetatable(toggle, { __index = Addons })
table.insert(self.Elements, toggle)
register('Toggles', idx, toggle)
return toggle
end
Container.AddCheckbox = Container.AddToggle
--// button (+ sub buttons in the same row) \\--
local function buttonInfo(...)
local first, second = select(1, ...), select(2, ...)
local info = {}
if type(first) == 'table' or type(second) == 'table' then
local params = type(first) == 'table' and first or second
info.Text, info.Func = params.Text or '', params.Func or params.Callback or noop
info.DoubleClick, info.Tooltip, info.DisabledTooltip = params.DoubleClick, params.Tooltip, params.DisabledTooltip
info.Risky, info.Disabled, info.Visible = params.Risky or false, params.Disabled or false, params.Visible ~= false
info.Idx = type(second) == 'table' and first or nil
else
info.Text, info.Func, info.Idx = first or '', second or noop, select(3, ...)
info.DoubleClick, info.Risky, info.Disabled, info.Visible = false, false, false, true
end
return info
end
local function makeButton(owner, holder, info, parentButton)
local button = {
Text = info.Text, Func = info.Func, DoubleClick = info.DoubleClick, Tooltip = info.Tooltip, DisabledTooltip = info.DisabledTooltip, Risky = info.Risky,
Disabled = info.Disabled, Visible = info.Visible, Addons = {}, Type = parentButton and 'SubButton' or 'Button', Library = Library,
}
local outer, inner = Panel(holder, { 'a', 'c' }, { Size = dim2(1, 0, 1, 0) })
outer.LayoutOrder = #holder:GetChildren()
local face = New('TextButton', { Text = button.Text, AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), TextSize = Library.TextSize, RichText = true, Active = not button.Disabled, Parent = inner })
button.Base, button.Holder, button.TextLabel = face, outer, face
local armed = false
attachTooltip(button, face)
local function baseKey() return button.Risky and 'risky' or 'text' end
function button:UpdateColors()
face.TextColor3 = button.Disabled and Palette.dim or Palette[baseKey()]
face.TextTransparency = button.Disabled and 0.4 or 0
end
function button:SetText(text)
button.Text = text
face.Text = text
end
function button:SetDisabled(disabled)
button.Disabled = disabled
face.Active = not disabled
if button.TooltipTable then button.TooltipTable.Disabled = disabled end
button:UpdateColors()
end
function button:SetVisible(visible)
button.Visible = visible
outer.Visible = visible
end
function button:Destroy()
button.Destroyed = true
outer:Destroy()
end
local function fire()
if button.Disabled then return end
Library:SafeCallback(button.Func)
Tween(inner, { BackgroundColor3 = Palette.accent }, 0.04)
task.delay(0.06, function() if inner.Parent then Tween(inner, { BackgroundColor3 = Palette.c }, 0.25) end end)
end
Connect(face.MouseButton1Click, function()
if button.Disabled then return end
if button.DoubleClick then
if armed then
armed = false
face.Text = button.Text
fire()
else
armed = true
face.Text = 'click again'
face.TextColor3 = Palette.accent
task.delay(2, function()
if armed and face.Parent then
armed = false
face.Text = button.Text
button:UpdateColors()
end
end)
end
else
fire()
end
end)
Connect(face.MouseEnter, function() if not button.Disabled then Tween(inner, { BackgroundColor3 = Palette.g }, 0.1) end end)
Connect(face.MouseLeave, function() Tween(inner, { BackgroundColor3 = Palette.c }, 0.15) end)
button:UpdateColors()
outer.Visible = button.Visible
return button
end
function Container:AddButton(...)
local info = buttonInfo(...)
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(20)), LayoutOrder = self:NextOrder(), Parent = self.Container })
New('UIListLayout', { FillDirection = Enum.FillDirection.Horizontal, HorizontalFlex = Enum.UIFlexAlignment.Fill, Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = row })
local button = makeButton(self, row, info, nil)
button.Row, button.Container = row, self.Container
function button:AddButton(...)
local sub = makeButton(self, row, buttonInfo(...), button)
table.insert(self.Elements or {}, sub)
return sub
end
setmetatable(button, { __index = Addons })
table.insert(self.Elements, button)
if info.Idx ~= nil then Library.Buttons[info.Idx] = button else table.insert(Library.Buttons, button) end
local base = button.Destroy
function button:Destroy()
base(self)
row:Destroy()
end
return button
end
--// slider \\--
function Container:AddSlider(idx, info)
info = Validate(info, Templates.Slider)
local slider = {
Text = info.Text, Value = info.Default, Min = info.Min, Max = info.Max, Prefix = info.Prefix, Suffix = info.Suffix, Compact = info.Compact, Rounding = info.Rounding,
HideMax = info.HideMax, Tooltip = info.Tooltip, DisabledTooltip = info.DisabledTooltip, Callback = info.Callback, Changed = info.Changed, Disabled = info.Disabled,
Visible = info.Visible, AllowRightClickInput = info.AllowRightClickInput, Type = 'Slider', Library = Library,
}
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, info.Compact and H(20) or H(32)), LayoutOrder = self:NextOrder(), Visible = slider.Visible, Parent = self.Container })
local title = (not info.Compact) and New('TextLabel', { Text = slider.Text or '', BackgroundTransparency = 1, Size = dim2(0.6, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, RichText = true, Parent = row }) or nil
local readout = New('TextLabel', {
Text = '', BackgroundTransparency = 1, AnchorPoint = vec2(1, 0), Position = dim2(1, 0, 0, 0), Size = dim2(0.5, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Right, ZIndex = 3, Parent = row,
}, { TextColor3 = 'dim' })
local barY, barHeight = info.Compact and 0 or H(17), info.Compact and H(20) or H(12)
local bar = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Position = dim2(0, 0, 0, barY), Size = dim2(1, 0, 0, barHeight), Active = true, Parent = row })
local outline, track = Panel(bar, { 'd', 'b' }, { Size = dim2(1, 0, 1, 0) })
outline.Name = 'Track'
local fillFrame = New('Frame', { Size = dim2(0, 0, 1, 0), ZIndex = 2, Parent = track }, { BackgroundColor3 = 'accent' })
if info.Compact then
readout.AnchorPoint, readout.Position, readout.Size, readout.TextXAlignment = vec2(0.5, 0.5), dim2(0.5, 0, 0.5, 0), dim2(1, 0, 1, 0), Enum.TextXAlignment.Center
readout.ZIndex = 5
Library.Registry[readout] = { TextColor3 = 'text' }
readout.TextColor3 = Palette.text
readout.Parent = bar
end
slider.Holder, slider.Bar, slider.Fill, slider.Container = row, bar, fillFrame, self.Container
attachTooltip(slider, bar)
local function formatNumber(value)
if slider.Rounding and slider.Rounding > 0 then
local text = string.format('%.' .. slider.Rounding .. 'f', value)
return (text:gsub('(%..-)0+$', '%1'):gsub('%.$', ''))
end
return tostring(value)
end
function slider:Display()
if Library.Unloaded then return end
local custom = info.FormatDisplayValue and info.FormatDisplayValue(slider, slider.Value)
if custom then
readout.Text = tostring(custom)
elseif info.Compact then
readout.Text = string.format('%s: %s%s%s', slider.Text or '', slider.Prefix, formatNumber(slider.Value), slider.Suffix)
elseif info.HideMax then
readout.Text = string.format('%s%s%s', slider.Prefix, formatNumber(slider.Value), slider.Suffix)
else
readout.Text = string.format('%s%s%s/%s%s%s', slider.Prefix, formatNumber(slider.Value), slider.Suffix, slider.Prefix, formatNumber(slider.Max), slider.Suffix)
end
local alpha = slider.Max ~= slider.Min and clamp((slider.Value - slider.Min) / (slider.Max - slider.Min), 0, 1) or 0
if Animating() and not slider.Dragging and fillFrame.AbsoluteSize.X > 0 then
Tween(fillFrame, { Size = dim2(alpha, 0, 1, 0) }, 0.08, Enum.EasingStyle.Quad)
else
fillFrame.Size = dim2(alpha, 0, 1, 0)
end
readout.TextTransparency, fillFrame.BackgroundTransparency = slider.Disabled and 0.5 or 0, slider.Disabled and 0.6 or 0
if title then title.TextTransparency = slider.Disabled and 0.5 or 0 end
end
function slider:OnChanged(callback) slider.Changed = callback end
function slider:RunChanged() runChanged(slider) end
function slider:SetValue(raw)
if slider.Disabled then return end
local number = tonumber(raw)
if not number or number == slider.Value then return end
slider.Value = clamp(number, slider.Min, slider.Max)
slider:Display()
slider:RunChanged()
end
function slider:SetMax(value)
slider.Max = value
slider:SetValue(clamp(slider.Value, slider.Min, value))
slider:Display()
end
function slider:SetMin(value)
slider.Min = value
slider:SetValue(clamp(slider.Value, value, slider.Max))
slider:Display()
end
function slider:SetPrefix(prefix) slider.Prefix = prefix slider:Display() end
function slider:SetSuffix(suffix) slider.Suffix = suffix slider:Display() end
function slider:SetText(text)
slider.Text = text
if title then title.Text = text end
slider:Display()
end
function slider:SetDisabled(disabled)
slider.Disabled = disabled
if slider.TooltipTable then slider.TooltipTable.Disabled = disabled end
slider:Display()
end
function slider:SetVisible(visible)
slider.Visible = visible
row.Visible = visible
end
-- dragging: the shared router only calls us while a drag is running, and the value is read once per frame
local moveHandle, endHandle, renderHandle, moved
local function fromMouse()
local x = (MouseAbs().X - bar.AbsolutePosition.X) / max(bar.AbsoluteSize.X, 1)
local value = slider.Min + (slider.Max - slider.Min) * clamp(x, 0, 1)
value = Round(value, 10 ^ -(slider.Rounding or 0))
slider:SetValue(clamp(value, slider.Min, slider.Max))
end
local function stopDrag()
slider.Dragging = false
for _, listener in ipairs({ moveHandle, endHandle, renderHandle }) do listener.Disconnect() end
moveHandle, endHandle, renderHandle = nil, nil, nil
slider:Display()
end
Connect(bar.InputBegan, function(input)
if slider.Disabled or moveHandle then return end
if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then
slider.Dragging = true
fromMouse()
moveHandle = Library:OnInput('Changed', function(changed)
if changed.UserInputType == Enum.UserInputType.MouseMovement or changed.UserInputType == Enum.UserInputType.Touch then moved = true end
end)
renderHandle = Library:OnInput('Render', function()
if moved then
moved = false
fromMouse()
end
end)
endHandle = Library:OnInput('Ended', function(ended)
if ended.UserInputType == Enum.UserInputType.MouseButton1 or ended.UserInputType == Enum.UserInputType.Touch then stopDrag() end
end)
end
end)
-- right click: type a value
if slider.AllowRightClickInput then
local typing = New('TextBox', {
Text = '', BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), ZIndex = 8, TextSize = Library.TextSize, ClearTextOnFocus = false, Visible = false, Parent = bar,
})
Library.Registry[typing] = { TextColor3 = 'accent' }
typing.TextColor3 = Palette.accent
Connect(bar.MouseButton2Click, function()
if slider.Disabled then return end
typing.Visible, typing.Text = true, tostring(slider.Value)
typing:CaptureFocus()
end)
Connect(typing.FocusLost, function()
local number = tonumber(typing.Text)
typing.Visible = false
if number then
if slider.Rounding == 0 then number = Round(number, 1) end
slider:SetValue(clamp(number, slider.Min, slider.Max))
end
end)
end
slider:Display()
slider.Default = slider.Value
destroyable(self, slider, row, 'Options', idx)
table.insert(self.Elements, slider)
register('Options', idx, slider)
return slider
end
--// input \\--
function Container:AddInput(idx, info)
info = Validate(info, Templates.Input)
if type(info.VerifyValue) == 'function' and info.Finished ~= true then info.Finished = true end
local input = {
Text = info.Text, Value = info.Default, Finished = info.Finished, Numeric = info.Numeric, ClearTextOnFocus = info.ClearTextOnFocus, ClearTextOnBlur = info.ClearTextOnBlur,
Placeholder = info.Placeholder, AllowEmpty = info.AllowEmpty, EmptyReset = info.EmptyReset, Tooltip = info.Tooltip, DisabledTooltip = info.DisabledTooltip,
Callback = info.Callback, Changed = info.Changed, VerifyValue = info.VerifyValue, Disabled = info.Disabled, Visible = info.Visible, Type = 'Input', Library = Library,
}
local hasLabel = type(info.Text) == 'string' and info.Text ~= ''
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, hasLabel and (H(20) + 16) or H(20)), LayoutOrder = self:NextOrder(), Visible = input.Visible, Parent = self.Container })
local title = New('TextLabel', { Text = input.Text or '', BackgroundTransparency = 1, Size = dim2(1, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, RichText = true, Visible = hasLabel, Parent = row })
local frame = New('Frame', { BackgroundTransparency = 1, AnchorPoint = vec2(0, 1), Position = dim2(0, 0, 1, 0), Size = dim2(1, 0, 0, H(20)), Parent = row })
local _, field = Panel(frame, { 'd', 'b' }, { Size = dim2(1, 0, 1, 0) })
local box = New('TextBox', {
Text = tostring(input.Value or ''), PlaceholderText = input.Placeholder, BackgroundTransparency = 1, Size = dim2(1, -8, 1, 0), Position = dim2(0, 5, 0, 0), TextSize = Library.TextSize,
TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = input.ClearTextOnFocus, TextEditable = not input.Disabled, Parent = field,
})
box.PlaceholderColor3 = Palette.dim
input.Holder, input.Box, input.Container = row, box, self.Container
attachTooltip(input, frame)
local internal = false
local function accept(text)
if input.Numeric and text ~= '' and not tonumber(text) and text ~= '-' and text ~= '.' then return false end
return true
end
function input:OnChanged(callback) input.Changed = callback end
function input:RunChanged() runChanged(input) end
function input:SetValue(text)
text = text == nil and '' or tostring(text)
if input.Numeric and text ~= '' and not tonumber(text) then return end
if type(input.VerifyValue) == 'function' and text ~= input.EmptyReset and input.VerifyValue(text) ~= true then return end
input.Value = text
internal = true
box.Text = text
internal = false
input:RunChanged()
end
function input:SetDisabled(disabled)
input.Disabled = disabled
box.TextEditable = not disabled
box.TextTransparency = disabled and 0.5 or 0
if input.TooltipTable then input.TooltipTable.Disabled = disabled end
end
function input:SetVisible(visible)
input.Visible = visible
row.Visible = visible
end
function input:SetText(text)
input.Text = text
title.Text = text or ''
title.Visible = text ~= nil and text ~= ''
end
Connect(box:GetPropertyChangedSignal('Text'), function()
if internal or input.Finished then return end
if input.Numeric and not accept(box.Text) then
internal = true
box.Text = input.Value or ''
internal = false
return
end
input:SetValue(box.Text)
end)
Connect(box.FocusLost, function()
if box.Text == '' and not input.AllowEmpty then
input:SetValue(input.EmptyReset)
elseif input.Finished then
input:SetValue(box.Text)
end
if input.ClearTextOnBlur then
internal = true
box.Text = ''
internal = false
end
Tween(field, { BackgroundColor3 = Palette.b }, 0.15)
end)
Connect(box.Focused, function() Tween(field, { BackgroundColor3 = Palette.g }, 0.1) end)
input.Default = input.Value
destroyable(self, input, row, 'Options', idx)
table.insert(self.Elements, input)
register('Options', idx, input)
return input
end
end
-- ==== 41_popups.lua ====
-- ZkxVader elements (2/2): dropdown, colour picker, key picker. Popups live on Library.Overlay (never clipped by a scrolling section).
do
local Container, Addons = Classes.Container, Classes.Addons
local Templates, Validate = Library.Templates, function(info, template) return Library.Validate(Library, info, template) end
--// popup plumbing: one open at a time, closed by an outside click, a tab change or the menu closing \\--
local function placePopup(frame, anchor, width, height, preferBelow)
local scale = Library.GetScale()
-- popups are positioned inside the overlay, whose own AbsolutePosition carries the top-bar inset
local abs, size = (anchor.AbsolutePosition - Library.Overlay.AbsolutePosition) / scale, anchor.AbsoluteSize / scale
local view = Library.Main.AbsoluteSize / scale
local x = clamp(abs.X, 2, max(2, view.X - width - 2))
local y = abs.Y + size.Y + 2
if preferBelow == false or y + height > view.Y - 4 then
local above = abs.Y - height - 2
if above >= 4 or y + height > view.Y - 4 then y = max(4, above) end
end
frame.Position = UDim2.fromOffset(x, y)
frame.Size = UDim2.fromOffset(width, height)
end
local function popupPanel(width, height)
local frame, inner = Panel(Library.Overlay, { 'a', 'b', 'e' }, { Size = UDim2.fromOffset(width, height), Clip = false })
frame.ZIndex = 100
frame.Active = true
frame.Visible = false
return frame, inner
end
Library:OnInput('Began', function(input)
local kind = input.UserInputType
if kind ~= Enum.UserInputType.MouseButton1 and kind ~= Enum.UserInputType.MouseButton2 and kind ~= Enum.UserInputType.Touch then return end
local popup = Library.Popup
if popup and popup.Frame and popup.Frame.Visible and not IsMouseOver(popup.Frame) and not (popup.Anchor and IsMouseOver(popup.Anchor)) then
Library:ClosePopup()
end
end)
Library.OnToggled = function(open) if not open then Library:ClosePopup() end end
--// dropdown \\--
local function playersList(excludeSelf)
local list = {}
for _, player in ipairs(Players:GetPlayers()) do
if not (excludeSelf and player == LocalPlayer) then table.insert(list, player.Name) end
end
table.sort(list)
return list
end
local function teamsList()
local list = {}
for _, team in ipairs(svc('Teams'):GetTeams()) do table.insert(list, team.Name) end
table.sort(list)
return list
end
function Container:AddDropdown(idx, info)
local owner = self
info = Validate(info, Templates.Dropdown)
if info.SpecialType == 'Player' then
info.Values, info.AllowNull = playersList(info.ExcludeLocalPlayer), true
elseif info.SpecialType == 'Team' then
info.Values, info.AllowNull = teamsList(), true
end
local dropdown = {
Text = type(info.Text) == 'string' and info.Text or nil, Value = info.Multi and {} or nil, Values = info.Values, DisabledValues = info.DisabledValues, Multi = info.Multi,
SpecialType = info.SpecialType, ExcludeLocalPlayer = info.ExcludeLocalPlayer, Tooltip = info.Tooltip, DisabledTooltip = info.DisabledTooltip, Callback = info.Callback,
Changed = info.Changed, Disabled = info.Disabled, Visible = info.Visible, Type = 'Dropdown', Library = Library,
}
local hasLabel = dropdown.Text ~= nil
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, hasLabel and (H(20) + 18) or (H(20) + 2)), LayoutOrder = self:NextOrder(), Visible = dropdown.Visible, Parent = self.Container })
local title = New('TextLabel', { Text = dropdown.Text or '', BackgroundTransparency = 1, Size = dim2(1, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, RichText = true, Visible = hasLabel, Parent = row })
local frame = New('Frame', { BackgroundTransparency = 1, AnchorPoint = vec2(0, 1), Position = dim2(0, 0, 1, 0), Size = dim2(1, 0, 0, H(20)), Parent = row })
local _, field = Panel(frame, { 'd', 'b' }, { Size = dim2(1, 0, 1, 0) })
local display = New('TextButton', { Text = '---', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, -18, 1, 0), Position = dim2(0, 6, 0, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, Parent = field })
local arrow = New('TextLabel', { Text = 'v', BackgroundTransparency = 1, AnchorPoint = vec2(1, 0), Position = dim2(1, -5, 0, 0), Size = dim2(0, 10, 1, 0), TextSize = Library.TextSize, Parent = field }, { TextColor3 = 'dim' })
dropdown.Holder, dropdown.Container = row, self.Container
local tooltipHost = display
if type(dropdown.Tooltip) == 'string' or type(dropdown.DisabledTooltip) == 'string' then
dropdown.TooltipTable = Library:AddTooltip(dropdown.Tooltip, dropdown.DisabledTooltip, tooltipHost)
dropdown.TooltipTable.Disabled = dropdown.Disabled == true
end
-- popup
local itemHeight = H(18)
local popup, popupInner = popupPanel(100, 40)
local searchBox
local list = New('ScrollingFrame', {
BackgroundTransparency = 1, ScrollBarThickness = 2, AutomaticCanvasSize = Enum.AutomaticSize.Y, CanvasSize = dim2(0, 0, 0, 0), Size = dim2(1, 0, 1, 0), Active = true, ZIndex = 101, Parent = popupInner,
})
Library.Theme(list, 'ScrollBarImageColor3', 'accent')
New('UIListLayout', { SortOrder = Enum.SortOrder.LayoutOrder, Parent = list })
if info.Searchable then
local searchRow = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(20)), ZIndex = 102, Parent = popupInner })
local _, searchField = Panel(searchRow, { 'd', 'b' }, { Size = dim2(1, -4, 1, -4), Position = dim2(0, 2, 0, 2) })
searchBox = New('TextBox', { Text = '', PlaceholderText = 'Search', BackgroundTransparency = 1, Size = dim2(1, -8, 1, 0), Position = dim2(0, 4, 0, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = false, ZIndex = 103, Parent = searchField })
searchBox.PlaceholderColor3 = Palette.dim
list.Position, list.Size = dim2(0, 0, 0, H(20)), dim2(1, 0, 1, -H(20))
end
local buttons = {}
local isOpen = false
local function active() return dropdown.Multi and dropdown.Value or (dropdown.Value and { [dropdown.Value] = true } or {}) end
local function activeCount()
local count = 0
for _ in pairs(active()) do count += 1 end
return count
end
function dropdown:GetActiveValues(returnCount)
local out = {}
for value in pairs(active()) do table.insert(out, value) end
return returnCount == true and #out or out
end
function dropdown:Display()
if Library.Unloaded then return end
local text = ''
if dropdown.Multi then
local parts = {}
for _, value in ipairs(dropdown.Values) do
if dropdown.Value[value] then table.insert(parts, info.FormatDisplayValue and tostring(info.FormatDisplayValue(value)) or tostring(value)) end
end
text = table.concat(parts, ', ')
else
text = dropdown.Value and tostring(dropdown.Value) or ''
if text ~= '' and info.FormatDisplayValue then text = tostring(info.FormatDisplayValue(text)) end
end
if #text > 30 then text = text:sub(1, 27) .. '...' end
display.Text = text == '' and '---' or text
display.TextColor3 = (text == '' or dropdown.Disabled) and Palette.dim or Palette.text
end
local function refreshButtons()
local chosen = active()
for _, entry in ipairs(buttons) do
local selected = chosen[entry.Value] == true
entry.Selected = selected
local key = entry.Disabled and 'dim' or (selected and 'accent' or 'text')
Library.Registry[entry.Button] = { TextColor3 = key }
entry.Button.TextColor3 = Palette[key]
entry.Mark.Visible = selected
end
end
local function heightFor(count) return min(max(count, 1), info.MaxVisibleDropdownItems or 8) * itemHeight + 2 + (searchBox and H(20) or 0) end
function dropdown:BuildDropdownList()
for _, entry in ipairs(buttons) do entry.Holder:Destroy() end
table.clear(buttons)
local needle = searchBox and searchBox.Text:lower() or ''
local shown = 0
for _, value in ipairs(dropdown.Values) do
local text = tostring(info.FormatListValue and info.FormatListValue(value) or value)
if needle == '' or text:lower():find(needle, 1, true) then
shown += 1
local disabled = table.find(dropdown.DisabledValues or {}, value) ~= nil
local holder = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, itemHeight), LayoutOrder = disabled and 1 or 0, ZIndex = 101, Parent = list })
local mark = New('Frame', { Size = dim2(0, 2, 1, -4), Position = dim2(0, 2, 0, 2), ZIndex = 103, Visible = false, Parent = holder }, { BackgroundColor3 = 'accent' })
local button = New('TextButton', { Text = text, AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 102, Parent = holder })
New('UIPadding', { PaddingLeft = dim(0, 8), Parent = button })
local entry = { Value = value, Button = button, Mark = mark, Holder = holder, Disabled = disabled }
table.insert(buttons, entry)
Connect(button.MouseEnter, function() if not disabled then Tween(holder, { BackgroundTransparency = 0.82 }, 0.08) end holder.BackgroundColor3 = Palette.g end)
Connect(button.MouseLeave, function() Tween(holder, { BackgroundTransparency = 1 }, 0.1) end)
if not disabled then
Connect(button.MouseButton1Click, function()
local selected = active()[value] == true
local try = not selected
if activeCount() == 1 and not try and not info.AllowNull then
-- the last selected value stays selected
elseif dropdown.Multi then
dropdown.Value[value] = try and true or nil
else
dropdown.Value = try and value or nil
end
dropdown:Display()
refreshButtons()
Library:UpdateDependencyBoxes()
dropdown:RunChanged()
if not dropdown.Multi then Library:ClosePopup() end
end)
end
end
end
refreshButtons()
if isOpen then
local current = popup.Size
popup.Size = UDim2.fromOffset(current.X.Offset, heightFor(shown))
end
return shown
end
function dropdown:RunChanged()
Library:SafeCallback(dropdown.Callback, dropdown.Value)
Library:SafeCallback(dropdown.Changed, dropdown.Value)
end
function dropdown:OnChanged(callback) dropdown.Changed = callback end
function dropdown:SetValue(value)
if dropdown.Multi then
local map = {}
for key, on in pairs(value or {}) do
if type(on) ~= 'boolean' then
map[on] = true
elseif on and table.find(dropdown.Values, key) then
map[key] = true
end
end
dropdown.Value = map
else
if table.find(dropdown.Values, value) then
dropdown.Value = value
elseif not value then
dropdown.Value = nil
end
end
dropdown:Display()
refreshButtons()
if not dropdown.Disabled then
Library:UpdateDependencyBoxes()
dropdown:RunChanged()
end
end
function dropdown:SetValues(values)
dropdown.Values = values
dropdown:BuildDropdownList()
dropdown:Display()
end
function dropdown:AddValues(values)
if type(values) == 'table' then
for _, value in ipairs(values) do table.insert(dropdown.Values, value) end
elseif type(values) == 'string' then
table.insert(dropdown.Values, values)
else
return
end
dropdown:BuildDropdownList()
end
function dropdown:SetDisabledValues(values)
dropdown.DisabledValues = values
dropdown:BuildDropdownList()
end
function dropdown:AddDisabledValues(values)
if type(values) == 'table' then
for _, value in ipairs(values) do table.insert(dropdown.DisabledValues, value) end
elseif type(values) == 'string' then
table.insert(dropdown.DisabledValues, values)
end
dropdown:BuildDropdownList()
end
function dropdown:SetDisabled(disabled)
dropdown.Disabled = disabled
if dropdown.TooltipTable then dropdown.TooltipTable.Disabled = disabled end
Library:ClosePopup()
dropdown:Display()
title.TextTransparency = disabled and 0.5 or 0
end
function dropdown:SetVisible(visible)
dropdown.Visible = visible
row.Visible = visible
end
function dropdown:SetText(text)
dropdown.Text = text
title.Text = text or ''
title.Visible = text ~= nil
row.Size = dim2(1, 0, 0, text and (H(20) + 18) or (H(20) + 2))
end
local function close()
isOpen = false
arrow.Text = 'v'
Tween(popup, { Size = UDim2.fromOffset(popup.Size.X.Offset, 0) }, 0.1)
task.delay(0.11, function()
if not isOpen then popup.Visible = false end
end)
if searchBox then searchBox.Text = '' end
end
local handle = { Frame = popup, Anchor = frame, Close = close }
local function open()
if dropdown.Disabled then return end
if info.SpecialType == 'Player' then dropdown.Values = playersList(info.ExcludeLocalPlayer) elseif info.SpecialType == 'Team' then dropdown.Values = teamsList() end
local shown = dropdown:BuildDropdownList()
Library:OpenPopup(handle)
isOpen = true
arrow.Text = '^'
local width = frame.AbsoluteSize.X / Library.GetScale()
local height = heightFor(shown)
placePopup(popup, frame, width, height, true)
popup.Visible = true
if Animating() then
popup.Size = UDim2.fromOffset(width, 0)
Tween(popup, { Size = UDim2.fromOffset(width, height) }, 0.14, Enum.EasingStyle.Quint)
end
if searchBox then searchBox:CaptureFocus() end
end
Connect(display.MouseButton1Click, function()
if Library.Popup == handle and isOpen then Library:ClosePopup() else open() end
end)
dropdown.Open = function() if not (Library.Popup == handle and isOpen) then open() end end
dropdown.Close = function() Library:ClosePopup() end
if searchBox then Connect(searchBox:GetPropertyChangedSignal('Text'), function() dropdown:BuildDropdownList() end) end
Library:OnUnload(function() popup:Destroy() end)
-- defaults: a value, a list of values, or an index into Values
local defaults = {}
if type(info.Default) == 'string' then
local at = table.find(dropdown.Values, info.Default)
if at then table.insert(defaults, at) end
elseif type(info.Default) == 'table' then
for _, value in pairs(info.Default) do
local at = table.find(dropdown.Values, value)
if at then table.insert(defaults, at) end
end
elseif info.Default ~= nil and dropdown.Values[info.Default] ~= nil then
table.insert(defaults, info.Default)
end
for _, at in ipairs(defaults) do
if dropdown.Multi then dropdown.Value[dropdown.Values[at]] = true else
dropdown.Value = dropdown.Values[at]
break
end
end
dropdown:Display()
dropdown:BuildDropdownList()
dropdown.Default, dropdown.DefaultValues = defaults, dropdown.Values
if dropdown.Disabled then dropdown:SetDisabled(true) end
function dropdown:Destroy()
dropdown.Destroyed = true
popup:Destroy()
row:Destroy()
if Library.Options[idx] == dropdown then Library.Options[idx] = nil end
local at = table.find(owner.Elements or {}, dropdown)
if at then table.remove(owner.Elements, at) end
end
table.insert(self.Elements, dropdown)
if idx ~= nil then Library.Options[idx] = dropdown end
return dropdown
end
--// colour picker (addon) \\--
local HUE_KEYS = {}
for index = 0, 6 do table.insert(HUE_KEYS, ColorSequenceKeypoint.new(index / 6, hsv(index / 6, 1, 1))) end
local HUE_SEQUENCE = ColorSequence.new(HUE_KEYS)
function Addons:AddColorPicker(idx, info)
info = Validate(info, Templates.ColorPicker)
local parent = self
local host = parent.RightHolder
if not host then return parent end
local picker = {
Value = info.Default, Transparency = type(info.Transparency) == 'number' and info.Transparency or 0, Title = info.Title, Callback = info.Callback, Changed = info.Changed,
Type = 'ColorPicker', Library = Library, HasAlpha = info.Transparency ~= nil and info.Transparency ~= false,
}
picker.Hue, picker.Sat, picker.Vib = info.Default:ToHSV()
local swatchOuter, swatch = Panel(host, { 'a', 'g' }, { Size = UDim2.fromOffset(H(20), H(10)) })
swatchOuter.LayoutOrder = 10 + #parent.Addons
Library.Registry[swatch] = nil -- the swatch shows the picked colour; the theme registry must not repaint it
local swatchButton = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), ZIndex = 4, Parent = swatch })
swatch.BackgroundColor3 = picker.Value
local width, svH = 190, 110
local popup, inner = popupPanel(width, svH + 76 + (picker.HasAlpha and 16 or 0))
New('UIPadding', { PaddingTop = dim(0, 6), PaddingLeft = dim(0, 6), PaddingRight = dim(0, 6), PaddingBottom = dim(0, 6), Parent = inner })
local heading = New('TextLabel', { Text = (picker.Title or 'Color'), BackgroundTransparency = 1, Size = dim2(1, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 102, Parent = inner }, { TextColor3 = 'dim' })
local svOuter, svBase = Panel(inner, { 'd' }, { Size = UDim2.fromOffset(width - 12 - 22, svH), Position = dim2(0, 0, 0, 18) })
svOuter.ZIndex = 101
local sv = New('TextButton', { Text = '', AutoButtonColor = false, Size = dim2(1, 0, 1, 0), BackgroundColor3 = hsv(picker.Hue, 1, 1), ZIndex = 102, Parent = svBase })
local white = New('Frame', { Size = dim2(1, 0, 1, 0), BackgroundColor3 = rgb(255, 255, 255), ZIndex = 103, Parent = sv })
New('UIGradient', { Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 0), NumberSequenceKeypoint.new(1, 1) }), Parent = white })
local black = New('Frame', { Size = dim2(1, 0, 1, 0), BackgroundColor3 = rgb(0, 0, 0), ZIndex = 104, Parent = sv })
New('UIGradient', { Rotation = 90, Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 1), NumberSequenceKeypoint.new(1, 0) }), Parent = black })
local knob = New('Frame', { Size = UDim2.fromOffset(7, 7), AnchorPoint = vec2(0.5, 0.5), BackgroundTransparency = 1, ZIndex = 106, Parent = sv })
New('UIStroke', { Color = rgb(255, 255, 255), Thickness = 1, Parent = knob })
local hueOuter, hueBase = Panel(inner, { 'd' }, { Size = UDim2.fromOffset(16, svH), Position = dim2(1, -16, 0, 18) })
hueOuter.ZIndex = 101
local hue = New('TextButton', { Text = '', AutoButtonColor = false, Size = dim2(1, 0, 1, 0), BackgroundColor3 = rgb(255, 255, 255), ZIndex = 102, Parent = hueBase })
New('UIGradient', { Color = HUE_SEQUENCE, Rotation = 90, Parent = hue })
local hueMark = New('Frame', { Size = dim2(1, 0, 0, 2), AnchorPoint = vec2(0, 0.5), BackgroundColor3 = rgb(255, 255, 255), ZIndex = 106, Parent = hue })
local alphaBase, alpha, alphaMark, alphaGradient
if picker.HasAlpha then
local alphaOuter
alphaOuter, alphaBase = Panel(inner, { 'd' }, { Size = UDim2.new(1, 0, 0, 12), Position = dim2(0, 0, 0, 18 + svH + 6) })
alphaOuter.ZIndex = 101
alpha = New('TextButton', { Text = '', AutoButtonColor = false, Size = dim2(1, 0, 1, 0), BackgroundColor3 = rgb(255, 255, 255), ZIndex = 102, Parent = alphaBase })
alphaGradient = New('UIGradient', { Color = ColorSequence.new(picker.Value), Transparency = NumberSequence.new({ NumberSequenceKeypoint.new(0, 0), NumberSequenceKeypoint.new(1, 1) }), Parent = alpha })
alphaMark = New('Frame', { Size = dim2(0, 2, 1, 0), AnchorPoint = vec2(0.5, 0), BackgroundColor3 = rgb(255, 255, 255), ZIndex = 106, Parent = alpha })
end
local hexRow = New('Frame', { BackgroundTransparency = 1, Position = dim2(0, 0, 0, 18 + svH + 6 + (picker.HasAlpha and 18 or 0)), Size = dim2(1, 0, 0, 20), ZIndex = 101, Parent = inner })
local _, hexField = Panel(hexRow, { 'd', 'b' }, { Size = dim2(1, 0, 1, 0) })
local hexBox = New('TextBox', { Text = '', PlaceholderText = 'hex', BackgroundTransparency = 1, Size = dim2(1, -8, 1, 0), Position = dim2(0, 5, 0, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = false, ZIndex = 103, Parent = hexField })
hexBox.PlaceholderColor3 = Palette.dim
function picker:Display()
picker.Value = hsv(picker.Hue, picker.Sat, picker.Vib)
swatch.BackgroundColor3 = picker.Value
sv.BackgroundColor3 = hsv(picker.Hue, 1, 1)
knob.Position = dim2(picker.Sat, 0, 1 - picker.Vib, 0)
hueMark.Position = dim2(0, 0, picker.Hue, 0)
if alphaGradient then
alphaGradient.Color = ColorSequence.new(picker.Value)
alphaMark.Position = dim2(1 - picker.Transparency, 0, 0, 0)
end
if not hexBox:IsFocused() then hexBox.Text = picker.Value:ToHex():upper() end
end
function picker:OnChanged(callback) picker.Changed = callback end
function picker:RunChanged()
Library:SafeCallback(picker.Callback, picker.Value)
Library:SafeCallback(picker.Changed, picker.Value)
end
-- Obsidian's contract: SetValue takes {h, s, v} or a Color3; the transparency only counts when the picker has alpha
function picker:SetValue(hsvTable, transparency)
if typeof(hsvTable) == 'Color3' then
picker:SetValueRGB(hsvTable, transparency)
return
end
picker.Hue, picker.Sat, picker.Vib = hsvTable[1], hsvTable[2], hsvTable[3]
picker.Transparency = picker.HasAlpha and (transparency or 0) or 0
picker:Update()
end
function picker:SetValueRGB(color, transparency)
-- Library.ColorMigrations[id] = { from = Color3, to = Color3 }: a saved value that equals an old stock colour is shown as its replacement
local migration = idx ~= nil and Library.ColorMigrations and Library.ColorMigrations[idx]
if migration and math.abs(color.R - migration.from.R) < 0.004 and math.abs(color.G - migration.from.G) < 0.004 and math.abs(color.B - migration.from.B) < 0.004 then
color = migration.to
end
picker.Hue, picker.Sat, picker.Vib = color:ToHSV()
picker.Transparency = picker.HasAlpha and (transparency or 0) or 0
picker:Update()
end
function picker:Update()
picker:Display()
picker:RunChanged()
end
function picker:Refresh() swatchOuter.Visible = parent.Visible ~= false end
local dragging = nil
local function drag()
local mouse = MouseAbs()
if dragging == 'sv' then
picker.Sat = clamp((mouse.X - sv.AbsolutePosition.X) / max(sv.AbsoluteSize.X, 1), 0, 1)
picker.Vib = 1 - clamp((mouse.Y - sv.AbsolutePosition.Y) / max(sv.AbsoluteSize.Y, 1), 0, 1)
elseif dragging == 'hue' then
picker.Hue = clamp((mouse.Y - hue.AbsolutePosition.Y) / max(hue.AbsoluteSize.Y, 1), 0, 1)
elseif dragging == 'alpha' and alpha then
picker.Transparency = 1 - clamp((mouse.X - alpha.AbsolutePosition.X) / max(alpha.AbsoluteSize.X, 1), 0, 1)
end
picker:Display()
picker:RunChanged()
end
local moveHandle, endHandle, renderHandle, moved
local function stopDrag()
dragging = nil
for _, listener in ipairs({ moveHandle, endHandle, renderHandle }) do listener.Disconnect() end
moveHandle, endHandle, renderHandle = nil, nil, nil
end
for name, target in pairs({ sv = sv, hue = hue, alpha = alpha }) do
if target then
Connect(target.InputBegan, function(input)
if (input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch) and not moveHandle then
dragging = name
drag()
moveHandle = Library:OnInput('Changed', function(changed)
if changed.UserInputType == Enum.UserInputType.MouseMovement or changed.UserInputType == Enum.UserInputType.Touch then moved = true end
end)
renderHandle = Library:OnInput('Render', function()
if moved then
moved = false
drag()
end
end)
endHandle = Library:OnInput('Ended', function(ended)
if ended.UserInputType == Enum.UserInputType.MouseButton1 or ended.UserInputType == Enum.UserInputType.Touch then stopDrag() end
end)
end
end)
end
end
Connect(hexBox.FocusLost, function()
local text = hexBox.Text:gsub('#', '')
if #text == 6 and text:match('^%x+$') then picker:SetValueRGB(Color3.fromHex(text), picker.Transparency) else picker:Display() end
end)
local isOpen = false
local function close()
isOpen = false
popup.Visible = false
end
local handle = { Frame = popup, Anchor = swatchOuter, Close = close }
local function togglePopup()
if isOpen then
Library:ClosePopup()
return
end
Library:OpenPopup(handle)
isOpen = true
placePopup(popup, swatchOuter, width, popup.Size.Y.Offset, true)
popup.Visible = true
picker:Display()
end
picker.TogglePopup = togglePopup
Connect(swatchButton.MouseButton1Click, togglePopup)
Library:OnUnload(function() popup:Destroy() end)
picker:Display()
picker.Default = picker.Value
function picker:Destroy()
picker.Destroyed = true
popup:Destroy()
swatchOuter:Destroy()
local at = table.find(parent.Addons, picker)
if at then table.remove(parent.Addons, at) end
if Library.Options[idx] == picker then Library.Options[idx] = nil end
end
table.insert(parent.Addons, picker)
if idx ~= nil then Library.Options[idx] = picker end
return parent
end
--// key picker (addon) \\--
local SPECIAL_KEYS = { MB1 = Enum.UserInputType.MouseButton1, MB2 = Enum.UserInputType.MouseButton2, MB3 = Enum.UserInputType.MouseButton3 }
local SPECIAL_INPUT = { [Enum.UserInputType.MouseButton1] = 'MB1', [Enum.UserInputType.MouseButton2] = 'MB2', [Enum.UserInputType.MouseButton3] = 'MB3' }
local MODIFIERS = {
LAlt = Enum.KeyCode.LeftAlt, RAlt = Enum.KeyCode.RightAlt, LCtrl = Enum.KeyCode.LeftControl, RCtrl = Enum.KeyCode.RightControl,
LShift = Enum.KeyCode.LeftShift, RShift = Enum.KeyCode.RightShift, Tab = Enum.KeyCode.Tab, CapsLock = Enum.KeyCode.CapsLock,
}
local MODIFIER_INPUT = {}
for name, code in pairs(MODIFIERS) do MODIFIER_INPUT[code] = name end
local SHORT = {
LeftShift = 'LShift', RightShift = 'RShift', LeftControl = 'LCtrl', RightControl = 'RCtrl', LeftAlt = 'LAlt', RightAlt = 'RAlt', Backspace = 'BkSp',
Return = 'Enter', Insert = 'Ins', Delete = 'Del', PageUp = 'PgUp', PageDown = 'PgDn', Backquote = '`', CapsLock = 'Caps',
}
Library.KeybindToggles = Library.KeybindToggles or {}
Library.IsPicking = false
function Addons:AddKeyPicker(idx, info)
info = Validate(info, Templates.KeyPicker)
local parent = self
local isButton = parent.Type == 'Button' or parent.Type == 'SubButton'
local host = parent.RightHolder
if isButton then
assert(info.Mode == 'Press', "KeyPicker on Buttons can only be applied with the 'Press' mode.")
host = parent.Row
end
if not host then return parent end
local picker = {
Text = info.Text, Value = info.Default, Modifiers = info.DefaultModifiers, DisplayValue = info.Default, Blacklisted = info.Blacklisted, BlacklistedModifiers = info.BlacklistedModifiers,
Whitelisted = info.Whitelisted, WhitelistedModifiers = info.WhitelistedModifiers, Toggled = false, Mode = info.Mode, SyncToggleState = info.SyncToggleState, Callback = info.Callback,
ChangedCallback = info.ChangedCallback, Changed = info.Changed, Clicked = info.Clicked, Type = 'KeyPicker', Library = Library, Connections = {},
}
local modes = info.Modes
if picker.Mode == 'Press' then
assert(parent.Type == 'Label' or isButton, "KeyPicker with the mode 'Press' can be only applied on Labels and Buttons.")
picker.SyncToggleState, modes, info.Mode = false, { 'Press' }, 'Press'
end
if picker.SyncToggleState then
modes = { 'Toggle', 'Hold' }
if not table.find(modes, info.Mode) then picker.Mode = 'Toggle' end
end
-- the checkbox is the source of truth in Toggle mode (pickers that already sync it are left alone)
local function drivesToggle() return parent.Type == 'Toggle' and picker.Mode == 'Toggle' and not picker.SyncToggleState end
local function flipToggled()
if drivesToggle() then picker.Toggled = not parent.Value else picker.Toggled = not picker.Toggled end
end
local function mirrorToggled()
if drivesToggle() and parent.Value ~= picker.Toggled then
parent:SetValue(picker.Toggled)
picker.Toggled = parent.Value
end
end
local function activeModifiers()
local out = {}
for name, code in pairs(MODIFIERS) do
if UserInputService:IsKeyDown(code) then table.insert(out, name) end
end
return out
end
local function modifiersHeld(required)
if type(required) ~= 'table' or #required == 0 then return true end
local held = activeModifiers()
for _, name in ipairs(required) do
if not table.find(held, name) then return false end
end
return true
end
local function verifyModifiers(list)
local out = {}
if type(list) ~= 'table' then return out end
for _, name in ipairs(list) do
if MODIFIERS[name] then table.insert(out, name) end
end
return out
end
picker.Modifiers = verifyModifiers(picker.Modifiers)
-- the key button
local buttonOuter, buttonInner = Panel(host, { 'a', 'c' }, { Size = UDim2.fromOffset(H(40), H(14)) })
buttonOuter.LayoutOrder = 20 + #parent.Addons
if isButton then
buttonOuter.Size = UDim2.fromOffset(44, H(20))
buttonOuter.LayoutOrder = 1000
end
local keyButton = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), TextSize = Library.TextSize, ZIndex = 4, Parent = buttonInner })
-- row in the keybind list
local listHolder = New('TextButton', { Text = '', AutoButtonColor = false, Active = Library.IsMobile, BackgroundTransparency = 1, Size = dim2(0, 0, 0, Library.IsMobile and 24 or 14), AutomaticSize = Enum.AutomaticSize.X, ClipsDescendants = true, Visible = false, Parent = Library.KeybindContainer })
local listLabel = New('TextLabel', { Text = '', BackgroundTransparency = 1, Size = dim2(0, 0, 1, 0), AutomaticSize = Enum.AutomaticSize.X, TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, Parent = listHolder }, { TextColor3 = 'dim' })
if Library.IsMobile then
Connect(listHolder.MouseButton1Click, function()
if picker.Mode == 'Toggle' then
flipToggled()
picker:DoClick()
mirrorToggled()
elseif picker.Mode == 'Press' then
picker:DoClick()
end
picker:Update()
end)
end
-- mode menu (right click)
local menu, menuInner = popupPanel(70, #modes * 18 + 4)
New('UIListLayout', { SortOrder = Enum.SortOrder.LayoutOrder, Parent = menuInner })
local modeButtons = {}
for index, mode in ipairs(modes) do
local button = New('TextButton', { Text = mode, AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 18), LayoutOrder = index, TextSize = Library.TextSize, ZIndex = 102, Parent = menuInner }, { TextColor3 = 'dim' })
modeButtons[mode] = button
Connect(button.MouseButton1Click, function()
picker:SetMode(mode)
Library:ClosePopup()
end)
end
function picker:SetMode(mode)
picker.Mode = mode
for name, button in pairs(modeButtons) do ThemeSet(button, 'TextColor3', name == mode and 'accent' or 'dim') end
picker:Update()
end
local picking = false
local lastShown
function picker:Display(text)
if Library.Unloaded then return end
local shown = text or picker.DisplayValue
shown = shown:gsub('%a+', function(word) return SHORT[word] or word end)
if picker.Value == 'None' or picker.Value == 'Unknown' then shown = text or 'none' end
if shown ~= lastShown then
lastShown = shown
keyButton.Text = shown
buttonOuter.Size = UDim2.fromOffset(TextWidth(shown, Library.TextSize) + 12, isButton and H(20) or H(14))
end
end
table.insert(Library.FontListeners, function()
lastShown = nil
picker:Display()
end)
function picker:GetState()
if picker.Mode == 'Always' then return true end
if picker.Mode == 'Hold' then
local key = picker.Value
if key == 'None' or key == 'Unknown' then return false end
if not modifiersHeld(picker.Modifiers) or picking then return false end
if SPECIAL_KEYS[key] then
if Library.Toggled then return false end
return UserInputService:IsMouseButtonPressed(SPECIAL_KEYS[key]) and not UserInputService:GetFocusedTextBox()
end
local ok, code = pcall(function() return Enum.KeyCode[key] end)
return ok and code ~= nil and UserInputService:IsKeyDown(code) and not UserInputService:GetFocusedTextBox() or false
end
return picker.Toggled
end
local function listText()
local key = picker.DisplayValue:gsub('%a+', function(word) return SHORT[word] or word end)
if picker.Value == 'None' or picker.Value == 'Unknown' then key = 'Tap' end
return ('[%s] %s (%s)'):format(key, picker.Text, picker.Mode)
end
function picker:Update()
picker:Display()
if info.NoUI then return end
if picker.Mode == 'Toggle' and parent.Type == 'Toggle' and parent.Disabled then
listHolder.Visible = false
return
end
local state = picker:GetState()
if picker.SyncToggleState and parent.Value ~= state then parent:SetValue(state) end
-- only keys that are bound are listed: the row pops in when a key gets bound and leaves when it is cleared
local bound = picker.Value ~= 'None' and picker.Value ~= 'Unknown'
-- a phone has no keys: there every togglable bind is listed, and tapping its row flips it
if not bound and not (Library.IsMobile and (picker.Mode == 'Toggle' or picker.Mode == 'Press')) then
listHolder.Visible = false
return
end
listLabel.Text = listText()
ThemeSet(listLabel, 'TextColor3', state and 'accent' or 'dim')
if not listHolder.Visible then
listHolder.Visible = true
if Animating() then
listHolder.Size = dim2(0, 0, 0, 0)
Tween(listHolder, { Size = dim2(0, 0, 0, Library.IsMobile and 24 or 14) }, 0.22, Enum.EasingStyle.Back)
FX.Scramble(listLabel, listLabel.Text, 0.3)
end
end
end
function picker:OnChanged(callback) picker.Changed = callback end
function picker:OnClick(callback) picker.Clicked = callback end
function picker:DoClick()
if picking then return end
if picker.Mode == 'Press' then
if picker.Toggled and info.WaitForCallback == true then return end
picker.Toggled = true
end
Library:SafeCallback(picker.Callback, picker.Toggled)
Library:SafeCallback(picker.Clicked, picker.Toggled)
if isButton then Library:SafeCallback(parent.Func, picker.Toggled) end
if Library.ToggleKeybind == picker and Library.Toggle then Library:Toggle() end
if picker.Mode == 'Press' then picker.Toggled = false end
end
function picker:RunChanged(valid, code)
if valid == nil or code == nil then
valid, code = pcall(function()
if picker.Value == 'None' then return nil end
if SPECIAL_KEYS[picker.Value] == nil then return Enum.KeyCode[picker.Value] end
return SPECIAL_KEYS[picker.Value]
end)
end
local converted = {}
for _, name in ipairs(picker.Modifiers) do table.insert(converted, MODIFIERS[name]) end
Library:SafeCallback(picker.ChangedCallback, code, converted)
Library:SafeCallback(picker.Changed, code, converted)
end
function picker:SetValue(data)
local key, mode, modifiers = data[1], data[2], data[3]
local valid, code = pcall(function()
if key == 'None' then
key = nil
return nil
end
if SPECIAL_KEYS[key] == nil then return Enum.KeyCode[key] end
return SPECIAL_KEYS[key]
end)
if key == nil then
picker.Value = 'None'
elseif valid then
picker.Value = key
else
picker.Value = 'Unknown'
end
picker.Modifiers = verifyModifiers(type(modifiers) == 'table' and modifiers or picker.Modifiers)
picker.DisplayValue = #picker.Modifiers > 0 and (table.concat(picker.Modifiers, ' + ') .. ' + ' .. picker.Value) or picker.Value
if mode and modeButtons[mode] then picker:SetMode(mode) end
picker:Update()
picker:RunChanged(valid, code)
end
function picker:SetText(text)
picker.Text = text
picker:Update()
end
local function setPicking(state)
picking = state
Library.IsPicking = state
parent.AnyKeyPickerPicking = state
picker:Update()
end
local function startPicking()
if picking or Library.IsPicking then return end
setPicking(true)
keyButton.Text, buttonOuter.Size = '...', UDim2.fromOffset(28, isButton and H(20) or H(14))
local current
local function valid(input)
if input.KeyCode == Enum.KeyCode.Escape then return true end
local isModifier = input.UserInputType == Enum.UserInputType.Keyboard and MODIFIER_INPUT[input.KeyCode] ~= nil
local name
if SPECIAL_INPUT[input.UserInputType] then
name = SPECIAL_INPUT[input.UserInputType]
elseif input.UserInputType == Enum.UserInputType.Keyboard then
name = isModifier and MODIFIER_INPUT[input.KeyCode] or input.KeyCode.Name
end
if name then
if isModifier then
if picker.WhitelistedModifiers and #picker.WhitelistedModifiers > 0 and not table.find(picker.WhitelistedModifiers, name) then return false end
if picker.BlacklistedModifiers and table.find(picker.BlacklistedModifiers, name) then return false end
else
if picker.Whitelisted and #picker.Whitelisted > 0 and not table.find(picker.Whitelisted, name) then return false end
if picker.Blacklisted and table.find(picker.Blacklisted, name) then return false end
end
end
return true
end
while true do
local input = UserInputService.InputBegan:Wait()
if UserInputService:GetFocusedTextBox() ~= nil or Library.Unloaded then
setPicking(false)
return
end
if valid(input) then
current = input
break
end
end
local key = 'Unknown'
if SPECIAL_INPUT[current.UserInputType] then
key = SPECIAL_INPUT[current.UserInputType]
elseif current.UserInputType == Enum.UserInputType.Keyboard then
key = current.KeyCode == Enum.KeyCode.Escape and 'None' or current.KeyCode.Name
end
picker.Toggled = parent.Type == 'Toggle' and parent.Value or false
picker:SetValue({ key, picker.Mode, {} })
-- Always ignores its key, so a key bound in Always mode would never do anything: binding one to a checkbox switches the picker to Toggle
if picker.Mode == 'Always' and parent.Type == 'Toggle' and key ~= 'None' and key ~= 'Unknown' and modeButtons.Toggle then
picker:SetMode('Toggle')
picker.Toggled = parent.Value
picker:Update()
Library:Notify({ Title = picker.Text, Description = picker.DisplayValue .. ' now toggles it. Right-click the key to use Hold or Always instead.', Time = 4 })
end
-- a key still reported as held (alt-tab while binding, a lost key-up) must not keep picking mode on forever
local deadline = os.clock() + 1.5
repeat
task.wait()
local down = false
if SPECIAL_INPUT[current.UserInputType] then
down = UserInputService:IsMouseButtonPressed(current.UserInputType)
elseif current.UserInputType == Enum.UserInputType.Keyboard then
down = UserInputService:IsKeyDown(current.KeyCode)
end
until not down or os.clock() > deadline or Library.Unloaded
setPicking(false)
end
Connect(keyButton.MouseButton1Click, function()
if picking or Library.IsPicking then return end
local ok, err = pcall(startPicking)
if picking then setPicking(false) end
if not ok then warn('[ZkxVader] KeyPicker: ' .. tostring(err)) end
end)
local menuOpen = false
local menuHandle = { Frame = menu, Anchor = keyButton, Close = function() menuOpen = false menu.Visible = false end }
local function toggleMenu()
if menuOpen then
Library:ClosePopup()
return
end
Library:OpenPopup(menuHandle)
menuOpen = true
placePopup(menu, buttonOuter, 70, #modes * 18 + 4, true)
menu.Visible = true
end
picker.ToggleMenu = toggleMenu
Connect(keyButton.MouseButton2Click, toggleMenu)
local function relevant(input)
-- a key press only matters to a picker when it is that picker's key, a modifier of its combo, or the picker holds (state follows the key)
if SPECIAL_INPUT[input.UserInputType] == picker.Value then return true end
if input.UserInputType ~= Enum.UserInputType.Keyboard then return false end
return input.KeyCode.Name == picker.Value or (#picker.Modifiers > 0 and MODIFIER_INPUT[input.KeyCode] ~= nil)
end
Library:OnInput('Began', function(input)
if Library.Unloaded then return end
local isMouse = SPECIAL_INPUT[input.UserInputType] ~= nil
if picker.Mode == 'Always' or picker.Value == 'Unknown' or picker.Value == 'None' or picking or Library.IsPicking or UserInputService:GetFocusedTextBox() or (isMouse and Library.Toggled) then return end
local holding = modifiersHeld(picker.Modifiers) and (SPECIAL_INPUT[input.UserInputType] == picker.Value or (input.UserInputType == Enum.UserInputType.Keyboard and input.KeyCode.Name == picker.Value))
if picker.Mode == 'Toggle' then
if holding then
flipToggled()
picker:DoClick()
mirrorToggled()
end
elseif picker.Mode == 'Press' then
if holding then picker:DoClick() end
end
if holding or (picker.Mode == 'Hold' and relevant(input)) then picker:Update() end
end)
Library:OnInput('Ended', function(input)
if Library.Unloaded or picker.Mode ~= 'Hold' then return end
local isMouse = SPECIAL_INPUT[input.UserInputType] ~= nil
if picker.Value == 'Unknown' or picker.Value == 'None' or picking or Library.IsPicking or UserInputService:GetFocusedTextBox() or (isMouse and Library.Toggled) then return end
if relevant(input) then picker:Update() end
end)
picker:SetMode(picker.Mode)
Library:OnUnload(function() menu:Destroy() end)
picker.Default, picker.DefaultModifiers = picker.Value, table.clone(picker.Modifiers or {})
function picker:Destroy()
picker.Destroyed = true
menu:Destroy()
buttonOuter:Destroy()
listHolder:Destroy()
local at = table.find(parent.Addons, picker)
if at then table.remove(parent.Addons, at) end
if Library.Options[idx] == picker then Library.Options[idx] = nil end
end
table.insert(parent.Addons, picker)
if idx ~= nil then Library.Options[idx] = picker end
picker:Update()
return parent
end
end
-- ==== 42_viewport.lua ====
-- ZkxVader viewport element (the ESP tab's live preview): a ViewportFrame inside a Vaderhaxx panel, Obsidian's Viewport contract.
do
local Container = Classes.Container
function Container:AddViewport(idx, info)
info = Library:Validate(info, { Object = nil, Camera = nil, Clone = true, AutoFocus = true, Interactive = false, Height = 200, Visible = true })
local owner = self
local object = info.Object
if info.Clone and typeof(object) == 'Instance' then
local wasArchivable = object.Archivable
object.Archivable = true
object = object:Clone()
object.Archivable = wasArchivable
end
local viewport = {
Destroyed = false, Object = object, Camera = info.Camera or Instance.new('Camera'), Interactive = info.Interactive, AutoFocus = info.AutoFocus, Visible = info.Visible,
Type = 'Viewport', Library = Library,
}
assert(typeof(viewport.Object) == 'Instance' and (viewport.Object:IsA('BasePart') or viewport.Object:IsA('Model')), 'Instance must be a BasePart or Model.')
assert(typeof(viewport.Camera) == 'Instance' and viewport.Camera:IsA('Camera'), 'Camera must be a valid Camera instance.')
local holder = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, info.Height), LayoutOrder = self:NextOrder(), Visible = viewport.Visible, Parent = self.Container })
local _, box = Panel(holder, { 'a', 'b', 'd' }, { Size = dim2(1, 0, 1, 0) })
local frame = New('ViewportFrame', { BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), CurrentCamera = viewport.Camera, Active = viewport.Interactive, Parent = box })
viewport.Camera.Parent = frame
viewport.Object.Parent = frame
viewport.Holder, viewport.Frame = holder, frame
local function focus()
local object = viewport.Object
local size = object:IsA('BasePart') and object.Size or select(2, object:GetBoundingBox())
local extent = math.max(size.X, size.Y, size.Z)
local position = object:GetPivot().Position
viewport.Camera.CFrame = CFrame.new(position + Vector3.new(0, extent / 2, extent * 2), position)
end
if viewport.AutoFocus then focus() end
-- the interactive orbit (right mouse drag, wheel zoom) of the stock widget
local dragging, last = false, nil
Connect(frame.InputBegan, function(input)
if viewport.Interactive and input.UserInputType == Enum.UserInputType.MouseButton2 then dragging, last = true, input.Position end
end)
Library:OnInput('Ended', function(input)
if input.UserInputType == Enum.UserInputType.MouseButton2 then dragging = false end
end)
Library:OnInput('Changed', function(input)
if Library.Unloaded or not viewport.Interactive or not dragging or input.UserInputType ~= Enum.UserInputType.MouseMovement then return end
local delta = input.Position - last
last = input.Position
local position = viewport.Object:GetPivot().Position
local camera = viewport.Camera
camera.CFrame = CFrame.new(position) * CFrame.fromAxisAngle(Vector3.new(0, 1, 0), -delta.X * 0.01) * CFrame.new(-position) * camera.CFrame
local pitched = CFrame.new(position) * CFrame.fromAxisAngle(camera.CFrame.RightVector, -delta.Y * 0.01) * CFrame.new(-position) * camera.CFrame
if pitched.UpVector.Y > 0.1 then camera.CFrame = pitched end
end)
Connect(frame.InputChanged, function(input)
if viewport.Interactive and input.UserInputType == Enum.UserInputType.MouseWheel then
viewport.Camera.CFrame += viewport.Camera.CFrame.LookVector * (input.Position.Z * 2)
end
end)
function viewport:SetObject(newObject, clone)
assert(newObject, 'Object cannot be nil.')
if clone then newObject = newObject:Clone() end
if viewport.Object then viewport.Object:Destroy() end
viewport.Object = newObject
viewport.Object.Parent = frame
end
function viewport:SetHeight(height) holder.Size = dim2(1, 0, 0, height) end
function viewport:Focus() if viewport.Object then focus() end end
function viewport:SetCamera(camera)
viewport.Camera = camera
frame.CurrentCamera = camera
end
function viewport:SetInteractive(interactive)
viewport.Interactive = interactive
frame.Active = interactive
end
function viewport:SetVisible(visible)
viewport.Visible = visible
holder.Visible = visible
end
function viewport:Destroy()
viewport.Destroyed = true
holder:Destroy()
local at = table.find(owner.Elements, viewport)
if at then table.remove(owner.Elements, at) end
if idx ~= nil and Library.Options[idx] == viewport then Library.Options[idx] = nil end
end
table.insert(self.Elements, viewport)
viewport.Idx = idx
if idx ~= nil then Library.Options[idx] = viewport end
return viewport
end
end
-- ==== 43_search.lua ====
-- ZkxVader search: a box in the header; typing lists every matching control of every tab ("tab > box > name"); a click jumps there and flashes the row.
-- (The real rows are never hidden or moved, so searching can not disturb layouts or dependency boxes.)
do
local Window = Classes.Window
local function plain(text) return (tostring(text or ''):gsub('<[^>]->', '')) end
local function walk(container, visit)
for _, element in ipairs(container.Elements or {}) do
if element.Type == 'DependencyBox' then walk(element, visit) else visit(element) end
end
end
-- every searchable element of the menu: { tab, box, boxName, element }
local function collect()
local found = {}
for _, tab in ipairs(Library.Window and Library.Window.Tabs or {}) do
for name, box in pairs(tab.Groupboxes) do
walk(box, function(element) table.insert(found, { tab = tab, box = box, boxName = name, element = element }) end)
end
for _, tabbox in pairs(tab.Tabboxes) do
for _, sub in ipairs(tabbox.Tabs or {}) do
walk(sub, function(element)
table.insert(found, { tab = tab, box = sub, boxName = tostring(tabbox.Name or 'tabs') .. ' / ' .. tostring(sub.Name), element = element, sub = sub })
end)
end
end
end
return found
end
local SEARCHABLE = { Toggle = true, Slider = true, Dropdown = true, Input = true, Label = true, Button = true }
local function flash(element)
local holder = element.Holder
if not (holder and holder.Parent) then return end
local glow = New('Frame', { Size = dim2(1, 8, 1, 4), Position = dim2(0, -4, 0, -2), BackgroundTransparency = 0.7, ZIndex = holder.ZIndex + 5, Active = false, Parent = holder }, { BackgroundColor3 = 'accent' })
Tween(glow, { BackgroundTransparency = 1 }, 1.4, Enum.EasingStyle.Quad)
task.delay(1.5, function() glow:Destroy() end)
end
local function jump(entry)
entry.tab:Show(true)
local root = entry.box.Root or entry.box
if root.SetCollapsed then root:SetCollapsed(false) end
if entry.sub then entry.sub:Show() end
task.delay(0.12, function()
local holder = entry.element.Holder
local column = entry.box.Container and entry.box.Container:FindFirstAncestorWhichIsA('ScrollingFrame')
if holder and column then
local target = holder.AbsolutePosition.Y - column.AbsolutePosition.Y + column.CanvasPosition.Y - 24
column.CanvasPosition = Vector2.new(0, max(0, target))
end
flash(entry.element)
end)
end
function Window:BuildSearch()
local header = self.TitleHolder
local footerWidth = self.FooterLabel.Visible and (TextWidth(self.FooterLabel.Text, Library.TextSize) + 16) or 8
local outer, field = Panel(header, { 'a', 'd' }, { Size = UDim2.fromOffset(Library.IsMobile and 130 or 170, H(20)), AnchorPoint = vec2(1, 0.5), Position = UDim2.new(1, -footerWidth, 0.5, 0) })
outer.ZIndex = 5
local box = New('TextBox', {
Text = '', PlaceholderText = 'Search', BackgroundTransparency = 1, Size = dim2(1, -10, 1, 0), Position = dim2(0, 6, 0, 0), TextSize = Library.TextSize,
TextXAlignment = Enum.TextXAlignment.Left, ClearTextOnFocus = false, ZIndex = 6, Parent = field,
})
box.PlaceholderColor3 = Palette.dim
self.SearchBox = box
-- results page
local page = New('Frame', { Name = 'SearchResults', BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), Visible = false, ZIndex = 30, Parent = self.Pages })
New('Frame', { Size = dim2(1, 0, 1, 0), ZIndex = 30, Parent = page }, { BackgroundColor3 = 'e' })
local list = New('ScrollingFrame', {
BackgroundTransparency = 1, Size = dim2(1, -24, 1, -24), Position = dim2(0, 12, 0, 12), ScrollBarThickness = 2, AutomaticCanvasSize = Enum.AutomaticSize.Y,
CanvasSize = dim2(0, 0, 0, 0), ZIndex = 31, Active = true, Parent = page,
})
Library.Theme(list, 'ScrollBarImageColor3', 'accent')
New('UIListLayout', { Padding = dim(0, 2), SortOrder = Enum.SortOrder.LayoutOrder, Parent = list })
local heading = New('TextLabel', { Text = '', BackgroundTransparency = 1, Size = dim2(1, 0, 0, 16), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, LayoutOrder = 0, ZIndex = 32, Parent = list }, { TextColor3 = 'dim' })
local rows = {}
local token = 0
local function close()
page.Visible = false
if self.ActiveTab then self.ActiveTab.Page.Visible = true end
end
local function refresh()
token += 1
local mine = token
task.delay(0.12, function()
if mine ~= token or Library.Unloaded then return end
local needle = plain(box.Text):lower()
for _, row in ipairs(rows) do row:Destroy() end
table.clear(rows)
if needle == '' then
close()
return
end
local count = 0
for _, entry in ipairs(collect()) do
local element = entry.element
local text = plain(element.Text)
if SEARCHABLE[element.Type] and text ~= '' and text:lower():find(needle, 1, true) then
count += 1
if count <= 80 then
local row = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 20), LayoutOrder = count, ZIndex = 32, Parent = list })
local crumb = string.format('%s > %s', entry.tab.Name, tostring(entry.boxName))
New('TextLabel', { Text = text, BackgroundTransparency = 1, Size = dim2(0.5, 0, 1, 0), Position = dim2(0, 8, 0, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 33, Parent = row })
New('TextLabel', {
Text = crumb, BackgroundTransparency = 1, Size = dim2(0.5, -16, 1, 0), Position = dim2(0.5, 8, 0, 0), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Right, ZIndex = 33, Parent = row,
}, { TextColor3 = 'dim' })
Connect(row.MouseEnter, function()
row.BackgroundColor3 = Palette.accent
Tween(row, { BackgroundTransparency = 0.85 }, 0.08)
end)
Connect(row.MouseLeave, function() Tween(row, { BackgroundTransparency = 1 }, 0.1) end)
Connect(row.MouseButton1Click, function()
box.Text = ''
close()
jump(entry)
end)
table.insert(rows, row)
end
end
end
heading.Text = count == 0 and 'no matches' or (count > 80 and ('showing 80 of ' .. count .. ' matches') or (count .. ' match' .. (count == 1 and '' or 'es')))
if self.ActiveTab then self.ActiveTab.Page.Visible = false end
page.Visible = true
end)
end
Connect(box:GetPropertyChangedSignal('Text'), refresh)
Connect(box.FocusLost, function(entered)
if entered and box.Text == '' then close() end
end)
-- changing tab leaves the search
local previousHook = Library.OnTabChanged
Library.OnTabChanged = function(tab)
if page.Visible and not box:IsFocused() then
page.Visible = false
box.Text = ''
end
if previousHook then previousHook(tab) end
end
end
end
-- ==== 44_watermark.lua ====
-- ZkxVader watermark (Vaderhaxx's): "[ ZkxVader ] fps: 144 ping: 32ms" - the name in a flowing rainbow gradient, the details after it, draggable.
-- The details are a list of fields the user picks (Library:SetWatermarkFields): Username, Executor, FPS, Ping, Map, Server, Time. Values show in the accent colour.
-- Library:SetWatermarkProvider(id, getter, interval) swaps how one field is read (the hub gives Map and Server their Rivals lookups); a getter returns a string, or nil to leave the field out.
-- It stays on screen while enabled (also with the menu closed). Library:SetWatermarkVisible(false) hides it and stops its counter.
do
local Stats = svc('Stats')
local watermark = { Frame = nil, Name = nil, Info = nil, Visible = true, Selected = { FPS = true, Ping = true } }
local renderHandle
local liveFps, livePing = 0, 0
local function readPing()
local ok, value = pcall(function() return Stats.Network.ServerStatsItem['Data Ping']:GetValue() end)
if ok and type(value) == 'number' then return floor(value + 0.5) end
local okPing, ping = pcall(function() return LocalPlayer:GetNetworkPing() * 1000 end)
return okPing and floor(ping + 0.5) or 0
end
local executorName
local function readExecutor()
if executorName == nil then
executorName = 'unknown'
if type(identifyexecutor) == 'function' then
local ok, name = pcall(identifyexecutor)
if ok and type(name) == 'string' and name ~= '' then executorName = name end
end
end
return executorName
end
-- display order is the order of this list; Interval = seconds a value is kept before the getter runs again (none = every refresh)
local FIELD_ORDER = { 'Username', 'Executor', 'FPS', 'Ping', 'Map', 'Server', 'Time' }
local fields = {
Username = { Label = 'user', Get = function() return LocalPlayer.Name end },
Executor = { Label = 'exec', Get = readExecutor },
FPS = { Label = 'fps', Get = function() return tostring(liveFps) end },
Ping = { Label = 'ping', Get = function() return livePing .. 'ms' end },
Map = { Label = 'map', Get = function() return nil end, Interval = 2 },
Server = { Label = 'server', Get = function() return nil end, Interval = 5 },
Time = { Label = 'time', Get = function() return os.date('%H:%M:%S') end },
}
local function readField(field, now)
if field.Interval and field.ReadAt and now - field.ReadAt < field.Interval then return field.Value end
field.ReadAt = now
local ok, value = pcall(field.Get)
field.Value = (ok and type(value) == 'string' and value ~= '') and value or nil
return field.Value
end
local function escapeRich(text)
return (text:gsub('&', '&'):gsub('<', '<'):gsub('>', '>'))
end
local function renderInfo()
if not watermark.Info then return end
local now = os.clock()
local color = Palette.accent:ToHex()
local parts = {}
for _, id in ipairs(FIELD_ORDER) do
if watermark.Selected[id] then
local field = fields[id]
local value = readField(field, now)
if value then parts[#parts + 1] = ('%s: <font color="#%s">%s</font>'):format(field.Label, color, escapeRich(value)) end
end
end
watermark.Info.Text = #parts > 0 and (' ] ' .. table.concat(parts, ' ')) or ' ]'
end
-- notifications start below the watermark while it is up in the same corner
local function moveNotifications()
Library.WatermarkOffset = (watermark.Frame and watermark.Visible) and 30 or 0
if Library.LayoutNotifications then Library:LayoutNotifications() end
end
local function stopCounter()
if renderHandle then
renderHandle.Disconnect()
renderHandle = nil
end
end
local function startCounter()
if renderHandle or not watermark.Info then return end
local frames, elapsed = 0, 0
renderHandle = Library:OnInput('Render', function(dt)
frames += 1
elapsed += dt
if elapsed >= 0.5 then
liveFps, livePing = floor(frames / elapsed + 0.5), readPing()
frames, elapsed = 0, 0
renderInfo()
end
end)
end
function Library:AddWatermark(info)
info = info or {}
Library:EnsureGui()
if watermark.Frame and watermark.Frame.Parent then return watermark end
local outer = New('Frame', {
Name = 'Watermark', AutomaticSize = Enum.AutomaticSize.XY, Size = dim2(0, 0, 0, 0), Position = info.Position or dim2(0, 14, 0, 14), ZIndex = 45, Parent = Library.Main,
}, { BackgroundColor3 = 'c' })
New('UIPadding', { PaddingTop = dim(0, 1), PaddingBottom = dim(0, 1), PaddingLeft = dim(0, 1), PaddingRight = dim(0, 1), Parent = outer })
local inner = New('Frame', { AutomaticSize = Enum.AutomaticSize.XY, Size = dim2(0, 0, 0, 0), Parent = outer }, { BackgroundColor3 = 'e' })
New('UIPadding', { PaddingLeft = dim(0, 6), PaddingRight = dim(0, 6), PaddingTop = dim(0, 3), PaddingBottom = dim(0, 4), Parent = inner })
New('UIListLayout', { FillDirection = Enum.FillDirection.Horizontal, SortOrder = Enum.SortOrder.LayoutOrder, VerticalAlignment = Enum.VerticalAlignment.Center, Parent = inner })
local function piece(text, order)
return New('TextLabel', { Text = text, BackgroundTransparency = 1, AutomaticSize = Enum.AutomaticSize.X, Size = dim2(0, 0, 0, 14), TextSize = Library.TextSize, LayoutOrder = order, ZIndex = 46, Parent = inner })
end
piece('[ ', 1)
watermark.Name = piece(tostring(info.Name or 'ZkxVader'), 2)
watermark.Info = piece(' ]', 3)
watermark.Info.RichText = true
FX.Rainbow(watermark.Name, { always = true })
watermark.Frame = outer
Library:ApplyLayout('watermark', outer)
Library.MakeDraggable(outer, outer, nil, 'watermark')
watermark.Visible = info.Visible ~= false
outer.Visible = watermark.Visible
renderInfo()
if watermark.Visible then startCounter() end
moveNotifications()
return watermark
end
function Library:SetWatermarkVisible(visible)
watermark.Visible = visible ~= false
if watermark.Frame then watermark.Frame.Visible = watermark.Visible end
if watermark.Visible then startCounter() else stopCounter() end
moveNotifications()
end
-- the ids of the fields in display order (the hub builds its picker from this)
function Library:GetWatermarkFieldIds()
return table.clone(FIELD_ORDER)
end
-- list of ids ({ 'FPS', 'Ping' }) or a multi-dropdown value ({ FPS = true }); unknown ids are ignored, an empty list shows the name only
function Library:SetWatermarkFields(list)
local selected = {}
for key, value in pairs(type(list) == 'table' and list or {}) do
local id = type(key) == 'number' and value or (value == true and key or nil)
if type(id) == 'string' and fields[id] then selected[id] = true end
end
watermark.Selected = selected
renderInfo()
end
function Library:SetWatermarkProvider(id, getter, interval)
local field = fields[id]
if not field or type(getter) ~= 'function' then return false end
field.Get, field.Interval, field.ReadAt, field.Value = getter, interval or field.Interval, nil, nil
renderInfo()
return true
end
-- Obsidian's name for the text: here it is the part inside the brackets
function Library:SetWatermark(text)
if watermark.Name then watermark.Name.Text = tostring(text) end
end
Library.Watermark = watermark
end
-- ==== 45_layout.lua ====
-- ZkxVader layout memory: where the movable windows (main window, keybind panel, watermark, floating labels) were left, kept across unload / reload.
-- One small JSON file (<StorageDir>/layout.json), written a moment after a drag or resize ends and when the library unloads. Positions are stored as
-- UDim2 parts (scale + offset), so they survive a different screen size; a saved spot that would be off the screen is ignored.
do
local path = Library.StorageDir .. '/layout.json'
local data, dirty = nil, false
local saveToken = 0
local function canFile() return type(isfile) == 'function' and type(readfile) == 'function' and type(writefile) == 'function' end
local function load()
if data then return data end
data = {}
if canFile() then
local ok, decoded = pcall(function()
if isfile(path) then return HttpService:JSONDecode(readfile(path)) end
return nil
end)
if ok and type(decoded) == 'table' then data = decoded end
end
return data
end
local function flush()
if not dirty or not canFile() then return end
dirty = false
pcall(function()
if type(isfolder) == 'function' and type(makefolder) == 'function' and not isfolder(Library.StorageDir) then makefolder(Library.StorageDir) end
writefile(path, HttpService:JSONEncode(data))
end)
end
Library:OnUnload(flush)
local function number(value) return type(value) == 'number' and value == value and math.abs(value) < 1e6 end
local function udim2(parts)
if type(parts) == 'table' and number(parts[1]) and number(parts[2]) and number(parts[3]) and number(parts[4]) then
return UDim2.new(parts[1], parts[2], parts[3], parts[4])
end
return nil
end
-- is the saved spot still reachable on this screen? (a window left in the corner of a bigger monitor must not vanish on a smaller one)
local function reachable(frame, position)
local main = Library.Main
if not main or main.AbsoluteSize.X <= 0 then return true end
local scale = max(Library.GetScale(), 0.01)
local view = main.AbsoluteSize / scale
local size = frame.AbsoluteSize / scale
if size.X <= 0 then size = Vector2.new(120, 24) end
local left = position.X.Scale * view.X + position.X.Offset - frame.AnchorPoint.X * size.X
local top = position.Y.Scale * view.Y + position.Y.Offset - frame.AnchorPoint.Y * size.Y
return left + size.X > 60 and left < view.X - 60 and top + size.Y > 24 and top < view.Y - 40
end
function Library:ApplyLayout(id, frame, withSize)
local entry = id and frame and load()[id]
if type(entry) ~= 'table' then return false end
local position = udim2(entry.p)
if not position or not reachable(frame, position) then return false end
frame.Position = position
if withSize then
local size = udim2(entry.s)
local minimumWidth, minimumHeight = Library.IsMobile and 320 or 560, Library.IsMobile and 200 or 380
local screen = Library.Viewport()
if size and size.X.Offset >= minimumWidth and size.Y.Offset >= minimumHeight and size.X.Scale == 0 and size.Y.Scale == 0 then
frame.Size = UDim2.fromOffset(min(size.X.Offset, screen.X - 16), min(size.Y.Offset, screen.Y - 16))
end
end
return true
end
function Library:SaveLayout(id, frame, withSize)
if not id or not frame or not frame.Parent then return end
local position = frame.Position
local entry = { p = { position.X.Scale, position.X.Offset, position.Y.Scale, position.Y.Offset } }
if withSize then
local size = frame.Size
entry.s = { size.X.Scale, size.X.Offset, size.Y.Scale, size.Y.Offset }
end
load()[id] = entry
dirty = true
saveToken += 1
local mine = saveToken
task.delay(0.4, function()
if mine == saveToken then flush() end
end)
end
end
-- ==== 46_mobile.lua ====
-- ZkxVader mobile: an on-screen button that opens / closes the menu (a phone has no menu key). Draggable, remembered, only built on touch devices.
do
local Window = Classes.Window
function Window:BuildMobileButton()
if not Library.IsMobile then return end
local size = H(44)
local outer, inner = Panel(Library.Main, { 'a', 'b', 'e' }, { Size = UDim2.fromOffset(size, size), Position = dim2(0, 14, 0, 70) })
outer.Name = 'MobileButton'
outer.ZIndex = 300
Library:ApplyLayout('mobile', outer)
local icon
if self.Info and self.Info.Icon then
icon = New('ImageLabel', { BackgroundTransparency = 1, Image = tostring(self.Info.Icon), Size = dim2(1, -10, 1, -10), Position = dim2(0, 5, 0, 5), ZIndex = 301, Parent = inner })
else
New('TextLabel', { Text = 'Z', BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), TextSize = Library.TextSize + 6, ZIndex = 301, Parent = inner }, { TextColor3 = 'accent' })
end
local button = New('TextButton', { Text = '', AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), ZIndex = 302, Active = true, Parent = inner })
Library.MakeDraggable(outer, button, nil, 'mobile')
-- a tap opens or closes the menu; a drag only moves the button
local pressedAt
Connect(button.InputBegan, function(input)
if input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.MouseButton1 then pressedAt = outer.AbsolutePosition end
end)
Connect(button.MouseButton1Click, function()
if pressedAt and (outer.AbsolutePosition - pressedAt).Magnitude > 8 then return end
Library:Toggle()
end)
-- the button shows the state: accent outline while the menu is open
Library.OnToggled = (function(previous)
return function(open)
outer.BackgroundColor3 = open and Palette.accent or Palette.a
if previous then previous(open) end
end
end)(Library.OnToggled)
self.MobileButton = outer
end
end
-- ==== 47_list.lua ====
-- ZkxVader list: a scrolling single-select list. The chosen row is outlined in green (outline only). Same contract as a dropdown:
-- Value (the chosen row text), SetValues, SetValue, OnChanged, SetText, SetVisible, SetDisabled. Row text may use rich text.
-- Serverhop uses it; the Obsidian build has no such widget and the hub falls back to a dropdown there.
do
local Container = Classes.Container
local function noop() end
local SELECT_COLOR = rgb(60, 220, 110)
function Container:AddList(idx, info)
info = type(info) == 'table' and info or {}
local owner = self
local list = {
Type = 'List', Text = type(info.Text) == 'string' and info.Text or nil, Values = {}, Value = nil, Tooltip = info.Tooltip,
Callback = info.Callback or noop, Changed = info.Changed or noop, Disabled = info.Disabled == true, Visible = info.Visible ~= false, Library = Library,
}
local height = tonumber(info.Height) or 160
local hasLabel = list.Text ~= nil
local row = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, height + (hasLabel and 18 or 2)), LayoutOrder = self:NextOrder(), Visible = list.Visible, Parent = self.Container })
local title = New('TextLabel', { Text = list.Text or '', BackgroundTransparency = 1, Size = dim2(1, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, RichText = true, Visible = hasLabel, Parent = row })
local frame = New('Frame', { BackgroundTransparency = 1, AnchorPoint = vec2(0, 1), Position = dim2(0, 0, 1, 0), Size = dim2(1, 0, 0, height), Parent = row })
local _, field = Panel(frame, { 'd', 'b' }, { Size = dim2(1, 0, 1, 0), Clip = true })
local scroller = New('ScrollingFrame', {
BackgroundTransparency = 1, ScrollBarThickness = 3, AutomaticCanvasSize = Enum.AutomaticSize.Y, CanvasSize = dim2(0, 0, 0, 0), Size = dim2(1, 0, 1, 0), Active = true, Parent = field,
})
Library.Theme(scroller, 'ScrollBarImageColor3', 'accent')
New('UIListLayout', { SortOrder = Enum.SortOrder.LayoutOrder, Padding = dim(0, 2), Parent = scroller })
New('UIPadding', { PaddingLeft = dim(0, 3), PaddingRight = dim(0, 5), PaddingTop = dim(0, 3), PaddingBottom = dim(0, 3), Parent = scroller })
local empty = New('TextLabel', {
Text = type(info.Empty) == 'string' and info.Empty or 'Nothing to show', BackgroundTransparency = 1, Size = dim2(1, 0, 0, 24), TextSize = Library.TextSize, ZIndex = 2, Parent = field,
}, { TextColor3 = 'dim' })
list.Holder, list.Container = row, self.Container
local buttons = {}
local function paint()
for _, entry in ipairs(buttons) do
local chosen = entry.Value == list.Value
entry.Stroke.Enabled = chosen
entry.Button.BackgroundTransparency = chosen and 0.86 or 1
entry.Button.BackgroundColor3 = chosen and SELECT_COLOR or Palette.g
end
end
local function build()
for _, entry in ipairs(buttons) do entry.Button:Destroy() end
table.clear(buttons)
for index, value in ipairs(list.Values) do
local button = New('TextButton', {
Text = tostring(value), AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(20)), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left,
RichText = true, LayoutOrder = index, Parent = scroller,
})
New('UIPadding', { PaddingLeft = dim(0, 6), Parent = button })
local stroke = New('UIStroke', { Thickness = 1, Color = SELECT_COLOR, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, Enabled = false, Parent = button })
local entry = { Value = value, Button = button, Stroke = stroke }
table.insert(buttons, entry)
Connect(button.MouseEnter, function()
if entry.Value ~= list.Value then Tween(button, { BackgroundTransparency = 0.9 }, 0.08) end
end)
Connect(button.MouseLeave, function()
if entry.Value ~= list.Value then Tween(button, { BackgroundTransparency = 1 }, 0.1) end
end)
Connect(button.MouseButton1Click, function()
if list.Disabled then return end
list:SetValue(entry.Value)
end)
end
empty.Visible = #list.Values == 0
paint()
end
function list:RunChanged()
Library:SafeCallback(list.Callback, list.Value)
Library:SafeCallback(list.Changed, list.Value)
end
function list:OnChanged(callback) list.Changed = callback end
function list:SetValue(value)
if value ~= nil and not table.find(list.Values, value) then return end
if list.Value == value then return end
list.Value = value
paint()
list:RunChanged()
end
function list:SetValues(values)
list.Values = type(values) == 'table' and values or {}
-- the pick survives a refresh when its row is still there
if list.Value ~= nil and not table.find(list.Values, list.Value) then
list.Value = nil
build()
list:RunChanged()
return
end
build()
end
function list:GetValue() return list.Value end
function list:SetText(text)
list.Text = text
title.Text = text or ''
title.Visible = text ~= nil
row.Size = dim2(1, 0, 0, height + (text and 18 or 2))
end
function list:SetVisible(visible)
list.Visible = visible
row.Visible = visible
end
function list:SetDisabled(disabled)
list.Disabled = disabled
scroller.Active = not disabled
title.TextTransparency = disabled and 0.5 or 0
end
function list:Destroy()
list.Destroyed = true
row:Destroy()
if Library.Options[idx] == list then Library.Options[idx] = nil end
local at = table.find(owner.Elements or {}, list)
if at then table.remove(owner.Elements, at) end
end
if type(info.Values) == 'table' then list.Values = info.Values end
build()
if info.Default ~= nil and table.find(list.Values, info.Default) then list.Value = info.Default; paint() end
if list.Disabled then list:SetDisabled(true) end
table.insert(self.Elements, list)
if idx ~= nil then Library.Options[idx] = list end
return list
end
end
-- ==== 50_misc.lua ====
-- ZkxVader misc: notifications, dialog, draggable label.
do
local Window, Dialog, Container = Classes.Window, Classes.Dialog, Classes.Container
--// notifications \\--
local holder
local counter = 0
-- where the stack lives (Trello #32) and how a notification looks (Trello #33); both are remembered by UI Settings
local PLACEMENTS = {
['Bottom Left'] = { Left = true, Vertical = Enum.VerticalAlignment.Bottom },
['Bottom Right'] = { Left = false, Vertical = Enum.VerticalAlignment.Bottom },
['Top Left'] = { Left = true, Vertical = Enum.VerticalAlignment.Top },
['Top Right'] = { Left = false, Vertical = Enum.VerticalAlignment.Top },
['Middle Left'] = { Left = true, Vertical = Enum.VerticalAlignment.Center },
['Middle Right'] = { Left = false, Vertical = Enum.VerticalAlignment.Center },
}
Library.NotifyPlacementNames = { 'Bottom Left', 'Bottom Right', 'Top Left', 'Top Right', 'Middle Left', 'Middle Right' }
Library.NotifyStyleNames = { 'Premium', 'Classic' }
Library.NotifyPlacement = PLACEMENTS[Library.NotifyPlacement] and Library.NotifyPlacement or 'Top Right'
Library.NotifyStyle = Library.NotifyStyle == 'Classic' and 'Classic' or 'Premium'
local function placement() return PLACEMENTS[Library.NotifyPlacement] or PLACEMENTS['Top Right'] end
-- the stack sits below the watermark while that is up in the top-left corner
function Library:LayoutNotifications()
local frame = Library.NotifyHolder
if not (frame and frame.Parent) then return end
local info = placement()
local top = Library.NotifyPlacement == 'Top Left' and (Library.WatermarkOffset or 0) or 0
frame.AnchorPoint = vec2(info.Left and 0 or 1, 0)
frame.Position = dim2(info.Left and 0 or 1, info.Left and 14 or -14, 0, 14 + top)
frame.Size = dim2(0, min(300, Library.Viewport().X - 28), 1, -(28 + top))
local layout = frame:FindFirstChildOfClass('UIListLayout')
if layout then
layout.HorizontalAlignment = info.Left and Enum.HorizontalAlignment.Left or Enum.HorizontalAlignment.Right
layout.VerticalAlignment = info.Vertical
end
end
function Library:SetNotifyPlacement(name)
if PLACEMENTS[name] == nil then return false end
Library.NotifyPlacement = name
Library:LayoutNotifications()
return true
end
function Library:SetNotifyStyle(name)
if name ~= 'Premium' and name ~= 'Classic' then return false end
Library.NotifyStyle = name
return true
end
local function ensureHolder()
Library:EnsureGui()
if holder and holder.Parent then return holder end
holder = New('Frame', { Name = 'Notifications', BackgroundTransparency = 1, Size = dim2(0, 300, 1, -28), ZIndex = 200, Parent = Library.Main })
New('UIListLayout', { Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = holder })
Library.NotifyHolder = holder
Library:LayoutNotifications()
return holder
end
-- numbers read in the accent colour, like the coloured values of a hit log (only plain text; markup is left alone)
local function highlight(text)
if #text > 160 or text:find('<', 1, true) then return text end
local hex = Palette.accent:ToHex()
return (text:gsub('%f[%w]%d[%d%.,]*%%?', function(number) return ('<font color="#%s">%s</font>'):format(hex, number) end))
end
-- the old look: three nested outlines, scrambled title, dim description, countdown bar
local function buildClassic(data, wrapper)
local panel, inner = Panel(wrapper, { 'a', 'b', 'e' }, { Size = dim2(1, 0, 0, 0), Auto = true })
New('UIListLayout', { Padding = dim(0, 3), SortOrder = Enum.SortOrder.LayoutOrder, Parent = inner })
New('UIPadding', { PaddingTop = dim(0, 6), PaddingBottom = dim(0, 8), PaddingLeft = dim(0, 8), PaddingRight = dim(0, 8), Parent = inner })
local parts = { Panel = panel }
if data.Title then
parts.Title = New('TextLabel', {
Text = data.Title, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 14), TextSize = Library.TextSize, TextXAlignment = Enum.TextXAlignment.Left, LayoutOrder = 1, RichText = true, ZIndex = 201, Parent = inner,
}, { TextColor3 = 'accent' })
end
parts.Body = New('TextLabel', {
Text = data.Description, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, TextSize = Library.TextSize, TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = 2, RichText = true, ZIndex = 201, Parent = inner,
}, { TextColor3 = data.Title and 'dim' or 'text' })
parts.BarHolder = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 2), LayoutOrder = 3, Parent = inner })
parts.Bar = New('Frame', { Size = dim2(1, 0, 1, 0), Parent = parts.BarHolder }, { BackgroundColor3 = 'accent' })
parts.Hidden = function(left) return { Position = dim2(left and -1.15 or 1.15, 0, 0, 0) } end
parts.Shown = { Position = dim2(0, 0, 0, 0) }
parts.Scramble = true
return parts
end
-- premium: one translucent card, a 2 px accent edge on the screen side, accent header over a hairline, white text with
-- coloured numbers, a 1 px countdown line, fades and slides in as one piece (CanvasGroup)
local function buildPremium(data, wrapper, left)
local card = New('CanvasGroup', {
BackgroundTransparency = 1, BorderSizePixel = 0, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, GroupTransparency = 0, ZIndex = 201, Parent = wrapper,
})
local _, face = Panel(card, { 'b', 'd' }, { Size = dim2(1, 0, 0, 0), Auto = true, InnerTransparency = 0.08 })
local stripe = New('Frame', { Size = dim2(0, 2, 1, 0), Position = dim2(left and 0 or 1, left and 0 or -2, 0, 0), ZIndex = 3, Parent = face }, { BackgroundColor3 = 'accent' })
local inner = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, ZIndex = 2, Parent = face })
New('UIListLayout', { Padding = dim(0, 4), SortOrder = Enum.SortOrder.LayoutOrder, Parent = inner })
New('UIPadding', {
PaddingTop = dim(0, 6), PaddingBottom = dim(0, 7), PaddingLeft = dim(0, left and 11 or 9), PaddingRight = dim(0, left and 9 or 11), Parent = inner,
})
local parts = { Panel = card, Stripe = stripe }
if data.Title then
parts.Title = New('TextLabel', {
Text = data.Title, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 13), TextSize = Library.TextSize + 1, TextXAlignment = Enum.TextXAlignment.Left, LayoutOrder = 1, RichText = true, ZIndex = 4, Parent = inner,
}, { TextColor3 = 'accent' })
New('Frame', { Size = dim2(1, 0, 0, 1), LayoutOrder = 2, BackgroundTransparency = 0.35, ZIndex = 3, Parent = inner }, { BackgroundColor3 = 'b' })
end
parts.Body = New('TextLabel', {
Text = highlight(data.Description), BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, TextSize = Library.TextSize, TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = 3, RichText = true, ZIndex = 4, Parent = inner,
}, { TextColor3 = 'text' })
parts.BarHolder = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 1), LayoutOrder = 4, ZIndex = 3, Parent = inner })
parts.Bar = New('Frame', { Size = dim2(1, 0, 1, 0), BackgroundTransparency = 0.2, ZIndex = 4, Parent = parts.BarHolder }, { BackgroundColor3 = 'accent' })
parts.Hidden = function(isLeft) return { Position = dim2(0, isLeft and -26 or 26, 0, 0), GroupTransparency = 1 } end
parts.Shown = { Position = dim2(0, 0, 0, 0), GroupTransparency = 0 }
parts.Highlight = true
return parts
end
function Library:Notify(...)
if Library.Unloaded then return nil end
local data = {}
local first = select(1, ...)
if type(first) == 'table' then
data.Title = first.Title ~= nil and tostring(first.Title) or nil
data.Description = first.Description ~= nil and tostring(first.Description) or ''
data.Time = first.Time or 5
data.Persist = first.Persist
data.TitleColor, data.DescriptionColor = first.TitleColor, first.DescriptionColor
else
data.Description = tostring(first)
data.Time = select(2, ...) or 5
end
local left = placement().Left
local parent = ensureHolder()
counter += 1
local wrapper = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, LayoutOrder = counter, Parent = parent })
local parts = Library.NotifyStyle == 'Classic' and buildClassic(data, wrapper) or buildPremium(data, wrapper, left)
local panel, titleLabel, body, barHolder, bar = parts.Panel, parts.Title, parts.Body, parts.BarHolder, parts.Bar
if data.TitleColor and titleLabel then titleLabel.TextColor3 = data.TitleColor end
if data.DescriptionColor then body.TextColor3 = data.DescriptionColor end
local notification = { Destroyed = false }
local dismissed = false
function notification:Destroy()
if dismissed then return end
dismissed = true
notification.Destroyed = true
if not wrapper.Parent then return end
local height = wrapper.AbsoluteSize.Y / max(Library.GetScale(), 0.01)
if Animating() then
wrapper.AutomaticSize = Enum.AutomaticSize.None
wrapper.Size = dim2(1, 0, 0, height)
Tween(panel, parts.Hidden(placement().Left), 0.28, Enum.EasingStyle.Quint, Enum.EasingDirection.In)
task.delay(0.22, function()
if wrapper.Parent then Tween(wrapper, { Size = dim2(1, 0, 0, 0) }, 0.18, Enum.EasingStyle.Quint) end
end)
task.delay(0.45, function() wrapper:Destroy() end)
else
wrapper:Destroy()
end
end
function notification:ChangeTitle(text)
if titleLabel then titleLabel.Text = tostring(text) end
end
function notification:ChangeDescription(text)
text = tostring(text)
body.Text = parts.Highlight and highlight(text) or text
end
-- slide in, count down
if Animating() then
for property, value in pairs(parts.Hidden(left)) do panel[property] = value end
Tween(panel, parts.Shown, parts.Scramble and 0.45 or 0.32, Enum.EasingStyle.Quint)
if parts.Scramble then
if titleLabel then
FX.Scramble(titleLabel, data.Title, 0.35)
FX.Glitch(titleLabel, 0.2)
elseif #data.Description <= 70 and not data.Description:find('<') then
FX.Scramble(body, data.Description, 0.3)
end
end
else
for property, value in pairs(parts.Shown) do panel[property] = value end
end
local duration = tonumber(data.Time)
if data.Persist or not duration then
barHolder.Visible = false
if typeof(data.Time) == 'Instance' then
data.Time.Destroying:Once(function() notification:Destroy() end)
end
else
Tween(bar, { Size = dim2(0, 0, 1, 0) }, duration, Enum.EasingStyle.Linear)
task.delay(duration, function() notification:Destroy() end)
end
return notification
end
--// dialog \\--
local VARIANTS = { Primary = 'accent', Destructive = 'risky', Ghost = 'text' }
function Window:AddDialog(idx, info)
info = info or {}
local settings = {
Title = info.Title or 'Dialog', Description = info.Description or '', AutoDismiss = info.AutoDismiss ~= false, OutsideClickDismiss = info.OutsideClickDismiss ~= false,
FooterButtons = info.FooterButtons or {},
}
Library:EnsureGui()
Library.Dialogs = Library.Dialogs or {}
local existing = Library.Dialogs[idx]
if existing then existing:Dismiss(true) end
local layer = New('TextButton', {
Text = '', AutoButtonColor = false, Size = dim2(1, 0, 1, 0), BackgroundColor3 = rgb(0, 0, 0), BackgroundTransparency = 1, ZIndex = 150, Active = true, Parent = Library.Main,
})
Tween(layer, { BackgroundTransparency = 0.45 }, 0.2)
local panel, inner = Panel(layer, { 'a', 'b', 'c', 'e' }, { Size = dim2(0, min(440, Library.Viewport().X - 24), 0, 0), Auto = true, AnchorPoint = vec2(0.5, 0.5), Position = dim2(0.5, 0, 0.5, 0) })
panel.ZIndex = 151
panel.AutomaticSize = Enum.AutomaticSize.Y
New('UIListLayout', { Padding = dim(0, 8), SortOrder = Enum.SortOrder.LayoutOrder, Parent = inner })
New('UIPadding', { PaddingTop = dim(0, 12), PaddingBottom = dim(0, 12), PaddingLeft = dim(0, 14), PaddingRight = dim(0, 14), Parent = inner })
local title = New('TextLabel', { Text = settings.Title, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 16), TextSize = 14, TextXAlignment = Enum.TextXAlignment.Left, LayoutOrder = 1, ZIndex = 152, Parent = inner }, { TextColor3 = 'accent' })
New('TextLabel', {
Text = settings.Description, BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, TextSize = Library.TextSize, TextWrapped = true,
TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, LayoutOrder = 2, ZIndex = 152, Visible = settings.Description ~= '', Parent = inner,
}, { TextColor3 = 'dim' })
local content = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, 0), AutomaticSize = Enum.AutomaticSize.Y, LayoutOrder = 3, ZIndex = 152, Parent = inner })
New('UIListLayout', { Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = content })
local footer = New('Frame', { BackgroundTransparency = 1, Size = dim2(1, 0, 0, H(22)), LayoutOrder = 4, ZIndex = 152, Parent = inner })
New('UIListLayout', { FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Right, Padding = dim(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = footer })
local dialog = setmetatable({ Library = Library, Container = content, Elements = {}, Type = 'Dialog', Index = idx, Dismissed = false, Panel = panel, Layer = layer }, Dialog)
function dialog:Dismiss(instant)
if dialog.Dismissed then return end
dialog.Dismissed = true
Library:ClosePopup()
if Library.Dialogs[idx] == dialog then Library.Dialogs[idx] = nil end
for index = #dialog.Elements, 1, -1 do
local element = dialog.Elements[index]
if element and element.Destroy then pcall(element.Destroy, element) end
end
if instant ~= true and Animating() then
Tween(layer, { BackgroundTransparency = 1 }, 0.15)
task.delay(0.16, function() layer:Destroy() end)
else
layer:Destroy()
end
end
local ordered = {}
for name, button in pairs(settings.FooterButtons) do table.insert(ordered, { Name = name, Info = button }) end
table.sort(ordered, function(a, b) return (a.Info.Order or 0) < (b.Info.Order or 0) end)
for index, entry in ipairs(ordered) do
local variantKey = VARIANTS[entry.Info.Variant or 'Ghost'] or 'text'
local text = tostring(entry.Info.Title or entry.Name)
local outer, face = Panel(footer, { variantKey == 'text' and 'a' or 'a', 'c' }, { Size = UDim2.fromOffset(TextWidth(text, Library.TextSize) + 22, H(22)) })
outer.LayoutOrder, outer.ZIndex = index, 153
local button = New('TextButton', { Text = text, AutoButtonColor = false, BackgroundTransparency = 1, Size = dim2(1, 0, 1, 0), TextSize = Library.TextSize, ZIndex = 154, Parent = face })
Library.Registry[button] = { TextColor3 = variantKey }
button.TextColor3 = Palette[variantKey]
Connect(button.MouseEnter, function() Tween(face, { BackgroundColor3 = Palette.g }, 0.1) end)
Connect(button.MouseLeave, function() Tween(face, { BackgroundColor3 = Palette.c }, 0.15) end)
Connect(button.MouseButton1Click, function()
Library:SafeCallback(entry.Info.Callback, dialog)
if settings.AutoDismiss and not dialog.Dismissed then dialog:Dismiss() end
end)
end
Connect(layer.MouseButton1Click, function()
if settings.OutsideClickDismiss and not IsMouseOver(panel) then dialog:Dismiss() end
end)
if Animating() then
panel.Position = dim2(0.5, 0, 0.5, 14)
Tween(panel, { Position = dim2(0.5, 0, 0.5, 0) }, 0.28, Enum.EasingStyle.Quint)
FX.Scramble(title, settings.Title, 0.3)
end
Library.Dialogs[idx] = dialog
return dialog
end
--// draggable label (a small movable text panel) \\--
function Library:AddDraggableLabel(text)
Library:EnsureGui()
-- the hub restyles `.Label` (TextSize, AnchorPoint, Position) and expects it to float on the screen by itself, as Obsidian's did
local label = New('TextLabel', {
Name = 'DraggableLabel', Text = tostring(text or ''), BackgroundTransparency = 0.2, AutomaticSize = Enum.AutomaticSize.XY, Size = dim2(0, 0, 0, 0),
AnchorPoint = vec2(0.5, 0), Position = dim2(0.5, 0, 0, 90), TextSize = Library.TextSize + 2, RichText = true, ZIndex = 45, Active = true, Parent = Library.Main,
}, { BackgroundColor3 = 'e' })
New('UIPadding', { PaddingLeft = dim(0, 12), PaddingRight = dim(0, 12), PaddingTop = dim(0, 6), PaddingBottom = dim(0, 7), Parent = label })
New('UIStroke', { Thickness = 1, ApplyStrokeMode = Enum.ApplyStrokeMode.Border, Parent = label }, { Color = 'c' })
local layoutId = 'label:' .. (tostring(text or ''):match('%a+') or 'label'):lower()
Library.MakeDraggable(label, label, nil, layoutId)
task.defer(function()
if label.Parent then Library:ApplyLayout(layoutId, label) end
end)
local object = { Frame = label, Label = label, Type = 'DraggableLabel', Visible = true }
function object:SetText(value) label.Text = tostring(value) end
function object:SetVisible(visible)
object.Visible = visible
label.Visible = visible
end
function object:Destroy() label:Destroy() end
return object
end
end
-- ==== 99_end.lua ====
-- like Obsidian, publish the instance (the hub's unload path and external tooling look for getgenv().Library)
pcall(function()
local env = (type(getgenv) == 'function' and getgenv()) or _G
env.Library = Library
end)
-- done: the Library table is the module
return Library
end
-- ======================================================================
-- UI library: config save / load (SaveManager)
-- ======================================================================
__ZkhHub_shared.obsidian_save_manager = function()
local cloneref = (cloneref or clonereference or function(instance) return instance end)
local clonefunction = (clonefunction or copyfunction or function(func) return func end)
local HttpService = cloneref(game:GetService('HttpService'))
local isfolder, isfile, listfiles = isfolder, isfile, listfiles
if typeof(clonefunction) == 'function' then
local isfolder_copy, isfile_copy, listfiles_copy = clonefunction(isfolder), clonefunction(isfile), clonefunction(listfiles)
local isfolder_success, isfolder_error = pcall(
function() return isfolder_copy('test' .. tostring(math.random(1000000, 9999999))) end
)
if isfolder_success == false or typeof(isfolder_error) ~= 'boolean' then
isfolder = function(folder)
local success, data = pcall(isfolder_copy, folder)
return (if success then data else false)
end
isfile = function(file)
local success, data = pcall(isfile_copy, file)
return (if success then data else false)
end
listfiles = function(folder)
local success, data = pcall(listfiles_copy, folder)
return (if success then data else {})
end
end
end
local SaveManager = {
Library = nil,
Folder = 'ObsidianLibSettings',
SubFolder = '',
Ignore = {},
LoadingOrder = {},
UseLoadingOrder = false,
AutoloadConfig = nil,
}
function SaveManager:SetLibrary(Library) SaveManager.Library = Library end
--// Element Parser \\--
local SpecialValueParser = {
UDim2 = {
Encode = function(Value)
return {
X = { Scale = Value.X.Scale, Offset = Value.X.Offset },
Y = { Scale = Value.Y.Scale, Offset = Value.Y.Offset },
}
end,
Decode = function(Data)
local DataType = typeof(Data)
if DataType == 'table' then
return UDim2.new(Data.X.Scale, Data.X.Offset, Data.Y.Scale, Data.Y.Offset)
elseif DataType == 'UDim2' then
return Data
end
return nil
end,
},
}
local ElementParser = {}
do
local function CreateParser(ElementType, LibaryIndex, Save, Load, CustomElementFetcher)
ElementParser[ElementType] = {
Save = function(Index, Element, ...)
local Data = Save(Index, Element, ...)
Data.type = ElementType
Data.idx = Index
return Data
end,
Load = function(Index, Data)
if CustomElementFetcher == true then return Load(nil, Data) end
local Elements = SaveManager.Library and SaveManager.Library[LibaryIndex]
local Element = Elements and Elements[Index]
return Load(Element, Data)
end,
}
end
CreateParser('Toggle', 'Toggles', function(Index, Toggle) return { value = Toggle.Value } end, function(Element, Data)
if not Element then return end
if Element.Value == Data.value then
Element:RunChanged()
return
end
Element:SetValue(Data.value)
end)
CreateParser('Slider', 'Options', function(Index, Slider) return { value = tostring(Slider.Value) } end, function(Element, Data)
if not Element then return end
if Element.Value == Data.value then
Element:RunChanged()
return
end
Element:SetValue(Data.value)
end)
CreateParser(
'Dropdown',
'Options',
function(Index, Dropdown) return { value = Dropdown.Value, multi = Dropdown.Multi } end,
function(Element, Data)
if not Element then return end
if Element.Value == Data.value then
Element:RunChanged()
return
end
Element:SetValue(Data.value)
end
)
CreateParser(
'ColorPicker',
'Options',
function(Index, ColorPicker) return { value = ColorPicker.Value:ToHex(), transparency = ColorPicker.Transparency } end,
function(Element, Data)
if not Element then return end
Element:SetValueRGB(Color3.fromHex(Data.value), Data.transparency)
end
)
CreateParser(
'KeyPicker',
'Options',
function(Index, KeyPicker)
return { mode = KeyPicker.Mode, key = KeyPicker.Value, modifiers = KeyPicker.Modifiers, toggled = KeyPicker.Toggled }
end,
function(Element, Data)
if not Element then return end
Element:SetValue({ Data.key, Data.mode, Data.modifiers })
if Data.mode == 'Toggle' and Data.toggled ~= nil then
Element.Toggled = Data.toggled
Element:Update()
end
end
)
CreateParser('Input', 'Options', function(Index, Input) return { text = Input.Value } end, function(Element, Data)
if not Element then return end
if typeof(Data.text) ~= 'string' then return end
if Element.Value == Data.text then
Element:RunChanged()
return
end
Element:SetValue(Data.text)
end)
CreateParser(
'Groupbox',
'Tabs',
function(Index, Groupbox, TabIndex) return { collapsed = Groupbox.Collapsed, tabIdx = TabIndex } end,
function(_, Data)
local TabIndex, Index = Data.tabIdx, Data.idx
if typeof(TabIndex) ~= 'string' or typeof(Index) ~= 'string' then return end
local Tabs = SaveManager.Library and SaveManager.Library.Tabs
local Tab = Tabs and Tabs[TabIndex]
if not Tab then return end
local Groupbox = Tab.Groupboxes[Index]
if not Groupbox or Groupbox.Collapsed == Data.collapsed then return end
Groupbox:SetCollapsed(Data.collapsed == true)
end,
true
)
end
--// Helpers \\--
local function Trim(Text) return Text:match('^%s*(.-)%s*$') end
local function IsStringEmpty(String) return if typeof(String) == 'string' then Trim(String) == '' else true end
local function IsValidFolderPath(Name)
return typeof(Name) == 'string' and (Trim(Name) ~= '' and not Name:match('^%s*$') and not Name:find('[<>:"|%?%*%z]'))
end
--// Folder helper \\--
local function SplitPath(Path)
local Result = {}
local Current = ''
for Part in string.gmatch(Path, '[^/]+') do
Current = if Current == '' then Part else (Current .. '/' .. Part)
table.insert(Result, Current)
end
return Result
Loads code from the internet. It downloads more code when it runs (loadstring + HttpGet), which can change after it was posted. From: github.com.
full source zkxhub for rivals
Stay safe
Never paste your .ROBLOSECURITY cookie anywhere, and use an alt account with scripts.

Universal