CityRP Documentation Repository

UI Scaling

Last updated

The cityrp.ui library (gamemode/core/libraries/cl_ui.lua) is the mandatory way to scale client UI — Derma panels, entity menus, HUD overlays, and any other screen-space layout or drawing. It combines automatic resolution scaling (1920×1080 reference) with the player's cl_llhud_scale preference and keeps every consumer in sync when either input changes.

When to use it

Use cityrp.ui for any client-side UI dimension that should track screen size or the player's scale setting:

  • Panel sizes, margins, padding, and positions
  • Font sizes
  • Icon and button dimensions
  • draw.* coordinates in custom Paint functions

Do not roll your own scale factor. Ad-hoc ScrW() / 1920 math, calling LimelightHUD.GetScaleFactor() directly, or hardcoded pixel values that never rebuild on scale change are legacy patterns — they drift from the HUD, ignore cl_llhud_scale, and break when resolution changes mid-session.

Enforcement: new or modified lines that introduce ScrW() / 1920, ScrH() / 1080, or LimelightHUD.GetScaleFactor() fail standards-diff.py in CI (legacy core/derma/cl_hud_*.lua panels are grandfathered until migrated). See Coding Standards.

Core API

Function Purpose
cityrp.ui.Scale(value) Scale a single pixel measurement to the nearest whole pixel. Use for widths, heights, margins, positions, and any other screen-space dimension.
cityrp.ui.GetScaleFactor() Return the raw, unrounded scale multiplier. Use only when you need the factor itself (e.g. proportional math before a single final round), not for individual pixel measurements — prefer Scale() for those.
cityrp.ui.CreateScaledFont(name, baseFont, baseSize[, weight]) Create a font at the current scale. baseSize is the size at 1920×1080.
cityrp.ui.OnScaleChanged(id, callback) Register a callback that runs immediately, then again (debounced) whenever cl_llhud_scale or screen resolution changes. Use to build and rebuild fonts and relayout panels. The callback must be safe to run at registration time.
cityrp.ui.Shade(colour, amount) Lighten or darken a colour for hover/pressed states. Cache the result at style time, not inside Paint.
cityrp.ui.ScaleCheckBox(checkbox, font) Apply a scaled font to a DCheckBoxLabel and grow its tick box to match.
cityrp.ui.AddTitleBarCloseButton(frame, titleBarHeight, onClose) Add the shared text-only close button used on CityRP menu title bars.

Panel kit

cityrp.ui also carries the panel kit — StyleFrame, StyleButton, ButtonColours, StyleList, StyleCard, PaintCard, and the FONT_TITLE / FONT_BODY / FONT_CAPTION fonts. Those are a matter of values rather than scaling, so they are documented in UI Style, which names the helper for each pattern.

Standard patterns

Scale dimensions

local pad = cityrp.ui.Scale(10)
frame:SetSize(cityrp.ui.Scale(400), cityrp.ui.Scale(300))
button:DockMargin(pad, pad, pad, pad)

Scaled fonts

OnScaleChanged runs the callback once at registration, so a single registration both builds the fonts and keeps them in sync — there is no separate load-time call:

cityrp.ui.OnScaleChanged("mymenu", function()
	cityrp.ui.CreateScaledFont("MyMenuTitle", "Roboto", 17, 500)
	cityrp.ui.CreateScaledFont("MyMenuBody", "Roboto", 13, 400)
end)

That first call is synchronous, so the fonts exist by the line after the registration.

Relayout on scale change

If a panel's geometry depends on scaled values, rebuild or relayout inside the OnScaleChanged callback — not only the fonts. Menus that watched only cl_llhud_scale but not resolution kept the sizes they were built with when the screen size changed underneath them; OnScaleChanged watches both.

Because the callback also runs at registration, a relayout handler must guard its own preconditions rather than assume the thing it relayouts already exists:

cityrp.ui.OnScaleChanged("mymenu.layout", function()
	if not IsValid(panel) then return end
	panel:SetPos(cityrp.ui.Scale(16), cityrp.ui.Scale(16))
end)

What not to do

❌ Forbidden ✅ Use instead
ScrW() / 1920 (or similar resolution ratios) cityrp.ui.Scale() / cityrp.ui.GetScaleFactor()
LimelightHUD.GetScaleFactor() in new code cityrp.ui.GetScaleFactor()
surface.CreateFont with a fixed size for UI text cityrp.ui.CreateScaledFont
Hardcoded pixel sizes with no scale rebuild cityrp.ui.Scale() + cityrp.ui.OnScaleChanged
cvars.AddChangeCallback("cl_llhud_scale", ...) alone cityrp.ui.OnScaleChanged (also watches resolution)

Existing HUD derma under gamemode/core/derma/cl_hud_*.lua still calls LimelightHUD.GetScaleFactor() directly — that predates cityrp.ui. New or refactored UI code must go through the library.