Mega UI

Widgets

Everything you can stack on a screen, and the rule that decides where state lives.

The rule for all of them

State lives in your mod, never in the widget

A Toggle asks you whether it is on, and tells you when it is clicked. It does not remember. That is why a screen can never drift out of step with the value it shows — there is only one copy, and it is yours.

The practical shape of that: anything variable is a Func, and it is called again every time the window opens. And every colour is optional — see Look and feel.

Text

Header

csharp
ui.Header("Warehouse");

A section title — bigger, brighter, with room above it.

Label

csharp
ui.Label("Everything currently in storage.");

A line of plain text that never changes.

Value

csharp
ui.Value(() => $"{Warehouse.Count} boxes");

Text that is read again on every open. This is the one you want for anything live.

Keep the function cheap

It runs on the main thread, on open, and nothing is cached between calls. A database round trip in there is a hitch the player feels.

Note

csharp
ui.Note("Staff refill a shelf as soon as it drops below this.");

Small dim text, indented, for the line that explains the control above it. It is what stops a settings screen from being a column of unexplained switches.

Controls

Button

csharp
ui.Button("Order more", Warehouse.Order);

Full width when it stacks; sized to its text inside a row. The label shrinks to fit rather than running past the edge.

Your handler is wrapped, and here is why it matters

If your click handler throws, the exception is logged and swallowed. That is not politeness: an exception escaping into the game's EventSystem takes down click handling for the whole scene, and the player loses the mouse everywhere — not just in your app.

Toggle

csharp
ui.Toggle("Warn me about things",
          () => Config.Warn,
          v  => Config.Warn = v);

A checkbox. The getter runs on open, the setter on every click.

Stepper

csharp
ui.Stepper("How many at most", 1, 20,
           () => Config.Limit,
           v  => Config.Limit = v);

A whole number with minus and plus buttons. The value handed to your setter is already clamped to the range, so you never have to check it. Pass the bounds in either order — they are sorted for you.

Slider

csharp
ui.Slider("Spawn rate", 0.5f, 3f,
          () => Config.Rate,
          v  => Config.Rate = v,
          format: "0.0");        // whole: true snaps to integers

A continuous value with the current reading beside it. Use it where the exact number matters less than the feel of it; use a Stepper where the player is going to want 7.

Dropdown

csharp
ui.Dropdown("Difficulty", new[] { "Relaxed", "Normal", "Brutal" },
            () => Config.Mode,
            i  => Config.Mode = i);

One of a short list. The list opens above the rest of the window, so it is not clipped by the card it sits in.

TextField

csharp
ui.TextField("Shop name", () => Config.Name, v => Config.Name = v,
             placeholder: "Untitled");
Typing does not reach the game

While the field has focus the keyboard belongs to it, so writing "Wagon" does not walk the player across the shop and open their inventory halfway through the word. Your setter is called when the edit ends, not on every keystroke — otherwise you would be handed "W", "Wa", "Wag" and would save all three.

Showing numbers

Stats

csharp
ui.Stats(
    ("Revenue today", () => "$" + Day.Revenue.ToString("N0")),
    ("Customers",     () => Day.Customers.ToString()),
    ("Items sold",    () => Day.Items.ToString()));

A row of big numbers with small captions. Put it at the top: it answers "how is it going" before the player reads a word.

ProgressBar

csharp
ui.ProgressBar("Restocking", () => Restock.Progress,
               text: () => $"{Restock.Done} of {Restock.Total}");

The fraction is clamped to 0–1 for you.

Image

csharp
ui.Image(builtinIcon: "truck", height: 90f);
ui.Image(png: MyArt.LogoBytes, height: 140f);

A built-in icon or your own PNG, full width, at the height you ask for. Aspect is preserved, so a wide image is centred rather than stretched.

Structure

Card

csharp
ui.Card(c => { c.Header("Today"); c.Label("..."); });

A panel that sizes itself to its contents. Everything works inside: rows, headers, even another card. See Rows and cards.

Columns

csharp
ui.Columns(
    left  => { left.Header("Stock");  left.Value(() => "..."); },
    right => { right.Header("Staff"); right.Value(() => "..."); });

Side by side, sharing the width evenly. Each column is a full builder, and both end up as tall as the taller one.

Tabs

csharp
ui.Tabs(new[] { "You", "Staff" }, () => _tab, i => { _tab = i; app.Refresh(); });

if (_tab == 0) ui.Label("Your upgrades");
else           ui.Label("Staff upgrades");

Mega UI draws them and reports the click; you decide what each tab shows, by checking the index while you build.

List

csharp
ui.List(new[] { "Bakery", "Butchery", "Drinks" },
        () => _picked, i => { _picked = i; app.Refresh(); },
        icons: new[] { "package", "package", "package" });

Separator and Gap

csharp
ui.Separator();
ui.Gap();        // 12 pixels
ui.Gap(40f);

Row widgets

Inside ui.Row(r => ...) the set is smaller, because a row is for one line of related things rather than a whole section.

CallWhat it is
r.Label(text)Text, sized to what it contains
r.Value(func)Text re-read on every open, resized as it changes
r.Button(text, action)A button sized to its text
r.Icon(name)A built-in icon at row height
r.Spacer()Pushes everything after it to the right edge
r.Gap(px)Blank horizontal space

When stacking is not enough

A tree, a map, a flow chart or a whole window of your own does not stack. That is free positioning — still no RectTransform, but you say where things go.

csharp
ui.Diagram(400f, d =>
{
    d.Link(300, 60, 300, 140);
    d.Node(300,  40, "Root", "git-branch", owned: true);
    d.Node(220, 160, "Left", "zap");
});

And the last resort

ui.Raw(height) hands you a positioned rectangle of the right width and gets out of the way. What you put in it is yours, and the layout keeps flowing underneath because the height was already counted.

If you reach for Raw, say so

No widget set covers everything, and a library that traps you is worse than no library — you uninstall it and go back to writing RectTransforms by hand. Raw is there so that never happens. But every use of it is a widget that should exist: tell me which one.