UI themes

The module PulseFoundationUI gives all Pulse widgets, and your own, one shared look. A theme is a data asset that holds colors, text styles, button styles, panel styles, brushes, widget styles, texts, sounds and motion timing. Widgets do not name a style; they name a role (“body text”, “primary button”, “popup panel”), and the theme decides how that role looks. One asset changes the look of every widget that uses it, in every Pulse plugin. Plugins and games add values for their own roles with theme extensions.

At a glance

  1. Project Settings > Pulse > Foundation > UI: set Default Theme (default: /PulseFoundation/UI/DA_PulseTheme_Default).
  2. In the UMG designer, select a widget, add the UI component Pulse Style and pick a Style Role, for example Pulse.UI.Text.Body on a Common Text Block or Pulse.UI.Panel.Popup on a Common Border. The designer shows the result right away.
  3. Make your own look: create a Pulse UI Theme data asset, set Parent Theme to the default theme and override only the roles you want to change.
  4. Change the look at runtime per player: Set Player Theme (or UPulseUIThemeSubsystem::SetTheme), for example a darker variant in a dangerous area. Widgets with Pulse Style components update themselves.

Roles

Roles are gameplay tags. The standard roles are native tags in PulseUITags.h; any other tag works as well, so games and plugins can add their own.

All values of a theme are in its Values (FPulseUIThemeValues), one map per kind:

Kind Map in Values Standard roles
Colors Colors Pulse.UI.Color.Accent, .Background (HUD elements: compass band, hotbar, marker pins), .Surface (panels), .Text, .TextMuted, .Positive, .Negative, .Warning, .Highlight, .Outline, .OnAccent, .Inset, .Backdrop, .BarLag (trailing loss of health bars)
Text styles TextStyles Pulse.UI.Text.Title, .Heading, .Subheading, .Label (small upper case labels), .Body, .Strong (emphasized body text, notification titles), .Small, .Caption, .Button, .Number, .Key (key names), .Overlay (text over the game world, with shadow), .OverlayLarge (damage numbers), .OverlayHeading (boss names), .Subtitle (spoken lines)
Letter case TextTransforms Per text or button role: ToUpper, ToLower or None; the default theme writes labels and buttons in upper case
Button styles ButtonStyles Pulse.UI.Button.Primary, .Secondary, .Danger, .Tab, .ListEntry, .Icon
Panels BorderStyles Pulse.UI.Panel.Window, .Popup, .Tooltip, .Toast, .Backdrop, .Inset, .HUD (containers on the HUD such as the hotbar)
Brushes Brushes Pulse.UI.Brush.Divider, .Focus, .Highlight, .Shadow, .MarkerDot, .MarkerPin, .EdgeArrow, .Glow, .Grain, .KeyCap (key cap behind key names without icon), .CompassBand, .Tick, .ProgressRing (material, scalar Percent), .Vignette, .Crosshair, .SlotActive (frame of the active slot); icons Pulse.UI.Icon.Chevron, .Arrow, .Close, .Check, .Dot, .Diamond, .Info, .Warning
Widget styles ProgressBarStyles, SliderStyles, CheckBoxStyles, ScrollBarStyles Pulse.UI.ProgressBar.Default, Pulse.UI.Slider.Default, Pulse.UI.CheckBox.Default, Pulse.UI.ScrollBar.Default
Texts Texts Labels widgets show, localizable; plugins use Pulse.UI.String.<Plugin>.<Name>
Sounds Sounds Pulse.UI.Sound.Open, .Close, .Notification, .Hint, .Error; each a sound asset or a Pulse Sound event (Sounds)
Aliases Aliases A role that takes the value of another role, for every kind (see below)

Text, button and panel styles are ordinary CommonUI style classes (UCommonTextStyle, UCommonButtonStyle, UCommonBorderStyle).

Themes and inheritance

UPulseUITheme (Primary Asset Type PulseUITheme) looks a role up in this order: the theme itself, its Parent Theme chain, the default theme of the settings, the theme extensions (higher priority first), then nothing (the widget keeps its own look). So a theme only needs the roles it changes. Motion timing is taken from the first theme in the chain with Override Motion set.

Aliases

An alias gives a role the value of another role, for every kind of value: Pulse.UI.Color.Selection -> Pulse.UI.Color.Accent makes the selection follow the accent color. Each theme and extension checks its own value of a role first, then its alias; the role of the alias is looked up again from the first theme. So an alias in a child theme wins over a value of the same role in its parent. Aliases that lead back to a role they passed (a ring) give no value; the log warns once per role and the asset check reports them.

The plugin ships two themes in /PulseFoundation/UI/:

