BGE/UI
Alias: BGE.UI
Static Methods
loadBackgroundImage(path: string): dynamic
Loads an image at path for use as Theme.backgroundImage, picking between a stretchable BGE.UI.NinePatchImage and a plain BGE.UI.ImageBackground automatically: a path ending in ".9.png" (case-insensitive), Android's own naming convention for this asset format, loads as a 9-patch (see loadNinePatchImage()); any other path loads as a plain image, stretched to fill entirely with no unstretched corners. Returns invalid if the image at path could not be loaded.
Parameters
path(string) — a pkg:/ path to a background image, ".9.png"-suffixed for a 9-patch
Returns
dynamic
isNinePatchPath(path: string): boolean
Parameters
path(string)
Returns
boolean
loadNinePatchImage(path: string): BGE.UI.NinePatchImage
Loads a PNG authored in Android's .9.png convention (a 1px transparent border with black-opaque pixel runs marking the stretch region on the top row and left column) and returns a ready-to-use NinePatchImage with insets read from the image itself - see BGE.UI.NinePatchImage for the full format. If a border has no marker pixels at all, that axis defaults to a 0 inset (the whole image stretches on that axis) rather than throwing - a game developer authoring their own asset should notice this visually (unstretched corners look wrong), but a missing marker is not otherwise treated as an error. Returns invalid if the image at path could not be loaded.
Parameters
path(string) — a pkg:/ path to a .9.png-style PNG
Returns
BGE.UI.NinePatchImage
loadBitmapOrLog(path: string, description: string): roBitmap
Loads a roBitmap from a pkg:/ path, logging and returning invalid instead of throwing if it can't be loaded - shared by loadNinePatchImage() and BGE.UI.loadBackgroundImage() instead of each duplicating this guard.
Parameters
path(string) — a pkg:/ path to an imagedescription(string) — what kind of image this is, for the log message (e.g. "9-patch bitmap")
Returns
roBitmap
parseNinePatchBitmap(bitmap: roBitmap): BGE.UI.NinePatchImage
Same as loadNinePatchImage(), but takes an already-loaded roBitmap - split out specifically so this parsing logic is testable against a synthetic in-memory bitmap without needing a packaged asset file.
Parameters
bitmap(roBitmap)
Returns
BGE.UI.NinePatchImage
findMarkerRun(pixels: roByteArray, lengthPx: integer): object
Internal helper for parseNinePatchBitmap() - not part of the public API surface (BrighterScript has no visibility modifier for a namespaced function), so deliberately not written as a JSDoc doc comment.
Scans a 1-pixel-wide/tall RGBA byte array (4 bytes/pixel, lengthPx being the top row's width or the left column's height) for the first and last near-black, near-opaque pixel, treating index 0 and index (length-1) as the border corners (never part of a marker run - Android's convention reserves the very first/last border pixel unconditionally). Returns {startIndex, endIndex, found} in full-bitmap pixel coordinates (both inclusive); if no marker pixel was found, found is false and the run spans the entire content region ({startIndex: 1, endIndex: lengthPx - 2}) so the caller's left/top = startIndex - 1 and right/bottom = contentLength - (endIndex - 1) - 1 formulas both resolve to 0 on that axis (the whole image stretches). (A sentinel of startIndex = lengthPx would NOT resolve to 0 under those formulas - verified by hand against a synthetic no-marker test case - so this is deliberately not "no run found" encoded as an empty/inverted range.)
Parameters
pixels(roByteArray)lengthPx(integer)
Returns
object
Enums
BGE.UI.HorizAlignment
Properties
left(default: "left")center(default: "center")right(default: "right")
BGE.UI.VertAlignment
Properties
top(default: "top")center(default: "center")bottom(default: "bottom")
BGE.UI.FocusNavigationMode
How BGE.UI.FocusManager moves focus on directional input.
Properties
list(default: "list") — Default: discrete list navigation, like a typical menu - Up/Down/Left/ Right step currentlyFocused to the next/previous registered widget, no spatial cursor or hit-testing involved.pointer(default: "pointer") — Opt-in: a virtual pixel cursor (cursorPosition) moves by cursorStep and is hit-tested against widget bounds each frame - see FocusManager.
BGE.UI.SelectStyle
Select's interaction style. inline (default): Left/Right cycle the current option in place, matching Slider's interaction pattern. popup: OK expands a list of every option; Up/Down move a highlight, OK commits, back cancels. horizontal: every option renders side-by-side in one row, with the current selection visually distinct; Left/Right move directly to the adjacent shown option (clamping at the ends, no wrap - there's nothing to wrap to once every option is already on screen). See Select.style.
Properties
inline(default: "inline")popup(default: "popup")horizontal(default: "horizontal")
Other
NINE_PATCH_FILENAME_SUFFIX
Filename suffix marking a 9-patch source asset, matching Android's own .9.png naming convention (see NinePatchLoader's own doc comment for the pixel-format side of that convention). Checked case-insensitively.
Default: .9.png
Button
Extends: UiWidget
A focusable, clickable button: a label over a themed background. Set onActivate to a function(button as Button) to handle clicks, or subclass and override onClick() directly.
Parameters
game(BGE.Game)
Properties
drawableText(BGE.DrawableText)onActivate(dynamic) — Called from onClick() when set - function(button as Button) as void
Returns
BGE.UI.Button
Checkbox
Extends: UiWidget
A focusable checkbox: a small toggle box plus a label. Set onChanged to a function(checkbox as Checkbox) to react to toggles.
Parameters
game(BGE.Game)
Properties
checked(boolean)drawableText(BGE.DrawableText)lastChangedValue(dynamic)onChanged(dynamic) — Called from onClick() after checked flips - function(checkbox as Checkbox) as void
Returns
BGE.UI.Checkbox
CURSOR_ANALOG_DEADZONE
Minimum analog-stick magnitude before updateAnalogCursor() moves the cursor at all. Local to the UI cursor, not applied in ControlMap - other axis consumers get the raw stick value.
Default: 0.15
FocusManager
Owns the single, global focused-widget/cursor state shared by every UiContainer that opts in (UiContainer.focusEnabled) - one Game instance owns one FocusManager (Game.focusManager). Widget positions are already absolute UI-canvas coordinates (each level of UiContainer.draw() adds its own parent's position - see UiWidget.getWorldPosition()), so one shared cursor/hit-test naturally spans nested containers with no translation.
Parameters
game(BGE.Game)
Properties
game(BGE.Game)navigationMode(BGE.UI.FocusNavigationMode) — How directional input moves focus - see BGE.UI.FocusNavigationMode. ' Defaults to list (discrete next/previous, like a typical menu); a game ' opts into pointer (spatial cursor) explicitly.wrapFocus(boolean) — list mode only: does moving past the last (or first) registered widget ' wrap around to the other end? Off by default - most menus stop at the ' ends rather than cycling.focusOrder(dynamic) — Every focusable widget registered across every focusEnabled container ' (gameUi and any of its focusEnabled descendants) - flat, in register() ' order. In list mode this order IS the navigation order; in pointer mode ' it's used only to seed initial focus and as the hit-test target.currentlyFocusedIndex(integer) — list mode only: index of currentlyFocused within focusOrder, or -1 ' before anything is focused.cursorPosition(BGE.Math.Vector) — pointer mode only: virtual cursor position, in UI-canvas coordinate ' space. Moved by directional input; hit-tested against focusOrder each ' frame to drive hover/focus (cursor-primary - hovering a widget focuses ' it).cursorStep(float) — pointer mode only: pixels the cursor moves per press/held-frame.analogAxisName(dynamic) — Opt-in: name of a BGE.Controller.ControlMap axis (bound via ' ControlMap.bindAxis()) that continuously drives cursorPosition in pointer ' mode, alongside the existing d-pad press/held stepping. invalid (the ' default) means no analog input drives the cursor at all - zero behavior ' change. See updateAnalogCursor(). ' dynamic, not string, so a game can reset it back to invalid (e.g. when ' leaving the room that opted in) - the type checker rejects assigning ' invalid to a string-typed field.cursorAnalogSpeed(float) — Pixels/second the cursor moves at full stick deflection (magnitude 1.0).currentlyFocused(BGE.UI.UiWidget)hasSeededFocus(boolean)consumedThisFrame(boolean) — Latched true as soon as anything consumes during a frame, and reset ' once per frame (see resetFrame()). Game.bs dispatches more than one ' event per frame (a press, plus a synthesized held event while the ' button stays down) - without this latch a later, unconsumed event ' would undo an earlier one's input capture.repeatThrottle(BGE.UI.RepeatThrottle) — Throttles repeat-while-held navigation to one step per ' WIDGET_REPEAT_DELAY_MS, the same way Slider/Select throttle their own ' adjustment - shared between list and pointer mode since only one mode ' is ever active at a time.
Returns
BGE.UI.FocusManager
ImageBackground
A plain (non-9-patch) image background: the whole source image is stretched to fill the widget's bounds, corners included - unlike BGE.UI.NinePatchImage, which keeps its corners unstretched. Assign to Theme.backgroundImage the same as a NinePatchImage; see BGE.UI.loadBackgroundImage(), which picks this or a NinePatchImage automatically from the asset's filename.
Parameters
sourceImage(roBitmap) — the full source image, stretched to fill on draw()
Properties
sourceImage(roBitmap)sourceWidth(integer)sourceHeight(integer)
Returns
BGE.UI.ImageBackground
Label
Extends: UiWidget
Parameters
game(BGE.Game)
Properties
drawableText(BGE.DrawableText)
Returns
BGE.UI.Label
MessagePanelButton
One button spec for MessagePanel: a label plus the same onActivate callback shape BGE.UI.Button itself uses (function(button as BGE.UI.Button) as void).
Parameters
label(string)onActivate(dynamic)
Properties
label(string)onActivate(dynamic)
Returns
BGE.UI.MessagePanelButton
MessagePanel
Extends: BGE.UI.UiContainer
A centered modal panel (translucent background, optional heading, optional body message, a vertical stack of buttons) for pause menus, win/game-over screens, and similar - promoted from the same shape hand-rolled independently by several BGE.UI-based examples, including the same focus-safety fix each needed by hand: a Button registers itself with the global FocusManager the instant it's added to any focusEnabled container, even one not yet attached to gameUi - so a panel must be built fresh right before it's shown and torn down (not just hidden) when closed, or its buttons stay focusable/clickable while invisible. show()/hide() handle this internally: hide() removes the panel (and its buttons) from its parent entirely, and show() rebuilds its children fresh each time.
Font-name assumption: the title is measured AND drawn with the same resolved "heading"-or-"default" font (self-consistent), but the message label is measured with "body"-or-"default" while actually DRAWN with whatever font its resolved theme yields (via the normal Label/resolveTheme() path). If a consumer's container theme sets a different font than what they registered as "body", the message text could measure slightly off from how it's centered.
Parameters
game(BGE.Game)panelWidth(float)panelHeight(float)buttons(Array.<BGE.UI.MessagePanelButton>) — shown top to bottomtitle(string, optional, default: "\"\"") — optional heading, drawn in a larger fontmessage(string, optional, default: "\"\"") — optional body text below the heading
Properties
title(string) — Panel heading, shown in a larger font above the message/buttons. Empty ("") means ' no heading is drawn.message(string) — Body message, shown below the heading. Empty ("") means no message is drawn. Call ' setMessage() (not this field directly) to update it after the panel is already ' shown - it re-measures/re-centers the label.buttons(dynamic) — Buttons, top to bottom, in the order given to the constructor.buttonWidgets(dynamic) — The actual BGE.UI.Button widgets built frombuttons, in the same order - only ' valid after show() has built the panel's children.
Returns
BGE.UI.MessagePanel
NinePatchSliceSpec
Describes one of a NinePatchImage's 9 slices: which segment of the source/destination grid it occupies along each axis. 0 = the fixed leading segment (the left/top inset), 1 = the stretchable middle segment, 2 = the fixed trailing segment (the right/bottom inset). Both slicing (in new()) and drawing (in draw()) are driven generically from this list instead of writing out 9 near-identical blocks by hand.
Properties
name(string)gridX(integer)gridY(integer)
NinePatchImage
A 9-patch ("scale-9") stretchable background image: a source bitmap is sliced once (at construction) into 9 roRegions - 4 corners (drawn at fixed size), 4 edges (stretched along one axis), and a center (stretched both axes) - so a single small source texture can back a background of any width/height without visibly stretching its corners. Assign to Theme.backgroundImage to opt a widget's background fill into this instead of a flat color - see Theme.backgroundImage's own doc comment.
Takes a roBitmap, not a roRegion: CreateObject("roRegion", ...) only accepts a roBitmap as its source, not another roRegion (confirmed via Roku's own component type data and an actual runtime Type Mismatch when a roRegion was tried) - so slicing has to start from the real bitmap.
Parameters
sourceBitmap(roBitmap) — the full source imageleft(integer) — px from the left edge that stay unstretchedtop(integer) — px from the top edge that stay unstretchedright(integer) — px from the right edge that stay unstretchedbottom(integer) — px from the bottom edge that stay unstretched
Properties
slices(object) — Slice name (see specs) -> roRegion, invalid if that slice's inset ' resolved to 0 (see makeSlice()).specs(object) — The 9 slice specs, computed once in new() and reused by draw() every ' frame - see NinePatchSliceSpec. A fixed, unchanging list, so building ' it once avoids 9 fresh AA allocations on every single draw() call.left(integer)top(integer)right(integer)bottom(integer)sourceWidth(integer)sourceHeight(integer)
Returns
BGE.UI.NinePatchImage
NINE_PATCH_MARKER_THRESHOLD
RGBA byte threshold below which a channel is considered "black" - PNG export/compression can introduce minor color drift in marker pixels, so this isn't an exact &h000000FF match.
Default: 40
NINE_PATCH_MARKER_MIN_ALPHA
Default: 200
Select
Extends: UiWidget
A focusable option picker. Its interaction style is controlled by Select.style (see BGE.UI.SelectStyle): inline (the default) cycles through m.options in place with Left/Right, matching Slider's existing interaction pattern; popup expands a full option list on OK; horizontal shows every option side-by-side in one row, with Left/Right moving directly between them.
Parameters
game(BGE.Game)
Properties
options(dynamic)selectedIndex(integer)drawableText(BGE.DrawableText)onChanged(dynamic) — Called after selectedIndex changes - function(select as Select) as voidlastChangedValue(dynamic) — Test accessor: last value an onChanged callback stashed here. Not part ' of the public API - exists purely because a plain local var captured by ' an anonymous sub assigned to onChanged doesn't reliably update under ' brs-cli, so tests need a field on the widget itself to observe.style(BGE.UI.SelectStyle) — Which interaction style this Select uses - see BGE.UI.SelectStyle. ' Defaults to inline (Left/Right cycling), matching this widget's original ' (and only) behavior - popup is opt-in, not a breaking change.expanded(boolean) — popup style only: is the option list currently expanded?highlightedIndex(integer) — popup style only: index within m.options currently highlighted while expanded.lastDrawnRowCount(integer) — Test accessor: how many option rows drawOverlay() drew last call. Not ' part of the public API - exists purely so tests can distinguish ' drawOverlay() actually rendering the popup list from its no-op paths.optionTextDrawables(object) — popup and horizontal styles only: one BGE.DrawableText per unique ' option string, keyed by that string - reused across frames/rows instead ' of drawOverlay()/drawHorizontal() cycling every option's text through ' the single m.drawableText field (also used by draw() for the inline/ ' popup current value), which would defeat DrawableText.getTextImage()'s ' own cache (keyed on the instance's last text/color) every single frame ' the popup stays open or a horizontal Select is drawn. See getOptionText(). ' m.options is a plain public field with no change hook, so entries for ' strings no longer present in m.options are pruned each use (see ' pruneOptionTextCache()) rather than left to accumulate forever across ' an options reassignment (e.g. search/filter results changing over time).repeatThrottle(BGE.UI.RepeatThrottle) — Throttles repeat-while-held Left/Right to one step per ' WIDGET_REPEAT_DELAY_MS - see handleInput().
Returns
BGE.UI.Select
Slider
Extends: UiWidget
A horizontal value picker: a label, a filled bar, and a numeric readout. Adjust with increase()/decrease() (step-sized) or set an exact value with setValue().
Parameters
game(BGE.Game)
Properties
minValue(float)maxValue(float)step(float)value(float)repeatThrottle(BGE.UI.RepeatThrottle) — Throttles repeat-while-held Left/Right to one step per ' WIDGET_REPEAT_DELAY_MS - see handleInput().barColor(dynamic)backgroundColor(dynamic)drawableText(BGE.DrawableText)
Returns
BGE.UI.Slider
DEFAULT_PADDING
Default: 32
WIDGET_REPEAT_DELAY_MS
Minimum time (ms) a direction must be held before a Slider/Select repeats its adjustment. Game.bs dispatches a press AND a synthesized held event (heldTimeMs ~0) in the same frame as every button press, so without this threshold a single tap would step twice - see RepeatThrottle below.
Default: 300
RepeatThrottle
Throttles repeat-while-held directional input to one action per WIDGET_REPEAT_DELAY_MS, while letting a single tap's press+synthesized- held pair act exactly once. Shared by Slider/Select.handleInput() and BGE.UI.FocusManager's list/pointer navigation - anywhere directional input should act at most once per tap, then repeat while held.
Properties
lastActionMs(float) — heldTimeMs of the last held event acted on, or -1 when no hold is in ' progress.
OffsetSize
Parameters
tOffset(float, optional, default: 0)rOffset(dynamic, optional, default: "invalid")bOffset(dynamic, optional, default: "invalid")lOffset(dynamic, optional, default: "invalid")
Properties
top(float)right(float)bottom(float)left(float)
Returns
BGE.UI.OffsetSize
TextInput
Extends: UiWidget
A focusable single-line text entry widget. Characters arrive via onECPKeyboard() (Roku's mobile-app on-screen keyboard sends individual characters this way - see Game.bs's existing onECPKeyboard dispatch, which already reaches every UiContainer child unconditionally, same as onInput()). Left/Right (while focused) move the caret; OK fires onSubmit. No on-screen virtual keyboard is drawn by this widget - it relies entirely on the platform's own text-entry mechanism delivering onECPKeyboard() characters.
Parameters
game(BGE.Game)
Properties
text(string) — Current text content.cursorIndex(integer) — Caret position, 0..Len(text). Moved by Left/Right while focused, and ' advances/retreats as characters are inserted/deleted.maxLength(integer) — Maximum text length, 0 = unlimited. A character typed once text.Len() ' already equals maxLength is silently dropped.placeholder(string) — Text shown (dimmed) when text is empty and this widget isn't focused.onChanged(dynamic) — Called after any edit (insert/delete) - function(input as TextInput) as voidonSubmit(dynamic) — Called on OK-press while focused - function(input as TextInput) as voidlastChangedValue(dynamic) — Test accessor: last value an onChanged callback stashed here. Not part ' of the public API - exists purely because a plain local var captured by ' an anonymous sub assigned to onChanged doesn't reliably update under ' brs-cli, so tests need a field on the widget itself to observe.drawableText(BGE.DrawableText)repeatThrottle(BGE.UI.RepeatThrottle) — Throttles repeat-while-held Left/Right to one step per ' WIDGET_REPEAT_DELAY_MS - see handleInput().
Returns
BGE.UI.TextInput
Theme
Default colors/fonts/spacing for BGE.UI widgets. Game.defaultTheme is the engine-wide default (matching today's previously-hardcoded widget colors, so existing consumers see no visual change); a UiContainer.theme overrides it for that container's subtree.
Resolution happens on every draw() call, not once at add-time: a widget resolves the theme from its immediate parent's effectiveTheme() (which itself falls back to Game.defaultTheme when that parent has no theme set), and any per-widget color field left invalid picks up the theme's value. There is no multi-level walk up the tree - only the immediate parent is consulted, so a grandparent container's own override does not reach a grandchild through an intervening container that has no theme of its own.
font, when set, is applied once to a widget's rendered text - see UiWidget.applyThemeFont(), called from every widget that owns a BGE.DrawableText (Button, Label, Checkbox, Slider, Select, TextInput) right after resolveTheme() in its own draw(). This is a one-time application per widget (not dynamic font-swapping): re-applying on every theme change would mean detecting whether font changed, which can't be done by comparing roFont instances (= on native components is a runtime crash in BrightScript, confirmed for roFont specifically - even a cast as object doesn't avoid it). fontSize is still accepted and stored but not applied - a roFont from Game.loadFont()/GetFont() already has its size baked in at load time, so this field is effectively vestigial.
Properties
backgroundColor(integer)foregroundColor(integer)borderColor(integer)focusedBorderColor(integer)hoveredBackgroundColor(integer)disabledColor(integer)font(roFont)fontSize(integer)defaultPadding(OffsetSize)defaultMargin(OffsetSize)cursorColor(integer)cursorSize(float)backgroundImage(dynamic) — A background image, drawn instead of a flat backgroundColor fill when ' set. invalid (the default) means "flat color" - exactly today's ' behavior, so this is purely additive. Either a BGE.UI.NinePatchImage ' (a stretchable image with unstretched corners) or a BGE.UI.ImageBackground ' (a plain image, stretched to fill entirely) - see ' BGE.UI.loadBackgroundImage(), which picks the right one automatically ' from the asset's filename. Does not vary by hover/focus state - a ' themeable hovered/focused background image is a possible future ' follow-up, not supported here.
Returns
BGE.UI.Theme
UiContainer
Extends: BGE.UI.UiWidget
Parameters
game(BGE.Game)
Properties
backgroundRGBA(integer) — RGBA value for the background of the window/containershowBackground(boolean) — RGBA value for the background of the window/containerchildren(dynamic)theme(BGE.UI.Theme) — This container's own theme override. When invalid, effectiveTheme() ' falls back to m.game.defaultTheme.focusEnabled(boolean) — Does this container register its focusable children with the shared ' BGE.UI.FocusManager (Game.focusManager)? gameUi wants this (issue ' #133); debugUi opts out (set to false by Game.bs) since debug UI ' should be non-blocking and never capture input away from gameplay ' entities - its widgets, if any, would just never be focusable, and it ' still gets onInput()'s plain broadcast like any other GameEntity. ' Focus is global (issue #178): every focusEnabled container anywhere in ' the tree shares the same one focused widget, via Game.focusManager ' (driven once per frame by Game.processFocusManagerInput() - not ' per-container) - not tracked per-container.
Returns
BGE.UI.UiContainer
UiWidget
Extends: BGE.GameEntity
Base Abstract class for all UI Elements
Parameters
game(BGE.Game)
Properties
customPosition(boolean) — If position = "custom", then m.customX is horizontal position of this element from the parent position ' and m.customY is the vertical position of this element from the parent position (positive is down)customX(float)customY(float)horizAlign(string) — If customPosition is false, this dictates where horizontally in the container this element should go. Can be: "left", "center" or "right"vertAlign(string) — If customPosition is false, this dictates where vertically in the container this element should go. Can be: "top", "center" or "bottom"width(integer) — Width of the elementheight(integer) — Height of the elementfocusable(boolean) — Can this widget receive focus/hover from its owning UiContainer's cursor?focused(boolean) — Does this widget currently have focus? Set by the owning UiContainer - do not set directly.hovered(boolean) — Is the owning UiContainer's cursor currently over this widget? Set by the owning UiContainer.parentContainer(UiWidget) — The UiContainer this widget was added to via addChild() - set by ' UiContainer.addChild(). Used by BGE.UI.FocusManager to reposition a ' focusable widget correctly when seeding initial focus before the first ' draw() has ever positioned it (see FocusManager.update()).canvas(BGE.Canvas)padding(OffsetSize)margin(OffsetSize)hasAppliedThemeFont(boolean) — Latches true the first time this widget applies a resolved theme's ' custom font to its own DrawableText (see applyThemeFont()) - a theme's ' font is only ever applied once per widget, which sidesteps having to ' compare two roFont instances with=(a runtime crash on native ' components in BrightScript) to detect a later theme/font change.
Returns
BGE.UI.UiWidget