Files
mmkrp_2026/addons/_libnyx/lua/libnyx/lib/libnyx_components.lua
2026-03-15 14:54:49 +03:00

3206 lines
125 KiB
Lua

-- libNyx by MaryBlackfild
-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw
if SERVER then
AddCSLuaFile()
return
end
local libNyx = libNyx or {}
libNyx.UI = libNyx.UI or {}
_G.libNyx = libNyx
local RNDX = include("libnyx/lib/rndx.lua")
local BASE_W, BASE_H = 1920, 1080
local cvarScale = CreateClientConVar("cl_libnyx_ui_scale", "0", true, false, "UI scale multiplier (0 = auto).", 0, 2)
local cvarScaleMode = CreateClientConVar("cl_libnyx_ui_scalemode", "min", true, false, "Auto scale mode: min | h | w | avg")
local _scale, _sx, _sy = 1.0, 1.0, 1.0
local function ComputeScale()
_sx = math.max(0.001, ScrW() / BASE_W)
_sy = math.max(0.001, ScrH() / BASE_H)
local manual = tonumber(cvarScale:GetString()) or 0
if manual > 0 then
_scale = math.Clamp(manual, 0.50, 2.00)
return
end
local mode = string.lower(tostring(cvarScaleMode:GetString() or "min"))
local s
if mode == "h" then s = _sy
elseif mode == "w" then s = _sx
elseif mode == "avg" then s = (_sx + _sy) * 0.5
else s = math.min(_sx, _sy)
end
_scale = math.Clamp(s, 0.75, 1.75)
end
local function RecomputeStyle()
local S = libNyx.UI.Style
_scale = _scale or 1
S.radius = math.floor(12 * _scale)
S.padding = math.floor(14 * _scale)
S.iconSize = math.floor(24 * _scale)
S.btnHeight = math.floor(44 * _scale)
S.rowHeight = math.floor(82 * _scale)
S.shadowSpread = math.floor(22 * _scale)
S.shadowIntensity = math.floor(36 * _scale)
S.strokeWidth = math.max(1, math.floor(1 * _scale))
S.headerIndentX = math.floor(24 * _scale)
S.headerIndentY = math.floor(24 * _scale)
S.comboItemH = math.floor(34 * _scale)
end
function libNyx.UI.Scale(n, axis)
local v = tonumber(n) or 0
if axis == "w" then return math.floor(v * _sx) end
if axis == "h" then return math.floor(v * _sy) end
if axis == "min" then return math.floor(v * math.min(_sx, _sy)) end
if axis == "max" then return math.floor(v * math.max(_sx, _sy)) end
return math.floor(v * _scale)
end
function libNyx.UI.ScaleW(n) return libNyx.UI.Scale(n, "w") end
function libNyx.UI.ScaleH(n) return libNyx.UI.Scale(n, "h") end
function libNyx.UI.ScalePair(w, h) return libNyx.UI.ScaleW(w), libNyx.UI.ScaleH(h) end
local function _refresh()
ComputeScale()
RecomputeStyle()
end
libNyx.UI.RecomputeScale = _refresh
hook.Add("OnScreenSizeChanged", "libNyx.UI.Scale.refresh", _refresh)
cvars.AddChangeCallback("cl_libnyx_ui_scale", function() timer.Simple(0, _refresh) end, "libNyx.UI.Scale.cb1")
cvars.AddChangeCallback("cl_libnyx_ui_scalemode", function() timer.Simple(0, _refresh) end, "libNyx.UI.Scale.cb2")
local c_mode = CreateClientConVar("cl_gsims_glass_mode", "1", true, false, "", 0, 2)
local c_budget = CreateClientConVar("cl_gsims_glass_budget", "6", true, false, "", 0, 128)
local c_minarea = CreateClientConVar("cl_gsims_glass_minarea", "16384", true, false, "", 0, 524288)
local c_str = CreateClientConVar("cl_gsims_glass_strength", "3", true, false, "", 1, 8)
local function nowMode() return math.floor(c_mode:GetFloat()) end
local function nowBudget() return math.floor(c_budget:GetFloat()) end
local function nowMinArea() return math.floor(c_minarea:GetFloat()) end
local function nowStrength() return math.Clamp(math.floor(c_str:GetFloat()), 1, 8) end
local function col(a,b,c,d) return Color(a or 255,b or 255,c or 255,d or 255) end
local function getRNDX()
return rawget(_G,"rndx") or rawget(_G,"RNDX") or rawget(_G,"Rdx") or rawget(_G,"RDX")
end
local function callRNDXBlur(x,y,w,h,str)
local R = getRNDX()
if not R then return false end
if isfunction(R.DrawBlurRect) then R.DrawBlurRect(x,y,w,h,str) return true end
if istable(R.Draw) and isfunction(R.Draw.BlurRect) then R.Draw.BlurRect(x,y,w,h,str) return true end
if isfunction(R.BlurRect) then R.BlurRect(x,y,w,h,str) return true end
if istable(R.UI) and isfunction(R.UI.BlurRect) then R.UI.BlurRect(x,y,w,h,str) return true end
if isfunction(R.Blur) then R.Blur(x,y,w,h,str) return true end
return false
end
local G = {}
G.fid = -1
G.used = 0
function G:beginFrame()
local f = FrameNumber()
if f ~= self.fid then
self.fid = f
self.used = 0
end
end
local function rb(x,y,w,h,r,c) draw.RoundedBox(r, x,y,w,h, c) end
local function ro(x,y,w,h,r,c,wid)
surface.SetDrawColor(c)
surface.DrawOutlinedRect(x,y,w,h, wid)
end
function G:fake(x,y,w,h,opt)
local r = (opt and opt.radius) or (libNyx.UI.Style.radius or 6)
local fill = (opt and opt.fill) or (libNyx.UI.Style.glassFill or col(255,255,255,14))
local stroke = (opt and opt.stroke ~= false) and ((opt and opt.strokeColor) or (libNyx.UI.Style.glassStroke or col(255,255,255,22)))
rb(x,y,w,h,r,fill)
if stroke then ro(x,y,w,h,r,stroke, libNyx.UI.Style.strokeWidth or 1) end
end
function G:maskBegin(x,y,w,h,r)
render.ClearStencil()
render.SetStencilEnable(true)
render.SetStencilWriteMask(255)
render.SetStencilTestMask(255)
render.SetStencilReferenceValue(1)
render.SetStencilCompareFunction(STENCIL_ALWAYS)
render.SetStencilPassOperation(STENCIL_REPLACE)
rb(x,y,w,h,r, col(255,255,255,255))
render.SetStencilCompareFunction(STENCIL_EQUAL)
render.SetStencilPassOperation(STENCIL_KEEP)
end
function G:maskEnd()
render.SetStencilEnable(false)
end
function G:blur(x,y,w,h,opt)
local r = (opt and opt.radius) or (libNyx.UI.Style.radius or 6)
local s = nowStrength()
self:maskBegin(x,y,w,h,r)
render.SetScissorRect(x, y, x + w, y + h, true)
local ok = callRNDXBlur(x,y,w,h,s)
render.SetScissorRect(0,0,0,0,false)
self:maskEnd()
local fill = (opt and opt.fill) or (libNyx.UI.Style.glassTint or col(255,255,255,8))
local stroke = (opt and opt.stroke ~= false) and ((opt and opt.strokeColor) or (libNyx.UI.Style.glassStroke or col(255,255,255,22)))
if ok then
rb(x,y,w,h,r,fill)
if stroke then ro(x,y,w,h,r,stroke, libNyx.UI.Style.strokeWidth or 1) end
else
self:fake(x,y,w,h,opt)
end
end
local gsims_glass_orig = Draw and Draw.Glass
local function gsims_glass(x,y,w,h,opt)
G:beginFrame()
local m = nowMode()
if m == 0 then return G:fake(x,y,w,h,opt) end
if m == 2 then
G.used = G.used + 1
return G:blur(x,y,w,h,opt)
end
local area = math.max(1, math.floor(w) * math.floor(h))
local allow = G.used < nowBudget()
local big = area >= nowMinArea()
local force = opt and opt.forceBlur == true
if force or (allow and big) then
G.used = G.used + 1
return G:blur(x,y,w,h,opt)
else
return G:fake(x,y,w,h,opt)
end
end
if Nyx and Nyx.UI and Nyx.UI.Draw then
Nyx.UI.Draw.Glass = gsims_glass
end
concommand.Add("gsims_glass", function(p,cmd,args)
local sub = string.lower(args[1] or "")
if sub == "mode" then
local v = tonumber(args[2] or "") or 1
RunConsoleCommand("cl_gsims_glass_mode", tostring(math.Clamp(v,0,2)))
elseif sub == "budget" then
local v = tonumber(args[2] or "") or 6
RunConsoleCommand("cl_gsims_glass_budget", tostring(math.max(0, math.floor(v))))
elseif sub == "minarea" then
local v = tonumber(args[2] or "") or 16384
RunConsoleCommand("cl_gsims_glass_minarea", tostring(math.max(0, math.floor(v))))
elseif sub == "strength" then
local v = tonumber(args[2] or "") or 3
RunConsoleCommand("cl_gsims_glass_strength", tostring(math.Clamp(math.floor(v),1,8)))
elseif sub == "preset" then
local v = string.lower(args[2] or "balanced")
if v == "fast" then
RunConsoleCommand("cl_gsims_glass_mode","1")
RunConsoleCommand("cl_gsims_glass_budget","2")
RunConsoleCommand("cl_gsims_glass_minarea","32768")
RunConsoleCommand("cl_gsims_glass_strength","2")
elseif v == "pretty" then
RunConsoleCommand("cl_gsims_glass_mode","1")
RunConsoleCommand("cl_gsims_glass_budget","12")
RunConsoleCommand("cl_gsims_glass_minarea","8192")
RunConsoleCommand("cl_gsims_glass_strength","4")
else
RunConsoleCommand("cl_gsims_glass_mode","1")
RunConsoleCommand("cl_gsims_glass_budget","6")
RunConsoleCommand("cl_gsims_glass_minarea","16384")
RunConsoleCommand("cl_gsims_glass_strength","3")
end
elseif sub == "show" then
local have = getRNDX() and "ok" or "miss"
local msg = string.format("[gSims] glass mode=%d budget=%d minarea=%d strength=%d used=%d rndx=%s",
nowMode(), nowBudget(), nowMinArea(), nowStrength(), G.used, have)
if chat and chat.AddText then chat.AddText(Color(160,220,255), msg) end
MsgN(msg)
end
end)
local Style = {
bgColor = Color(10,10,14,150),
panelColor = Color(16,18,24,120),
cardColor = Color(20,22,30,130),
accentColor = Color(90,160,255),
hoverColor = Color(52,58,70,120),
textColor = Color(245,245,250),
glassFill = Color(20,24,32,110),
glassStroke = Color(255,255,255,22),
blurIntensity = 1.0,
radius = 12,
padding = 14,
iconSize = 24,
btnHeight = 44,
rowHeight = 82,
shadowSpread = 22,
shadowIntensity= 36,
strokeWidth = 1,
headerIndentX = 24,
headerIndentY = 24,
gradientAlphaRow = 120,
gradientAlphaChip = 110,
gradientAlphaBtn = 130,
}
libNyx.UI.Style = Style
libNyx.UI.RecomputeScale()
local baseFont = "Manrope"
local function hasSystemFont(name)
local test = ("libNyx.FontCheck.%s"):format(name)
surface.CreateFont(test, {font = name, size = 12, weight = 400, extended = true})
surface.SetFont(test)
local w, h = surface.GetTextSize("Aa")
return (w or 0) > 0 and (h or 0) > 0
end
if not hasSystemFont(baseFont) then baseFont = "Tahoma" end
local fontCache = {}
for sz = 10, 200 do
local key = ("libNyx.%s.%d"):format(baseFont, sz)
surface.CreateFont(key, {font = baseFont, size = sz, weight = (sz >= 28) and 500 or 400, extended = true})
fontCache[sz] = key
end
function libNyx.UI.Font(size)
size = math.Clamp(math.floor(tonumber(size) or 14), 10, 200)
return fontCache[size]
end
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local GRAD_PALETTE = {
Color( 90,160,255),
Color(255,125,155),
Color(120,220,160),
Color(255,200,120),
Color(170,120,255),
Color(120,200,255),
}
local function PalettePick(key)
key = tostring(key or "")
local sum = 0
for i = 1, #key do sum = sum + key:byte(i) end
local idx = (sum % #GRAD_PALETTE) + 1
return GRAD_PALETTE[idx]
end
libNyx.UI.Sounds = {
hover = "nyx_uniqueui/nyxclick_2.mp3",
click = "nyx_uniqueui/nyxclick_3.mp3",
}
function libNyx.UI.PlayHover()
surface.PlaySound(libNyx.UI.Sounds.hover)
end
function libNyx.UI.PlayClick()
surface.PlaySound(libNyx.UI.Sounds.click)
end
if not libNyx.UI._sfxRedirected then
local _PlaySound = surface.PlaySound
function surface.PlaySound(snd)
if snd == "buttons/lightswitch2.wav" then
snd = (libNyx.UI.Sounds and libNyx.UI.Sounds.hover) or snd
elseif snd == "ui/buttonclick.wav" or snd == "ui/buttonclickrelease.wav" then
snd = (libNyx.UI.Sounds and libNyx.UI.Sounds.click) or snd
end
return _PlaySound(snd)
end
libNyx.UI._sfxRedirected = true
end
libNyx.UI._nobg = libNyx.UI._nobg or {}
local function _NoBG(p)
if not IsValid(p) then return end
if p.SetPaintBackground then p:SetPaintBackground(false) end
if p.SetPaintBackgroundEnabled then p:SetPaintBackgroundEnabled(false) end
if p.SetPaintBorderEnabled then p:SetPaintBorderEnabled(false) end
if p.SetDrawBackground then p:SetDrawBackground(false) end
end
function libNyx.UI.AutoNoBG(root)
if not IsValid(root) then return end
_NoBG(root)
for _, ch in ipairs(root:GetChildren()) do _NoBG(ch) end
if root._libnyx_nobg_hooked then return end
local old = root.OnChildAdded
root.OnChildAdded = function(self, pnl)
if isfunction(old) then old(self, pnl) end
_NoBG(pnl)
end
root._libnyx_nobg_hooked = true
end
libNyx.UI.Draw = {}
function libNyx.UI.Draw.Glass(x, y, w, h, opts)
opts = opts or {}
local r = opts.radius or Style.radius
local fill = opts.fill or Style.glassFill
local stroke = opts.stroke ~= false and (opts.strokeColor or Style.glassStroke)
local flags = opts.shape and opts.shape or 0
local blurI = tonumber(opts.blurIntensity) or Style.blurIntensity
local T = RNDX()
T.Rect(x, y, w, h):Rad(r):Blur(blurI):Draw()
RNDX.Draw(r, x, y, w, h, fill, flags)
if stroke then
RNDX.DrawOutlined(r, x, y, w, h, stroke, Style.strokeWidth, flags)
end
end
function libNyx.UI.CreateFrame(opts)
opts = opts or {}
local Style = libNyx.UI.Style
local W = math.max(tonumber(opts.w) or 900, libNyx.UI.Scale(240))
local H = math.max(tonumber(opts.h) or 620, libNyx.UI.Scale(200))
local title = tostring(opts.title or "")
local f = vgui.Create("DFrame")
f:SetTitle("")
f:ShowCloseButton(false)
f:SetSizable(false)
f:MakePopup()
libNyx.UI.AutoNoBG(f)
f._targetW = W
f._targetH = H
f._minW = math.max(2, math.floor(W * 0.12))
f._minH = math.max(2, math.floor(H * 0.12))
f._overshoot = 1.04
f._durOpen = 0.22
f._durClose = 0.18
f._phase = "opening"
f._t0 = SysTime()
f._contentAlpha = 0
f._title = title
local cx, cy = ScrW() * 0.5, ScrH() * 0.5
f:SetSize(f._minW, f._minH)
f:SetPos(cx - f._minW/2, cy - f._minH/2)
local function easeOutBack(t) local c1=1.70158 local c3=c1+1 local u=t-1 return 1 + c3*(u*u*u) + c1*(u*u) end
local function easeInCubic(t) return t*t*t end
local function expApproach(cur, tgt, spd) local k=1-math.exp(-(spd or 10)*FrameTime()) return cur+(tgt-cur)*k end
local function applyContentAlpha(self, a)
a = math.Clamp(math.floor(a or 0), 0, 255)
for _, ch in ipairs(self:GetChildren()) do if IsValid(ch) then ch:SetAlpha(a) end end
end
local oldAdd = f.OnChildAdded
function f:OnChildAdded(pnl)
if oldAdd then oldAdd(self, pnl) end
libNyx.UI.AutoNoBG(pnl)
pnl:SetAlpha(math.floor(self._contentAlpha or 0))
end
local closeBtn
if libNyx.UI.Components and libNyx.UI.Components.CreateButton then
closeBtn = libNyx.UI.Components.CreateButton(f, "", {variant="ghost", align="center", radius=Style.radius})
closeBtn:SetSize(libNyx.UI.Scale(40), libNyx.UI.Scale(40))
if closeBtn.SetRippleStyle then closeBtn:SetRippleStyle(2) end
closeBtn._onClick = function() f:Close() end
else
closeBtn = vgui.Create("DButton", f)
closeBtn:SetText("")
closeBtn:SetSize(libNyx.UI.Scale(40), libNyx.UI.Scale(40))
libNyx.UI.AutoNoBG(closeBtn)
closeBtn.DoClick = function() f:Close() end
end
function f:PerformLayout(w, h)
closeBtn:SetPos(w - Style.headerIndentX - closeBtn:GetWide(), Style.headerIndentY)
end
function f:Think()
local now = SysTime()
if self._phase == "opening" then
local t = math.TimeFraction(self._t0, self._t0 + self._durOpen, now)
if t >= 1 then
self._phase = "idle"
self:SetSize(self._targetW, self._targetH)
self:Center()
self._contentAlpha = 255
applyContentAlpha(self, self._contentAlpha)
self:SetMouseInputEnabled(true)
self:SetKeyboardInputEnabled(true)
else
local e = easeOutBack(t)
local ow, oh = self._targetW * self._overshoot, self._targetH * self._overshoot
local w, h = Lerp(e, self._minW, ow), Lerp(e, self._minH, oh)
if t > 0.85 then
local t2 = math.TimeFraction(0.85, 1.0, t)
local e2 = 1 - (1 - t2) * (1 - t2)
w = Lerp(e2, ow, self._targetW)
h = Lerp(e2, oh, self._targetH)
end
self:SetSize(math.max(1, w), math.max(1, h))
self:SetPos(cx - w/2, cy - h/2)
self._contentAlpha = expApproach(self._contentAlpha or 0, 255 * math.Clamp(t ^ 1.6, 0, 1), 16)
applyContentAlpha(self, self._contentAlpha)
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
elseif self._phase == "closing" then
local t = math.TimeFraction(self._t0, self._t0 + self._durClose, now)
if t >= 1 then
self:Remove()
else
local e = easeInCubic(t)
local w = Lerp(e, self._targetW, self._minW)
local h = Lerp(e, self._targetH, self._minH)
self:SetSize(math.max(1, w), math.max(1, h))
self:SetPos(cx - w/2, cy - h/2)
self._contentAlpha = expApproach(self._contentAlpha or 255, 0, 18)
applyContentAlpha(self, self._contentAlpha)
end
end
end
function f:Paint(w, h)
local r = math.max(Style.radius, libNyx.UI.Scale(14))
libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Style.bgColor, stroke = true, strokeColor = Style.glassStroke, blurIntensity = 1.1})
if self._title ~= "" then
draw.SimpleText(self._title, libNyx.UI.Font(libNyx.UI.Scale(30)), Style.headerIndentX, Style.headerIndentY, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
end
end
local realRemove = f.Remove
function f:Close()
if self._phase == "closing" then return end
self._phase = "closing"
self._t0 = SysTime()
self:SetMouseInputEnabled(false)
self:SetKeyboardInputEnabled(false)
end
function f:Remove()
if self._phase ~= "closing" and self._phase ~= "idle" then return end
realRemove(self)
end
return f
end
function libNyx.UI.Draw.Panel(x, y, w, h, opts)
opts = opts or {}
local r = opts.radius or Style.radius
local col = opts.color or Style.cardColor
local doShadow = opts.shadow == true
local flags = opts.shape or 0
if opts.glass == true or opts.blur == true then
libNyx.UI.Draw.Glass(x, y, w, h, {
radius = r,
fill = col,
stroke = opts.stroke,
strokeColor = opts.strokeColor,
shape = flags,
blurIntensity = opts.blurIntensity or Style.blurIntensity
})
return
end
if doShadow then
RNDX.DrawShadows(r, x, y, w, h, Color(0,0,0, 190), Style.shadowSpread, Style.shadowIntensity, flags)
end
RNDX.Draw(r, x, y, w, h, col, flags)
if opts.outline then
RNDX.DrawOutlined(r, x, y, w, h, opts.outlineColor or Style.glassStroke, opts.outline, flags)
end
end
libNyx.UI.Components = libNyx.UI.Components or {}
local Components = libNyx.UI.Components
libNyx.UI._fx = libNyx.UI._fx or nil
local function FX()
if IsValid(libNyx.UI._fx) then return libNyx.UI._fx end
local pnl = vgui.Create("DPanel")
pnl:SetZPos(32767)
pnl:SetMouseInputEnabled(false)
pnl:SetKeyboardInputEnabled(false)
pnl:SetSize(ScrW(), ScrH())
pnl:SetPos(0,0)
libNyx.UI.AutoNoBG(pnl)
pnl._anims = {}
pnl.Paint = function(s,w,h)
local now = SysTime()
for i = #s._anims, 1, -1 do
local a = s._anims[i]
local t = math.TimeFraction(a.t0, a.t0 + a.dur, now)
if t >= 1 then
if isfunction(a.cb) then a.cb() end
table.remove(s._anims, i)
else
local e = t < 0.5 and (4*t*t*t) or (1 - (-2*t+2)^3/2)
local x = Lerp(e, a.sx, a.ex)
local y = Lerp(e, a.sy, a.ey - a.arc*math.sin(t*math.pi))
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(a.mat)
surface.DrawTexturedRect(x - a.size/2, y - a.size/2, a.size, a.size)
end
end
if #s._anims == 0 then s:SetVisible(false) end
end
hook.Add("OnScreenSizeChanged","libNyx.UI.FX",function()
if not IsValid(pnl) then return end
pnl:SetSize(ScrW(), ScrH())
pnl:SetPos(0,0)
end)
libNyx.UI._fx = pnl
return pnl
end
function libNyx.UI.FlyIcon(mat, sx, sy, ex, ey, size, dur, cb)
local fx = FX()
fx:SetVisible(true)
table.insert(fx._anims, {
mat = mat, sx = sx, sy = sy, ex = ex, ey = ey,
size = size or libNyx.UI.Scale(36),
dur = dur or 0.35, t0 = SysTime(),
arc = math.min(64, math.Distance(sx,sy,ex,ey)*0.25),
cb = cb
})
end
local function FitModel(mp)
if not IsValid(mp) or not IsValid(mp.Entity) then return end
local mn, mx = mp.Entity:GetRenderBounds()
local size = mx - mn
local center = (mn + mx) * 0.5
local maxdim = math.max(size.x, size.y, size.z)
local ang = Angle(12, 25, 0)
local fov = mp:GetFOV()
local dist = (maxdim * 0.55) / math.rad(fov * 0.5)
local camPos = center + ang:Forward() * -dist + Vector(0, 0, size.z * 0.08)
mp:SetCamPos(camPos)
mp:SetLookAt(center + Vector(0, 0, size.z * 0.05))
mp:SetAmbientLight(Vector(255, 255, 255))
mp:SetDirectionalLight(BOX_TOP, Color(255,255,255))
end
function Components.CreateCell(parent, opts)
opts = opts or {}
local sz = opts.size or libNyx.UI.Scale(88)
local r = opts.radius or libNyx.UI.Scale(14)
local tint = opts.tint or Style.accentColor
local cell = vgui.Create("DButton", parent)
cell:SetText("")
cell:SetSize(sz, sz)
cell._radius = r
cell._tint = tint
cell._iconMat = nil
cell._iconSize = math.floor(sz * 0.6)
cell._mp = nil
cell._info = nil
cell._infoBox = nil
cell._lastScreenPos = {x = 0, y = 0}
local function expApproach(cur, tgt, spd)
local k = 1 - math.exp(-(spd or 10) * FrameTime())
return cur + (tgt - cur) * k
end
local function normalizeTags(t)
local out = {}
if not istable(t) then return out end
for _, v in ipairs(t) do
if isstring(v) then
out[#out+1] = {text = v, color = PalettePick(v)}
elseif istable(v) then
out[#out+1] = {
text = tostring(v.text or v.label or v[1] or ""),
color = v.color or v.col or v[2] or PalettePick(v.text or v[1] or "?")
}
end
end
return out
end
local function removeInfoBox()
if IsValid(cell._infoBox) then
cell._infoBox:Remove()
cell._infoBox = nil
end
end
local function ensureInfoBox()
if IsValid(cell._infoBox) then return cell._infoBox end
if not cell._info then return nil end
local ib = vgui.Create("DPanel")
ib:SetDrawOnTop(true)
ib:SetMouseInputEnabled(true)
ib:SetKeyboardInputEnabled(false)
ib._title = tostring(cell._info.title or "")
ib._desc = tostring(cell._info.desc or "")
ib._tags = normalizeTags(cell._info.tags)
ib._tint = cell._tint
ib._state = "opening"
ib._t0 = SysTime()
ib._durOpen = 0.16
ib._durClose = 0.14
ib._vis = 0
ib._hoverA = 0
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20)))
local tw, th = surface.GetTextSize(ib._title ~= "" and ib._title or " ")
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(16)))
local dw, dh = surface.GetTextSize(ib._desc ~= "" and ib._desc or " ")
local pad = libNyx.UI.Scale(12)
local gap = libNyx.UI.Scale(8)
local tagH = libNyx.UI.Scale(22)
local ww = math.max(libNyx.UI.Scale(240), tw + pad*2, dw + pad*2)
local hh = pad + th + (ib._desc ~= "" and (gap + dh) or 0) + ( (#ib._tags>0) and (gap + tagH) or 0 ) + pad
ib:SetSize(ww, hh)
local function place()
local sx, sy = cell:LocalToScreen(0, 0)
local cw, ch = cell:GetSize()
local w, h = ib:GetSize()
local x = sx + cw + libNyx.UI.Scale(10)
local y = sy + math.floor((ch - h) * 0.5)
x = math.min(x, ScrW() - w - libNyx.UI.Scale(8))
y = math.Clamp(y, libNyx.UI.Scale(8), ScrH() - h - libNyx.UI.Scale(8))
ib:SetPos(x, y)
end
place()
ib.Think = function(s)
local sx, sy = cell:LocalToScreen(0, 0)
if sx ~= (cell._lastScreenPos.x or 0) or sy ~= (cell._lastScreenPos.y or 0) then
place()
cell._lastScreenPos.x, cell._lastScreenPos.y = sx, sy
end
local tgtH = s:IsHovered() and 1 or 0
s._hoverA = expApproach(s._hoverA or 0, tgtH, 10)
local now = SysTime()
if s._state == "opening" then
local t = math.TimeFraction(s._t0, s._t0 + s._durOpen, now)
if t >= 1 then
s._state = "open"
s._vis = 1
else
s._vis = 1 - (1 - t) * (1 - t)
end
elseif s._state == "closing" then
local t = math.TimeFraction(s._t0, s._t0 + s._durClose, now)
if t >= 1 then
s:Remove()
cell._infoBox = nil
return
else
s._vis = 1 - (t * t * t)
end
else
s._vis = expApproach(s._vis or 1, 1, 12)
if (not cell:IsHovered()) and (not s:IsHovered()) then
s._state = "closing"
s._t0 = SysTime()
end
end
end
ib.Paint = function(s, w, h)
local v = math.Clamp(s._vis or 0, 0, 1)
local sc = Lerp(v, 0.92, 1.00)
local dx = math.floor((w - w*sc) * 0.5)
local dy = math.floor((h - h*sc) * 0.5)
local baseFillA = Lerp(s._hoverA or 0, 90, 70)
local strokeA = 20
local fillCol = Color(16,18,24, math.floor(baseFillA * v))
local strokeCol = Color(255,255,255, math.floor(strokeA * v))
local blurI = 1.15 + 0.20 * (s._hoverA or 0)
libNyx.UI.Draw.Glass(dx, dy, math.floor(w*sc), math.floor(h*sc), {
radius = libNyx.UI.Scale(10),
fill = fillCol,
stroke = true,
strokeColor = strokeCol,
blurIntensity = blurI
})
local ca = math.floor(255 * v)
local slide = math.floor(libNyx.UI.Scale(4) * (1 - v))
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20)))
draw.SimpleText(s._title or "", libNyx.UI.Font(libNyx.UI.Scale(20)),
dx + libNyx.UI.Scale(12), dy + libNyx.UI.Scale(12) + slide,
Color(Style.textColor.r, Style.textColor.g, Style.textColor.b, ca),
TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
if (s._desc or "") ~= "" then
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(16)))
local _, th = surface.GetTextSize(s._title ~= "" and s._title or " ")
draw.SimpleText(s._desc or "", libNyx.UI.Font(libNyx.UI.Scale(16)),
dx + libNyx.UI.Scale(12), dy + libNyx.UI.Scale(12) + th + libNyx.UI.Scale(6) + slide,
Color(255,255,255, math.floor(220 * v)),
TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
end
if #s._tags > 0 then
local pad = libNyx.UI.Scale(12)
local tagH = libNyx.UI.Scale(22)
local chipR = libNyx.UI.Scale(8)
local y = dy + h*sc - tagH - pad
local x = dx + pad
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(14)))
for _, tg in ipairs(s._tags) do
local t = tg.text or ""
local tw = select(1, surface.GetTextSize(t))
local cw = tw + libNyx.UI.Scale(14)
libNyx.UI.Draw.Panel(x, y, cw, tagH, {
radius = chipR,
color = Color((tg.color or Style.accentColor).r, (tg.color or Style.accentColor).g, (tg.color or Style.accentColor).b, math.floor(150 * v)),
glass = true,
stroke = true,
strokeColor = Color(255,255,255, math.floor(16 * v))
})
draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(14)),
x + cw/2, y + tagH/2,
Color(255,255,255, math.floor(240 * v)), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
x = x + cw + libNyx.UI.Scale(6)
end
end
end
ib.OnCursorExited = function(s)
if not cell:IsHovered() and s._state ~= "closing" then
s._state = "closing"
s._t0 = SysTime()
end
end
cell._infoBox = ib
return ib
end
function cell:SetItemIcon(mat, size, info)
if isstring(mat) then mat = Material(mat, "noclamp smooth") end
self._iconMat = mat
if size then self._iconSize = size end
self._info = info
if IsValid(self._mp) then self._mp:Remove() self._mp = nil end
removeInfoBox()
self:InvalidateLayout(true)
end
function cell:SetItemModel(path)
self._iconMat = nil
if not IsValid(self._mp) then
self._mp = vgui.Create("DModelPanel", self)
self._mp:Dock(FILL)
self._mp:DockMargin(libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6))
self._mp:SetFOV(28)
function self._mp:LayoutEntity(ent) end
end
self._mp:SetModel(path or "models/props_c17/oildrum001.mdl")
removeInfoBox()
end
function cell:ClearItem()
self._iconMat = nil
if IsValid(self._mp) then self._mp:Remove() self._mp = nil end
self._info = nil
removeInfoBox()
self:InvalidateLayout(false)
end
function cell:HasItem()
return self._iconMat ~= nil or IsValid(self._mp)
end
function cell:GetCenterScreen()
local x, y = self:LocalToScreen(self:GetWide()/2, self:GetTall()/2)
return x, y
end
function cell:PerformLayout(w, h)
local dock = self:GetDock()
if dock == LEFT or dock == RIGHT then
self:SetWide(h)
elseif dock == TOP or dock == BOTTOM then
self:SetTall(w)
elseif dock ~= FILL then
local side = math.min(w, h)
self:SetSize(side, side)
end
end
function cell:OnSizeChanged(w, h)
local side = math.min(w, h)
self._iconSize = math.floor(side * 0.6)
self._lastScreenPos.x, self._lastScreenPos.y = -1, -1
end
function cell:OnCursorEntered()
if self:HasItem() and self._info then
local ib = ensureInfoBox()
if IsValid(ib) and ib._state == "closing" then
ib._state = "opening"
ib._t0 = SysTime()
end
end
end
function cell:OnCursorExited()
local ib = self._infoBox
if IsValid(ib) and (not ib:IsHovered()) and ib._state ~= "closing" then
ib._state = "closing"
ib._t0 = SysTime()
end
end
function cell:OnRemove()
removeInfoBox()
end
cell.Paint = function(s, w, h)
libNyx.UI.Draw.Glass(0, 0, w, h, {
radius = s._radius,
fill = Color(16,18,24,120),
stroke = true,
strokeColor = Style.glassStroke,
blurIntensity = 1.05
})
if s._iconMat then
surface.SetDrawColor(255, 255, 255, 255)
surface.SetMaterial(s._iconMat)
surface.DrawTexturedRect((w - s._iconSize)/2, (h - s._iconSize)/2, s._iconSize, s._iconSize)
end
end
return cell
end
libNyx.UI._drag = libNyx.UI._drag or {active=false}
local function easeInOutCubic(t)
if t < 0.5 then return 4*t*t*t end
t = (t - 1); return 1 + 4*t*t*t
end
local function ensureDragHook()
if libNyx.UI._drag._hooked then return end
hook.Add("PostRenderVGUI", "zzz.libNyx.UI.DragOverlay", function()
local d = libNyx.UI._drag
if not d or not d.active then return end
local mx, my = gui.MousePos()
mx = mx + (d.offx or 0)
my = my + (d.offy or 0)
local now = SysTime()
local a = 1
local boxSize, rad, iconSize
if d.phase == "pickup" then
local t = math.TimeFraction(d.t0, d.t0 + d.dur, now)
if t >= 1 then
d.phase = "drag"
t = 1
end
local e = easeInOutCubic(math.Clamp(t, 0, 1))
boxSize = Lerp(e, d.boxFrom, d.boxTo)
rad = Lerp(e, d.radFrom, d.radTo)
iconSize = Lerp(e, d.iconFrom, d.iconTo)
elseif d.phase == "drop" then
local t = math.TimeFraction(d.t0, d.t0 + d.dur, now)
if t >= 1 then
d.active = false
if IsValid(d.applyTo) and d.mat then
d.applyTo:SetItemIcon(d.mat, d.origIconSize, d.info)
elseif IsValid(d.src) and d.mat then
d.src:SetItemIcon(d.mat, d.origIconSize, d.info)
end
if d.onDone then pcall(d.onDone) end
return
end
local e = easeInOutCubic(math.Clamp(t, 0, 1))
boxSize = Lerp(e, d.boxTo, d.boxTo * 0.92)
rad = Lerp(e, d.radTo, d.radTo * 0.85)
iconSize = Lerp(e, d.iconTo, d.iconTo * 0.90)
a = 1 - e
else
boxSize = d.boxTo
rad = d.radTo
iconSize = d.iconTo
end
local bx = mx - boxSize/2
local by = my - boxSize/2
libNyx.UI.Draw.Glass(bx, by, boxSize, boxSize, {
radius = rad,
fill = Color(20,24,32, math.floor(120 * a)),
stroke = true,
strokeColor = Color(255,255,255, math.floor(22 * a)),
blurIntensity = 1.25
})
if d.mat then
surface.SetDrawColor(255, 255, 255, math.floor(255 * a))
surface.SetMaterial(d.mat)
surface.DrawTexturedRect(mx - iconSize/2, my - iconSize/2, iconSize, iconSize)
end
local tgt = vgui.GetHoveredPanel()
while IsValid(tgt) and not tgt._isInteractiveCell do tgt = tgt:GetParent() end
local S = libNyx.UI.Style
if IsValid(tgt) and tgt._isInteractiveCell then
local tx, ty = tgt:LocalToScreen(0, 0)
d._tx, d._ty = tx, ty
d._tw, d._th = tgt:GetWide(), tgt:GetTall()
d._tr = tgt._radius or S.radius
d._talpha = 1
else
d._talpha = 0
end
local dt = FrameTime()
local k = 1 - math.pow(0.0001, dt * 60)
local ka = 1 - math.pow(0.0001, dt * 45)
d._hx = Lerp(k, d._hx or d._tx or 0, d._tx or (d._hx or 0))
d._hy = Lerp(k, d._hy or d._ty or 0, d._ty or (d._hy or 0))
d._hw = Lerp(k, d._hw or d._tw or 0, d._tw or (d._hw or 0))
d._hh = Lerp(k, d._hh or d._th or 0, d._th or (d._hh or 0))
d._hr = Lerp(k, d._hr or d._tr or S.radius, d._tr or (d._hr or S.radius))
d._ha = Lerp(ka, d._ha or 0, d._talpha or 0)
if (d._ha or 0) > 0.01 and d._hx and d._hy and d._hw and d._hh then
local acc = S.accentColor
local x = math.floor(d._hx + 0.5)
local y = math.floor(d._hy + 0.5)
local w = math.floor(d._hw + 0.5)
local h = math.floor(d._hh + 0.5)
local r = math.floor((d._hr or S.radius) + 0.5)
local aL = math.floor(210 * d._ha)
local sw = math.max(1, math.floor(1 + d._ha))
RNDX.DrawOutlined(r, x, y, w, h, Color(acc.r, acc.g, acc.b, aL), sw, 0)
end
end)
libNyx.UI._drag._hooked = true
end
function libNyx.UI.StartDragIcon(src, mat, size, offx, offy)
if isstring(mat) then mat = Material(mat, "noclamp smooth") end
local S = libNyx.UI.Style
local base = tonumber(size) or libNyx.UI.Scale(36)
ensureDragHook()
libNyx.UI._drag = {
active = true,
src = src,
mat = mat,
origIconSize = base,
offx = offx or 0,
offy = offy or 0,
info = (IsValid(src) and src._info) or nil,
phase = "pickup",
t0 = SysTime(),
dur = 0.16,
boxFrom = math.max(18, base * 0.88),
boxTo = base + libNyx.UI.Scale(18),
radFrom = math.max(4, S.radius * 0.5),
radTo = math.floor(S.radius * 1.15),
iconFrom = base * 0.92,
iconTo = base,
_tx=nil,_ty=nil,_tw=nil,_th=nil,_tr=nil,_talpha=0,
_hx=nil,_hy=nil,_hw=nil,_hh=nil,_hr=nil,_ha=0,
}
end
function libNyx.UI.StopDragIcon(applyTo)
local d = libNyx.UI._drag
if not d or not d.active then return end
d.applyTo = applyTo
d.phase = "drop"
d.t0 = SysTime()
d.dur = 0.18
end
local function libnyx_find_cell_under_cursor()
local p = vgui.GetHoveredPanel()
while IsValid(p) and not p._isInteractiveCell do p = p:GetParent() end
return p
end
libNyx.UI._inv = libNyx.UI._inv or {holding=nil, from=nil}
function Components.CreateInteractiveCell(parent, opts)
local cell = Components.CreateCell(parent, opts)
cell._isInteractiveCell = true
function cell:OnMousePressed(mc)
if mc ~= MOUSE_LEFT then return end
if libNyx.UI._drag and libNyx.UI._drag.active then return end
if not self:HasItem() or not self._iconMat then return end
local lx, ly = self:LocalCursorPos()
local offx = lx - self:GetWide()/2
local offy = ly - self:GetTall()/2
libNyx.UI.StartDragIcon(self, self._iconMat, self._iconSize, offx, offy)
self:ClearItem()
self:MouseCapture(true)
surface.PlaySound("ui/buttonclick.wav")
end
function cell:OnMouseReleased(mc)
if mc ~= MOUSE_LEFT then return end
self:MouseCapture(false)
local tgt = libnyx_find_cell_under_cursor()
if IsValid(tgt) and tgt ~= self and not tgt:HasItem() then
libNyx.UI.StopDragIcon(tgt)
else
libNyx.UI.StopDragIcon(self)
end
surface.PlaySound("ui/buttonclickrelease.wav")
end
return cell
end
libNyx.UI._rippleStyle = libNyx.UI._rippleStyle or 1
function libNyx.UI.SetRippleStyle(style)
if isstring(style) then
local s = string.lower(style)
if s == "ring" or s == "2" then libNyx.UI._rippleStyle = 2 else libNyx.UI._rippleStyle = 1 end
else
libNyx.UI._rippleStyle = (tonumber(style) == 2) and 2 or 1
end
end
function libNyx.UI.GetRippleStyle() return libNyx.UI._rippleStyle end
local function makeRipple(btn, style)
btn._ripples = {}
btn._rippleDur = 0.45
btn._rippleSpeed = 340
btn._rippleStyle = style and ((tonumber(style) == 2 or string.lower(tostring(style)) == "ring") and 2 or 1) or nil
btn._rippleThick = libNyx.UI.Scale(4)
function btn:SetRippleStyle(s)
self._rippleStyle = (tonumber(s) == 2 or string.lower(tostring(s)) == "ring") and 2 or 1
end
end
local function paintRipples(self, w, h, color)
local now = SysTime()
local variant = self._rippleStyle or libNyx.UI.GetRippleStyle()
local thick = math.max(1, self._rippleThick or libNyx.UI.Scale(3))
for i = #self._ripples, 1, -1 do
local r = self._ripples[i]
local dt = now - r.t0
if dt >= self._rippleDur then
table.remove(self._ripples, i)
else
local radius = dt * self._rippleSpeed
local alpha = math.Clamp(220 * (1 - dt / self._rippleDur), 0, 220)
if variant == 2 then
surface.SetDrawColor(color.r, color.g, color.b, alpha)
for t = 0, thick - 1 do
surface.DrawCircle(r.x, r.y, math.max(0, radius - t), color.r, color.g, color.b, alpha)
end
else
surface.SetDrawColor(color.r, color.g, color.b, alpha)
draw.NoTexture()
local seg = 28
local verts = {{x = r.x, y = r.y}}
for k = 0, seg do
local ang = (k / seg) * math.pi * 2
verts[#verts+1] = {x = r.x + math.cos(ang) * radius, y = r.y + math.sin(ang) * radius}
end
surface.DrawPoly(verts)
end
end
end
end
function Components.CreateCheckbox(parent, opts)
opts = opts or {}
local box = vgui.Create("DButton", parent)
box:SetText("")
box:SetCursor("hand")
box._variant = string.lower(opts.variant or "switch")
box._checked = tobool(opts.checked)
box._tint = opts.tint or Style.accentColor
box._label = opts.label or ""
box._size = math.max(libNyx.UI.Scale(20), tonumber(opts.size) or libNyx.UI.Scale(22))
box._gap = libNyx.UI.Scale(10)
box._font = libNyx.UI.Font(libNyx.UI.Scale(18))
box._anim = box._checked and 1 or 0
box._onChange = opts.onChange
box._group = opts.group
libNyx.UI._checkboxGroups = libNyx.UI._checkboxGroups or {}
local function ensureGroup(self, name)
if not name or name == "" then return end
local t = libNyx.UI._checkboxGroups
t[name] = t[name] or {}
if not table.HasValue(t[name], self) then table.insert(t[name], self) end
end
local function leaveGroup(self, name)
if not name or name == "" then return end
local t = libNyx.UI._checkboxGroups[name]
if not t then return end
for i = #t, 1, -1 do
if t[i] == self then table.remove(t, i) end
end
end
local function notifyGroup(self)
if self._variant ~= "radio" then return end
local name = self._group
if not name or name == "" then return end
local t = libNyx.UI._checkboxGroups[name] or {}
for _, other in ipairs(t) do
if IsValid(other) and other ~= self and other._variant == "radio" then
other._checked = false
other._anim = 0
other:InvalidateLayout(false)
end
end
end
function box:SetGroup(name)
if self._group == name then return end
leaveGroup(self, self._group)
self._group = name
ensureGroup(self, name)
end
if box._variant == "radio" and (not box._group or box._group == "") then
box._group = "auto_radio_group_" .. tostring(parent or box:GetParent() or "root")
end
ensureGroup(box, box._group)
function box:ControlSize()
local h = self._size
if self._variant == "radio" then
return h, h
end
local mul = (self._variant == "switch") and 1.95 or 1.65
return math.floor(h * mul), h
end
function box:_RefreshSize()
local cw, ch = self:ControlSize()
surface.SetFont(self._font)
local tw = surface.GetTextSize(self._label or "")
local w = cw + (tw > 0 and (self._gap + tw) or 0) + libNyx.UI.Scale(4)
local h = math.max(ch, libNyx.UI.Scale(28))
self:SetSize(w, h)
end
function box:SetLabel(t) self._label = tostring(t or "") self:_RefreshSize() end
function box:SetVariant(v)
v = string.lower(v or "switch")
if self._variant == "radio" and v ~= "radio" then
leaveGroup(self, self._group)
end
self._variant = v
if self._variant == "radio" and (not self._group or self._group == "") then
self._group = "auto_radio_group_" .. tostring(self:GetParent() or "root")
ensureGroup(self, self._group)
end
self:_RefreshSize()
end
function box:SetValue(b)
b = tobool(b)
if self._variant == "radio" then
if self._checked ~= b then
self._checked = b
if b then notifyGroup(self) end
if isfunction(self._onChange) then self._onChange(self._checked, self) end
end
else
if self._checked ~= b then
self._checked = b
if isfunction(self._onChange) then self._onChange(self._checked, self) end
end
end
self._anim = self._checked and 1 or 0
self:InvalidateLayout(false)
end
function box:GetValue() return self._checked end
function box:SetOnChange(fn) self._onChange = fn end
function box:OnCursorEntered() end
function box:DoClick()
if self._variant == "radio" then
self._checked = not self._checked
if self._checked then notifyGroup(self) end
if isfunction(self._onChange) then self._onChange(self._checked, self) end
else
self._checked = not self._checked
if isfunction(self._onChange) then self._onChange(self._checked, self) end
end
surface.PlaySound("ui/buttonclickrelease.wav")
end
function box:OnRemove()
leaveGroup(self, self._group)
end
box.Think = function(self)
local target = self._checked and 1 or 0
self._anim = Lerp(FrameTime() * 12, self._anim or target, target)
end
box.Paint = function(self, w, h)
local tint = self._tint
local cw, ch = self:ControlSize()
local cx = 0
local cy = (h - ch) / 2
local a = math.Clamp(self._anim or 0, 0, 1)
if self._variant == "radio" then
local r = math.floor(ch/2)
local strokeA = 38 + (120 - 38) * a
libNyx.UI.Draw.Glass(cx, cy, ch, ch, {radius = r, fill = Color(20,24,32,110), stroke = true, strokeColor = Color(tint.r, tint.g, tint.b, math.floor(strokeA)), blurIntensity = 1.0})
local dot = math.floor(ch * 0.5 * a)
if a > 0.01 and dot > 0 then
local dx = cx + (ch - dot)/2
local dy = cy + (ch - dot)/2
libNyx.UI.Draw.Panel(dx, dy, dot, dot, {radius = dot/2, color = Color(tint.r, tint.g, tint.b, math.floor(215*a)), glass = true})
end
else
local r = math.floor(ch/2)
if self._variant == "switch" then
local trackCol = Color(Lerp(a, 30, tint.r), Lerp(a, 34, tint.g), Lerp(a, 42, tint.b), math.floor(Lerp(a, 100, 170)))
libNyx.UI.Draw.Panel(cx, cy, cw, ch, {radius = r, color = trackCol, glass = true, stroke = true})
local pad = libNyx.UI.Scale(2)
local knob = ch - pad*2
local kx = Lerp(a, cx + pad, cx + cw - pad - knob)
libNyx.UI.Draw.Panel(kx, cy + pad, knob, knob, {radius = knob/2, color = Color(245,245,252,230), glass = true, stroke = true})
else
libNyx.UI.Draw.Panel(cx, cy, cw, ch, {radius = r, color = Color(30,34,42,120), glass = true, stroke = true})
local pad = libNyx.UI.Scale(3)
local knob = ch - pad*2
local kx = Lerp(a, cx + pad, cx + cw - pad - knob)
local fillA = math.floor(Lerp(a, 60, 205))
libNyx.UI.Draw.Panel(kx, cy + pad, knob, knob, {radius = knob/2, color = Color(tint.r, tint.g, tint.b, fillA), glass = true})
RNDX.DrawOutlined(knob/2, kx, cy + pad, knob, knob, Color(255,255,255, math.floor(Lerp(a, 40, 140))), 1)
end
end
if self._label ~= "" then
local xText = cx + cw + self._gap
draw.SimpleText(self._label, self._font, xText, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
end
box:_RefreshSize()
return box
end
function Components.CreateButton(parent, text, opts)
opts = opts or {}
local btn = vgui.Create("DButton", parent)
btn:SetText("")
btn:SetTall(opts.h or Style.btnHeight)
btn:SetFont(libNyx.UI.Font(libNyx.UI.Scale(20)))
btn._text = text or "Button"
btn._icon = opts.icon
btn._align = opts.align or "center"
btn._variant = opts.variant or "primary"
btn._radius = opts.radius or Style.radius
btn._iconSize = opts.iconSize or Style.iconSize
btn._onClick = opts.onClick
btn._tint = opts.tint
btn._centerTint = opts.centerTint or opts.tint2
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth")
local function lighten(col, k)
return Color(
math.Clamp(col.r + (255 - col.r) * k, 0, 255),
math.Clamp(col.g + (255 - col.g) * k, 0, 255),
math.Clamp(col.b + (255 - col.b) * k, 0, 255),
col.a or 255
)
end
local function DuoDefaults()
local base = Color(104, 76, 230, 205)
local center = Color(184, 160, 255, 140)
return base, center
end
makeRipple(btn,2)
function btn:DoClick()
libNyx.UI.PlayClick()
if isfunction(self._onClick) then self._onClick(self) end
end
function btn:OnCursorEntered()
local mx, my = gui.MousePos()
mx, my = self:ScreenToLocal(mx, my)
table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()})
libNyx.UI.PlayHover()
end
btn.Paint = function(self, w, h)
local pad = Style.padding
local r = self._radius
local v = self._variant
local align = ((v == "primary_center") or (v == "center_duo")) and "center" or (self._align or "center")
if v == "center_duo" then
local defBase, defCenter = DuoDefaults()
local baseCol = self._tint or defBase
local centerCol = self._centerTint or (self._tint and lighten(self._tint, 0.26)) or defCenter
local aAdj = self:IsDown() and 20 or (self:IsHovered() and 10 or 0)
local bg = Color(baseCol.r, baseCol.g, baseCol.b, math.Clamp((baseCol.a or 205) + aAdj, 120, 255))
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = bg, glass = true})
local light = Color(centerCol.r, centerCol.g, centerCol.b, math.Clamp((centerCol.a or 140) + aAdj, 0, 255))
local coverage = 0.78
local halfCov = math.floor(w * coverage * 0.5)
local cx = math.floor(w * 0.5)
local overlap = 2
local ro = 0
if not (MAT_GRADIENT_L:IsError() or MAT_GRADIENT_R:IsError()) then
local lx, lw = cx - halfCov - overlap, halfCov + overlap
RNDX.DrawMaterial(ro, lx, 0, lw, h, light, MAT_GRADIENT_R, 0)
local rx, rw = cx, halfCov + overlap
RNDX.DrawMaterial(ro, rx, 0, rw, h, light, MAT_GRADIENT_L, 0)
end
elseif v == "primary" or v == "primary_center" then
local a = self:IsDown() and 235 or (self:IsHovered() and 220 or 205)
local col = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, a)
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true})
elseif v == "ghost" then
local col = self:IsHovered() and Style.hoverColor or Color(0,0,0,0)
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true, stroke = true})
elseif v == "gradient" then
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(20,22,30,120), glass = true, stroke = true})
if not MAT_GRADIENT_L:IsError() then
local tint = self._tint or PalettePick(self._text)
local a = self:IsDown() and (Style.gradientAlphaBtn + 20)
or self:IsHovered() and (Style.gradientAlphaBtn + 10)
or Style.gradientAlphaBtn
local gcol = Color(tint.r, tint.g, tint.b, a)
local gw = w * 0.6
RNDX.DrawMaterial(r, 0, 0, gw, h, gcol, MAT_GRADIENT_L, 0)
end
else
local col = self:IsHovered() and Color(Style.panelColor.r,Style.panelColor.g,Style.panelColor.b, 160) or Style.panelColor
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = col, glass = true})
end
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(20)))
local tw = surface.GetTextSize(self._text or "")
if align == "left" then
local x = pad
if self._icon and not self._icon:IsError() then
surface.SetDrawColor(Style.textColor)
surface.SetMaterial(self._icon)
surface.DrawTexturedRect(x, (h - self._iconSize)/2, self._iconSize, self._iconSize)
x = x + self._iconSize + pad
end
draw.SimpleText(self._text, libNyx.UI.Font(libNyx.UI.Scale(20)), x, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
else
local icw = (self._icon and not self._icon:IsError()) and (self._iconSize + pad) or 0
local content = icw + tw
local startX = math.floor((w - content) * 0.5)
if self._icon and not self._icon:IsError() then
surface.SetDrawColor(Style.textColor)
surface.SetMaterial(self._icon)
surface.DrawTexturedRect(startX, (h - self._iconSize)/2, self._iconSize, self._iconSize)
startX = startX + icw
end
draw.SimpleText(self._text, libNyx.UI.Font(libNyx.UI.Scale(20)), startX, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
paintRipples(self, w, h, Style.textColor)
end
return btn
end
function libNyx.UI.Components.CreateSlider(parent, opts)
opts = opts or {}
local SUI = libNyx.UI
local Style = SUI.Style
local sld = vgui.Create("DPanel", parent)
sld:SetTall(Style.btnHeight)
SUI.AutoNoBG(sld)
sld._min = tonumber(opts.min) or 0
sld._max = tonumber(opts.max) or 100
sld._dec = math.max(0, tonumber(opts.decimals) or 0)
sld._value = math.Clamp(tonumber(opts.value) or sld._min, sld._min, sld._max)
sld._lerpVal = sld._value
sld._tint = opts.tint or Style.accentColor
sld._gap = SUI.Scale(10)
sld._dragging = false
sld._hoverA = 0
sld._knobA = 0
sld._pulse = 0
sld._lastText = ""
sld._font = SUI.Font(SUI.Scale(18))
sld._fontNum = SUI.Font(SUI.Scale(18))
local MAT_GRADIENT_L = Material("vgui/gradient-l","noclamp smooth")
local function fmtn(v,d) return (d or 0) <= 0 and tostring(math.Round(v)) or string.format("%."..d.."f", v) end
local function fracFromValue(v) local r=sld._max - sld._min if r<=0 then return 0 end return (v - sld._min)/r end
local function valueFromFrac(f) return sld._min + f*(sld._max - sld._min) end
local function clamp01(x) return x<0 and 0 or (x>1 and 1 or x) end
local function expApproach(cur,tgt,spd) local k=1-math.exp(-(spd or 12)*FrameTime()) return cur+(tgt-cur)*k end
local function counterWidth(txt)
surface.SetFont(sld._fontNum)
local w = select(1, surface.GetTextSize(txt))
return math.Clamp(w + SUI.Scale(24), SUI.Scale(50), SUI.Scale(140))
end
local function trackBounds(w,h)
local num = fmtn(sld._lerpVal, sld._dec)
local ta = counterWidth(num)
local th = SUI.Scale(6)
local tx = 0
local tw = math.max(1, w - ta - sld._gap)
local ty = math.floor(h*0.5 - th*0.5)
return tx, ty, tw, th, ta
end
function sld:SetMin(v) self._min = tonumber(v) or self._min self:SetValue(self._value) end
function sld:SetMax(v) self._max = tonumber(v) or self._max self:SetValue(self._value) end
function sld:SetDecimals(d) self._dec = math.max(0, tonumber(d) or self._dec) end
function sld:SetValue(v)
local nv = math.Clamp(tonumber(v) or self._value, self._min, self._max)
if nv == self._value then return end
self._value = nv
if isfunction(self.OnValueChanged) then self:OnValueChanged(nv) end
end
function sld:GetValue() return self._value end
function sld:OnMousePressed(mc)
if mc ~= MOUSE_LEFT then return end
local lx, ly = self:LocalCursorPos()
local tx, ty, tw, th = trackBounds(self:GetWide(), self:GetTall())
if lx >= tx and lx <= (tx+tw) and ly >= ty - SUI.Scale(8) and ly <= ty + th + SUI.Scale(8) then
self._dragging = true
self:MouseCapture(true)
local f = clamp01((lx - tx) / tw)
self:SetValue(valueFromFrac(f))
end
end
function sld:OnMouseReleased(mc)
if mc ~= MOUSE_LEFT then return end
self._dragging = false
self:MouseCapture(false)
end
function sld:OnCursorMoved(x,y)
if not self._dragging then return end
local tx, _, tw = trackBounds(self:GetWide(), self:GetTall())
local f = clamp01((x - tx) / tw)
self:SetValue(valueFromFrac(f))
end
function sld:OnMouseWheeled(dl)
local base = (self._max - self._min) * 0.01
local gran = self._dec > 0 and (1 / (10 ^ self._dec)) or 1
local step = math.max(base, gran)
self:SetValue(self._value + step * (dl > 0 and 1 or -1))
end
function sld:Think()
self._lerpVal = Lerp(FrameTime()*12, self._lerpVal or self._value, self._value)
local txt = fmtn(self._lerpVal, self._dec)
if txt ~= self._lastText then
self._lastText = txt
self._pulse = 1
end
local hov = self:IsHovered() or self._dragging
self._hoverA = expApproach(self._hoverA, hov and 1 or 0, 10)
self._knobA = expApproach(self._knobA, self._dragging and 1 or (hov and 0.6 or 0), 12)
self._pulse = expApproach(self._pulse, 0, 8)
if self._dragging then self:SetCursor("sizewe") else self:SetCursor("hand") end
end
function sld:Paint(w,h)
local tx, ty, tw, th = trackBounds(w,h)
local f = (self._max - self._min) == 0 and 0 or (self._lerpVal - self._min) / (self._max - self._min)
local kn = SUI.Scale(16) + SUI.Scale(6) * self._knobA
SUI.Draw.Panel(tx, ty, tw, th, {radius = th/2, color = Color(30,34,42,130 + math.floor(30*self._knobA)), glass = true})
local minCenter = kn * 0.5
local maxCenter = math.max(minCenter, tw - kn * 0.5)
local centerOff = math.Clamp(tw * f, minCenter, maxCenter)
local kx = math.floor(tx + centerOff)
local ky = math.floor(h*0.5 - kn*0.5)
local fillW = math.max(th, math.min(tw, kx - tx))
if not MAT_GRADIENT_L:IsError() then
local gcol = Color(self._tint.r, self._tint.g, self._tint.b, 170)
if RNDX and RNDX.DrawMaterial then RNDX.DrawMaterial(th/2, tx, ty, fillW, th, gcol, MAT_GRADIENT_L, 0)
else draw.RoundedBox(th/2, tx, ty, fillW, th, self._tint) end
else
draw.RoundedBox(th/2, tx, ty, fillW, th, self._tint)
end
SUI.Draw.Panel(kx - kn/2, ky, kn, kn, {radius = kn/2, color = Color(240,240,255, math.floor(180 + 50*self._knobA)), glass = true, stroke = true})
local num = fmtn(self._lerpVal, self._dec)
local scale = 1 + 0.40 * self._pulse
local fs = SUI.Font(math.Clamp(math.floor(18 * scale), 10, 200))
draw.SimpleText(num, fs, w - SUI.Scale(8), h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
sld._lastText = fmtn(sld._value, sld._dec)
return sld
end
function Components.CreateDropdown(parent, opts)
opts = opts or {}
local combo = vgui.Create("DComboBox", parent)
combo._valueStr = ""
combo._placeholder = opts.placeholder or "Выберите…"
combo._tint = opts.tint or PalettePick("dropdown")
combo:SetText("")
if IsValid(combo.DropButton) then
combo.DropButton:SetText("")
combo.DropButton.Paint = function() end
end
function combo:SetText(_) end
function combo:SetValue(str) self._valueStr = tostring(str or "") end
function combo:GetValue() return self._valueStr or "" end
function combo:Paint(w,h)
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = Style.panelColor, glass = true, stroke = true})
if not MAT_GRADIENT_L:IsError() then
local gw = w * 0.45
local col = Color(self._tint.r, self._tint.g, self._tint.b, 90)
RNDX.DrawMaterial(Style.radius, 0, 0, gw, h, col, MAT_GRADIENT_L, 0)
end
local txt = (self._valueStr ~= "" and self._valueStr) or self._placeholder
draw.SimpleText(txt, libNyx.UI.Font(libNyx.UI.Scale(20)), Style.padding, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
draw.SimpleText("", libNyx.UI.Font(libNyx.UI.Scale(18)), w - Style.padding, h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
local function normalizeChoices()
local out, ch = {}, istable(opts.choices) and opts.choices or {}
for _, v in ipairs(ch) do
if istable(v) then
local lbl = v.label or v.text or v[1] or ""
local ic = v.icon or v[2]
if isstring(ic) then ic = Material(ic, "noclamp smooth") end
out[#out+1] = { label = tostring(lbl), icon = ic }
else
out[#out+1] = { label = tostring(v) }
end
end
return out
end
function combo:OpenMenu()
if IsValid(self.Menu) then self.Menu:Remove() self.Menu = nil end
local m = DermaMenu(self)
m:SetDrawOnTop(true)
m:SetPaintBackground(false)
m:SetPaintBorderEnabled(false)
local radius = math.max(Style.radius, libNyx.UI.Scale(12))
local itemH = math.max(libNyx.UI.Scale(58), tonumber(opts.itemHeight) or 0)
local iconSize = libNyx.UI.Scale(22)
local fontOpt = libNyx.UI.Font(libNyx.UI.Scale(20))
m._openT = SysTime()
m._dur = 0.18
m._rev = 0
m._tint = self._tint
m.Paint = function(s,w,h)
local t = math.TimeFraction(s._openT, s._openT + s._dur, SysTime())
s._rev = math.Clamp(1 - (1 - t) ^ 3, 0, 1)
local clip = s._rev < 0.999
if clip then
local sx, sy = s:LocalToScreen(0, 0)
local rh = math.max(1, h * s._rev)
render.SetScissorRect(sx, sy, sx + w, sy + rh, true)
end
libNyx.UI.Draw.Glass(0, 0, w, h, {radius = radius, fill = Color(16,18,24, 70), stroke = true, strokeColor = Color(255,255,255,22), blurIntensity = 1.35})
if clip then render.SetScissorRect(0,0,0,0,false) end
end
for _, data in ipairs(normalizeChoices()) do
local label, icon = data.label, data.icon
local opt = m:AddOption(label, function()
self:SetValue(label)
if isfunction(opts.onSelect) then opts.onSelect(label) end
end)
opt:SetText("")
opt:SetTall(itemH)
opt._label = label
opt._icon = icon
opt._iconSize = iconSize
opt._tint = self._tint
makeRipple(opt,2)
function opt:OnCursorEntered()
local mx, my = gui.MousePos()
mx, my = self:ScreenToLocal(mx, my)
self._ripples = self._ripples or {}
table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()})
surface.PlaySound("buttons/lightswitch2.wav")
end
opt.Paint = function(s, w, h)
local hovered = s:IsHovered()
local r = libNyx.UI.Scale(10)
libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Color(20,24,32, hovered and 95 or 65), stroke = false, blurIntensity = hovered and 1.20 or 0.95})
if hovered and not MAT_GRADIENT_L:IsError() then
local gcol = Color(s._tint.r, s._tint.g, s._tint.b, 120)
RNDX.DrawMaterial(r, 0, 0, math.floor(w * 0.55), h, gcol, MAT_GRADIENT_L, 0)
end
local x = Style.padding
if s._icon and not s._icon:IsError() then
surface.SetDrawColor(Style.textColor)
surface.SetMaterial(s._icon)
surface.DrawTexturedRect(x, (h - s._iconSize)/2, s._iconSize, s._iconSize)
x = x + s._iconSize + Style.padding
end
draw.SimpleText(s._label or "", fontOpt, x, h/2, Style.textColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
paintRipples(s, w, h, Style.textColor)
end
end
local x, y = self:LocalToScreen(0, self:GetTall() + 4)
m:Open()
m:SetMinimumWidth(self:GetWide())
m:SetAlpha(0)
m:SetPos(x, y - libNyx.UI.Scale(8))
m:MoveTo(x, y, m._dur, 0, 0.2)
m:AlphaTo(255, m._dur, 0)
self.Menu = m
return m
end
return combo
end
function Components.CreateList(parent, opts)
opts = opts or {}
local list = vgui.Create("DScrollPanel", parent)
list._rowH = math.max(libNyx.UI.Scale(66), opts.rowHeight or Style.rowHeight)
list._rows = {}
local vbar = list:GetVBar()
vbar:SetWide(opts.vbarWidth or libNyx.UI.Scale(12))
function vbar:Paint(w,h) draw.RoundedBox(4,0,0,w,h, Color(36,36,44,200)) end
function vbar.btnUp:Paint() end
function vbar.btnDown:Paint() end
function vbar.btnGrip:Paint(w,h) draw.RoundedBox(4,0,0,w,h, Style.accentColor) end
function list:SetRowHeight(h) self._rowH = math.max(libNyx.UI.Scale(66), h or self._rowH) end
function list:GetSelected() return self._selected end
function list:SetSelected(row) self._selected = row end
function list:ClearRows()
for _, r in ipairs(self._rows) do if IsValid(r) then r:Remove() end end
self._rows = {}
self._selected = nil
end
local function makeRowPaint(row)
row.Paint = function(self, w, h)
local base = (list:GetSelected() == self) and Color(Style.accentColor.r,Style.accentColor.g,Style.accentColor.b, 190)
or (self:IsHovered() and Style.hoverColor or Style.cardColor)
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = base, glass = true})
if not self._plain and not MAT_GRADIENT_L:IsError() then
local gw = w * 0.5
local tint = PalettePick(self._title or self._rightText or "row")
local col = Color(tint.r, tint.g, tint.b, Style.gradientAlphaRow)
RNDX.DrawMaterial(Style.radius, 0, 0, gw, h, col, MAT_GRADIENT_L, 0)
end
local pad = Style.padding
local x = pad
local y = pad
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(22)))
local rightW = 0
if self._rightText and self._rightText ~= "" then
rightW = select(1, surface.GetTextSize(self._rightText)) + pad
end
local rightLimitX = w - pad - rightW
if self._icon and not self._icon:IsError() then
local ic = math.max(libNyx.UI.Scale(24), Style.iconSize)
surface.SetDrawColor(255,255,255)
surface.SetMaterial(self._icon)
surface.DrawTexturedRect(x, (h - ic)/2, ic, ic)
x = x + ic + pad
end
draw.SimpleText(self._title or "Без названия", libNyx.UI.Font(libNyx.UI.Scale(26)), x, y + libNyx.UI.Scale(2), Style.textColor)
if istable(self._labels) and #self._labels > 0 then
local chipH = libNyx.UI.Scale(26)
local chipY = h - pad - chipH
local cx = x
local hidden = 0
surface.SetFont(libNyx.UI.Font(libNyx.UI.Scale(18)))
for i, chip in ipairs(self._labels) do
local t = chip.text or tostring(chip) or ""
local tw = select(1, surface.GetTextSize(t))
local cw, ch = tw + libNyx.UI.Scale(18), chipH
if (cx + cw) > rightLimitX then
hidden = (#self._labels - i + 1)
break
end
local baseCol = chip.color or PalettePick(t)
local fill = Color( math.max(0, baseCol.r - 10), math.max(0, baseCol.g - 10), math.max(0, baseCol.b - 10), 175 )
local chipR = libNyx.UI.Scale(9)
libNyx.UI.Draw.Panel(cx, chipY, cw, ch, {radius = chipR, color = fill, glass = true, stroke = true, strokeColor = Color(255,255,255,18)})
if not MAT_GRADIENT_L:IsError() then
local gw = cw * 0.65
local gcol = Color(baseCol.r, baseCol.g, baseCol.b, Style.gradientAlphaChip)
RNDX.DrawMaterial(chipR, cx, chipY, gw, ch, gcol, MAT_GRADIENT_L, 0)
end
draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(18)), cx + cw/2, chipY + ch/2, chip.textColor or Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
cx = cx + cw + libNyx.UI.Scale(6)
end
if hidden > 0 and (cx + libNyx.UI.Scale(34)) <= rightLimitX then
local t = "+" .. hidden
local tw = select(1, surface.GetTextSize(t))
local cw, ch = tw + libNyx.UI.Scale(18), chipH
local chipR = libNyx.UI.Scale(9)
libNyx.UI.Draw.Panel(cx, chipY, cw, ch, {radius = chipR, color = Color(36,36,44,175), glass = true, stroke = true, strokeColor = Color(255,255,255,18)})
if not MAT_GRADIENT_L:IsError() then
local gw = cw * 0.65
local gcol = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, Style.gradientAlphaChip)
RNDX.DrawMaterial(chipR, cx, chipY, gw, ch, gcol, MAT_GRADIENT_L, 0)
end
draw.SimpleText(t, libNyx.UI.Font(libNyx.UI.Scale(18)), cx + cw/2, chipY + ch/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
if self._rightText and self._rightText ~= "" then
draw.SimpleText(self._rightText, libNyx.UI.Font(libNyx.UI.Scale(28)), w - pad, h/2, Style.textColor, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
paintRipples(self, w, h, Style.textColor)
end
end
function list:AddRow(data)
data = data or {}
local row = list:Add("DButton")
row:Dock(TOP)
row:DockMargin(0, 0, 0, libNyx.UI.Scale(10))
row:SetTall(self._rowH)
row:SetText("")
row._title = data.title or ""
row._subtitle = data.subtitle
row._icon = data.icon
row._labels = istable(data.labels) and data.labels or {}
row._rightText = data.rightText
row._onClick = data.onClick
row._plain = data.gradient == false or data.plain == true
makeRipple(row)
makeRowPaint(row)
function row:DoClick()
list:SetSelected(self)
if isfunction(self._onClick) then self._onClick(self) end
end
function row:OnCursorEntered()
local mx, my = gui.MousePos()
mx, my = self:ScreenToLocal(mx, my)
table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()})
surface.PlaySound("buttons/lightswitch2.wav")
end
table.insert(self._rows, row)
return row
end
return list
end
function Components.CreateVBox(parent, opts)
opts = opts or {}
local w = opts.w or libNyx.UI.Scale(140)
local h = opts.h or libNyx.UI.Scale(200)
local pnl = vgui.Create("DButton", parent)
pnl:SetText("")
pnl:SetSize(w, h)
pnl._variant = (opts.variant or "center_gradient")
pnl._tint = opts.tint or Color(140,120,255)
pnl._title = opts.title or ""
pnl._icon = opts.icon
pnl._iconSize = opts.iconSize or libNyx.UI.Scale(40)
pnl._onClick = opts.onClick
pnl._phase = 0
pnl._hover = false
pnl._hoverA = 0
pnl._pulseT = 0
pnl._titleFont = libNyx.UI.Font(libNyx.UI.Scale(22))
pnl._modelA = 0
pnl._spinA = 0
if isstring(pnl._icon) then pnl._icon = Material(pnl._icon, "noclamp smooth") end
function pnl:DoClick()
surface.PlaySound("nyx_uniqueui/nyxclick_3.wav")
if isfunction(self._onClick) then self._onClick(self) end
end
function pnl:OnCursorEntered()
self._hover = true
surface.PlaySound("nyx_uniqueui/nyxclick_2.wav")
end
function pnl:OnCursorExited()
self._hover = false
end
local wantModel = (pnl._variant == "model") or (pnl._variant == "vertical_gradient" and isstring(opts.model) and opts.model ~= "")
if wantModel then
pnl._mp = vgui.Create("DModelPanel", pnl)
pnl._mp:SetSize(w, h)
pnl._mp:SetPos(0, 0)
pnl._mp:SetModel(opts.model or "models/props_c17/oildrum001.mdl")
pnl._mp:SetFOV(28)
function pnl._mp:LayoutEntity(ent) end
pnl._baseCam = nil
local function FitModelLocal(mp)
if not IsValid(mp) or not IsValid(mp.Entity) then return end
local mn, mx = mp.Entity:GetRenderBounds()
local size = mx - mn
local center = (mn + mx) * 0.5
local maxdim = math.max(size.x, size.y, size.z)
local ang = Angle(12, 25, 0)
local fov = mp:GetFOV()
local dist = (maxdim * 0.52) / math.tan(math.rad(fov * 0.5))
local camPos = center + ang:Forward() * -dist + Vector(0, 0, size.z * 0.04)
mp:SetCamPos(camPos)
mp:SetLookAt(center)
mp:SetAmbientLight(Vector(255, 255, 255))
mp:SetDirectionalLight(BOX_TOP, Color(255,255,255))
mp:SetDirectionalLight(BOX_FRONT, Color(pnl._tint.r, pnl._tint.g, pnl._tint.b))
mp:SetDirectionalLight(BOX_RIGHT, Color(210,220,255))
mp:SetDirectionalLight(BOX_LEFT, Color(210,210,210))
mp:SetDirectionalLight(BOX_BACK, Color(190,200,220))
mp:SetDirectionalLight(BOX_BOTTOM, Color(160,170,180))
pnl._baseCam = {center = center, dist = dist, ang = ang}
end
timer.Simple(0, function() if IsValid(pnl._mp) then FitModelLocal(pnl._mp) end end)
end
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth")
local function lighten(c, k)
return Color(
math.Clamp(c.r + (255 - c.r) * k, 0, 255),
math.Clamp(c.g + (255 - c.g) * k, 0, 255),
math.Clamp(c.b + (255 - c.b) * k, 0, 255),
c.a or 255
)
end
local function expApproach(cur, tgt, spd)
local k = 1 - math.exp(-(spd or 10) * FrameTime())
return cur + (tgt - cur) * k
end
pnl.Think = function(self)
self._hoverA = expApproach(self._hoverA, self._hover and 1 or 0, 8)
self._pulseT = (self._pulseT + FrameTime() * (0.8 + 1.2 * self._hoverA)) % (math.pi * 2)
if self._variant == "sunburst" then
local sp = Lerp(self._hoverA, 0.35, 1.1)
self._phase = (self._phase + FrameTime() * sp) % (math.pi * 2)
end
if IsValid(self._mp) and self._baseCam then
self._modelA = expApproach(self._modelA, self._hover and 1 or 0, 6)
self._spinA = expApproach(self._spinA, self._hover and 1 or 0, 5)
local distMul = Lerp(self._modelA, 1.0, 0.75)
local yawJig = math.sin(CurTime() * (0.6 + 1.0 * self._spinA)) * 6 * self._spinA
local ang = Angle(self._baseCam.ang.p, self._baseCam.ang.y + yawJig, self._baseCam.ang.r)
local camPos = self._baseCam.center + ang:Forward() * -(self._baseCam.dist * distMul)
self._mp:SetCamPos(camPos)
self._mp:SetLookAt(self._baseCam.center)
self._mp:SetFOV(Lerp(self._modelA, 28, 26))
end
end
pnl.Paint = function(self, w, h)
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = Style.radius, color = Color(0,0,0,0), glass = true, stroke = true})
local cx, cy = w * 0.5, h * 0.5
if self._variant == "center_gradient" then
local base = self._tint
local light = lighten(base, 0.26) light.a = math.floor(Lerp(self._hoverA, 120, 155))
local coverage = Lerp(self._hoverA, 0.80, 0.92)
local halfCov = math.floor(w * coverage * 0.5)
local cxpix = math.floor(w * 0.5 + math.sin(self._pulseT) * libNyx.UI.Scale(4) * self._hoverA)
local overlap = 2
RNDX.DrawMaterial(0, cxpix - halfCov - overlap, 0, halfCov + overlap, h, light, MAT_GRADIENT_R, 0)
RNDX.DrawMaterial(0, cxpix, 0, halfCov + overlap, h, light, MAT_GRADIENT_L, 0)
elseif self._variant == "vertical_gradient" then
local base = self._tint
local col = Color(
math.Clamp(base.r + (255 - base.r) * Lerp(self._hoverA, 0.18, 0.28), 0, 255),
math.Clamp(base.g + (255 - base.g) * Lerp(self._hoverA, 0.18, 0.28), 0, 255),
math.Clamp(base.b + (255 - base.b) * Lerp(self._hoverA, 0.18, 0.28), 0, 255),
Lerp(self._hoverA, 120, 165)
)
local gh = math.floor(h * Lerp(self._hoverA, 0.65, 0.78))
local y0 = h - gh - math.floor(libNyx.UI.Scale(4) * self._hoverA)
local MAT_GRADIENT_U = Material("vgui/gradient-u", "noclamp smooth")
local rect = RNDX().Rect(0, y0, w, gh):Radii(0, 0, Style.radius, Style.radius):Material(MAT_GRADIENT_U):Color(col)
local mat = rect:GetMaterial()
surface.SetMaterial(mat)
surface.SetDrawColor(col)
surface.DrawTexturedRectUV(0, y0, w, gh, 0, 1, 1, 0)
elseif self._variant == "sunburst" then
local rays, step = 18, (math.pi * 2) / 18
local R = math.max(w, h)
local a1 = math.floor(Lerp(self._hoverA, 95, 140))
local a2 = math.floor(Lerp(self._hoverA, 40, 80))
local col1 = Color(self._tint.r, self._tint.g, self._tint.b, a1)
local col2 = Color(self._tint.r, self._tint.g, self._tint.b, a2)
draw.NoTexture()
for i = 0, rays - 1 do
local a0 = self._phase + i * step
local a1r = a0 + step * Lerp(self._hoverA, 0.55, 0.70)
local v = {
{x = cx, y = cy},
{x = cx + math.cos(a0) * R, y = cy + math.sin(a0) * R},
{x = cx + math.cos(a1r) * R, y = cy + math.sin(a1r) * R},
}
surface.SetDrawColor((i % 2 == 0) and col1 or col2)
surface.DrawPoly(v)
end
end
if not wantModel and self._icon and not self._icon:IsError() then
local s = self._iconSize * (1 + 0.08 * self._hoverA + 0.04 * math.sin(self._pulseT))
surface.SetDrawColor(Style.textColor)
surface.SetMaterial(self._icon)
surface.DrawTexturedRect(cx - s/2, cy - s/2 - libNyx.UI.Scale(10), s, s)
end
end
pnl.PaintOver = function(self, w, h)
local padX = libNyx.UI.Scale(10)
local padY = libNyx.UI.Scale(6)
local txt = self._title or ""
surface.SetFont(self._titleFont)
local tw, th = surface.GetTextSize(txt)
local bw, bh = tw + padX * 2, th + padY * 2
local x = math.floor((w - bw) / 2)
local y = h - Style.padding - bh - math.floor(libNyx.UI.Scale(2) * self._hoverA)
libNyx.UI.Draw.Glass(x, y, bw, bh, {radius = libNyx.UI.Scale(8), fill = Color(16,18,24, math.floor(150 + 30 * self._hoverA)), stroke = true, strokeColor = Color(255,255,255,20), blurIntensity = 1.0})
draw.SimpleText(txt, self._titleFont, x + bw/2, y + bh/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
return pnl
end
function Components.CreateTabs(parent, opts)
opts = opts or {}
local pnl = vgui.Create("DPanel", parent)
pnl._items = istable(opts.items) and opts.items or {}
pnl._active = opts.default
pnl._onChange = opts.onChange
pnl:SetPaintBackground(false)
pnl:SetPaintBorderEnabled(false)
pnl:SetPaintBackgroundEnabled(false)
pnl.Paint = nil
local font = libNyx.UI.Font(libNyx.UI.Scale(18))
local tabH = math.max(libNyx.UI.Scale(42), tonumber(opts.height or 0))
local padY = libNyx.UI.Scale(6)
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local rail = vgui.Create("DPanel", pnl)
rail:Dock(FILL)
rail:DockMargin(Style.padding, padY, Style.padding, padY)
rail:SetPaintBackground(false)
rail:SetPaintBorderEnabled(false)
rail:SetPaintBackgroundEnabled(false)
rail.Paint = nil
pnl._btns = {}
rail._indX = 0
rail._indW = 0
rail._indH = tabH
rail._tgtX = 0
rail._tgtW = 0
rail._tgtH = tabH
rail._haveInd = false
local function syncIndicatorTo(id, snap)
local b = pnl._btns[id]
if not IsValid(b) then rail._haveInd = false return end
local x, y = b:GetPos()
local w, h = b:GetSize()
rail._tgtX = x
rail._tgtW = w
rail._tgtH = tabH
rail._haveInd = true
if snap then
rail._indX = rail._tgtX
rail._indW = rail._tgtW
rail._indH = rail._tgtH
end
rail:InvalidateLayout(false)
end
local function expApproach(cur, tgt, speed)
local k = 1 - math.exp(-(speed or 12) * FrameTime())
return cur + (tgt - cur) * k
end
rail.Think = function(s)
if not s._haveInd then return end
local hovering = false
for _, b in pairs(pnl._btns) do
if IsValid(b) and b:IsHovered() then hovering = true break end
end
local grow = libNyx.UI.Scale(6)
s._tgtH = hovering and (tabH + grow) or tabH
s._indX = expApproach(s._indX or s._tgtX, s._tgtX, 10)
s._indW = expApproach(s._indW or s._tgtW, s._tgtW, 12)
s._indH = expApproach(s._indH or s._tgtH, s._tgtH, 9)
end
rail.Paint = function(s, w, h)
if not s._haveInd then return end
local r = libNyx.UI.Scale(10)
local bx = math.floor(s._indX + 0.5)
local bw = math.floor(s._indW + 0.5)
local bh = math.floor(s._indH + 0.5)
local by = math.floor((tabH - bh) * 0.5)
libNyx.UI.Draw.Panel(bx, by, bw, bh, {radius = r, color = Color(32,38,48,135), glass = true})
if not MAT_GRADIENT_L:IsError() then
local c = Color(Style.accentColor.r, Style.accentColor.g, Style.accentColor.b, 120)
RNDX.DrawMaterial(r, bx, by, bw, bh, c, MAT_GRADIENT_L, 0)
end
end
local function makeBtn(it, idx)
local b = vgui.Create("DButton", rail)
b:SetText("")
b:SetDrawBackground(false)
b:SetPaintBackgroundEnabled(false)
b:SetPaintBorderEnabled(false)
b:Dock(LEFT)
b:DockMargin(libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6), libNyx.UI.Scale(6))
b:SetTall(tabH)
b._id = tostring(it.id or it.label or idx or "")
b._label = tostring(it.label or b._id)
b._icon = isstring(it.icon) and Material(it.icon, "noclamp smooth") or it.icon
b._iconSize = libNyx.UI.Scale(18)
surface.SetFont(font)
local tw = surface.GetTextSize(b._label)
local padX = libNyx.UI.Scale(16)
local icw = (b._icon and not b._icon:IsError()) and (b._iconSize + libNyx.UI.Scale(6)) or 0
b:SetWide(padX * 2 + icw + tw)
makeRipple(b, 2)
function b:OnCursorEntered()
local mx, my = gui.MousePos()
mx, my = self:ScreenToLocal(mx, my)
self._ripples = self._ripples or {}
table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()})
if libNyx.UI and libNyx.UI.PlayHover then libNyx.UI.PlayHover() end
end
function b:DoClick()
pnl:SetActive(self._id)
if isfunction(pnl._onChange) then pnl._onChange(self._id, self) end
surface.PlaySound("ui/buttonclickrelease.wav")
end
b.Paint = function(s, w, h)
local selected = (pnl._active == s._id)
local r = libNyx.UI.Scale(10)
if not selected and s:IsHovered() then
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(30,34,42,90), glass = true})
end
local x = padX
if s._icon and not s._icon:IsError() then
surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, selected and 255 or 200)
surface.SetMaterial(s._icon)
surface.DrawTexturedRect(x, (h - s._iconSize) / 2, s._iconSize, s._iconSize)
x = x + s._iconSize + libNyx.UI.Scale(6)
end
draw.SimpleText(
s._label, font, x, h / 2,
selected and Style.textColor or Color(Style.textColor.r, Style.textColor.g, Style.textColor.b, 200),
TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER
)
paintRipples(s, w, h, Style.textColor)
end
pnl._btns[b._id] = b
return b
end
function pnl:SetItems(items)
for _, c in ipairs(rail:GetChildren()) do if IsValid(c) then c:Remove() end end
pnl._btns = {}
pnl._items = items or {}
for i, it in ipairs(pnl._items) do makeBtn(it, i) end
rail:InvalidateLayout(true)
timer.Simple(0, function()
if not IsValid(pnl) then return end
local first = pnl._active
if not first and #pnl._items > 0 then
first = pnl._items[1].id or pnl._items[1].label
pnl._active = first
end
syncIndicatorTo(pnl._active, true)
end)
end
function pnl:SetActive(id)
pnl._active = id
syncIndicatorTo(id, false)
rail:InvalidateLayout(false)
end
function pnl:GetActive()
return pnl._active
end
pnl.SetOnChange = function(self, fn) self._onChange = fn end
pnl:SetItems(pnl._items)
if pnl._active == nil and #pnl._items > 0 then
pnl:SetActive(pnl._items[1].id or pnl._items[1].label)
else
pnl:SetActive(pnl._active)
end
return pnl
end
function Components.CreateCategoryCard(parent, opts)
opts = opts or {}
local card = vgui.Create("DButton", parent)
card:SetText("")
card:SetTall(opts.h or libNyx.UI.Scale(120))
card._title = opts.title or "Category"
card._desc = opts.desc or ""
card._icon = opts.icon
if isstring(card._icon) then
card._icon = Material(card._icon, "noclamp smooth")
end
if not (card._icon and not card._icon:IsError()) then
card._icon = Material("icon16/star.png", "noclamp smooth")
end
card._from = opts.from or Color(125, 82,255)
card._to = opts.to or Color( 40,192,255)
card._variant = opts.variant or "vibrant"
card._radius = opts.radius or libNyx.UI.Scale(18)
card._onClick = opts.onClick
card._titleFont = libNyx.UI.Font(libNyx.UI.Scale(30))
card._descFont = libNyx.UI.Font(libNyx.UI.Scale(17))
card._hoverA = 0
makeRipple(card)
function card:DoClick()
if libNyx.UI and libNyx.UI.PlayClick then libNyx.UI.PlayClick() end
if isfunction(self._onClick) then self._onClick(self) end
end
function card:OnCursorEntered()
local mx, my = gui.MousePos()
mx, my = self:ScreenToLocal(mx, my)
self._ripples = self._ripples or {}
table.insert(self._ripples, {x = mx, y = my, t0 = SysTime()})
if libNyx.UI and libNyx.UI.PlayHover then libNyx.UI.PlayHover() end
end
local function expApproach(cur, tgt, spd)
local k = 1 - math.exp(-(spd or 10) * FrameTime())
return cur + (tgt - cur) * k
end
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local MAT_GRADIENT_R = Material("vgui/gradient-r", "noclamp smooth")
local function lighten(c, k)
return Color(
math.Clamp(c.r + (255 - c.r) * k, 0, 255),
math.Clamp(c.g + (255 - c.g) * k, 0, 255),
math.Clamp(c.b + (255 - c.b) * k, 0, 255),
c.a or 255
)
end
card.Think = function(self)
self._hoverA = expApproach(self._hoverA or 0, self:IsHovered() and 1 or 0, 10)
end
card.Paint = function(self, w, h)
local r = self._radius
local a = math.Clamp(self._hoverA or 0, 0, 1)
if self._variant == "glass" then
local fillA = math.floor(Lerp(a, 120, 145))
local strokeA = math.floor(Lerp(a, 20, 32))
libNyx.UI.Draw.Glass(0, 0, w, h, {radius = r, fill = Color(20,24,32, fillA), stroke = true, strokeColor = Color(255,255,255, strokeA), blurIntensity = 1.1 + 0.15 * a})
if not MAT_GRADIENT_L:IsError() then
local c1 = Color(self._from.r, self._from.g, self._from.b, math.floor(Lerp(a, 95, 140)))
local c2 = Color(self._to.r, self._to.g, self._to.b, math.floor(Lerp(a, 95, 140)))
RNDX.DrawMaterial(r, 0, 0, w*0.65, h, c1, MAT_GRADIENT_L, 0)
RNDX.DrawMaterial(r, w*0.35, 0, w*0.65, h, c2, MAT_GRADIENT_R, 0)
end
else
local baseCol = Color(self._from.r, self._from.g, self._from.b, math.floor(Lerp(a, 220, 235)))
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = baseCol, shadow = true})
if not (MAT_GRADIENT_L:IsError() or MAT_GRADIENT_R:IsError()) then
local colL = Color(self._to.r, self._to.g, self._to.b, 255)
local colR = Color(self._from.r, self._from.g, self._from.b, 255)
RNDX.DrawMaterial(r, 0, 0, w, h, colL, MAT_GRADIENT_L, 0)
RNDX.DrawMaterial(r, 0, 0, w, h, colR, MAT_GRADIENT_R, 0)
end
local outlineA = math.floor(18 * a)
if outlineA > 0 then
RNDX.DrawOutlined(r, 0, 0, w, h, Color(255,255,255, outlineA), 1)
end
end
if self._icon and not self._icon:IsError() then
local sz = math.floor(h * 1.25)
local s = 1 + 0.06 * a
local sz2 = math.floor(sz * s)
local oy = -libNyx.UI.Scale(4) * a
local alpha = (self._variant == "vibrant") and math.floor(Lerp(a, 44, 64)) or math.floor(Lerp(a, 36, 54))
surface.SetDrawColor(255,255,255, alpha)
surface.SetMaterial(self._icon)
surface.DrawTexturedRect(w - sz2 + libNyx.UI.Scale(28), (h - sz2) / 2 + oy, sz2, sz2)
end
local padX = libNyx.UI.Scale(18)
local topY = libNyx.UI.Scale(18)
surface.SetFont(self._titleFont)
local _, th = surface.GetTextSize(self._title or "")
local titleCol = lighten(Style.textColor, a * 0.08)
draw.SimpleText(self._title, self._titleFont, padX, topY + th/2, titleCol, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
surface.SetFont(self._descFont)
local _, dh = surface.GetTextSize(self._desc or "")
draw.SimpleText(self._desc or "", self._descFont, padX, topY + th + libNyx.UI.Scale(6) + dh/2, Color(255,255,255, math.floor(Lerp(a, 200, 230))), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
local dashW, dashH = libNyx.UI.Scale(18), libNyx.UI.Scale(4)
local dashA = math.floor(Lerp(a, 70, 110))
libNyx.UI.Draw.Panel(padX, h - libNyx.UI.Scale(18), dashW, dashH, {radius = dashH/2, color = Color(255,255,255, dashA)})
paintRipples(self, w, h, Style.textColor)
end
return card
end
function Components.CreateSearchBox(parent, opts)
opts = opts or {}
local box = vgui.Create("DPanel", parent)
box:SetTall(opts.h or libNyx.UI.Scale(38))
box._radius = opts.radius or libNyx.UI.Scale(12)
box._placeholder = opts.placeholder or "Search"
box._tint = opts.tint or Style.accentColor
box._debounce = tonumber(opts.debounce or 0.12)
box._focusA = 0
box._gradA = 0
box._langID = "EN"
box._lastText = ""
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
local MAT_ICON_SEARCH = Material("icon16/magnifier.png", "noclamp smooth")
local MAT_ICON_CLEAR = Material("icon16/cross.png", "noclamp smooth")
local btnClear = vgui.Create("DButton", box)
btnClear:SetText("")
btnClear:Dock(RIGHT)
btnClear:SetWide(box:GetTall())
btnClear.Paint = function(s,w,h)
if not IsValid(box.Entry) or (box.Entry:GetText() == "" or box.Entry:GetText() == nil) then return end
if s:IsHovered() then
libNyx.UI.Draw.Panel(libNyx.UI.Scale(6), libNyx.UI.Scale(5), w-libNyx.UI.Scale(12), h-libNyx.UI.Scale(10), {radius = (h-libNyx.UI.Scale(10))/2, color = Color(30,34,42,110), glass = true})
end
surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, 210)
local ic = libNyx.UI.Scale(14)
surface.SetMaterial(MAT_ICON_CLEAR)
surface.DrawTexturedRect((w-ic)/2, (h-ic)/2, ic, ic)
end
btnClear.DoClick = function()
if not IsValid(box.Entry) then return end
box.Entry:SetText("")
if isfunction(opts.onClear) then opts.onClear() end
if isfunction(opts.onChange) then opts.onChange("") end
end
local entry = vgui.Create("DTextEntry", box)
entry:SetUpdateOnType(true)
entry:SetText("")
entry:SetFont(libNyx.UI.Font(libNyx.UI.Scale(18)))
entry:SetTextColor(Style.textColor)
entry:SetCursorColor(Style.textColor)
entry:SetHistoryEnabled(false)
if entry.SetDrawLanguageID then entry:SetDrawLanguageID(false) end
box.Entry = entry
function box:PerformLayout(w,h)
local leftPad = libNyx.UI.Scale(12) + libNyx.UI.Scale(16) + libNyx.UI.Scale(8)
local rightPad = btnClear:GetWide() + libNyx.UI.Scale(6)
entry:SetPos(leftPad, 0)
entry:SetSize(w - leftPad - rightPad, h)
end
function entry:Paint(w,h)
self:DrawTextEntryText(Style.textColor, Color(255,255,255,20), Style.textColor)
end
function entry:OnChange()
if not isfunction(opts.onChange) then return end
local name = "libnyx_search_" .. tostring(box)
timer.Create(name, box._debounce, 1, function()
if IsValid(box) and IsValid(box.Entry) then
opts.onChange(box.Entry:GetText() or "")
end
end)
end
function entry:OnEnter()
if isfunction(opts.onSubmit) then opts.onSubmit(self:GetText() or "") end
end
function entry:OnGetFocus()
box._focused = true
end
function entry:OnLoseFocus()
box._focused = false
end
local function expApproach(cur, tgt, spd)
local k = 1 - math.exp(-(spd or 10) * FrameTime())
return cur + (tgt - cur) * k
end
local function isCyrillicChar(ch)
if not ch or ch == "" then return false end
if utf8 and utf8.codepoint then
local cp = utf8.codepoint(ch)
if cp then
if (cp >= 0x0400 and cp <= 0x04FF) or (cp >= 0x0500 and cp <= 0x052F) then return true end
end
end
return false
end
box.Think = function(self)
local f = self._focused and 1 or 0
self._focusA = expApproach(self._focusA, f, 9)
self._gradA = expApproach(self._gradA, f, 7)
local id
if entry.GetLanguageID then
id = entry:GetLanguageID()
if isstring(id) and #id >= 2 then
id = string.upper(string.sub(id,1,2))
else
id = nil
end
end
if not id then
local t = entry:GetText() or ""
if t ~= self._lastText then
self._lastText = t
local len = (utf8 and utf8.len and utf8.len(t)) or #t
if len > 0 and utf8 and utf8.sub then
local ch = utf8.sub(t, len, len)
if isCyrillicChar(ch) then
id = "RU"
end
end
end
end
if id then self._langID = id end
end
function box:Paint(w, h)
libNyx.UI.Draw.Glass(0, 0, w, h, {radius = self._radius, fill = Color(16,18,24,110), stroke = true, strokeColor = Style.glassStroke, blurIntensity = 1.0})
local baseCov = 0.45
local focusedCov = 0.34
local cov = Lerp(self._gradA, baseCov, focusedCov)
if not MAT_GRADIENT_L:IsError() then
local gcol = Color(self._tint.r, self._tint.g, self._tint.b, 70)
local gw = w * cov
RNDX.DrawMaterial(self._radius, 0, 0, gw, h, gcol, MAT_GRADIENT_L, 0)
end
local ic = libNyx.UI.Scale(16)
local ix = libNyx.UI.Scale(12)
surface.SetDrawColor(Style.textColor.r, Style.textColor.g, Style.textColor.b, 190)
surface.SetMaterial(MAT_ICON_SEARCH)
surface.DrawTexturedRect(ix, (h-ic)/2, ic, ic)
if (entry:GetText() == "" or entry:GetText() == nil) and not entry:HasFocus() then
draw.SimpleText(self._placeholder, libNyx.UI.Font(libNyx.UI.Scale(18)), ix + ic + libNyx.UI.Scale(8), h/2, Color(255,255,255,120), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
end
local focusAlpha = math.floor(180 * self._focusA)
if focusAlpha > 1 then
local sw = math.max(1, math.floor(1 + 2 * self._focusA))
RNDX.DrawOutlined(self._radius, 0, 0, w, h, Color(self._tint.r, self._tint.g, self._tint.b, focusAlpha), sw, 0)
end
if entry:HasFocus() then
local code = self._langID or "EN"
local showRU = code == "RU"
local rightPad = btnClear:GetWide() + libNyx.UI.Scale(6)
local rx = w - rightPad - libNyx.UI.Scale(6)
if showRU then
local ph = libNyx.UI.Scale(18)
local px = rx - libNyx.UI.Scale(4)
local py = (h - ph) / 2
local pw = libNyx.UI.Scale(28)
libNyx.UI.Draw.Panel(px - pw, py, pw, ph, {radius = ph/2, color = Color(30,34,42,160), glass = true})
draw.SimpleText("RU", libNyx.UI.Font(libNyx.UI.Scale(14)), px - pw/2, h/2, Style.textColor, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
else
draw.SimpleText(code, libNyx.UI.Font(libNyx.UI.Scale(14)), rx, h/2, Color(255,255,255,150), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
end
end
function box:GetText() return entry:GetText() end
function box:SetText(t) entry:SetText(t or "") end
function box:Focus() entry:RequestFocus() end
return box
end
libNyx.UI.SmoothScroll = libNyx.UI.SmoothScroll or {}
function libNyx.UI.SmoothScroll.ApplyToScrollPanel(sp, opts)
if not IsValid(sp) or sp._libnyxSmoothInstalled then return end
local vbar = sp:GetVBar()
if not IsValid(vbar) then return end
sp._libnyxSmoothInstalled = true
local step = tonumber(opts and opts.step) or libNyx.UI.Scale(90)
local speed = tonumber(opts and opts.speed) or 18
local fadeHold = tonumber(opts and opts.fadeHold) or 0.9
local barW = tonumber(opts and opts.width) or libNyx.UI.Scale(12)
local r = libNyx.UI.Scale(6)
vbar:SetWide(barW)
sp._ss = sp._ss or {}
sp._ss.cur = vbar:GetScroll()
sp._ss.tgt = sp._ss.cur
sp._ss.visA = 0
sp._ss.lastPing = 0
sp._ss.drag = false
local function expApproach(cur, tgt, spd)
local k = 1 - math.exp(-(spd) * FrameTime())
return cur + (tgt - cur) * k
end
local function maxScroll()
local can = IsValid(sp:GetCanvas()) and sp:GetCanvas():GetTall() or 0
return math.max(0, can - sp:GetTall())
end
local function ping()
sp._ss.lastPing = CurTime()
end
local oldAddScroll = vbar.AddScroll
vbar.AddScroll = function(self, dlta)
local m = maxScroll()
sp._ss.tgt = math.Clamp(sp._ss.tgt + dlta * step, 0, m)
ping()
end
local oldOnMousePressed = vbar.OnMousePressed
vbar.OnMousePressed = function(self, mc)
sp._ss.drag = true
if isfunction(oldOnMousePressed) then oldOnMousePressed(self, mc) end
ping()
end
local oldOnMouseReleased = vbar.OnMouseReleased
vbar.OnMouseReleased = function(self, mc)
sp._ss.drag = false
if isfunction(oldOnMouseReleased) then oldOnMouseReleased(self, mc) end
ping()
end
if IsValid(vbar.btnGrip) then
local og = vbar.btnGrip.OnMousePressed
vbar.btnGrip.OnMousePressed = function(s, mc)
sp._ss.drag = true
if isfunction(og) then og(s, mc) end
ping()
end
local org = vbar.btnGrip.OnMouseReleased
vbar.btnGrip.OnMouseReleased = function(s, mc)
sp._ss.drag = false
if isfunction(org) then org(s, mc) end
ping()
end
end
local MAT_GRADIENT_L = Material("vgui/gradient-l", "noclamp smooth")
vbar.Paint = function(s, w, h)
local a = math.floor(130 * sp._ss.visA)
if a <= 0 then return end
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(36,36,44, a), glass = true})
end
vbar.btnUp.Paint = function() end
vbar.btnDown.Paint = function() end
vbar.btnGrip.Paint = function(s, w, h)
local a = math.floor(200 * sp._ss.visA)
if a <= 4 then return end
libNyx.UI.Draw.Panel(0, 0, w, h, {radius = r, color = Color(24,28,36, a + 35), glass = true, stroke = true, strokeColor = Color(255,255,255, 16)})
if not MAT_GRADIENT_L:IsError() then
local c = Style.accentColor
local g = Color(c.r, c.g, c.b, math.Clamp(110 + math.floor(100 * sp._ss.visA), 80, 200))
RNDX.DrawMaterial(r, 0, 0, w, h, g, MAT_GRADIENT_L, 0)
end
end
local oldThink = sp.Think
sp.Think = function(self)
if isfunction(oldThink) then oldThink(self) end
local m = maxScroll()
sp._ss.tgt = math.Clamp(sp._ss.tgt, 0, m)
if sp._ss.drag then
sp._ss.cur = vbar:GetScroll()
sp._ss.tgt = sp._ss.cur
else
sp._ss.cur = expApproach(sp._ss.cur, sp._ss.tgt, speed)
if math.abs(sp._ss.cur - vbar:GetScroll()) > 0.5 then
vbar:SetScroll(sp._ss.cur)
end
end
local want = 0
if vbar:IsHovered() or (IsValid(vbar.btnGrip) and vbar.btnGrip:IsHovered()) or sp._ss.drag or (CurTime() - sp._ss.lastPing) < fadeHold then
want = 1
end
sp._ss.visA = expApproach(sp._ss.visA, want, 12)
end
end
function libNyx.UI.SmoothScroll.InstallUnder(root, opts)
if not IsValid(root) then return end
local function apply(p)
if not IsValid(p) then return end
if isfunction(p.GetVBar) and IsValid(p:GetVBar()) and p:GetVBar().SetUp then
libNyx.UI.SmoothScroll.ApplyToScrollPanel(p, opts)
end
for _, ch in ipairs(p:GetChildren()) do apply(ch) end
local old = p.OnChildAdded
p.OnChildAdded = function(s, child)
if isfunction(old) then old(s, child) end
apply(child)
end
end
apply(root)
end
function libNyx.UI.InstallGlobalScroll(root, opts)
libNyx.UI.SmoothScroll.InstallUnder(root, opts)
end
local ny = _G.libNyx or {}
ny.UI = ny.UI or {}
_G.libNyx = ny
local function NyScale(n) return (ny.UI.Scale and ny.UI.Scale(n)) or n end
local function NyFont(sz) return (ny.UI.Font and ny.UI.Font(sz)) or "DermaDefault" end
local function lerpExp(cur, tgt, speed)
local k = 1 - math.exp(-(speed or 14) * FrameTime())
return cur + (tgt - cur) * k
end
ny.__nyxMenuSkinInstalled = ny.__nyxMenuSkinInstalled or false
function ny.UI.InstallGlobalMenuSkin(opt)
if ny.__nyxMenuSkinInstalled then return end
opt = opt or {}
local S = ny.UI.Style or {}
local radius = opt.radius or S.radius or 10
local fill = opt.fill or Color(16,18,24, 210)
local stroke = opt.stroke or S.glassStroke or Color(255,255,255, 22)
local textColor = opt.textColor or Color(235,235,240)
local rowHover = opt.rowHover or Color(255,255,255, 10)
local rowActive = opt.rowActive or Color(255,255,255, 16)
local blurIntensity = opt.blurIntensity or 1.12
local fontObj = opt.font and (type(opt.font)=="function" and opt.font() or opt.font) or NyFont(NyScale(16))
local padding = opt.padding or NyScale(4)
local function styleMenuTable()
local T = vgui.GetControlTable("DMenu")
if T then
T.Paint = function(self, w, h)
if ny.UI.Draw and ny.UI.Draw.Glass then
ny.UI.Draw.Glass(0, 0, w, h, {
radius = NyScale(radius),
fill = fill,
stroke = true,
strokeColor = stroke,
blurIntensity = blurIntensity
})
else
draw.RoundedBox(NyScale(radius), 0, 0, w, h, fill)
surface.SetDrawColor(stroke)
surface.DrawOutlinedRect(0, 0, w, h)
end
end
local oldPerform = T.PerformLayout
T.PerformLayout = function(self, ...)
self:SetPadding(padding)
if oldPerform then oldPerform(self, ...) end
self:SizeToContents()
end
end
local O = vgui.GetControlTable("DMenuOption")
if O then
local oldSetText = O.SetText
O.SetText = function(self, txt)
oldSetText(self, txt)
self:SetFont(fontObj)
self:SetTextColor(textColor)
end
O.Paint = function(self, w, h)
self._nyxHover = lerpExp(self._nyxHover or 0, self:IsHovered() and 1 or 0, 18)
local a = self._nyxHover or 0
local r = NyScale(6)
local c = Color(rowHover.r, rowHover.g, rowHover.b, math.floor(rowHover.a * a))
if ny.UI.Draw and ny.UI.Draw.Panel then
ny.UI.Draw.Panel(2, 1, w-4, h-2, {
radius = r,
color = c,
glass = false,
stroke = false
})
else
draw.RoundedBox(r, 2, 1, w-4, h-2, c)
end
if self.m_bSelected or self:GetToggle() then
local act = Color(rowActive.r, rowActive.g, rowActive.b, rowActive.a)
if ny.UI.Draw and ny.UI.Draw.Panel then
ny.UI.Draw.Panel(2, 1, w-4, h-2, {radius = r, color = act})
else
draw.RoundedBox(r, 2, 1, w-4, h-2, act)
end
end
return false
end
end
local D = vgui.GetControlTable("DMenuDivider")
if D then
D.Paint = function(self, w, h)
surface.SetDrawColor(255,255,255, 18)
surface.DrawRect(NyScale(8), math.floor(h*0.5), w - NyScale(16), 1)
end
D.GetSpacing = function() return NyScale(6) end
end
end
if vgui.GetControlTable("DMenu") then
styleMenuTable()
else
timer.Simple(0, styleMenuTable)
end
ny.__nyxMenuSkinInstalled = true
end
if CLIENT and (ny.UI.AutoInstallMenuSkin ~= false) then
timer.Simple(0, function()
if _G.libNyx and _G.libNyx.UI and not _G.libNyx.__nyxMenuSkinInstalled then
_G.libNyx.UI.InstallGlobalMenuSkin()
end
end)
end
do
local ny = _G.libNyx or {}
ny.UI = ny.UI or {}
_G.libNyx = ny
local Style = ny.UI.Style
local SCALE = ny.UI.Scale
local FONT = ny.UI.Font
local DrawPanel = ny.UI.Draw and ny.UI.Draw.Panel
local DARK = (Style and (Style.panelColor or Color(20,22,30,130))) or Color(20,22,30,130)
local NOTIFY_GENERIC = _G.NOTIFY_GENERIC or 0
local NOTIFY_ERROR = _G.NOTIFY_ERROR or 1
local NOTIFY_UNDO = _G.NOTIFY_UNDO or 2
local NOTIFY_HINT = _G.NOTIFY_HINT or 3
local NOTIFY_CLEANUP = _G.NOTIFY_CLEANUP or 4
local ICONS = {
info = Material("icon16/information.png","noclamp smooth"),
ok = Material("icon16/accept.png","noclamp smooth"),
undo = Material("icon16/arrow_undo.png","noclamp smooth"),
error = Material("icon16/exclamation.png","noclamp smooth"),
broom = Material("icon16/bin.png","noclamp smooth"),
mail = Material("icon16/email.png","noclamp smooth"),
box = Material("icon16/box.png","noclamp smooth"),
star = Material("icon16/star.png","noclamp smooth"),
}
local TYPES = {
[NOTIFY_GENERIC] = { icon = ICONS.info },
[NOTIFY_ERROR] = { icon = ICONS.error },
[NOTIFY_UNDO] = { icon = ICONS.undo },
[NOTIFY_HINT] = { icon = ICONS.star },
[NOTIFY_CLEANUP] = { icon = ICONS.broom },
}
local function pickIcon(msg, fallback)
local m = string.lower(tostring(msg or ""))
if m:find("mail",1,true) or m:find("letter",1,true) or m:find("письм",1,true) then return ICONS.mail end
if m:find("parcel",1,true) or m:find("package",1,true) or m:find("посылк",1,true) then return ICONS.box end
return fallback or ICONS.info
end
ny.UI._Notify = ny.UI._Notify or {}
local function Layer()
if IsValid(ny.UI._Notify.layer) then return ny.UI._Notify.layer end
local L = vgui.Create("DPanel")
L:SetZPos(32767)
L:SetDrawOnTop(true)
L:SetMouseInputEnabled(false)
L:SetKeyboardInputEnabled(false)
L:SetSize(ScrW(), ScrH())
L:SetPos(0,0)
ny.UI.AutoNoBG(L)
L.list = {}
function L:Relayout()
local padR = SCALE(24)
local gap = SCALE(8)
local total = 0
for i = 1, #self.list do
local p = self.list[i]
if IsValid(p) then
total = total + p:GetTall()
if i < #self.list then total = total + gap end
end
end
local y = math.max(SCALE(14), math.floor(ScrH()*0.5 - total*0.5))
for i = 1, #self.list do
local p = self.list[i]
if IsValid(p) then
local x = ScrW() - padR - p:GetWide()
if p._spawn then
p:SetPos(x, y)
p._spawn = nil
else
p:MoveTo(x, y, 0.15, 0, 0.2)
end
y = y + p:GetTall() + gap
end
end
end
hook.Add("OnScreenSizeChanged","libNyx.Notify.Relayout",function()
if not IsValid(L) then return end
L:SetSize(ScrW(),ScrH())
L:SetPos(0,0)
L:Relayout()
end)
ny.UI._Notify.layer = L
return L
end
local function Toast(text, icon, life)
local L = Layer()
local p = vgui.Create("DButton", L)
p:SetText("")
p:SetDrawOnTop(true)
p:SetZPos(32766)
p:SetMouseInputEnabled(true)
p:SetKeyboardInputEnabled(false)
p._icon = icon or pickIcon(text, ICONS.info)
p._life = math.max(0.5, tonumber(life) or 4)
p._killAt = SysTime() + p._life
p._fade = 0
p._slide = 1
p._spawn = true
local padX = SCALE(14)
local h = SCALE(40)
local iconW = SCALE(22)
local font = FONT(SCALE(18))
surface.SetFont(font)
local tw = select(1, surface.GetTextSize(tostring(text or "")))
local w = math.Clamp(tw + padX*2 + iconW + SCALE(14), SCALE(220), math.floor(ScrW()*0.5))
p:SetSize(w, h)
function p:DoClick()
self._killAt = SysTime() - 0.01
surface.PlaySound("ui/buttonclickrelease.wav")
end
function p:OnCursorEntered()
self._killAt = SysTime() + 1.2 + self._life*0.25
surface.PlaySound("buttons/lightswitch2.wav")
end
p.Think = function(s)
local now = SysTime()
local want = (s._killAt or now) > now and 1 or 0
s._fade = s._fade + (want - s._fade) * math.min(FrameTime()*16,1)
s._slide = s._slide + ((want==1) and -s._slide or (1 - s._slide)) * math.min(FrameTime()*10,1)
if s._fade <= 0.02 and want == 0 then
s:Remove()
for i=#L.list,1,-1 do if L.list[i]==s then table.remove(L.list,i) break end end
L:Relayout()
end
end
p.Paint = function(s, w, h)
local a = math.Clamp(s._fade or 0, 0, 1)
if a <= 0.001 then return end
local r = math.max(Style.radius, SCALE(10))
local slide = math.floor(SCALE(6) * (s._slide or 0))
if RNDX and RNDX.EnsureFB then RNDX.EnsureFB() end
if DrawPanel then
DrawPanel(0,0,w,h,{radius=r,color=Color(DARK.r,DARK.g,DARK.b, math.floor(95*a)),glass=true})
end
if RNDX and RNDX().Liquid then
RNDX().Liquid(0,0,w,h)
:Rad(r)
:Strength(0.014)
:Speed(0.35)
:Saturation(1.06)
:Tint(20,24,32)
:TintStrength(0.10)
:Shimmer(22.0)
:Grain(0.005)
:Alpha(0.95*a)
:GlassBlur(0.02,0.38)
:EdgeSmooth(2.0)
:Draw()
end
if DrawPanel then
local sheenH = math.max(1, math.floor(h*0.26))
local sw = math.max(1, w - SCALE(16))
DrawPanel(SCALE(8), SCALE(6), sw, sheenH, {radius=math.floor(r*0.8), color=Color(255,255,255, math.floor(10*a))})
end
draw.SimpleText(
tostring(text or ""),
font,
padX, h/2 + slide*0.25,
Color(Style.textColor.r,Style.textColor.g,Style.textColor.b, math.floor(255*a)),
TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER
)
if s._icon and not s._icon:IsError() then
local ic = iconW
surface.SetDrawColor(255,255,255, math.floor(255*a))
surface.SetMaterial(s._icon)
surface.DrawTexturedRect(w - padX - ic, (h - ic)/2, ic, ic)
end
end
table.insert(L.list, 1, p)
L:Relayout()
return p
end
function ny.UI.PushNotify(msg, typeOrColor, len, icon)
local t = TYPES[tonumber(typeOrColor) or NOTIFY_GENERIC] or TYPES[NOTIFY_GENERIC]
local ico = icon or pickIcon(msg, t.icon)
return Toast(msg, ico, len)
end
if not ny.UI.__notifySkinInstalled then
ny.UI.__notifySkinInstalled = true
ny.UI._origNotification = ny.UI._origNotification or {}
ny.UI._origNotification.AddLegacy = notification.AddLegacy
ny.UI._origNotification.AddProgress = notification.AddProgress
ny.UI._origNotification.Kill = notification.Kill
notification.AddLegacy = function(txt, kind, length)
ny.UI.PushNotify(txt, kind, length)
end
local progress = {}
notification.AddProgress = function(id, txt, frac)
if not IsValid(progress[id]) then
local row = Toast(txt, ICONS.info, 9999)
row._progress = 0
row.PaintOver = function(s, w, h)
if not s._progress then return end
local pad = SCALE(8)
local ph = SCALE(4)
local pw = math.floor((w - pad*2) * math.Clamp(s._progress, 0, 1))
if DrawPanel then
DrawPanel(pad, h - ph - pad, w - pad*2, ph, {radius = ph/2, color = Color(40,44,52,150), glass = true})
if pw > 1 then
DrawPanel(pad, h - ph - pad, pw, ph, {radius = ph/2, color = Color(Style.accentColor.r,Style.accentColor.g,Style.accentColor.b,190), glass = true})
end
end
end
progress[id] = row
end
progress[id]._progress = tonumber(frac) or 0
return id
end
notification.Kill = function(id)
local p = progress[id]
if IsValid(p) then p._killAt = SysTime() - 0.01 end
progress[id] = nil
end
local function patchGamemode()
local gm = gmod and gmod.GetGamemode and gmod.GetGamemode()
if not gm or gm.__nyxAddNotifyPatched then return end
gm.__nyxAddNotifyPatched = true
gm._nyxOrigAddNotify = gm.AddNotify
function gm:AddNotify(txt, kind, length)
ny.UI.PushNotify(txt, kind, length)
end
end
timer.Simple(0, patchGamemode)
hook.Add("OnGamemodeLoaded","libNyx.Notify.PatchGM",patchGamemode)
hook.Add("DarkRPFinishedLoading","libNyx.Notify.PatchGM",patchGamemode)
end
function ny.UI.InstallGlobalNotificationSkin()
Layer()
end
timer.Simple(0, function()
if _G.libNyx and _G.libNyx.UI then
_G.libNyx.UI.InstallGlobalNotificationSkin()
end
end)
end
local ny = _G.libNyx or {}
ny.UI = ny.UI or {}
_G.libNyx = ny
local STYLE = ny.UI.Style or {}
local Components = ny.UI.Components or {}
local SCALE = NyScale
local FONT = NyFont
local function NyDialogFrame(title, w, h)
w = w or SCALE(460)
h = h or SCALE(210)
local f
if ny.UI.CreateFrame then
f = ny.UI.CreateFrame({title = title or "", w = w, h = h})
else
f = vgui.Create("DFrame")
f:SetTitle(title or "")
f:SetSize(w, h)
f:Center()
f:MakePopup()
f:ShowCloseButton(true)
end
local inner = vgui.Create("DPanel", f)
inner:Dock(FILL)
inner:DockMargin(SCALE(24), SCALE(70), SCALE(24), SCALE(24))
if ny.UI.AutoNoBG then ny.UI.AutoNoBG(inner) end
return f, inner
end
function ny.UI.CreateMessageBox(opt)
opt = opt or {}
local title = opt.title or ""
local text = opt.text or ""
local btnText = opt.buttonText or "OK"
local w = opt.w or SCALE(460)
local h = opt.h or SCALE(210)
local onClick = opt.onClick
local frame, inner = NyDialogFrame(title, w, h)
local top = vgui.Create("DPanel", inner)
top:Dock(FILL)
top:DockMargin(0, 0, 0, SCALE(8))
if ny.UI.AutoNoBG then ny.UI.AutoNoBG(top) end
local lbl = vgui.Create("DLabel", top)
lbl:Dock(FILL)
lbl:SetFont(FONT(SCALE(20)))
lbl:SetTextColor(STYLE.textColor or color_white)
lbl:SetText(text)
lbl:SetWrap(true)
lbl:SetContentAlignment(5)
local btnRow = vgui.Create("DPanel", inner)
btnRow:Dock(BOTTOM)
btnRow:SetTall((STYLE.btnHeight or SCALE(44)) + SCALE(4))
if ny.UI.AutoNoBG then ny.UI.AutoNoBG(btnRow) end
local ok
if Components.CreateButton then
ok = Components.CreateButton(btnRow, btnText, {variant = "primary_center"})
else
ok = vgui.Create("DButton", btnRow)
ok:SetText(btnText)
ok:SetFont(FONT(SCALE(18)))
end
surface.SetFont(FONT(SCALE(18)))
local tw = select(1, surface.GetTextSize(btnText))
ok:SetWide(math.max(SCALE(110), tw + SCALE(40)))
ok:SetTall(STYLE.btnHeight or SCALE(44))
function ok:DoClick()
if ny.UI.PlayClick then ny.UI.PlayClick() end
if isfunction(onClick) then onClick(frame) end
if frame.Close then frame:Close() else frame:Remove() end
end
function btnRow:PerformLayout(w, h)
if not IsValid(ok) then return end
local bw, bh = ok:GetWide(), ok:GetTall()
local x = math.floor((w - bw) * 0.5)
local y = math.floor((h - bh) * 0.5)
ok:SetPos(x, y)
end
function frame:OnKeyCodePressed(key)
if key == KEY_ENTER or key == KEY_PAD_ENTER or key == KEY_SPACE then
if IsValid(ok) then ok:DoClick() end
elseif key == KEY_ESCAPE then
if self.Close then self:Close() else self:Remove() end
end
end
return frame
end
function ny.UI.CreateInputBox(opt)
opt = opt or {}
local title = opt.title or ""
local text = opt.text or ""
local default = opt.default or ""
local confirmText = opt.confirmText or "OK"
local cancelText = opt.cancelText or "Cancel"
local onConfirm = opt.onConfirm
local onCancel = opt.onCancel
local w = opt.w or SCALE(500)
local h = opt.h or SCALE(250)
local frame, inner = NyDialogFrame(title, w, h)
local top = vgui.Create("DPanel", inner)
top:Dock(FILL)
top:DockMargin(0, 0, 0, SCALE(10))
if ny.UI.AutoNoBG then ny.UI.AutoNoBG(top) end
local input = vgui.Create("DTextEntry", top)
input:Dock(BOTTOM)
input:SetTall(SCALE(34))
input:DockMargin(0, SCALE(12), 0, 0)
input:SetFont(FONT(SCALE(18)))
input:SetText(default or "")
input:SetUpdateOnType(false)
input:SetHistoryEnabled(false)
function input:Paint(w, h)
if ny.UI.Draw and ny.UI.Draw.Panel then
ny.UI.Draw.Panel(0, 0, w, h, {
radius = SCALE(8),
color = Color(20, 24, 32, 150),
glass = true,
stroke = true,
strokeColor = STYLE.glassStroke or Color(255,255,255,24)
})
else
draw.RoundedBox(4, 0, 0, w, h, Color(20,24,32,220))
end
self:DrawTextEntryText(STYLE.textColor or color_white, Color(255,255,255,25), STYLE.textColor or color_white)
end
local lbl = vgui.Create("DLabel", top)
lbl:Dock(FILL)
lbl:SetFont(FONT(SCALE(20)))
lbl:SetTextColor(STYLE.textColor or color_white)
lbl:SetText(text)
lbl:SetWrap(true)
lbl:SetContentAlignment(5)
lbl:DockMargin(0, 0, 0, SCALE(6))
local btnRow = vgui.Create("DPanel", inner)
btnRow:Dock(BOTTOM)
btnRow:SetTall((STYLE.btnHeight or SCALE(44)) + SCALE(4))
btnRow:DockMargin(0, SCALE(4), 0, 0)
if ny.UI.AutoNoBG then ny.UI.AutoNoBG(btnRow) end
surface.SetFont(FONT(SCALE(18)))
local twOk = select(1, surface.GetTextSize(confirmText))
local twCancel= select(1, surface.GetTextSize(cancelText))
local wOk = math.max(SCALE(110), twOk + SCALE(40))
local wCancel = math.max(SCALE(110), twCancel + SCALE(40))
local btnCancel, btnOk
if Components.CreateButton then
btnCancel = Components.CreateButton(btnRow, cancelText, {variant = "ghost"})
btnOk = Components.CreateButton(btnRow, confirmText, {variant = "primary_center"})
else
btnCancel = vgui.Create("DButton", btnRow)
btnCancel:SetText(cancelText)
btnCancel:SetFont(FONT(SCALE(18)))
btnOk = vgui.Create("DButton", btnRow)
btnOk:SetText(confirmText)
btnOk:SetFont(FONT(SCALE(18)))
end
btnCancel:SetWide(wCancel)
btnOk:SetWide(wOk)
local btnH = STYLE.btnHeight or SCALE(44)
btnCancel:SetTall(btnH)
btnOk:SetTall(btnH)
local function doConfirm()
local txt = input:GetText() or ""
if ny.UI.PlayClick then ny.UI.PlayClick() end
if isfunction(onConfirm) then onConfirm(txt) end
if frame.Close then frame:Close() else frame:Remove() end
end
local function doCancel()
local txt = input:GetText() or ""
if ny.UI.PlayClick then ny.UI.PlayClick() end
if isfunction(onCancel) then onCancel(txt) end
if frame.Close then frame:Close() else frame:Remove() end
end
function btnOk:DoClick()
doConfirm()
end
function btnCancel:DoClick()
doCancel()
end
function btnRow:PerformLayout(w, h)
if not (IsValid(btnOk) and IsValid(btnCancel)) then return end
local gap = SCALE(12)
local total = btnOk:GetWide() + btnCancel:GetWide() + gap
local x = math.floor((w - total) * 0.5)
local y = math.floor((h - btnOk:GetTall()) * 0.5)
btnCancel:SetPos(x, y)
x = x + btnCancel:GetWide() + gap
btnOk:SetPos(x, y)
end
function input:OnEnter()
doConfirm()
end
function frame:OnKeyCodePressed(key)
if key == KEY_ENTER or key == KEY_PAD_ENTER then
if IsValid(btnOk) then btnOk:DoClick() end
elseif key == KEY_ESCAPE then
if IsValid(btnCancel) then btnCancel:DoClick() else
if self.Close then self:Close() else self:Remove() end
end
end
end
timer.Simple(0, function()
if IsValid(input) then
input:RequestFocus()
input:SelectAllText(true)
end
end)
return frame, input
end
local _orig_Derma_Message = Derma_Message
local _orig_Derma_StringRequest = Derma_StringRequest
function Derma_Message(text, title, button)
button = button or "OK"
if not _G.libNyx or not _G.libNyx.UI or not _G.libNyx.UI.CreateMessageBox then
if _orig_Derma_Message then
return _orig_Derma_Message(text, title, button)
end
end
return _G.libNyx.UI.CreateMessageBox({
title = title,
text = text,
buttonText = button
})
end
function Derma_StringRequest(title, subtitle, default, confirm, cancel, confirmText, cancelText)
confirmText = confirmText or "OK"
cancelText = cancelText or "Cancel"
if not _G.libNyx or not _G.libNyx.UI or not _G.libNyx.UI.CreateInputBox then
if _orig_Derma_StringRequest then
return _orig_Derma_StringRequest(title, subtitle, default, confirm, cancel, confirmText, cancelText)
end
end
local frame, entry = _G.libNyx.UI.CreateInputBox({
title = title,
text = subtitle,
default = default,
confirmText = confirmText,
cancelText = cancelText,
onConfirm = confirm,
onCancel = cancel
})
return frame, entry
end
-- libNyx by MaryBlackfild
-- JOIN DISCORD: https://discord.gg/rUEEz4mfXw