Utilities and internals
The helpers Ember builds itself out of — Util, Panel, Card, and the Metrics, Motion and Fx tables — for custom controls and your own GUI.
Ember hangs the parts it is made of off the module, so a control you register looks like one that shipped with the library rather than something bolted to the side of it.
local Util = Ember.UtilEverything here is stable API. It is also lower-level than the rest of the docs: reach for it when you are registering a control or painting your own GUI, not when a control already does the job.
Building instances#
Util.create is the constructor the whole library uses. Props, then children,
with Parent applied last so a frame is never briefly parented while half
configured.
local frame = Util.create("Frame", {
Parent = slot,
BackgroundColor3 = Ember.Theme.panel2,
Size = UDim2.fromOffset(120, 26),
}, { Util.corner(6), Util.stroke(Ember.Theme.border) })| Function | Returns | What it does |
|---|---|---|
Util.create(class, props, children?) | Instance | Builds one. children is an array parented into it. |
Util.label(text, size?, colour?, font?, richText?) | TextLabel | Transparent, left-aligned, vertically centred. Defaults to size 14 in the text role. |
Util.icon(source, size?, colour?) | ImageLabel | nil | A glyph from an icon name, asset id or sprite table. nil if the source is unknown, having warned. |
Util.setIcon(label, source) | boolean | Repoints an existing ImageLabel at another icon. false if the label or the source is wrong. |
Util.label coerces a non-string, so a number is safe. richText is off unless
you pass true.
Layout and shape#
Each returns a bare UI object for you to parent, or to pass in create's
children array.
| Function | Returns | What it does |
|---|---|---|
Util.corner(radius?) | UICorner | Defaults to Metrics.radius. |
Util.pill() | UICorner | A 999-radius corner, for anything fully rounded. |
Util.stroke(colour?, thickness?, transparency?) | UIStroke | Border-mode, round joins. Defaults to the border role at 1px. |
Util.padding(all, left?, right?, top?, bottom?) | UIPadding | Each side falls back to all, so padding(8) is even and padding(0, 9, 9) pads only the sides. |
Util.list(gap?, direction?, extra?) | UIListLayout | Vertical, LayoutOrder-sorted. extra is merged in as props. |
Util.hlist(gap?) | UIListLayout | Horizontal, vertically centred. |
Colour and style#
local look = Util.style("filled", Ember.Theme.accent)
local button = Util.create("TextButton", {
BackgroundColor3 = look.fill,
BackgroundTransparency = look.fillFade,
TextColor3 = look.ink,
}, { Util.stroke(look.edge, nil, look.edgeFade) })Util.style(name, tone) resolves one of four named looks against a tone colour
and returns { fill, fillFade, ink, edge, edgeFade, name }. An unknown name
falls back to outline, and name tells you which you actually got.
| Style | Looks like |
|---|---|
outline | Background fill, toned text, a border. The default. |
filled | Solid tone with accentInk text. |
ghost | No fill and no border until something else draws one. |
soft | A faint wash of the tone, with a matching faded edge. |
Util.brighten(colour, amount) lifts a Color3 toward white in HSV, amount
being 0–1. Use it for a hover tint that keeps the hue.
Animation#
Util.tween(inst, props, info?) is TweenService:Create plus the two things
every call site needed anyway: it cancels any in-flight tween touching the same
properties, and it honours the effects config. When animation is off, or
RespectReducedMotion is on and the player set that flag, it assigns the values
immediately and returns nil rather than animating.
Util.tween(frame, { BackgroundColor3 = Ember.Theme.accent }, Ember.Motion.fast)info defaults to Motion.fast. The return is the Tween, or nil when it
applied instantly — so do not assume there is something to :Play() again.
Hover, pointer and input#
| Function | Returns | What it does |
|---|---|---|
Util.hover(inst, enter, leave, region?) | subscription | Runs enter/leave on pointer or gamepad selection. region(inst) returning position, size narrows the hit area. |
Util.surface(button, tone, glyph?, glyphRest?, hoverFill?) | UIStroke | Wires the standard hover: fill, edge tint and glyph tint. tone takes a theme role name or a Color3. |
Util.interact(button, spec) | table | Swaps icon, caption and rotation on hover and click. See below. |
Util.drag(handle, callbacks) | subscription | Pointer and touch dragging. onStart(input) returning false refuses the drag; onMove(position); onEnd(). |
Util.isDisabled(node) | boolean | Whether the node sits inside a disabled control, stopping at the window. |
Util.rotatable(glyph) | ImageLabel | Wraps a glyph in a frame so it can spin inside a list layout without shifting its neighbours. |
Util.hover covers selection as well as the mouse, which is why controls
respond to a gamepad. Both callbacks are wrapped, so one that errors warns
rather than leaving the control stuck lit.
Util.interact's spec takes Glyph, Caption, and a Hover and Click table
of { Icon, Text, Scale, Rotate, Hold }. It returns a table with
SetIcon(name) and SetHoverIcon(name) for changing what the control rests at
later — which a control whose meaning flips, like a minimise button, needs.
Drop what you take
hover, drag and watchPointer all return something with :Disconnect().
Hand it to win:Track(...) or disconnect it in Destroy, or the connection
outlives the control.
The pointer helpers — pointerPosition, pointerInside, watchPointer,
setCursor and Util.Cursors — are documented under
Pointer and cursor helpers.
Cards and panels#
A control's row is a card. Controls.mount is the usual way in, since it also
registers the row for search:
Ember.RegisterControl("Badge", function(section, opts)
local card, slot = Ember.Controls.mount(section, opts)
-- put your control in `slot`
return { Instance = card }
end)Ember.Card.new(parent, opts) is the same card without the registration, for a
row outside a section. It returns six values:
| Value | What it is |
|---|---|
card | The row frame |
slot | Where your control goes, right-aligned |
title | The label TextLabel |
line | The horizontal row holding icon, labels and slot |
icon | The glyph, or nil |
fit(element, reserve?, minWidth?) | Caps that element's width against the card, re-measuring as the window resizes |
Ember.Panel.round(frame, radius?, sides?, colour?) rounds a frame and squares
off named sides, for a panel that has to butt up against another.
Ember.Panel.round(myFrame, 8, { "top", "left" })sides takes any of "top", "bottom", "left", "right". It returns
false if the frame is not a GuiObject.
The measurement tables#
Read these so a control you build lines up with the ones that ship.
Ember.Metrics.control --> 26, the height of every control
Ember.Metrics.ctlRadius --> 6, their corner radius
Ember.Metrics.controlCol --> 104, the minimum width of the right-hand column| Table | Holds |
|---|---|
Ember.Metrics | pad, gap, row, radius, winRadius, rail, topbar, statusbar, grip, control, ctlRadius, labelMin, controlCol |
Ember.Motion | fast (0.14s), base (0.22s), open (0.34s), shut (0.24s) — TweenInfos |
Ember.Fx | The dissolve's own numbers: tile, particles, burn, ember, emberHot, and the four timings |
Metrics is read, not written
These are the values the library itself uses, so changing one changes controls already on screen in ways nothing re-measures. Read them; set sizes through the options each control offers.
Tune the dissolve through
Configure rather than by
writing to Fx.
Odds and ends#
| Call | Returns | What it does |
|---|---|---|
Ember.Version | string | The library version, "3.0.0" |
Ember.Controls | table | Every control builder by name. Ember.Controls.mount is the one you call. |
Ember.Util.options(first, second, third, key?) | table | The argument shuffling every control does, so ("Text", fn) and ({ Text = …, Callback = fn }) both work in a control you wrote. |
Util.calibratePointer() | false | Kept so older scripts still load. Calibration is no longer needed and it does nothing. |
Section:ClosePopups(except?) shuts every open dropdown and picker in a
section, optionally sparing one instance. A control that opens its own popup
should call it so two menus are never open at once.