Mega UI

Free positioning

Trees, maps and flow charts - and windows bigger than the window. Still no RectTransform.

When a stack is the wrong shape

Rows and cards cover most screens. Some things are not a list at any angle — an upgrade tree, a shop floor plan, a node graph — and for those you have to say where things go.

ui.Diagram gives you a surface to do that on, and keeps drawing everything for you.

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");
    d.Node(380, 160, "Right", "hand");
});
y grows downwards

The origin is the top-left, x to the right and y down — the same convention as every layout tool. Raw Unity is the other way up, and that inversion is one of the things this saves you from. The conversion happens once, inside the library.

The pieces

CallWhat it draws
Node(x, y, ...)A disc with a ring, an icon, and a small level inside it
Link(x1, y1, x2, y2, ...)A connector. Straight lines join cleanly; diagonals are drawn rotated
Text(x, y, ...)Free text, left, centred or right of the point
Box(x, y, w, h, ...)A filled rectangle — a background, a divider, a bar
Button(x, y, w, h, ...)A button at an exact spot: a tab, a toolbar entry, a close box
Image(x, y, w, h, ...)A built-in icon or your own PNG
Region(x, y, w, h, ...)A window inside the window — see below
Raw(x, y, w, h)A positioned rectangle. What goes in it is yours

d.Size is the surface in pixels, so you can centre things without measuring, and d.Theme is there for anything you colour yourself.

Nodes have more than two states

owned: true is the shortcut, and it covers a lot. A real tree has more — bought, next in line, locked, cannot afford, coming soon — and each wants its own outline and tint. Say so explicitly and the shortcut steps aside:

csharp
d.Node(x, y,
    icon: "zap",
    size: 54f,
    tier: "III",
    tooltip: "Sprint Speed III\nRun faster when you hold sprint",
    onClick: () => Buy(u, 3),

    ringColor: bought ? accent : next ? Fade(accent, 0.6f) : dim,
    fillColor: bought ? Blend(card, accent, 0.12f) : next ? card : chip,
    iconColor: bought ? accent : next ? textDim : Fade(textMuted, 0.55f),
    tierColor: bought ? Fade(accent, 0.7f) : textMuted);
Draw the links first

A node is an opaque disc, so it hides the line running underneath it — and that is what makes a branch arrive at a node instead of crossing it. Draw the links, then the nodes, and the joins close by themselves.

Regions: bigger than the window

A tree does not fit in a shop monitor. Region is a rectangle that clips what you draw in it, so the part that does not fit is cut at the edge instead of spilling over your own header and footer.

csharp
d.Region(500, 260, 1000, 400,        // where it is, and how much is visible
    inner => DrawTree(inner),        // the drawing, in content coordinates
    contentWidth:  1400f,            // how big the drawing actually is
    contentHeight: 900f,
    pan: true,
    background: treeBg);

With pan: true the player gets:

GestureWhat it does
DragMoves the drawing, exactly as far as the pointer moved
WheelScrolls it up and down
Ctrl + wheelZooms, around the pointer rather than around a corner
Ctrl + middle clickPuts it all back where it started

The drawing cannot be dragged out of sight — its own edges are the limit — so there is no way to end up staring at an empty rectangle wondering what happened.

Every number is yours

The defaults suit an upgrade tree. A floor plan wants to pull much further back; a flow chart may want no zoom at all. None of these are baked in:

csharp
d.Region(x, y, w, h, Draw,
    contentWidth: cw, contentHeight: ch, pan: true,

    zoomMin:   0.25f,   // default 0.6
    zoomMax:   4f,      // default 1.8
    zoom:      0.8f,    // where it starts, and where Ctrl+MMB returns to
    zoomStep:  0.1f,    // per wheel notch, as a fraction
    wheelStep: 120f);   // pixels per notch when Ctrl is not held
Tooltips are not clipped

A tooltip on a node inside a region is drawn at window level, not inside the region, so it is never cut in half by the edge. It follows its node while you pan and zoom.

Owning the whole window

Ask for UiChrome.None, pass bleed: true, and give the diagram ui.Area.y: the surface is now the entire window, edge to edge, and the header, the tabs and the footer are all yours to draw.

csharp
// In your AppDefinition
Chrome = UiChrome.None,

// And in Build
ui.Diagram(ui.Area.y, d =>
{
    float w = d.Size.x, h = d.Size.y;

    d.Box(w * 0.5f, 31f, w, 62f, headerBg);
    d.Text(20f, 24f, "MY APP", textMain, 26f, bold: true, align: UiAlign.Left);

    d.Button(w - 34f, 31f, 34f, 30f, "X", () => app.Close(), card, textDim);

    d.Region(w * 0.5f, (62f + h - 44f) * 0.5f, w, h - 106f,
             inner => DrawContent(inner), pan: true);

    d.Box(w * 0.5f, h - 22f, w, 44f, headerBg);
}, bleed: true);

bleed drops the side margin the rest of the screen keeps. Without it a header bar stops 28 px short of the frame, which does not read as a header bar.

Text on a free surface

A point can mean three different things, so say which:

csharp
d.Text(20f,  y, "MEGA TREE", textMain, 26f, bold: true, align: UiAlign.Left);
d.Text(w/2f, y, "centred by default");
d.Text(w-15, y, "$9,593", textMain, 17f, bold: true, align: UiAlign.Right);

// A long status line that must not run past its box
d.Text(15f, y, controlsHint, textDim, 16f,
       align: UiAlign.Left, width: w * 0.82f, shrink: true);
Text that does not fit is not drawn at all

This is a TextMeshPro behaviour worth knowing about: with the default overflow mode, text too big for its box is not clipped and not ellipsised — it simply does not render. Mega UI works around it for you (free text overflows, button labels shrink), which is why a roman numeral inside a node and a "+" on a small button both appear. If you are drawing your own through Raw, this is the one to remember.