UI Framework
Learn how to create user interfaces with Brine2D's built-in UI framework: buttons, text inputs, dialogs, and more.
Quick Start
using Brine2D.UI;
using System.Numerics;
public class MenuScene : Scene
{
private readonly UICanvas _canvas;
public MenuScene(UICanvas canvas)
{
_canvas = canvas;
}
protected override Task OnLoadAsync(CancellationToken ct)
{
var playButton = new UIButton("Play", new Vector2(350, 250), new Vector2(100, 40));
playButton.OnClick += () =>
{
Logger.LogInformation("Play button clicked!");
};
_canvas.Add(playButton);
return Task.CompletedTask;
}
protected override void OnUpdate(GameTime gameTime)
{
_canvas.Update((float)gameTime.DeltaTime);
}
protected override void OnRender(GameTime gameTime)
{
_canvas.Render(Renderer);
}
}
Topics
System
| Guide | Description |
|---|---|
| UICanvas | DI setup, screen size, input layers, focus, find-by-name |
| Positioning & Anchoring | UIAnchor, AnchorOffset, ZOrder, TabIndex |
Components
| Guide | Description |
|---|---|
| Quick Reference | All components at a glance |
| Button | UIButton: states, textures, focus |
| Label & Rich Text | UILabel, UIRichTextLabel: BBCode, wrapping, shadows |
| Image | UIImage: tint, rotation, source rect, animation |
| Text Input & Area | UITextInput, UITextArea: selection, undo, password |
| Slider & SpinBox | UISlider, UISpinBox: orientation, step, labels |
| Checkbox & Radio | UICheckbox, UIRadioButton, UIRadioButtonGroup |
| Dropdown | UIDropdown: items, keyboard navigation |
| Progress Bar | UIProgressBar: direction, custom text, HUD patterns |
| Tooltip & Toast | UITooltip, UIToast: delay, positioning, dismissal |
Layout
| Guide | Description |
|---|---|
| Panel, Stack & Grid | UIPanel, UIStackPanel, UIGrid |
| Scroll View | UIScrollView: content sizing, scrollbars |
| Tab Container | UITabContainer: tabs, scrollable tab bar |
Dialogs & Menus
| Guide | Description |
|---|---|
| Dialog | UIDialog: modal, draggable, custom children |
| Menus | UIMenuBar, UIContextMenu, UIMenuItem |
Advanced
| Guide | Description |
|---|---|
| Virtual List | UIVirtualList\<T>: virtualized scrolling, custom row renderer |
| Tree View | UITreeView, UITreeNode: hierarchy, keyboard navigation |
| Drag & Drop | RegisterDraggable, UIDropTarget, IDragPayload |
| World Labels | UIWorldLabel: world-space projection |
| Animation & Tweens | UITween, UITweenSequence, UIEasing |
| Custom UI | Implement IUIComponent and IAnchoredUIComponent |
Key Concepts
UICanvas
The UICanvas manages all UI elements for a scene:
// Register as scoped (per-scene)
builder.Services.AddUICanvas();
// Inject in scene
public class MenuScene : Scene
{
private readonly UICanvas _canvas;
public MenuScene(UICanvas canvas)
{
_canvas = canvas;
}
}
Benefits: - Automatic input handling - Z-ordering (layers) - Lifecycle management - Input layer integration
Available Components
Brine2D includes 25+ production-ready UI components:
| Component | Description |
|---|---|
| UIButton | Clickable button with text and optional nine-slice textures |
| UILabel | Static or dynamic text |
| UIRichTextLabel | BBCode-formatted text with alignment, shadows, and wrapping |
| UITextInput | Single-line text entry with selection, undo/redo, and password mode |
| UITextArea | Multi-line text editor with selection, scrolling, and undo/redo |
| UISlider | Value slider with optional label and step snapping |
| UISpinBox | Numeric field with increment/decrement buttons |
| UICheckbox | Toggle on/off with label |
| UIRadioButton | Single selection from a UIRadioButtonGroup |
| UIProgressBar | Loading or health bar with optional percentage text |
| UIDropdown | Dropdown selection menu |
| UIPanel | Container with background and optional child clipping |
| UIScrollView | Scrollable container with optional horizontal/vertical scrollbars |
| UIStackPanel | Auto-stacks children vertically or horizontally |
| UIGrid | Arranges children in a fixed-column grid |
| UITabContainer | Tabbed interface with scrollable tab bar |
| UIDialog | Modal popup with title bar, message, and buttons |
| UIImage | Display a texture with optional animation, tint, and rotation |
| UITooltip | Hover information attached to any component |
| UIToast | Timed notification shown via UICanvas.ShowToast |
| UIContextMenu | Right-click overlay shown via UICanvas.ShowContextMenu |
| UIMenuBar | Horizontal menu bar with dropdown submenus |
| UIVirtualList\<T> | High-performance virtualized scrolling list |
| UITreeView | Hierarchical tree with expand/collapse and keyboard navigation |
| UIDropTarget | Drag-and-drop drop zone |
| UIWorldLabel | World-space label projected to screen coordinates |
Common Tasks
Create Button
var button = new UIButton("Start Game", new Vector2(350, 250), new Vector2(100, 40))
{
NormalColor = Color.Blue,
TextColor = Color.White
};
button.OnClick += () =>
{
Logger.LogInformation("Button clicked!");
};
_canvas.Add(button);
Create Text Input
var nameInput = new UITextInput(new Vector2(300, 200), new Vector2(200, 30))
{
Placeholder = "Enter your name..."
};
nameInput.OnTextChanged += text =>
{
Logger.LogInformation("Name: {Name}", text);
};
_canvas.Add(nameInput);
Create Slider
var volumeSlider = new UISlider(new Vector2(300, 250), new Vector2(200, 20))
{
MinValue = 0f,
MaxValue = 1f,
Value = 0.8f
};
volumeSlider.OnValueChanged += value =>
{
_audio.SetMasterVolume(value);
Logger.LogInformation("Volume: {Volume:P0}", value);
};
_canvas.Add(volumeSlider);
Create Dialog
var dialog = new UIDialog("Quit Game?", "Are you sure you want to quit?", new Vector2(300, 150));
dialog.AddButton("Yes", () =>
{
Application.Quit();
});
dialog.AddButton("No", () =>
{
dialog.Visible = false;
});
_canvas.Add(dialog);
HUD (Health Bar)
var healthLabel = new UILabel("Health:", new Vector2(10, 10))
{
Color = Color.White
};
var healthBar = new UIProgressBar(new Vector2(80, 10), new Vector2(200, 20))
{
MinValue = 0,
MaxValue = 100,
Value = 75,
FillColor = Color.Green,
BackgroundColor = new Color(50, 50, 50)
};
_canvas.Add(healthLabel);
_canvas.Add(healthBar);
// Update health each frame
healthBar.Value = _playerHealth;
Layout Container
// Automatic vertical layout
var menu = new UIStackPanel(new Vector2(350, 200))
{
Orientation = StackOrientation.Vertical,
Spacing = 10
};
menu.AddChild(new UIButton("Play", new Vector2(0, 0), new Vector2(100, 40)));
menu.AddChild(new UIButton("Options", new Vector2(0, 0), new Vector2(100, 40)));
menu.AddChild(new UIButton("Quit", new Vector2(0, 0), new Vector2(100, 40)));
_canvas.Add(menu);
// Buttons are automatically positioned vertically with 10 px spacing
Input Layers
The UICanvas implements IInputLayer and should be registered so it can consume input before game logic:
protected override Task OnLoadAsync(CancellationToken ct)
{
// Register canvas as a high-priority input layer (priority 1000)
_inputLayerManager.RegisterLayer(_canvas);
// Build UI...
return Task.CompletedTask;
}
When a UIDialog is open it automatically blocks input to anything below it. When the dialog closes, normal input resumes.
Best Practices
✅ DO
- Use UICanvas: one per scene, injected via DI
- Position with
Vector2:new Vector2(x, y)forPositionandSize - Handle events:
OnClick,OnValueChanged,OnTextChanged, etc. - Use layout panels:
UIStackPanelorUIGridfor automatic positioning - Z-order matters: last added renders on top when
ZOrderis equal
// ✅ Good pattern
protected override Task OnLoadAsync(CancellationToken ct)
{
CreateMenuUI();
return Task.CompletedTask;
}
protected override void OnUpdate(GameTime gameTime)
{
_canvas.Update((float)gameTime.DeltaTime);
}
protected override void OnRender(GameTime gameTime)
{
DrawGameObjects();
_canvas.Render(Renderer); // UI renders on top
}
❌ DON'T
- Don't skip
Update(): animations, tooltips, and toasts need it - Don't skip
Render(): nothing will appear - Don't hardcode positions: use anchors or layout panels for different screen sizes
// ❌ Bad: hardcoded for 1280×720
var button = new UIButton("Play", new Vector2(640, 360), new Vector2(100, 40));
// ✅ Good: anchored to center
var button = new UIButton("Play", Vector2.Zero, new Vector2(100, 40))
{
Anchor = UIAnchor.MiddleCenter,
AnchorOffset = new Vector2(-50, -20)
};
Styling
Custom Colors
var button = new UIButton("Custom", new Vector2(10, 10), new Vector2(120, 40))
{
NormalColor = new Color(50, 50, 50, 255),
HoverColor = new Color(70, 70, 70, 255),
PressedColor = new Color(30, 30, 30, 255),
TextColor = Color.White
};
Custom Fonts
var label = new UILabel("Custom Font", new Vector2(100, 100))
{
Font = await _assets.GetOrLoadFontAsync("assets/fonts/custom.ttf", 24),
Color = Color.Gold
};
Anchoring
Every component implementing IAnchoredUIComponent supports Anchor and AnchorOffset for resolution-independent placement:
var label = new UILabel("Score: 0", Vector2.Zero)
{
Anchor = UIAnchor.TopRight,
AnchorOffset = new Vector2(-120, 10) // 120 px from right edge, 10 px from top
};
_canvas.Add(label);
// UICanvas resolves the anchor position automatically during Render
Troubleshooting
UI Not Responding to Input
Symptom: Buttons don't respond to clicks.
Solutions:
- Check
Update()is called:
protected override void OnUpdate(GameTime gameTime)
{
_canvas.Update((float)gameTime.DeltaTime); // Must be called!
}
- Verify the component is added:
- Register the canvas as an input layer:
- Check Z-order: the topmost element receives input first:
_canvas.Add(background); // Lower (rendered first)
_canvas.Add(button); // Higher (receives input first)
UI Not Visible
Symptom: UI elements are not drawn.
Solutions:
- Check
Render()is called:
protected override void OnRender(GameTime gameTime)
{
_canvas.Render(Renderer); // Must be called!
}
- Check coordinates are on screen:
- Check visibility:
Text Not Showing
Symptom: Button or label has no text.
Solutions:
- Check font is loaded (if using a custom font):
- Check text color:
Related Topics
- UI Components: complete component reference
- Custom UI: build your own components
- Input Layers: priority-based input routing
- Mouse Input: mouse handling
Ready to build UI? Start with UI Components!