MIT License NuGet

Home / Docs / 2D controls

2D controls

Sprites, shapes and text are controls in the same panel tree as 3D models, drawn in the Overlay phase of the same frame. There is no second framework to learn, and no UI event system — a button is a control with a delegate on it.

Where you position things

You lay out against DesignResolution, 1280×720 by default. BasicResolution is the box you actually write coordinates in, and what it equals depends on the platform.

PropertyDesktop and webPhones and tablets
BasicResolutionEquals DeviceResolution — you are laying out in real pixels.Equals DesignResolution — the design box is preserved and scaled.
Scale1.The smaller of the two axis ratios, so the whole design box stays visible.
ExtendResolutionEquals DeviceResolution.The design box grown along the longer axis, covering the extra space a tall phone has.
DeviceResolutionAlways the true swapchain size in pixels.

The practical rule: anchor anything that must stay reachable to BasicResolution, and stretch backgrounds and edge decoration to ExtendResolution. That gives you a layout with no letterboxing on a 20:9 phone without maintaining a second design.

A resize recomputes Scale and ExtendResolution only. BasicResolution is deliberately left alone, so dragging a window edge does not reflow a mobile layout.

The five types

ControlFor
Sprite2DScreen-space images, including live compute outputs addressed by texture name.
Sprite3DWorld-anchored billboards: markers, emotes, floating icons.
ShapeDots, rectangles, circles, rounded rectangles and frames for panel chrome.
TextsMSDF text. See Text and fonts.
TextureThe engine-side texture handle, for custom uploads and shared references.
icon = new Sprite2D
{
    Name = "Assets/Setting.png",
    Color = Colors.White,
    PosX = 20, PosY = 20, Width = 48,
    OnClick = () => settingPanel.Alpha = 1f
};
AddControl(icon);

divider = new Shape
{
    ShapeType = ShapeType.Square,
    Color = Colors.DarkGray,
    PosX = 20, PosY = 80, Width = 240, Height = 1
};
AddControl(divider);

Sprites. SpriteBase carries what both sprite types need: a Color tint, Ext for the file extension, OriginWidth and OriginHeight for the source pixel size, and SourceX/Y/Width/Height for drawing one cell out of a sheet. Clock advances a sheet frame, FlipX and FlipY mirror it, and TextureOverride takes a one-shot upload — a path or already-decoded RGBA8 pixels — which the next update consumes and clears.

Shapes. ShapeType is Dot, Square, Circle, RoundRect, RectFrame, Gradual or GradualCircle, with Border for frame thickness. Curves are analytically antialiased rather than scaled bitmaps, so a circle is crisp at any size.

Billboards. Sprite3D.Mode chooses how it faces the camera: Spherical always faces you, Cylindrical turns only around world Y so a nameplate stays upright, and None hands orientation to the quaternion Rotation.

Interaction

Every control has OnClick and OnTouch, and both fire from inside the control's own Update. That has a consequence worth stating plainly: a control you forget to update never receives a click, even though it draws perfectly.

A click is consumed by the first control that takes it

Update returns true when it invoked OnClick, and it clears the pending release so nothing behind receives the same click. Update order is therefore hit-test priority: update your topmost panel first, and use the return value to stop walking the rest.

MouseOver is maintained for you and is part of the gate, along with Enable, Ready and Alpha > 0. There is no bubbling model, no focus system and no command pattern.

Draw order, and the two domains

Controls and panels implementing IRenderOrder sort by Layer, then Order, then insertion index — a stable total order, so a rebuilt list does not shuffle.

RenderDomain decides when in the frame a control is drawn, independently of where it sits in the tree.

RenderDomainMeaning
InheritFollow the parent. The default, and almost always right.
SceneDraw with the 3D scene, before post-processing. Tonemapped, bloomed, TAA-resolved.
OverlayDraw after everything. Untouched by post-processing, which is why UI stays crisp.

Putting a diegetic screen inside the world in Scene is exactly right. Putting a HUD there is how you accidentally make your text ghost under TAA.

A 2D-only application

Set the domain on the app itself and the 3D passes cost you nothing:

RenderDomain = RenderDomain.Overlay;

That is what Samples/Creator does — same controls, same panel model, none of the scene cost. Domain resolution is inherited down the tree, so one line on the app covers every panel under it.

Panels you do not have to write

PanelWhat you get
BoardPanelA framed board with a settable FrameColor. The background for most overlays.
FrameButtonText button with separate normal and hover colours for ground and text.
InputText field with description, alignment, abbreviation and an optional clear button.
MovePanelScrolling and sliding container with configurable padding, size and motion type.
Picker, SimplePickerSingle or multi-select lists over a List<EData>, with hover colours and OnSelect.
ImageView, FullViewThumbnail with an optional clear affordance, and its expanded form.
ObjectPickerNot a widget — the 3D pick and edit panel. See Picking and editing.
There is no layout system

No flexbox, no constraints, no anchors, no docking. Positions are numbers you compute, which is fine for a HUD and tedious for a settings screen with forty rows. Text entry also delegates to the platform: Input raises the native keyboard through DeviceServices.Dialog.ShowKeyboard, so editing looks like the operating system rather than like your application.