name: galaxy-ui-and-dialogs description: Dialog and dialog control creation, XML frame hookup, hero/upgrade selection panels, scoreboard panels, dialog events, HUD messages, localized text, minimap pings, and the SSF hooked-frame UI pattern in Galaxy script. Use when building or updating any in-game UI such as dialogs, buttons, labels, images, portraits, click handlers, or player-visible messages. Do not use for actor-based visuals (use galaxy-actor-and-visuals).
Galaxy Scripting – UI & Dialogs
Key References
Dialogs
Dialogs are overlay panels created either programmatically or hooked up to existing UI frames defined in layout XML.
Hooking up an existing XML frame (SSF pattern — preferred)
SSF binds to frames already defined in the SC2 UI XML layout:
// Hook a panel frame by path from the root UI hierarchy
dialogcontrol gv_UI_MasterFrame = DialogControlHookupStandard(
c_triggerControlTypePanel,
"UIContainer/FullscreenUpperContainer/SSF_CustomUI"
);
// Hook a specific child control (button, label, etc.) within a parent frame
dialogcontrol saveBtn = DialogControlHookup(
gv_UI_MasterFrame,
c_triggerControlTypeButton,
"Menu/SaveButton"
);
// Register click event on the hooked control
TriggerAddEventDialogControl(
TriggerCreate("Bank_ManualSave"),
c_playerAny,
saveBtn,
c_triggerControlEventTypeClick
);
The init pattern in UI-Main.galaxy:
void SSFCustomUI_Init() {
gv_UI_MasterFrame = DialogControlHookupStandard(c_triggerControlTypePanel, "UIContainer/FullscreenUpperContainer/SSF_CustomUI");
StatsInterface_Init(); // each UI subsystem hooks its own controls
HeroPanel_Init();
PlayerBoard_Init();
// ...
}
Creating a dialog programmatically (alternate)
dialog lv_dlg = DialogCreate(
1200, // width (pixels)
600, // height (pixels)
c_anchorCenter, // anchor position on screen
0, // x offset from anchor
0, // y offset from anchor
false // modal (blocks input to game underneath)
);
// Or capture:
gv_heroDialog = DialogLastCreated();
Showing / hiding
DialogSetVisible(lv_dlg, PlayerGroupAll(), true); // show to all
DialogSetVisible(lv_dlg, PlayerGroupSingle(3), false); // hide for player 3
Anchor constants
c_anchorTopLeft
c_anchorTop
c_anchorTopRight
c_anchorLeft
c_anchorCenter
c_anchorRight
c_anchorBottomLeft
c_anchorBottom
c_anchorBottomRight
Dialog Controls
⭐ SC2-IngameDevTools DialogControlHookup pattern (PRIMARY — use for existing XML frames)
The SC2-IngameDevTools codebase hooks into pre-existing XML UI frames rather than creating dialog controls in code. This is the preferred pattern when UI frames are defined in Base.SC2Data/UI/Layout/:
// Hookup an existing XML panel by its frame path (no creation needed)
// The path is relative to the UI layout root
static const string CONTAINERDLG_PATH =
"UIContainer/ConsoleUIContainer/CatalogManager/BehaviorManager";
static const string EDITBOX_PATH = "Item";
static const string ADDBTN_PATH = "AddButton";
static const string REMOVEBTN_PATH = "RemoveButton";
struct BehaviorContainerStruct {
int panel; // the hooked panel (int = dialog control handle)
int messageBox;
int addButton;
int removeButton;
};
BehaviorContainerStruct BehaviorContainer;
void BehaviorHandler_Init() {
trigger t;
// Hook the root panel by absolute path
BehaviorContainer.panel =
DialogControlHookupStandard(c_triggerControlTypePanel, CONTAINERDLG_PATH);
// Hook child controls relative to the panel
BehaviorContainer.messageBox =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeEditBox, EDITBOX_PATH);
BehaviorContainer.addButton =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeButton, ADDBTN_PATH);
BehaviorContainer.removeButton =
DialogControlHookup(BehaviorContainer.panel, c_triggerControlTypeButton, REMOVEBTN_PATH);
// Register click handler — trigger registered by STRING function name
t = TriggerCreate("BehaviorContainerSendHandler");
TriggerAddEventDialogControl(t, c_playerAny, BehaviorContainer.addButton,
c_triggerControlEventTypeClick);
TriggerAddEventDialogControl(t, c_playerAny, BehaviorContainer.removeButton,
c_triggerControlEventTypeClick);
}
Key functions used:
| Function | Purpose |
|---|---|
DialogControlHookupStandard(type, path) |
Hook a root-level frame by absolute XML path |
DialogControlHookup(parent, type, childPath) |
Hook a child control relative to a parent panel |
DialogControlGetPropertyAsString(ctrl, prop, player) |
Read text/string property |
DialogControlSetPropertyAsString(ctrl, prop, pg, val) |
Set text/string property |
DialogControlGetPropertyAsInt(ctrl, prop, player) |
Read int property (e.g. selection index) |
DialogControlGetSelectedItem(list, player) |
Get selected listbox item index |
DialogControlAddItem(list, pg, text) |
Add item to a listbox/pulldown |
DialogControlRemoveAllItems(list, pg) |
Clear all listbox items |
DialogControlGetItemCount(list, player) |
Count items in a listbox |
Button
libNtve_gf_CreateDialogItemButton(
lv_dlg, // parent dialog
200, 50, // width, height
c_anchorTopLeft, // anchor within dialog
10, 10, // x, y offset from anchor
StringToText(""), // tooltip
StringToText("Click Me"), // label
"" // style (empty = default)
);
dialogcontrol lv_btn = DialogControlLastCreated();
Label
libNtve_gf_CreateDialogItemLabel(
lv_dlg,
300, 40,
c_anchorTopLeft,
10, 60,
StringToText("Score: 0"), // initial text
ColorWithAlpha(255, 255, 255, 255), // white
false, // word wrap
0 // wrap width (0 = no limit)
);
dialogcontrol lv_label = DialogControlLastCreated();
Image
libNtve_gf_CreateDialogItemImage(
lv_dlg,
100, 100,
c_anchorTopLeft,
10, 110,
StringToText(""), // tooltip
"Assets\\Textures\\UI_Protoss_Something.dds",
c_triggeredImageTypeNormal,
false, // tiled
ColorWithAlpha(255, 255, 255, 255)
);
dialogcontrol lv_img = DialogControlLastCreated();
Portrait / unit image
libNtve_gf_CreateDialogItemPortrait(
lv_dlg, 150, 150, c_anchorTopLeft, 10, 10,
StringToText(""), -1,
null, // unit (null for no unit)
true // use unit model
);
Updating Controls
// Change text
libNtve_gf_SetDialogItemText(lv_label, StringToText("Score: 42"), PlayerGroupAll());
// Change image
libNtve_gf_SetDialogItemImage(lv_img, "Assets\\Textures\\NewImage.dds", PlayerGroupAll());
// Enable / disable
DialogControlSetEnabled(lv_btn, PlayerGroupAll(), true);
DialogControlSetEnabled(lv_btn, PlayerGroupSingle(lv_player), false);
// Guard against an uninitialized control (c_invalidDialogControlId == 0)
if (lv_label != c_invalidDialogControlId) {
libNtve_gf_SetDialogItemText(lv_label, StringToText("..."), PlayerGroupAll());
}
Dialog Events
Register click handler on ALL controls in a dialog
TriggerAddEventDialogControl(
myTrigger,
c_playerAny,
c_invalidDialogControlId, // 0 = any control in dialog
c_triggerControlEventTypeClick
);
Register click handler on a specific button
TriggerAddEventDialogControl(
myTrigger,
c_playerAny,
lv_btn,
c_triggerControlEventTypeClick
);
Reading the event inside the handler
bool MyClickHandler(bool testCond, bool runActions) {
dialogcontrol clicked = EventDialogControl();
int player = EventPlayer();
if (clicked == gv_LockInButton) {
HeroSelection_SelectHero(player, gv_SelectedHero[player]);
}
return true;
}
Hero Selection Dialog Pattern (SSF)
SSF uses hooked XML frames for hero selection. Each panel is a separate file under scripts/UI/. The HeroSelection.galaxy file drives the logic while UI-HeroPanel.galaxy owns the dialog controls:
// UI-HeroPanel.galaxy
static dialogcontrol HeroPanel_MainFrame;
static dialogcontrol[gv_MaxAmountHeroes + 1] HeroPanel_HeroButtons;
void HeroPanel_Init() {
HeroPanel_MainFrame = DialogControlHookup(gv_UI_MasterFrame, c_triggerControlTypePanel, "HeroPanel");
int i = 1;
for (; i <= gv_MaxAmountHeroes; i += 1) {
HeroPanel_HeroButtons[i] = DialogControlHookup(HeroPanel_MainFrame, c_triggerControlTypeButton, "Hero" + IntToString(i));
}
TriggerAddEventDialogControl(TriggerCreate("HeroPanel_Click"), c_playerAny, c_invalidDialogControlId, c_triggerControlEventTypeClick);
}
void HeroPanel_UpdatePlayer(int playerID) {
// Show/hide based on unlock state
int i = 1;
for (; i <= gv_MaxAmountHeroes; i += 1) {
bool unlocked = ((gv_PlayerStats[playerID].heroUnlocked & (1 << i)) != 0);
DialogControlSetEnabled(HeroPanel_HeroButtons[i], PlayerGroupSingle(playerID), unlocked);
}
}
Level-up upgrade panel
bool HeroLevelUp_Handler(bool testCond, bool runActions) {
unit hero = EventUnit();
int level = UnitXPGetCurrentLevel(hero);
int player = UnitGetOwner(hero);
// Show appropriate upgrade panel for this level
if (level == 2) {
DialogControlSetVisible(gv_UpgradeFrame_Level2, PlayerGroupSingle(player), true);
}
return true;
}
Messages & Feedback
Display message in HUD area
UIDisplayMessage(PlayerGroupAll(), c_messageAreaChat, StringToText("GG!"));
UIDisplayMessage(PlayerGroupAll(), c_messageAreaSubtitle, StringToText("Round 1 – Fight!"));
UIDisplayMessage(
PlayerGroupSingle(lv_player),
c_messageAreaChat,
StringToText("Not enough minerals!")
);
// Area constants
c_messageAreaChat // bottom-left chat area
c_messageAreaSubtitle // center subtitle
c_messageAreaDefault
c_messageAreaObjective
c_messageAreaWarning
Error message with sound
libNtve_gf_UIErrorMessage(
PlayerGroupSingle(lv_player),
StringToText("Cannot afford this upgrade!"),
SoundLink("UI_GenericError", -1)
);
Localized Text
// Read from GameStrings.txt / Trig/ namespace
text lv_msg = StringExternal("Trig/Bosskilled");
// With variable replacements (SSF pattern)
text lv_result = Utility_TextExpressionReplacement3(
"Trig/Bosskilled",
IntToText(gv_Part_AmountObjectivesDefeated),
IntToText(gv_Part_AmountObjectivesMax),
IntToText(gv_Difficulty_Points)
);
// Colorize player name
color playerColor = libNtve_gf_ConvertPlayerColorToColor(PlayerGetColorIndex(playerID, false));
text colored = TextWithColor(StringToText(PlayerName(playerID)), playerColor);
// Combine text
text lv_msg2 = StringToText("+") + FixedToText(amount, 2);
UI Alerts & Minimap
// Toggle built-in alert types
UISetAlertTypeVisible(PlayerGroupAll(), "AlertWorkerAttacked", false);
// Minimap ping
PingCreate(PlayerGroupAll(), lv_point, 10.0, ColorWithAlpha(255, 0, 0, 255), "");
Scoreboard / Stats Panel (SSF pattern)
SSF uses hooked XML frames for the player board, updated by calling PlayerBoard_UpdatePlayer(playerID):
// UI-PlayerBoard.galaxy
static dialogcontrol PlayerBoard_MainFrame;
static dialogcontrol[gv_MaxAmountPlayers + 1] PlayerBoard_KillsLabel;
static dialogcontrol[gv_MaxAmountPlayers + 1] PlayerBoard_ScoreLabel;
void PlayerBoard_Init() {
PlayerBoard_MainFrame = DialogControlHookup(gv_UI_MasterFrame, c_triggerControlTypePanel, "PlayerBoard");
int i = 1;
for (; i <= gv_MaxAmountPlayers; i += 1) {
PlayerBoard_KillsLabel[i] = DialogControlHookup(PlayerBoard_MainFrame, c_triggerControlTypeLabel, "Player" + IntToString(i) + "/Kills");
}
}
void PlayerBoard_UpdatePlayer(int playerID) {
libNtve_gf_SetDialogItemText(
PlayerBoard_KillsLabel[playerID],
IntToText(gv_PlayerStats[playerID].kills),
PlayerGroupAll()
);
}
UI mode control
// Switch player(s) to fullscreen UI (hides default game HUD)
UISetMode(PlayerGroupAll(), c_uiModeFullscreen, c_transitionDurationImmediate);
// Hide specific HUD frames
UISetFrameVisible(PlayerGroupAll(), c_syncFrameTypeSupply, false); // hide supply display
UISetFrameVisible(PlayerGroupAll(), c_syncFrameTypeResourcePanel, false); // hide minerals/gas panel
// Hide alert types
UISetAlertTypeVisible(PlayerGroupAll(), "AlertWorkerAttacked", false);
Help Panel (Campaign)
// Hide the tech tree button inside the help panel
HelpPanelEnableTechTreeButton(PlayerGroupAll(), false);
// Navigate to a specific help panel page
HelpPanelDisplayPage(PlayerGroupAll(), c_helpPanelPageTutorials);
// Register a unit type on the help panel's unit tab
libCamp_gf_AddUnitTypeToUnitHelpPanel("Marine", false, lv_player);
// Create a campaign tutorial entry (shows in the help panel)
libCamp_gf_CreateCampaignTutorial(
StringToText("Tutorial Title"),
StringToText("Explanation body text here."),
"Assets\\Textures\\btn-unit-terr-marine.dds", // icon path
"" // video path (or empty)
);
UI Alerts & Objective Pings
// Beacon / alert for a map point (flashes minimap + compass)
UIAlertPoint("TriggerName", PlayerGroupSingle(lv_player), StringExternal("Param/Alert/..."), null, lv_point);
// Beacon / alert centered on a specific unit
UIAlertUnit("TriggerName", lv_player, StringExternal("Param/Alert/..."), null, lv_unit);
// Minimap ping with facing angle (used for objectives)
libNtve_gf_CreatePingFacingAngle(
PlayerGroupAll(),
"PingObjective", // ping type from data
lv_point,
ColorWithAlpha(0, 100, 0, 0), // RGBA color (0-255 or 0-1 depending on version)
0.0, // duration (0 = permanent until destroyed)
270.0 // facing angle
);
ping lv_ping = PingLastCreated();
PingSetScale(lv_ping, 0.75);
PingSetTooltip(lv_ping, StringToText("Objective location"));
Dialog Layout & Resolution
Internal resolution system
Dialogs are sized in an internal pixel space — they look the same regardless of the player's monitor resolution. The SC2 client maps this internal space to the player's screen:
| Screen ratio | Internal resolution (width × height) |
|---|---|
| 4:3 | 1600 × 1200 |
| 16:9 | 2133 × 1200 |
| 16:10 | 1920 × 1200 |
Height is always 1200 internal pixels. Width scales by ratio.
Practical rule: Keep dialogs no wider than 1600 px (the narrowest 4:3 width) to ensure they never clip or overflow on any screen ratio. Design at 1600 px wide and you're safe for all players.
// Safe full-screen-width dialog (works on all ratios)
dialog gv_mainDialog = DialogCreate(
1600, // safe max width
1200, // full height
c_anchorCenter,
0, 0,
false
);
// For UI elements, anchor to edges rather than absolute coords
// so they stay on-screen across all ratios:
// c_anchorTopLeft, c_anchorTopRight, c_anchorBottomLeft, c_anchorBottomRight, c_anchorCenter
DialogControlCreateFromTemplate
Wrapper pattern that avoids the GUI editor crash when creating items from a template string:
// Use this helper instead of the GUI "Create Dialog Item From Template" action
int CreateDialogItemFromTemplate(int dialog, int type, string template) {
return DialogControlCreateFromTemplate(dialog, type, template);
}