Skip to the text
RoswaalTechnical specification
Draft 0.1
Try Roswaal

Part I · Principles

5 Visual grammar

How a graph is drawn: the canvas, the shape and size of every kind of node, pins, wires, colour and what is drawn over what.

Normative · Level 3Draft 0.120 requirements · Roswaal meets 18

A renderer conforming at level 3 (§1.3) draws a graph as this chapter says. Its numbers are canvas units (§1.4).

5-R1

Where a length is given, a renderer MUST use it, so that a graph drawn by one implementation can be laid over the same graph drawn by another and every pin meets the same wire.

Roswaal meets

5.1 The canvas and its grid

A graph is drawn on an unbounded canvas that pans and zooms. Nodes and comments have positions on it; wires are computed from those positions and are never stored (§9.2).

ValueNumberMeaning
Grid spacing24The distance between grid points, and the step positions snap to.
Coarse gridevery 5thWhere a ruled grid draws a heavier line.
Fine grid hidden below55% zoomBelow this, a renderer SHOULD stop drawing the fine grid rather than draw it as noise.
Zoom range20% to 250%The furthest out and in a renderer that lets the reader zoom SHOULD allow.
Zoom step× 1.1One step of the wheel or a zoom key.

Generated from core/canvasLayers.ts.

The grid MAY be drawn as dots at each grid point or as ruled lines, and at more than one strength; Roswaal offers both, with dots the default.

5.1-R1

The grid MUST be drawn below everything in the graph (§5.8).

Roswaal meets

5.2 Node geometry

An ordinary node is a header and a body of rows. Its size and the place of every pin follow from these numbers alone, never from measuring text, so a node is the same size in every implementation, every font and at every zoom.

NameValueMeaning
width216An ordinary node's width.
headerHeight30The header, with a title only.
headerHeightTall46The header when the node shows a second line under its title.
rowHeight24One row of the body: one input, one output, or one of each.
footer10Space under the last row.
tab30The width of the category tab in the header's corner (§5.4).
radius8A step's corner. A pure node's is twice this (§5.3).
nodeStroke1The node's border.
pinSlot16The square a pin is drawn in.
pinLane10How much of a row a pin's layout takes inside the node.
execAspect0.866A flow pin's width as a fraction of its height: an equilateral triangle.
execGap5The space between a flow pin and the node's edge.
compactHeight28A getter capsule's height.
rerouteSize22A reroute knot's diameter.
operatorRadius14An operator pill's corner.

Generated from core/nodeMetrics.ts.

Height

A node's height is its header's height, plus rowHeight for each row, plus footer. The number of rows is the larger of its inputs and its outputs, leaving out the flow pins that ride on the header (§5.5). A node whose only pins ride on the header has no rows; a node with no pins at all has one.

Where a pin is

  • The nth row's centre is headerHeight + n × rowHeight + rowHeight / 2 below the node's top, counting from 0.
  • A flow pin on the header is centred headerHeight / 2 below the node's top.
  • A data pin is centred on the node's left or right edge.
  • A flow pin's triangle is 13.86 wide (pinSlot × execAspect) and stands execGap clear of the edge, outside the node. Its wire meets it at the triangle's centre, execGap + width / 2 from the edge.
5.2-R1

A wire MUST end where its pin is, by these rules. A renderer that drew the pin elsewhere would show a wire ending in empty space.

Roswaal meets

5.3 Node kinds and shapes

Example · Luau for Robloxinformative
Connect EventBodySignalConnection
Get PropertyInstanceNameProperty
Get Variable
00+
A step, a value, a getter and an operator.

Shape is the first thing read about a node, so each kind has one of its own.

5.3-R1

A renderer MUST NOT draw one kind of node in another's shape.

Roswaal meets
KindShapeWhy
StepA card: a header with a tinted bar and the category tab (§5.4), a body of rows, radius corners.It happens, in order, once. Its flow pins are on its header.
ValueA card twice as round, with no header bar: its title and glyph are drawn in the category's colour, and the body takes a faint wash of it.It has no order: it is worked out where it is used (§7.2).
GetterA capsule 28 tall with fully round ends, its name inside and one output on its right. Its width is estimated from its name, never less than 96.Reading a variable is the commonest value and the smallest.
OperatorA pill with operatorRadius corners, its symbol in the middle, its inputs down the left and its one result centred on the right.An expression such as a + b reads as the expression.
Reroute knotA circle rerouteSize across, with both pins at its centre.A bend in a wire, not a node with sides.
CommentA translucent box in a chosen colour with a title bar, drawn behind the nodes it holds.It groups and labels; it never runs.

