Table of Contents

SquidStd.Tui

MVVM for terminal apps, built on Terminal.Gui v2 and CommunityToolkit.Mvvm. Observable ViewModels, a View↔ViewModel binder (fluent + opt-in convention), ViewModel-first navigation, and DryIoc wiring.

Install

dotnet add package SquidStd.Tui

Usage

public sealed partial class CounterViewModel : TuiViewModel
{
    [ObservableProperty] private string _title = "Counter";
    [ObservableProperty] private string _value = "0";

    [RelayCommand]
    private void Increment() => Value = (int.Parse(Value) + 1).ToString();
}

public sealed class CounterView : TuiView<CounterViewModel>
{
    private Label _value = null!;
    private Button _inc = null!;

    protected override void BuildLayout()
    {
        _value = new Label { X = 1, Y = 1 };
        _inc = new Button { X = 1, Y = 3, Text = "+1" };
        Add(_value, _inc);
    }

    protected override void Bind(ViewBinder b)
    {
        b.OneWayTitle(ViewModel, x => x.Title, this);
        b.OneWayText(ViewModel, x => x.Value, _value);
        b.Command(_inc, ViewModel.IncrementCommand);
    }
}

// boot
var container = new Container().RegisterTui();
container.RegisterView<CounterView, CounterViewModel>();
await container.Resolve<TuiApplicationHost>().RunAsync<CounterViewModel>();

Declarative UI (DSL)

Instead of imperative BuildLayout + Bind, derive from TuiComposedView<TViewModel> and return a node tree from Compose(). One node = widget + binding; direction is inferred (Label one-way, TextField two-way, Button command) and bindings are typed lambdas - no magic strings.

public sealed class CounterView : TuiComposedView<CounterViewModel>
{
    protected override TuiNode<CounterViewModel> Compose() =>
        Ui.VStack(
            Ui.Label(x => x.Title),
            Ui.Label(x => x.Value),
            Ui.Button("+1", x => x.IncrementCommand));
}

Ui.VStack/Ui.HStack arrange children automatically; Ui.TextField(x => x.Name) is two-way by default (pass BindMode.OneWay to override).

Key types

Type Purpose
TuiViewModel ViewModel base (ObservableObject + activation hooks + Navigator).
TuiView<TViewModel> View base: a Terminal.Gui Window with a typed ViewModel and a binder.
ViewBinder One-way / two-way / command binding; fluent typed + AutoBind by convention.
ITuiNavigator ViewModel-first stack navigation (NavigateToAsync, BackAsync).
TuiApplicationHost Boots the Terminal.Gui loop with a root ViewModel.
RegisterTui() / RegisterView<,>() DryIoc registration.

License

MIT - part of SquidStd.