View System
All UI in Xui is a tree of View objects. Each view participates in layout (measure, arrange, render), handles input, and reports its own hit region.
Source: Xui/Core/Core/UI/View.cs and the View.*.cs partials alongside it.
Core properties
public partial class View
{
public string? Id { get; set; } // lookup via FindViewById
public View? Parent { get; } // set by the container
public Rect Frame { get; } // final position in window coords
public Frame Margin { get; set; } // external spacing
public HorizontalAlignment HorizontalAlignment { get; set; } // default: Stretch
public VerticalAlignment VerticalAlignment { get; set; } // default: Stretch
public nfloat MinimumWidth { get; set; }
public nfloat MinimumHeight { get; set; }
public nfloat MaximumWidth { get; set; } // default: PositiveInfinity
public nfloat MaximumHeight { get; set; } // default: PositiveInfinity
}
Frame is in window coordinates (top-left origin). Use it inside RenderCore to know where to draw.
Layout protocol
Layout runs in three passes, each driven by View.Update(LayoutGuide).
Measure
protected virtual Size MeasureCore(Size availableBorderEdgeSize, IMeasureContext context)
Return the minimum size your content needs. availableBorderEdgeSize is already margin-subtracted — the engine handles margins. The base implementation measures children and returns their max.
Arrange
protected virtual void ArrangeCore(Rect rect, IMeasureContext context)
rect is the final border-edge rectangle. Position your children here:
protected override void ArrangeCore(Rect rect, IMeasureContext context)
{
var childRect = rect.Inset(padding);
myChild.Arrange(childRect, context);
}
Render
protected virtual void RenderCore(IContext context)
Draw into context. Frame is set by the time RenderCore is called. See Canvas API for drawing primitives.
Combining passes
Containers can combine all three passes in one call:
child.Update(new LayoutGuide
{
Pass = LayoutGuide.LayoutPass.Measure
| LayoutGuide.LayoutPass.Arrange
| LayoutGuide.LayoutPass.Render,
AvailableSize = availableSize,
Anchor = origin,
XSize = LayoutGuide.SizeTo.Exact,
YSize = LayoutGuide.SizeTo.Exact,
XAlign = LayoutGuide.Align.Start,
YAlign = LayoutGuide.Align.Start,
MeasureContext = context,
RenderContext = context,
});
LayoutPass flags
[Flags]
public enum LayoutPass : byte
{
Animate = 1 << 0, // advance per-frame state
Measure = 1 << 1, // compute desired size
Arrange = 1 << 2, // set Frame
Render = 1 << 3, // draw
}
SizeTo constraint
| Value | Meaning |
|---|---|
Exact |
View must match the given size exactly |
AtMost |
View may shrink to content, but not exceed the constraint |
Animation
protected virtual void AnimateCore(TimeSpan previousTime, TimeSpan currentTime) { }
Override AnimateCore to advance per-frame state. Call InvalidateRender() when visuals change and RequestAnimationFrame() to continue next frame:
protected override void AnimateCore(TimeSpan prev, TimeSpan current)
{
_angle += (current - prev).TotalSeconds * Math.PI;
RequestAnimationFrame();
InvalidateRender();
}
Lifecycle
Views have four lifecycle hooks called by the framework when they enter or leave the tree.
protected virtual void OnAttach(ref AttachEventRef e) { } // platform contexts available
protected virtual void OnDetach(ref DetachEventRef e) { } // release resources
protected virtual void OnActivate() { } // start animations / subscriptions
protected virtual void OnDeactivate() { } // stop animations / subscriptions
- Attach / Detach fire when the subtree joins or leaves the window's visual tree. Platform contexts (text measure, image pipeline) are reachable via
GetService<T>()inOnAttach. - Activate / Deactivate fire when the view becomes visible (e.g., a tab page becomes current). A view in a recycled panel stays attached but may be deactivated.
- Subtree order: Attach and Activate propagate parent-first (top-down). Detach and Deactivate propagate children-first (bottom-up).
Example — acquiring an image on attach:
public class ThumbnailView : View
{
private IImage? _image;
protected override void OnAttach(ref AttachEventRef e)
{
_image = this.GetService<IImage>();
_image.Load("Assets/thumbnail.png");
}
protected override void OnDetach(ref DetachEventRef e)
{
_image = null;
}
protected override void RenderCore(IContext context)
{
if (_image != null)
context.DrawImage(_image, Frame);
}
}
Hit testing
public virtual bool HitTest(Point point)
Default: checks children depth-first (reverse order), then the view's own Frame. Override to exclude transparent areas or expand the hit region:
public override bool HitTest(Point point)
{
// circular hit region
var center = Frame.Center;
var radius = Frame.Width / 2;
return Point.Distance(center, point) <= radius;
}
Children
View inherits a built-in child collection. Access children with this[i] and this.Count. Containers initialise their children declaratively:
public class MyPanel : View
{
private readonly Label _label = new Label { Text = "Hello" };
private readonly ImageView _icon = new ImageView();
public MyPanel()
{
Add(_label);
Add(_icon);
}
}
Built-in views
| Type | Purpose |
|---|---|
Label |
Single-line or multiline text |
ImageView |
Renders an IImage |
Border |
Background fill, border stroke, and a single child |
ScrollView |
Scrollable container for a single child |
HorizontalStack |
Lays out children left to right |
VerticalStack |
Lays out children top to bottom |
HorizontalUniformStack |
Equal-width horizontal layout |
VerticalUniformStack |
Equal-height vertical layout |
TextBox |
Single-line editable text input |
Custom view example
public class ProgressBar : View
{
public double Value { get; set; } = 0.5;
protected override Size MeasureCore(Size available, IMeasureContext context)
=> (available.Width, 8); // always 8 px tall, full width
protected override void RenderCore(IContext context)
{
// Track
context.SetFill(new Color(0xE0, 0xE0, 0xE0, 0xFF));
context.FillRect(Frame);
// Fill
context.SetFill(new Color(0x00, 0x78, 0xD4, 0xFF));
context.FillRect(new Rect(Frame.X, Frame.Y,
(nfloat)(Frame.Width * Value), Frame.Height));
}
}