Theme Look
DA_PulseTheme_Default “Pulse HUD Basisstil”: calm, dark, slightly transparent panels (Surface #141921 at 86 %, 1 px outline at 14 % white, soft shadow), three radii (8 panels, 5 buttons and slots, 3 bars), blue accent #5BC4F0, yellow highlight #F4C766; Roboto for text, Roboto Condensed for numbers, labels and keys
DA_PulseTheme_Light Light variant; inherits from the default theme and changes colors, text, button and panel styles

Colors have a meaning in the default theme: blue = active, selection, shield; yellow = goal and attention; green = health and success; red = damage and enemies; orange = warnings. Text over the game world uses the overlay roles with a shadow instead of a panel.

Lookup functions (Blueprint and C++): FindColor, GetColor, FindTextStyle, FindButtonStyle, FindBorderStyle, FindBrush, FindSound, FindText, GetText, FindTextTransform, FindButtonRole (role of a button style), GetMotion; C++ also FindBrushPtr, FindProgressBarStyle, FindSliderStyle, FindCheckBoxStyle, FindScrollBarStyle, FindValue(Role, &FPulseUIThemeValues::Colors) for any kind, VisitThemeChain and GatherValueSources. UPulseUITheme::Resolve(Theme) returns a theme, never null.

Data validation reports parent cycles, entries without role, empty style entries, aliases to the same role and alias rings.

Example: a theme that only changes colors, and a panel that uses style roles, key icons and motion (see Examples):

Theme extensions

A Pulse UI Theme Extension (UPulseUIThemeExtension, data asset) adds values to every theme without being part of one: a plugin gives its own roles (icons, texts, sounds) a look that works with any theme, a game adds roles or aliases for all its themes at once.

  • Values: the same maps as a theme.
  • Priority: order among the extensions, higher first. Themes always come before extensions, so a theme can change every value an extension brings.
  • Every extension of the project is used, wherever it lies (project, plugins, Fab packages): they are found by class in the asset registry and registered as Primary Asset Type PulseUIThemeExtension with the cook rule “always cook”, so nothing has to reference them. Two extensions with the same priority are ordered by asset path.
  • Project Settings > Pulse > Foundation > UI > Disabled Extensions turns single extensions off.
  • The check of an extension warns when another extension with the same priority sets the same role in the same map (which one wins depends on the path) and reports alias rings.
  • Console: Pulse.UI.Extensions lists the extensions in lookup order (Console).

C++: Pulse::UI::GetThemeExtensions(), ForEachThemeExtension, RefreshThemeExtensions (after changing extensions or the settings from code), FilterAndSortThemeExtensions, FindSharedRoles.

The Pulse Style component

UPulseUIStyleComponent (“Pulse Style”) applies roles to the widget it is attached to, in the designer (OnPreConstruct) and at runtime, and again when the player’s theme changes.

Widget StyleRole is looked up in
Common Text Block TextStyles (SetStyle) and the letter case of TextTransforms
Text Block, Rich Text Block TextStyles (font, color and shadow of the style); text blocks also take the letter case
Common Button ButtonStyles
Common Border BorderStyles
Border BorderStyles (background brush), else Brushes
Image Brushes, else Colors
Progress Bar, Slider, Check Box, Scroll Box the matching widget style map
  • ColorRole tints the widget afterwards (text color, image tint, border brush color, progress fill).
  • ThemeOverride makes the widget always use one theme.
  • SetStyleRole, SetColorRole, ApplyStyle change and apply roles at runtime.
  • Validation warns when the default theme has no entry for the role that fits the widget type.
  • Without component: Apply Style Role (UPulseUILibrary) once, or UPulseUIStyleComponent::ApplyRoleToWidget in C++. Pulse::UI::ApplyTextStyleToTextBlock gives a plain text block a CommonUI text style.

Key icons

Prompts, hints and buttons show keys as icons that follow the player’s input device.

  • Pulse Key Cap (UPulseUIKeyCap, palette “Pulse”): shows the key of an InputAction (Enhanced Input) or a fixed Key / GamepadKey, and updates itself when the player switches between keyboard and gamepad (CommonInput). Height sets the size; icons keep their aspect.
  • Pulse Key Decorator (UPulseUIKeyDecorator): add it to the decorator classes of a Rich Text Block and write <key id="E"/>, <key id="Gamepad_FaceButton_Bottom"/> or <key action="/Game/Input/IA_Jump.IA_Jump"/> into the text. HeightScale (default 2.2) sets the icon height relative to the font size.
  • C++: Pulse::UI::ResolveKeyDisplay, GetShortKeyName, MakeKeyCapWidget.

Icons come from CommonInput controller data (UCommonInputBaseControllerData). PulseFoundation ships three sets in /PulseFoundation/UI/Input/, drawn as soft 3D key caps and buttons:

Controller data Icons
CID_Pulse_KeyboardMouse A-Z, 0-9, F1-F12, Esc, Tab, Shift, Ctrl, Alt, Space, Caps, Enter, Backspace, Insert, Delete, Home, End, Page Up/Down, arrows, mouse buttons and wheel
CID_Pulse_Xbox A (green), B (red), X (blue), Y (yellow), LB/RB, LT/RT, View, Menu, sticks, D-pad; picked for XInput devices
CID_Pulse_Gamepad Generic gamepad: face buttons as direction symbols, L1/R1, L2/R2, sticks, Select, Start, D-pad

The key caps look the icon up in this order:

  1. The CommonInput platform settings of the project (Project Settings > Common Input > Platform Input > Windows > Controller Data). Register the Pulse sets there (or your own) to use them in CommonUI widgets too, for example the input action widgets of CommonUI buttons.
  2. Fallback Controller Data of the Pulse UI settings (default: the three Pulse sets). For gamepad keys the set whose hardware mapping matches the player’s last used device wins (Xbox for XInput), then the current gamepad name, then the generic set. So key icons work without any setup.
  3. No icon: the key name on a plain key cap (brush role Pulse.UI.Brush.KeyCap, text role Pulse.UI.Text.Key).

Theme per player

UPulseUIThemeSubsystem (local player) holds the theme of a player: SetTheme (null = default theme), GetTheme (never null), GetOwnTheme, events OnThemeChanged / OnThemeChangedNative. Nothing is replicated or saved. Pulse::UI::GetTheme(Widget) returns the theme of a widget’s owning player, in the designer the default theme. FindText and GetText of the subsystem read the texts of the player’s theme.

UI motion

UPulseUIMotionSubsystem (game instance) animates the render opacity, translation and scale of widgets, so widgets fade, slide and pop without UMG animations. It uses real time (runs while the game is paused) and only ticks while motions run. One motion per widget: a new one replaces the running one.

Preset Motion
FadeIn / FadeOut Opacity only (keeps the render translation, so it works on widgets that place themselves with it)
SlideIn / SlideOut Fade plus slide from or to a side (EPulseUIDirection) by Motion.SlideDistance
PopIn / PopOut Fade plus scale from or to Motion.PopScale
  • Blueprint: Play Pulse UI Motion (async, pins OnFinished and OnInterrupted) or Play Motion (fire and forget), Stop Motion.
  • C++: Pulse::UI::PlayMotion(Widget, Preset, Direction, Speed, OnFinished, Delay); the callback gets true at the end and false when the motion was replaced or stopped.
  • Durations (Fast, Normal, Slow), curves, distances and the stack transition come from FPulseUIMotion of the theme. Apply Stack Transition gives a CommonUI stack or queue the theme’s transition (call it before the container is shown).
  • Project setting Reduce Motion, or the player’s accessibility setting Reduce motion: widgets jump to the end state.

Text size: UPulseUIThemeSubsystem has a text scale per player (Set Text Scale, Get Text Scale; the accessibility setting Text size sets it). Text roles get the font size of their style times the scale, and widgets update like on a theme change.

Other functions

UPulseUILibrary: Get Widget Theme, Get Player Theme, Set Player Theme, Find Theme Text, Get Theme Text (labels a game can rename in its theme), Apply Style Role, Play Theme Sound (2D sound of a sound role, through Pulse::Sound::Play), Apply Stack Transition, Play Motion, Stop Motion.

Where menus of plugins show

Plugins that open a menu for a player (the save menu of Pulse Save, for example) hand it to a presenter (UPulseUIPresenter): Open Widget (player controller, widget class) shows the menu and returns it, Close Widgets closes every menu it opened, Find Widget returns the open one of a class. The default adds one widget per class to the viewport (ZOrder 50) and removes it when it closes. A game with layers or stacks of its own (Pulse HUD, CommonUI activatable stacks) derives a presenter in Blueprint or C++, overrides Open Widget to push the widget onto its stack and names the class in the settings of the plugin (PresenterClass).

Cheats

Cheat Does
PulseUITheme <name or path> Gives the local player a theme, e.g. PulseUITheme DA_PulseTheme_Light
PulseUIThemeReset Back to the default theme
PulseUIThemeList Lists the theme assets

Content

/PulseFoundation/UI/: the two themes, Styles/ (text styles TS_Pulse_*, button styles BS_Pulse_*, border styles BRS_Pulse_*, light variants in Styles/Light), Fonts/F_PulseRoboto (typefaces Regular, Medium, SemiBold, Bold, Italic of Roboto and CondensedMedium, CondensedBold of Roboto Condensed; both SIL Open Font License 1.1, see License/Roboto.txt and License/RobotoCondensed.txt), Textures/ (white, tintable icons, soft shadow, glow, grain, focus corners, key cap, compass band, marker pin, vignette, crosshair, active slot frame, hit arc), Materials/ (M_PulseUI_RadialProgress: ring with track and center disc, Percent, Thickness, TrackColor, CenterColor, fill = brush tint; M_PulseUI_RadialWipe: clockwise wipe for cooldowns, Percent) and Input/ (key icons and controller data).


PulseFoundation plugin documentation.

This site uses Just the Docs, a documentation theme for Jekyll.