--[[ | This file was obtained through the combined efforts | of Madbluntz & Plymouth Antiquarian Society. | | Credits: lifestorm, Gregory Wayne Rossel JR., | Maloy, DrPepper10 @ RIP, Atle! | | Visit for more: https://plymouth.thetwilightzone.ru/ --]] --[[ Three's Derma Lib Made by Threebow You are free to use this anywhere you like, or sell any addons made using this, as long as I am properly accredited. ]] local pairs = pairs local ipairs = ipairs local Color = Color local render = render local SysTime = SysTime local Lerp, RealFrameTime = Lerp, RealFrameTime local RoundedBox, RoundedBoxEx, NoTexture = draw.RoundedBox, draw.RoundedBoxEx, draw.NoTexture local SetDrawColor, DrawRect = surface.SetDrawColor, surface.DrawRect local DrawPoly = surface.DrawPoly local sui = sui local Panel = FindMetaTable("Panel") --[[ Constants ]] local BLUR = CreateMaterial("SUI.TDLib.Blur", "gmodscreenspace", { ["$basetexture"] = "_rt_fullframefb", ["$blur"] = (1 / 3) * 7, }) local COL_WHITE_1 = Color(255, 255, 255) local COL_WHITE_2 = Color(255, 255, 255, 30) --[[ credits to http://slabode.exofire.net/circle_draw.shtml ]] local calculate_circle do local cos = math.cos local sin = math.sin local round = math.Round local sqrt = math.sqrt local pi = math.pi calculate_circle = function(circle, x_centre, y_centre, r) if circle.x == x_centre and circle.y == y_centre and circle.r == r then return end local step = (2 * pi) / round(6 * sqrt(r)) local i = 0 for theta = 2 * pi, 0, -step do local x = x_centre + r * cos(theta) local y = y_centre - r * sin(theta) i = i + 1 circle[i] = { x = x, y = y } end for i = i + 1, #circle do circle[i] = nil end circle.x = x_centre circle.y = y_centre circle.r = r end end --[[ void DrawArc(float cx, float cy, float r, float start_angle, float arc_angle, int num_segments) { float theta = arc_angle / float(num_segments - 1);//theta is now calculated from the arc angle instead, the - 1 bit comes from the fact that the arc is open float tangetial_factor = tanf(theta); float radial_factor = cosf(theta); float x = r * cosf(start_angle);//we now start at the start angle float y = r * sinf(start_angle); glBegin(GL_LINE_STRIP);//since the arc is not a closed curve, this is a strip now for(int ii = 0; ii < num_segments; ii++) { glVertex2f(x + cx, y + cy); float tx = -y; float ty = x; x += tx * tangetial_factor; y += ty * tangetial_factor; x *= radial_factor; y *= radial_factor; } glEnd(); } ]] local copy_color = function(color) return Color(color.r, color.g, color.b, color.a) end local color_alpha = function(color, a) color.a = a return color end --[[ Collection of various utilities ]] local TDLibUtil = {} function TDLibUtil.DrawCircle(circle, x, y, r, color) calculate_circle(circle, x, y, r) SetDrawColor(color) NoTexture() DrawPoly(circle) end local DrawCircle = TDLibUtil.DrawCircle do local SetMaterial = surface.SetMaterial local UpdateScreenEffectTexture, DrawTexturedRect, SetScissorRect = render.UpdateScreenEffectTexture, surface.DrawTexturedRect, render.SetScissorRect local scrW, scrH = ScrW(), ScrH() hook.Add("OnScreenSizeChanged", "SUI.TDLib", function() scrW, scrH = ScrW(), ScrH() end) function TDLibUtil.BlurPanel(s) local x, y = s:LocalToScreen(0, 0) SetDrawColor(255, 255, 255) SetMaterial(BLUR) for i = 1, 2 do UpdateScreenEffectTexture() DrawTexturedRect(x * -1, y * -1, scrW, scrH) end end function TDLibUtil.DrawBlur(x, y, w, h) SetDrawColor(255, 255, 255) SetMaterial(BLUR) SetScissorRect(x, y, x + w, y + h, true) for i = 1, 2 do UpdateScreenEffectTexture() DrawTexturedRect(-1, -1, scrW, scrH) end SetScissorRect(0, 0, 0, 0, false) end end local LibClasses = {} do local on_funcs = {} function LibClasses:On(name, func) local old_func = self[name] if not old_func then self[name] = func return self end local name_2 = name .. "_funcs" -- we gotta avoid creating 13535035 closures if not on_funcs[name] then on_funcs[name] = function(s, a1, a2, a3, a4) local funcs = s[name_2] local i, n = 0, #funcs ::loop:: i = i + 1 if i <= n then funcs[i](s, a1, a2, a3, a4) goto loop end end end if not self[name_2] then self[name] = on_funcs[name] self[name_2] = { old_func, func } else table.insert(self[name_2], func) end return self end end do local UnPredictedCurTime = UnPredictedCurTime local transition_func = function(s) local transitions = s.transitions local i, n = 0, #transitions ::loop:: i = i + 1 if i <= n then local v = transitions[i] local name = v.name local v2 = s[name] if v.func(s) then if v.start_0 then v.start_1, v.start_0 = UnPredictedCurTime(), nil end if v2 ~= 1 then s[name] = Lerp((UnPredictedCurTime() - v.start_1) / v.time, v2, 1) end else if v.start_1 then v.start_0, v.start_1 = UnPredictedCurTime(), nil end if v2 ~= 0 then s[name] = Lerp((UnPredictedCurTime() - v.start_0) / v.time, v2, 0) end end goto loop end end function LibClasses:SetupTransition(name, time, func) self[name] = 0 local transition = { name = name, time = time, func = func, start_0 = 0, start_1 = 0, } if self.transitions then for k, v in ipairs(self.transitions) do if v.name == name then self.transitions[k] = transition return self end end table.insert(self.transitions, transition) else self.transitions = {transition} self:On("Think", transition_func) end return self end end function LibClasses:ClearPaint() self.Paint = nil self.Paint_funcs = nil local SetPaintBackgroundEnabled = self.SetPaintBackgroundEnabled if SetPaintBackgroundEnabled then SetPaintBackgroundEnabled(self, false) end return self end function LibClasses:RoundedBox(id, r, x, y, w, h, c) self.colors = self.colors or {} local colors = self.colors local id_c = colors[id] if not id_c then id_c = Color(c:Unpack()) colors[id] = id_c end sui.lerp_color(id_c, c) RoundedBox(r, x, y, w, h, id_c) end do local SetFGColor = Panel.SetFGColor local set_color = function(s, col) s.m_colText = col SetFGColor(s, col.r, col.g, col.b, col.a) end local paint = function(s) local col = s.sui_textcolor sui.lerp_color(col, s.new_col) set_color(s, col) end function LibClasses:TextColor(c, use_paint) local col = self.sui_textcolor if not col then col = Color(c:Unpack()) self.sui_textcolor = col if use_paint then self:On("Paint", paint) end end if use_paint then self.new_col = c else sui.lerp_color(col, c) self:SetTextColor(col) end end end do local fade_hover_Paint = function(s, w, h) if s.FadeHovers ~= 0 then color_alpha(s.fadehover_color, s.fadehover_old_alpha * s.FadeHovers) if s.fadehover_radius > 0 then RoundedBox(s.fadehover_radius, 0, 0, w, h, s.fadehover_color) else SetDrawColor(s.fadehover_color) DrawRect(0, 0, w, h) end end end function LibClasses:FadeHover(color, time, radius, func) color = copy_color(color or COL_WHITE_2) self.fadehover_color = color self.fadehover_radius = radius or 0 self.fadehover_old_alpha = color.a self:SetupTransition("FadeHovers", time or 0.8, func or TDLibUtil.HoverFunc) self:On("Paint", fade_hover_Paint) return self end end function LibClasses:BarHover(color, height, time) color = color or COL_WHITE_1 height = height or 2 time = time or 1.6 self:SetupTransition("BarHovers", time, TDLibUtil.HoverFunc) self:On("Paint", function(s, w, h) if s.BarHovers ~= 0 then local bar = Round(w * s.BarHovers) SetDrawColor(color) DrawRect((w / 2) - (bar / 2), h - height, bar, height) end end) return self end do local paint = function(s, w, h) draw.RoundedBox(0, 0, 0, w, h, s.SUI_GetColor("line")) end function LibClasses:Line(dock, m1, m2, m3, m4) self.making_line = true local line = self:Add("SAM.Panel") line:Dock(dock or TOP) if self.line_margin then line:DockMargin(unpack(self.line_margin)) else line:DockMargin(m1 or 0, m2 or 0, m3 or 0, m4 or 10) end line.no_scale = true line:SetTall(1) line.Paint = paint self.making_line = false return line end function LibClasses:LineMargin(m1, m2, m3, m4) self.line_margin = {m1 or 0, m2 or 0, m3 or 0, m4 or 0} return self end end do local background_Paint_1 = function(s) s:SetBGColor(s.background_color) end local background_Paint_2 = function(s, w, h) RoundedBoxEx(s.background_radius, 0, 0, w, h, s.background_color, true, true, true, true) end local background_Paint_3 = function(s, w, h) RoundedBoxEx(s.background_radius, 0, 0, w, h, s.background_color, s.background_r_tl, s.background_r_tr, s.background_r_bl, s.background_r_br) end function LibClasses:Background(color, radius, r_tl, r_tr, r_bl, r_br) self.background_color = color if isnumber(radius) and radius ~= 0 then self.background_radius = radius if isbool(r_tl) or isbool(r_tr) or isbool(r_bl) or isbool(r_br) then self.background_r_tl = r_tl self.background_r_tr = r_tr self.background_r_bl = r_bl self.background_r_br = r_br self:On("Paint", background_Paint_3) else self:On("Paint", background_Paint_2) end else self:SetPaintBackgroundEnabled(true) self:On("ApplySchemeSettings", background_Paint_1) self:On("PerformLayout", background_Paint_1) end return self end end function LibClasses:CircleClick(color, speed, target_radius) self.circle_click_color = color or COL_WHITE_2 speed = speed or 5 target_radius = isnumber(target_radius) and target_radius or false local radius, alpha, click_x, click_y = 0, -1, 0, 0 local circle = {} self:On("Paint", function(s, w) if alpha >= 0 then DrawCircle(circle, click_x, click_y, radius, ColorAlpha(self.circle_click_color, alpha)) local frame_time = RealFrameTime() radius, alpha = Lerp(frame_time * speed, radius, target_radius or w), Lerp(frame_time * speed, alpha, -1) end end) self:On("DoClick", function() click_x, click_y = self:CursorPos() radius, alpha = 0, self.circle_click_color.a end) return self end do local min = math.min function LibClasses:CircleClick2(color, speed, target_radius, start_radius) color = color or COL_WHITE_2 local _color = Color(color:Unpack()) speed = speed or 5 target_radius = isnumber(target_radius) and target_radius or false local radius, alpha = 0, -1 local circle = {} self:On("Paint", function(s, w, h) if alpha >= 0 then _color.a = alpha DrawCircle(circle, w / 2, h / 2, radius, _color) local frame_time = RealFrameTime() radius, alpha = Lerp(frame_time * speed, radius, target_radius or min(w, h) / 2), Lerp(frame_time * speed, alpha, -1) end end) self:On("DoClick", function() radius, alpha = start_radius or 0, color.a end) return self end end -- https://github.com/Facepunch/garrysmod/pull/1520#issuecomment-410458090 function LibClasses:Outline(color, width) color = color or COL_WHITE_1 width = width or 1 self:On("Paint", function(s, w, h) SetDrawColor(color) DrawRect(0, 0, w, width) DrawRect(0, h - width, w, width) DrawRect(0, width, width, h - (width * 2)) DrawRect(w - width, width, width, h - (width * 2)) end) return self end function LibClasses:LinedCorners(color, len) color = color or COL_WHITE_1 len = len or 15 self:On("Paint", function(s, w, h) SetDrawColor(color) DrawRect(0, 0, len, 1) DrawRect(0, 1, 1, len - 1) DrawRect(w - len, h - 1, len, 1) DrawRect(w - 1, h - len, 1, len - 1) end) return self end function LibClasses:SideBlock(color, size, side) color = color or COL_WHITE_1 size = size or 3 side = side or LEFT self:On("Paint", function(s, w, h) SetDrawColor(color) if side == LEFT then DrawRect(0, 0, size, h) elseif side == TOP then DrawRect(0, 0, w, size) elseif size == RIGHT then DrawRect(w - size, 0, size, h) elseif side == BOTTOM then DrawRect(0, h - size, w, size) end end) return self end function LibClasses:Blur() self:On("Paint", TDLibUtil.BlurPanel) return self end do local STENCILOPERATION_REPLACE = STENCILOPERATION_REPLACE local STENCILOPERATION_ZERO = STENCILOPERATION_ZERO local STENCILCOMPARISONFUNCTION_NEVER = STENCILCOMPARISONFUNCTION_NEVER local STENCILCOMPARISONFUNCTION_EQUAL = STENCILCOMPARISONFUNCTION_EQUAL local ClearStencil = render.ClearStencil local SetStencilEnable = render.SetStencilEnable local SetStencilWriteMask = render.SetStencilWriteMask local SetStencilTestMask = render.SetStencilTestMask local SetStencilFailOperation = render.SetStencilFailOperation local SetStencilPassOperation = render.SetStencilPassOperation local SetStencilZFailOperation = render.SetStencilZFailOperation local SetStencilCompareFunction = render.SetStencilCompareFunction local SetStencilReferenceValue = render.SetStencilReferenceValue local color_white = color_white local avatar_setplayer = function(s, ply, size) s.avatar:SetPlayer(ply, size) end local avatar_setsteamid = function(s, steamid, size) s.avatar:SetSteamID(steamid, size) end function LibClasses:CircleAvatar() local avatar = self:Add("AvatarImage") avatar:Dock(FILL) avatar:SetPaintedManually(true) self.avatar = avatar self.SetSteamID = avatar_setsteamid self.SetPlayer = avatar_setplayer local circle = {} local PaintManual = avatar.PaintManual self.Paint = function(s, w, h) ClearStencil() SetStencilEnable(true) SetStencilWriteMask(1) SetStencilTestMask(1) SetStencilFailOperation(STENCILOPERATION_REPLACE) SetStencilPassOperation(STENCILOPERATION_ZERO) SetStencilZFailOperation(STENCILOPERATION_ZERO) SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_NEVER) SetStencilReferenceValue(1) local a = w / 2 DrawCircle(circle, a, a, a, color_white) SetStencilFailOperation(STENCILOPERATION_ZERO) SetStencilPassOperation(STENCILOPERATION_REPLACE) SetStencilZFailOperation(STENCILOPERATION_ZERO) SetStencilCompareFunction(STENCILCOMPARISONFUNCTION_EQUAL) SetStencilReferenceValue(1) PaintManual(avatar) SetStencilEnable(false) end return self end end do function LibClasses:AnimationThinkInternal() local systime = SysTime() if self.Term and self.Term <= systime then self:Remove() return end local m_AnimList = self.m_AnimList if not m_AnimList then return end for i = #m_AnimList, 1, -1 do local anim = m_AnimList[i] if systime >= anim.StartTime then local frac = math.TimeFraction(anim.StartTime, anim.EndTime, systime) frac = math.Clamp(frac, 0, 1) local Think = anim.Think if Think then Think(anim, self, frac ^ (1.0 - (frac - 0.5))) end if frac == 1 then local OnEnd = anim.OnEnd if OnEnd then OnEnd(anim, self) end m_AnimList[i] = nil end end end end local sort = function(a, b) return a.EndTime > b.EndTime end function LibClasses:NewAnimation(length, delay, ease, callback) delay = delay or 0 ease = ease or -1 if self.m_AnimQueue then delay = delay + self:AnimTail() self.m_AnimQueue = false else delay = delay + SysTime() end local anim = { StartTime = delay, EndTime = delay + length, Ease = ease, OnEnd = callback } self:SetAnimationEnabled(true) if self.m_AnimList == nil then self.m_AnimList = {} end table.insert(self.m_AnimList, anim) table.sort(self.m_AnimList, sort) self.AnimationThink = self.AnimationThinkInternal return anim end local MoveThink = function(anim, panel, frac) if not anim.startx then anim.startx = panel.x anim.starty = panel.y end local x = Lerp(frac, anim.startx, anim.x) local y = Lerp(frac, anim.starty, anim.y) panel:SetPos(x, y) end function LibClasses:MoveTo(x, y, length, delay, ease, callback) if self.x == x and self.y == y then return end local anim = self:NewAnimation(length, delay, ease, callback) anim.x = x anim.y = y anim.Think = MoveThink end local SetSize = Panel.SetSize local SizeThink = function(anim, panel, frac) if not anim.startw then anim.startw, anim.starth = panel:GetSize() end local w, h if anim.sizew and anim.sizeh then w = Lerp(frac, anim.startw, anim.w) h = Lerp(frac, anim.starth, anim.h) SetSize(panel, w, h) elseif anim.sizew then w = Lerp(frac, anim.startw, anim.w) SetSize(panel, w, panel.starth) else h = Lerp(frac, anim.starth, anim.h) SetSize(panel, panel.startw, h) end if panel:GetDock() > 0 then panel:InvalidateParent() end end function LibClasses:SizeTo(w, h, length, delay, ease, callback) local anim = self:NewAnimation(length, delay, ease, callback) if w ~= -1 then anim.sizew = true end if h ~= -1 then anim.sizeh = true end anim.w, anim.h = w, h anim.Think = SizeThink return anim end local SetVisible = Panel.SetVisible local IsVisible = Panel.IsVisible local is_visible = function(s) local state = s.visible_state if state ~= nil then return state else return IsVisible(s) end end function LibClasses:AnimatedSetVisible(visible, cb) if visible == is_visible(self) then if cb then cb() end return end if visible then SetVisible(self, true) end self.visible_state = visible self:Stop() self:AlphaTo(visible and 255 or 0, 0.2, 0, function() SetVisible(self, visible) self:InvalidateParent(true) if cb then cb() end end) self:InvalidateParent(true) end function LibClasses:AnimatedToggleVisible() self:AnimatedSetVisible(not is_visible(self)) end function LibClasses:AnimatedIsVisible() return is_visible(self) end end function Panel:SUI_TDLib() for k, v in pairs(LibClasses) do self[k] = v end return self end TDLibUtil.Install = Panel.SUI_TDLib local count = 0 TDLibUtil.Start = function() count = count + 1 for k, v in pairs(LibClasses) do if not Panel["SUI_OLD" .. k] then local old = Panel[k] if old == nil then old = v end Panel[k], Panel["SUI_OLD" .. k] = v, old end end end TDLibUtil.End = function() count = count - 1 if count > 0 then return end for k, v in pairs(LibClasses) do local old = Panel["SUI_OLD" .. k] if old == v then Panel[k] = nil else Panel[k] = old end Panel["SUI_OLD" .. k] = nil end end TDLibUtil.HoverFunc = function(p) return p:IsHovered() and not p:GetDisabled() end TDLibUtil.DrawOutlinedBox = function(radius, x, y, w, h, bg, outline, thickness) thickness = thickness or 2 draw.RoundedBox(radius, x, y, w, h, outline) draw.RoundedBox(radius, x + thickness, y + thickness, w - (thickness * 2), h - (thickness * 2), bg) end do local cos, sin, sqrt = math.cos, math.sin, math.sqrt local clamp, floor = math.Clamp, math.floor local min, max = math.min, math.max local calc_ellipse_points = function(rx, ry) local points = sqrt(((rx * ry) / 2) * 6) return max(points, 8) end local M_PI = 3.14159265358979323846 calc_rect = function(c, r, x, y, w, h) if (c.r == r) and (c.x == x and c.y == y) and (c.w == w and c.h == h) then return end r = clamp(r, 0, min(w, h) / 2) local rx, ry = r, r if w >= 0.02 then rx = min(rx, w / 2.0 - 0.01) end if h >= 0.02 then ry = min(ry, h / 2.0 - 0.01) end local points = max(calc_ellipse_points(rx, ry) / 4, 1) points = floor(points) local half_pi = M_PI / 2 local angle_shift = half_pi / (points + 1) local phi = 0 for i = 1, points + 2 do c[i] = { x = x + rx * (1 - cos(phi)), y = y + ry * (1 - sin(phi)) } phi = phi + angle_shift end phi = half_pi for i = points + 3, 2 * (points + 2) do c[i] = { x = x + w - rx * (1 + cos(phi)), y = y + ry * (1 - sin(phi)) } phi = phi + angle_shift end phi = 2 * half_pi for i = (2 * (points + 2)) + 1, 3 * (points + 2) do c[i] = { x = x + w - rx * (1 + cos(phi)), y = y + h - ry * (1 + sin(phi)) } phi = phi + angle_shift end phi = 3 * half_pi for i = (3 * (points + 2)) + 1, 4 * (points + 2) do c[i] = { x = x + rx * (1 - cos(phi)), y = y + h - ry * (1 + sin(phi)) } phi = phi + angle_shift end local last = (points + 2) * 4 + 1 c[last] = c[1] for i = last + 1, #c do c[i] = nil end c.r = r c.x, c.y = x, y c.w, c.h = w, h end TDLibUtil.RoundedBox = function(c, r, x, y, w, h, color) calc_rect(c, r, x, y, w, h) SetDrawColor(color) NoTexture() DrawPoly(c) end end TDLibUtil.LibClasses = LibClasses sui.TDLib = TDLibUtil