Visual Tree & Fluent UI

Visuals

The fundamental building block is Visual:

  • participates in layout (Measure/Arrange)
  • participates in rendering
  • receives input events (keyboard/mouse) when enabled and routed

Controls (TextBox, Button, etc.) are Visual types. Containers (VStack, Grid, etc.) are also visuals.

One visual, one parent

A Visual instance can only live in one place in the visual tree.

If you try to reuse the same instance in two different containers, you'll get an exception similar to:

System.InvalidOperationException: The visual already has a parent.

If you need “the same UI twice”, create two visuals and bind them to the same state/model, or build a small factory method that creates a fresh visual tree each time.

Fluent configuration

Most controls provide fluent extension methods generated by the source generator:

new TextBox("Name").MinWidth(10)

Common Visual properties (and preferred fluent methods)

Many layout/visibility-related properties are defined on Visual itself and are bindable. The source generator produces fluent setters and binding helpers for them.

Property Typical fluent usage Notes
HorizontalAlignment .HorizontalAlignment(Align.Stretch) Start/Center/End/Stretch
VerticalAlignment .VerticalAlignment(Align.Stretch) Start/Center/End/Stretch
Both alignments .Stretch() Convenience for HorizontalAlignment(Stretch) + VerticalAlignment(Stretch)
MinWidth / MinHeight .MinWidth(10) / .MinHeight(3) In cells
MaxWidth / MaxHeight .MaxWidth(40) / .MaxHeight(10) In cells
Margin .Margin(new Thickness(1)) Outer margin handled by the parent layout
IsVisible .IsVisible(false) Hidden visuals don't render and don't participate in layout/input
IsEnabled .IsEnabled(false) Usually affects input and disabled styling
AutoFocus .AutoFocus(true) Lets TerminalApp pick an initial focus target

The generated API also includes Bind* variants for connecting bindings directly:

var showDetails = new State<bool>(false);

var panel = new VStack()
    .IsVisible(showDetails);

Binding via .Bind

Bindable properties expose a typed Binding<T> through the generated .Bind view. This is the preferred way to connect controls together:

var query = new State<string>("");

var box = new TextBox().Text(query);
var mirror = new TextBlock(query.Bind.Value);

See Binding for the full model.

Visibility, enabled state, and hit testing

IsEnabled and pointer hit testing are related but not identical:

  • IsVisible=false makes the visual effectively disappear (no layout, no render, no hit test).
  • IsEnabled=false typically disables interaction and may affect styling, but it may still be present in the tree.
  • IsHitTestVisible=false makes the visual transparent to pointer hit testing (useful for non-interactive overlays).

There are also generic helpers:

  • VisualExtensions.Update(...) for dynamic updates (binding-registered callbacks)
  • VisualExtensions.Add(...) to add children to a Panel fluently
  • VisualExtensions.Style(...) to apply a style instance

Static vs dynamic composition

XenoAtom.Terminal.UI supports retained-mode UI with optional computed subtrees:

  • Use containers and child properties for retained visuals.
  • Use ComputedVisual to build dynamic parts driven by state/bindings.
  • Use ContentSwitcher when you want to switch between a set of pre-built visuals.

When a binding changes, the framework re-runs only the required parts of:

  • dynamic updates
  • layout
  • rendering

Dynamic UI with ComputedVisual

ComputedVisual is a small “dynamic subtree” helper. It runs a Func<Visual?> during the prepare children pass and tracks any binding reads performed while building the subtree. When those bindings change, only this part of the tree is rebuilt.

This is a good fit for conditional UI, overlays, and “only show this panel when…” scenarios.

Example: a modal-like overlay driven by State<bool>:

var isOpen = new State<bool>(false);

var openButton = new Button("Open").OnClick(() => isOpen.Value = true);

var overlay = new ComputedVisual(() =>
{
    if (!isOpen.Value)
    {
        return null;
    }

    return new Backdrop(
        new Group
        {
            Content = new VStack(
                new TextBlock("Hello!"),
                new Button("Close").OnClick(() => isOpen.Value = false)
            ).Spacing(1).Pad(new Thickness(1))
        });
});

var root = new VStack(openButton, overlay);

Important: ComputedVisual is responsible for attaching/detaching the visual it produces. Don’t reuse visuals across parents (see “One visual, one parent”).

See also: