904cf3566c
Implement a responsive announcement display system with support for multiple styles (loader with progress bar and border timer). Features include: - Dynamic font scaling based on screen resolution - Smooth intro/outro animations with easing - Text wrapping for multi-line messages - Customizable styling and duration - Border timer visualization for remaining time - Network message handling for server-sent announcements
237 lines
8.5 KiB
Lua
237 lines
8.5 KiB
Lua
local rndx = include("lib/rndx.lua")
|
|
local logo = Material("nocode/announcement/logo.png")
|
|
|
|
-- client/ charge avant shared/, donc je m'assure que la table est là
|
|
BladW = BladW or {}
|
|
BladW.Announcement = BladW.Announcement or {}
|
|
|
|
local COL_ACCENT = Color(76, 119, 200)
|
|
local COL_WHITE = Color(255, 255, 255)
|
|
local COL_BG = Color(0, 0, 0, 200)
|
|
local COL_TRACK = Color(54, 54, 54, 200)
|
|
|
|
-- fonts responsives (refaites quand la résolution change)
|
|
local function AnnScale()
|
|
return math.min(ScrW() / 1920, ScrH() / 1080)
|
|
end
|
|
|
|
-- durées + easing de l'anim d'apparition/disparition
|
|
local INTRO_TIME = 0.45
|
|
local OUTRO_TIME = 0.35
|
|
local function EaseOutCubic(t) return 1 - (1 - t) ^ 3 end
|
|
|
|
local function BuildFonts()
|
|
local s = AnnScale()
|
|
surface.CreateFont("bladw_ann_title", {
|
|
font = "Poppins-SemiBold", size = math.max(1, math.Round(25 * s)), weight = 600, antialias = true,
|
|
})
|
|
surface.CreateFont("bladw_ann_text", {
|
|
font = "Poppins-SemiBold", size = math.max(1, math.Round(20 * s)), weight = 800, antialias = true,
|
|
})
|
|
end
|
|
BuildFonts()
|
|
hook.Add("OnScreenSizeChanged", "BladeWin:AnnouncementFonts", BuildFonts)
|
|
|
|
-- coupe le texte en lignes qui rentrent dans maxW (px) pour la police donnée
|
|
local function WrapText(text, font, maxW)
|
|
surface.SetFont(font)
|
|
local lines, line = {}, ""
|
|
|
|
for _, word in ipairs(string.Explode(" ", text or "")) do
|
|
local test = (line == "") and word or (line .. " " .. word)
|
|
if surface.GetTextSize(test) > maxW and line ~= "" then
|
|
lines[#lines + 1] = line
|
|
line = word
|
|
else
|
|
line = test
|
|
end
|
|
end
|
|
if line ~= "" then lines[#lines + 1] = line end
|
|
|
|
return lines
|
|
end
|
|
|
|
-- le contour du cadre sert de jauge : reste en colRemain, ce qui est passé en colDone (1 = plein, 0 = fini)
|
|
local function DrawBorderTimer(x, y, w, h, r, remaining, colRemain, colDone, thickness)
|
|
thickness = math.max(1, thickness or 3)
|
|
|
|
local pts = {}
|
|
local function line(x1, y1, x2, y2)
|
|
local steps = math.max(1, math.floor(math.Distance(x1, y1, x2, y2) / 4))
|
|
for i = 0, steps do
|
|
pts[#pts + 1] = { Lerp(i / steps, x1, x2), Lerp(i / steps, y1, y2) }
|
|
end
|
|
end
|
|
local function arc(cx, cy, a0, a1)
|
|
for i = 0, 6 do
|
|
local a = math.rad(Lerp(i / 6, a0, a1))
|
|
pts[#pts + 1] = { cx + math.cos(a) * r, cy + math.sin(a) * r }
|
|
end
|
|
end
|
|
|
|
-- sens horaire à partir du bord haut
|
|
line(x + r, y, x + w - r, y)
|
|
arc (x + w - r, y + r, 270, 360)
|
|
line(x + w, y + r, x + w, y + h - r)
|
|
arc (x + w - r, y + h - r, 0, 90)
|
|
line(x + w - r, y + h, x + r, y + h)
|
|
arc (x + r, y + h - r, 90, 180)
|
|
line(x, y + h - r, x, y + r)
|
|
arc (x + r, y + r, 180, 270)
|
|
|
|
local total = 0
|
|
for i = 1, #pts - 1 do
|
|
total = total + math.Distance(pts[i][1], pts[i][2], pts[i + 1][1], pts[i + 1][2])
|
|
end
|
|
if total == 0 then return end
|
|
|
|
local consumedLen = (1 - remaining) * total
|
|
draw.NoTexture()
|
|
|
|
local acc = 0
|
|
for i = 1, #pts - 1 do
|
|
local x1, y1 = pts[i][1], pts[i][2]
|
|
local x2, y2 = pts[i + 1][1], pts[i + 1][2]
|
|
local segLen = math.Distance(x1, y1, x2, y2)
|
|
|
|
surface.SetDrawColor(acc < consumedLen and colDone or colRemain)
|
|
surface.DrawTexturedRectRotated((x1 + x2) * 0.5, (y1 + y2) * 0.5,
|
|
segLen + thickness, thickness, -math.deg(math.atan2(y2 - y1, x2 - x1)))
|
|
|
|
acc = acc + segLen
|
|
end
|
|
end
|
|
|
|
-- état de l'annonce en cours (survit au reload)
|
|
BladW.Announcement.Active = BladW.Announcement.Active or false
|
|
BladW.Announcement.Title = BladW.Announcement.Title or "Annonce"
|
|
BladW.Announcement.Text = BladW.Announcement.Text or ""
|
|
BladW.Announcement.Style = BladW.Announcement.Style or "loader"
|
|
BladW.Announcement.Duration = BladW.Announcement.Duration or 10
|
|
BladW.Announcement.StartTime = BladW.Announcement.StartTime or 0
|
|
|
|
-- lance une annonce. style/duration vides = valeurs de la config
|
|
function BladW.Announcement.Show(title, text, style, duration)
|
|
local Conf = BladW.Announcement.Conf or {}
|
|
|
|
BladW.Announcement.Title = (title and title ~= "") and title or "Annonce"
|
|
BladW.Announcement.Text = text or ""
|
|
BladW.Announcement.Style = (style and style ~= "") and style or (Conf.announcement_style or "loader")
|
|
BladW.Announcement.Duration = (duration and duration > 0) and duration or (Conf.announcement_duration or 10)
|
|
BladW.Announcement.StartTime = CurTime()
|
|
BladW.Announcement.Active = true
|
|
|
|
if Conf.announcement_sound and Conf.announcement_sound ~= "" then
|
|
surface.PlaySound(Conf.announcement_sound)
|
|
end
|
|
end
|
|
|
|
-- le serveur m'envoie une annonce à afficher
|
|
net.Receive("bladWShowAnnouncement", function()
|
|
local title = net.ReadString()
|
|
local text = net.ReadString()
|
|
local style = net.ReadString()
|
|
local duration = net.ReadUInt(16)
|
|
BladW.Announcement.Show(title, text, style, duration)
|
|
end)
|
|
|
|
-- affichage (responsive + s'adapte au message)
|
|
hook.Add("HUDPaint", "BladeWin:Announcement", function()
|
|
if not BladW.Announcement.Active then return end
|
|
|
|
local ply = LocalPlayer()
|
|
if not IsValid(ply) then return end
|
|
|
|
local duration = BladW.Announcement.Duration or 10
|
|
local elapsed = CurTime() - BladW.Announcement.StartTime
|
|
|
|
-- temps écoulé -> on cache
|
|
if elapsed >= duration then
|
|
BladW.Announcement.Active = false
|
|
return
|
|
end
|
|
|
|
local style = BladW.Announcement.Style or "loader"
|
|
local title = BladW.Announcement.Title
|
|
local text = BladW.Announcement.Text
|
|
local remaining = math.Clamp(1 - (elapsed / duration), 0, 1)
|
|
|
|
-- échelle responsive
|
|
local s = AnnScale()
|
|
local padX = 26 * s
|
|
local padY = 18 * s
|
|
local gap = 8 * s
|
|
local logoS = 62 * s
|
|
local barW = 200 * s
|
|
local barH = 18 * s
|
|
local radius = 14 * s
|
|
|
|
-- wrap du message (largeur interne responsive, la vraie limite c'est le nb de caractères)
|
|
local wrapW = ScrW() * 0.8
|
|
local lines = WrapText(text, "bladw_ann_text", wrapW)
|
|
|
|
surface.SetFont("bladw_ann_title")
|
|
local titleW, titleH = surface.GetTextSize(title)
|
|
|
|
surface.SetFont("bladw_ann_text")
|
|
local _, lineH = surface.GetTextSize("Ay")
|
|
local msgW = 0
|
|
for _, l in ipairs(lines) do
|
|
msgW = math.max(msgW, (surface.GetTextSize(l)))
|
|
end
|
|
|
|
-- taille de la box selon le contenu
|
|
local contentW = math.max(titleW, msgW, style == "loader" and barW or 0)
|
|
local boxW = math.Clamp(contentW + padX * 2, 260 * s, ScrW() * 0.92)
|
|
local msgH = #lines * lineH
|
|
local boxH = padY + logoS + gap + titleH + gap + msgH
|
|
+ (style == "loader" and (gap + barH) or 0) + padY
|
|
|
|
-- anim : fondu + glisse du haut (et l'inverse quand ça part)
|
|
local appear = math.Clamp(elapsed / INTRO_TIME, 0, 1)
|
|
local disappear = math.Clamp((duration - elapsed) / OUTRO_TIME, 0, 1)
|
|
local anim = math.min(EaseOutCubic(appear), EaseOutCubic(disappear)) -- 0 -> 1 -> 0
|
|
local slide = (1 - anim) * 26 * s
|
|
|
|
local boxX = (ScrW() - boxW) * 0.5
|
|
local boxY = ScrH() * 0.035 - slide
|
|
local cx = boxX + boxW * 0.5
|
|
|
|
surface.SetAlphaMultiplier(anim)
|
|
|
|
-- fond
|
|
rndx.Draw(radius, boxX, boxY, boxW, boxH, nil, rndx.SHAPE_FIGMA + rndx.BLUR)
|
|
rndx.Draw(radius, boxX, boxY, boxW, boxH, COL_BG, rndx.SHAPE_FIGMA)
|
|
|
|
-- le loader garde son contour blanc (le simple lui a sa jauge sur le bord)
|
|
if style == "loader" then
|
|
rndx.DrawOutlined(radius, boxX, boxY, boxW, boxH, COL_WHITE, 3 * s, rndx.SHAPE_FIGMA)
|
|
end
|
|
|
|
-- contenu empilé et centré
|
|
local y = boxY + padY
|
|
rndx.DrawMaterial(0, cx - logoS * 0.5, y, logoS, logoS, COL_WHITE, logo)
|
|
y = y + logoS + gap
|
|
|
|
draw.SimpleText(title, "bladw_ann_title", cx, y, COL_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
|
|
y = y + titleH + gap
|
|
|
|
for _, l in ipairs(lines) do
|
|
draw.SimpleText(l, "bladw_ann_text", cx, y, COL_WHITE, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP)
|
|
y = y + lineH
|
|
end
|
|
|
|
-- jauge de temps
|
|
if style == "loader" then
|
|
y = y + gap
|
|
local bx = cx - barW * 0.5
|
|
rndx.Draw(barH * 0.5, bx, y, barW, barH, COL_TRACK, rndx.SHAPE_FIGMA)
|
|
rndx.Draw(barH * 0.5, bx, y, barW * remaining, barH, COL_ACCENT, rndx.SHAPE_FIGMA)
|
|
else
|
|
-- le bord bleu qui se vide en blanc
|
|
DrawBorderTimer(boxX, boxY, boxW, boxH, radius, remaining, COL_ACCENT, COL_WHITE, 3 * s)
|
|
end
|
|
|
|
surface.SetAlphaMultiplier(1)
|
|
end)
|