-- 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