A renderer MAY offer square corners as a preference. A value's corner then squares with a step's, so the two keep their difference in proportion; a capsule, a pill and a knot keep their shapes, which are what say what they are.

5.4 Headers and the category tab

  • A step's header is a tint of its category's colour over the node's body: 14% of it in a light theme and 24% in a dark one, with a line at twice that along its lower edge. Its title is drawn in the theme's text colour.
  • In the header's left corner is the category tab: tab wide, the full height of the header, filled with the category's colour, holding the category's glyph at 18 units in white. Glyphs are drawn as strokes 2.2 wide on a 24-unit grid, scaled to fit.
  • The title starts 8 units after the tab and is cut short with an ellipsis rather than wrapped. A second line, where a node has one, is set smaller under the title, in a monospaced face.
  • A value has no tab: its glyph, at 15 units, and its title are drawn in the category's colour, mixed with the text colour enough to stay readable on the body.
5.4-R1

A renderer MUST NOT fill a header with the category's colour at full strength and MUST NOT put the category's colour behind the title text. The colour belongs to the tab.

Roswaal meets
5.4-R2

A node that holds code SHOULD show the profile's mark for that kind of code in its tab, in place of a glyph, as the profile's code editor marks it. Roswaal's are { } for statements, ƒx for an expression and <T> for a type, in the theme's code colours on the code editor's surface.

Roswaal meets
5.4-R3

A function definition's second line SHOULD be its signature as types alone, each in its type's colour, such as (Model, BasePart) → boolean: the names are on its pins, and with them the line is too long to read.

Roswaal meets

Which categories exist, their colours and their glyphs belong to the profile (§10.1). A node that starts or ends a flow, or declares a function, takes the profile's colour and glyph for that, whatever its category, so a graph's entry points can be found first.

5.5 Pins, shapes and type chips

Flow pins

  • A flow pin is an equilateral triangle pointing right, drawn outside the node (§5.2), in the colour of a flow wire. It is drawn hollow until a wire is connected and solid after.
  • A node's first input, if it is an unnamed flow pin, rides on the header, and so does its first output. Every other flow pin, and every named one, such as Body or True, has a row, because its name is what says where that flow goes.

Data pins

Example · Luau for Robloxinformative
Call FunctionResultFunctionArgument
New TableTable
Get EventSignalInstanceTouchedEvent
A function's dot in a ring on Call Function, a table's diamond on New Table, whose output's name already says its type and so has no chip, and an object's square and a signal's hexagon on Get Event, whose unnamed output's chip says Signal.

A data pin is centred on the node's edge, in its type's colour, and its shape is its type's family (§6.2):

FamilyShapeHolds
valuecircleA plain value: a number, a string, a Vector3
objectrounded squareA thing in the game: an Instance or any class
tablediamondA table, an array or a dictionary
functiondot in a ringA function, to call or to pass along
signalhexagonSomething to listen to: an event

Generated from core/typeFamily.ts.

5.5-R1

A renderer MUST draw these shapes, so a pin's kind can be read without telling colours apart (§8.1).

A reroute knot's pin is drawn as a circle whatever it carries.

Roswaal partly

Each shape is drawn open while nothing is wired to it and filled once something is, with a thin ring of a dark well colour around it, so it keeps its colour against the node's body on one side and the canvas on the other.

Labels and type chips

  • A pin's name is drawn beside it, inside the node, nearly in the text colour.
  • An output also shows its type, as a chip: a short name in a rounded box tinted with the type's colour. The chip is left out when the pin is untyped or generic, and when the pin's name already says the type, so a type is never said twice.
  • A long type name MAY be shortened on the chip; the profile says how (§10.1).
  • A reroute knot, a getter and an operator pill are too small to hold a chip beside their one output, so theirs is centred under them, outside the node, where it takes no part in the node's size or its wires.
5.5-R2

The full name of a type on a chip MUST be shown when the reader asks, by pointing at the chip or tapping it, with what the type is.

The canvas does. The documentation's pictures draw the chips but cannot open their card, and do not yet draw a getter's, an operator's or a knot's type under it.

Roswaal partly

