Skip to content

Themed GUIs, HUD elements and appearance pages

Lesson 14.9

Players choose how Celestial looks in Appearance: the Vanilla style (the game’s own widgets) or the Fancy style (the Celestial design), and a colour theme. With the UI kit your screens, your HUD parts and your settings follow that choice by themselves. You write them once; the player sees them in their style.

This lesson builds the three parts of a small compass mod. The whole mod is in the SDK as sdk/examples/ui_kit_lua.

A themed screen

Add themed = true to a screen and use kit widgets:

local function open_settings()
  cl.screen.open{
    title = "Compass Plus",
    pause = false,
    themed = true,
    init = function(s)
      local x, y = s:width() // 2 - 150, 30
      s:panel(x - 14, y - 14, 328, 210)
      s:heading(x, y, "Compass Plus", 1)
      s:toggle(x, y + 30, 300, 20, "Show the compass bar", true, function(t)
        print("show: " .. t:value())          --> show: 0 (after a click)
      end)
      s:slider(x, y + 56, 300, 20, "Bar width", 80, 240, 160, 10, function(w)
        print("width: " .. w:value())
      end)
      s:dropdown(x, y + 86, 300, 20, "Labels", "Letters\nDegrees", 0, function(d)
        print("labels: " .. d:text())          --> labels: Degrees
      end)
      s:button(x + 210, y + 160, 90, 20, "Done", function() s:close() end)
    end,
  }
end

Open it and switch Appearance > Style between Vanilla and Fancy while it is open: the same screen redraws in the other style at once. In Vanilla the toggle is a button reading “Show the compass bar: ON” and the dropdown cycles through its choices when you click; in Fancy they are a switch and a field with a list.

The kit has toggle, icon_button, dropdown, tabs, list, panel, card, heading and color_picker, plus the widgets you already know (button, checkbox, slider, text_field, label). The UI Kit lists every argument.

A list

local list = s:list(x, y, 220, 120, 24, function(l)
  print("picked row " .. l:value())          --> picked row 2
end)
list:list_add("Home", "214 m north-west", "marker")
list:list_add("Death point", "38 m south", "warning")

The third argument of list_add is an icon name ("marker", "world", "star" …) or your own GUI sprite as "my_mod:waypoint".

Your own drawing in the right style

In a themed screen’s render, the drawing context knows the style too. Use the themed functions and your drawing matches the widgets:

render = function(s, g, mx, my)
  g:panel(10, 10, 120, 40)
  g:ui_text("Distance", 18, 16, "small", 0)   -- 0: the style's own colour
  g:ui_text("214 m", 18, 28, "h3", 0)
end,

cl.ui.fancy() tells you the style when you need to decide something yourself, and cl.ui.color("panel") or cl.ui.length("radius.box") give the theme’s values (Theme Tokens).

A HUD element

A HUD layer draws wherever you say. A HUD element is a box the player moves, scales and hides in the HUD editor:

local heading = 0
local bar = cl.hud.element_register{
  id = "compass", label = "Compass bar", icon = "marker",
  width = 160, height = 14, anchor = "top", x = 0, y = 6,
  panel = true, cached = true,
  render = function(g, w, h)
    g:ui_text(tostring(heading), w / 2 - 6, 2, "small", 0)
  end,
}

cl.on("tick.client_post", function()
  local yaw = math.floor(cl.entity.client_player():rotation().yaw) % 360
  if yaw ~= heading then
    heading = yaw
    bar:invalidate()                         -- draw it again with the new number
  end
end)
  • render(g, w, h) draws inside the element’s own box, from (0, 0).
  • panel = true puts the Fancy HUD panel behind it.
  • cached = true keeps the last drawing: the game replays it every frame and calls render again only after invalidate(). Your mod costs almost nothing while the player stands still.

Open the HUD editor (Appearance > HUD > Open HUD Editor): “Compass bar” is listed under From mods, and you can drag it.

An Appearance page

Settings that change how your mod looks belong in Appearance > Mods. A page shows rows bound to your client config:

local config = cl.config.open("client")
config:define_bool("bar.show", true)
config:define_i32("bar.width", 160, 80, 240)
config:define_str("bar.labels", "Letters")

cl.on("lifecycle.client_setup", function()
  local page = cl.ui.page_register{
    id = "settings", label = "Compass Plus", icon = "marker",
    on_change = function(page, key) bar:invalidate() end,
  }
  page:option("bar.show", "Show the compass bar", "")
  page:option("bar.width", "Bar width", "Width in GUI pixels")
  page:choice("bar.labels", "Labels", "", "Letters\nDegrees")
  page:hud_element(bar)
end)

The player edits the values like any other setting; they are saved in your config file and config:get_* returns them.

Try it

Add a Look tab to the settings screen with a colour picker for the compass marker. Save the colour in the client config as "#RRGGBB" and show the same option on the Appearance page.

Show a solution
config:define_str("bar.marker", "#FFAA00")

-- in init: a tabs widget whose second page holds the picker
local c = tonumber(config:get_str("bar.marker"):sub(2), 16)
local picker = s:color_picker(x, y + 30, 300, 20, "Marker colour", c, function(p)
  config:set_str("bar.marker", string.format("#%06X", math.floor(p:value()) & 0xFFFFFF))
  config:save()
  bar:invalidate()
end)
local general = s:toggle(x, y + 30, 300, 20, "Show the compass bar", true, function() end)
picker:set_visible(false)
s:tabs(x, y, 300, 20, "General\nLook", 0, function(t)
  general:set_visible(t:value() == 0)
  picker:set_visible(t:value() == 1)
end)

-- on the Appearance page
page:color("bar.marker", "Marker colour", "The centre marker")

Recap: themed = true + kit widgets for screens, g:panel / g:ui_text for your own drawing, cl.hud.element_register{...} for HUD parts the player places, cl.ui.page_register{...} for an Appearance page.