5.6 Wires and how they route

  • A wire runs from an output to an input. A flow wire is drawn in the theme's flow-wire colour; a data wire in the colour of the pin it leaves.
  • A flow wire is drawn heavier than a data wire.
5.6-R1

Where a data wire joins pins of different colours, as when a number feeds a string, it SHOULD fade from one colour to the other along its length.

Roswaal meets
5.6-R2

A wire MUST be drawn below every node (§5.8).

Roswaal meets

Roswaal offers three routes, as a preference: curved, a cubic curve that leaves and enters its pins horizontally, its control points at least 70 units out; rigid, horizontal and vertical runs; and angular, which cuts its corners. Curved is the default.

5.6-R3

A renderer MUST offer the curved route, and MAY offer others.

Roswaal meets

5.7 Colour tokens and themes

Every colour on the canvas comes from a theme, a set of named colours, except a category's colour and a type's, which come from the profile. A theme names these roles:

RoleTokenWhat
app--bg-appThe window behind everything
panel--bg-panelSidebars, toolbars, dialogs
canvas--bg-canvasThe graph surface
input--bg-inputText fields and dropdowns
text--fgBody text
subText--fg-mutedLabels and secondary lines
dimText--fg-faintDisabled and placeholder text
border--borderOrdinary dividers
borderStrong--border-strongEmphasised edges
nodeBody--node-bodyA node's fill
nodeBorder--node-borderA node's outline
wireExec--wire-execExecution wires
accent--accentPrimary buttons, focus, the active toggle
danger--dangerErrors and destructive actions
warning--warningWarnings
ok--okSuccess, and a clean compile
select--selectThe selection outline on the canvas
pure--pure-edgeA hint in the outline of a node with no side effects
capsule--capsule-bgInline chips and badges
capsuleBorder--capsule-borderTheir outline

Generated from core/theme.ts.

A theme says whether it is light or dark, and the translucent overlays (hover, the grid, the header tint) are worked out from that rather than chosen, so no theme can make one invisible.

5.7-R1

A theme MUST meet the contrasts of §8.2.

Roswaal meets

5.8 States and layering

What is drawn over what

LayerOrderHolds
background0The canvas colour.
grid10The grid.
watermark20The graph's name, in a corner of the canvas.
world25The layer the graph is drawn in. Every value below is ordered inside it.
comment30Comment boxes, behind the nodes they group.
commentHeader35Their title bars, still reachable when a node overlaps the box.
wire40Wires, behind nodes, so no pin is covered by its own wire.
wireDrag45The wire being dragged out of a pin.
node50Nodes.
nodeSelected55Selected nodes, above the rest.
problem58Problem notes, above every node, selected or not.
marquee60The selection rectangle, while it is drawn.
lock65The cover over the graph while it compiles.
menu70Menus and popovers.

Generated from core/canvasLayers.ts.

5.8-R1

A renderer MUST keep this order. The numbers are Roswaal's; another implementation MAY use any numbers that keep the same order.

Roswaal meets

The panels around the canvas are outside this stack, above it.

Problems are seen first

5.8-R2

A node with an error MUST be marked: its border in the theme's danger colour and a count of its errors on its corner.

Roswaal meets
5.8-R3

The first problem's sentence MUST be written under the node, in a note, with how many more there are. A pin a problem names MUST be marked in the node.

Roswaal meets
5.8-R4

Notes MUST be drawn above every node, comment and wire, selected or not, so nothing in the graph covers what is wrong with it. Only the panels around the canvas, its menus and the compile cover MAY cover one.

Roswaal meets
5.8-R5

A note MUST be drawn in a layer of its own rather than inside its node: a node is drawn as a unit, so nothing inside one can rise above the node next to it.

Roswaal meets
5.8-R6

Where two notes overlap, the one belonging to a selected node SHOULD be drawn on top.

Roswaal meets
5.8-R7

A note SHOULD NOT take the pointer, so the node under it can still be grabbed.

Roswaal meets

Other states

StateDrawn as
SelectedThe border in the selection colour, with a ring of it outside.
The anchor of a selectionA heavier ring: the node the others line up on (§4.3).
Wire in flightEvery pin it cannot land on fades; the ones it can are ringed.
A node a wire would growRinged in the success colour, when dropping the wire on it would add a pin.
Pointed at, or selectedControls that change the node, such as adding a pin, appear (§4.5).

Open question

What a problem note does zoomed far out, where its sentence cannot be read: shrink to its count, or keep its size on screen. To be decided in the editor before it is written here.