creating-mewui-controls

SKILL.md

Control Base Classes

Base Use Case
Control Interactive elements (buttons, inputs)
ContentControl Single-child containers
Panel Multi-child layouts

Basic Control Structure

public class MyButton : Control
{
    private bool _isPressed;

    // Property with invalidation
    public string Text
    {
        get;
        set
        {
            if (field != value)
            {
                field = value;
                InvalidateMeasure();
            }
        }
    } = "";

    // Enable focus by overriding (not setting property)
    public override bool Focusable => true;

    // Measure: calculate desired size
    protected override Size MeasureContent(Size availableSize)
    {
        var factory = GetGraphicsFactory();
        using var ctx = factory.CreateMeasurementContext(GetDpi());
        var textSize = ctx.MeasureText(Text, GetFont());
        return new Size(textSize.Width + Padding.Horizontal, textSize.Height + Padding.Vertical);
    }

    // Render: draw the control
    protected override void OnRender(IGraphicsContext context)
    {
        var bounds = new Rect(0, 0, Bounds.Width, Bounds.Height);

        var bgColor = _isPressed ? Theme.Palette.ButtonPressedBackground
            : IsMouseOver ? Theme.Palette.ButtonHoverBackground
            : Theme.Palette.ButtonFace;

        context.FillRoundedRectangle(bounds, 4, 4, bgColor);
        context.DrawRoundedRectangle(bounds, 4, 4, Theme.Palette.BorderColor, 1);
        context.DrawText(Text, bounds, GetFont(), Foreground,
            TextAlignment.Center, TextAlignment.Center, TextWrapping.NoWrap);
    }
}

Input Handling

// Mouse - note: MouseEventArgs, not MouseButtonEventArgs
protected override void OnMouseDown(MouseEventArgs e)
{
    if (e.LeftButton)
    {
        _isPressed = true;
        // Capture via Window
        var root = FindVisualRoot();
        if (root is Window window)
            window.CaptureMouse(this);
        InvalidateVisual();
        e.Handled = true;
    }
}

protected override void OnMouseUp(MouseEventArgs e)
{
    if (_isPressed)
    {
        _isPressed = false;
        var root = FindVisualRoot();
        if (root is Window window)
            window.ReleaseMouseCapture();
        if (IsMouseOver) Click?.Invoke(this, EventArgs.Empty);
        InvalidateVisual();
    }
}

// Keyboard
protected override void OnKeyDown(KeyEventArgs e)
{
    if (e.Key == Key.Space || e.Key == Key.Enter)
    {
        Click?.Invoke(this, EventArgs.Empty);
        e.Handled = true;
    }
}

Adding Binding Support

private ValueBinding<string>? _textBinding;

public void SetTextBinding(Func<string> get, Action<string>? set = null,
    Action<Action>? subscribe = null, Action<Action>? unsubscribe = null)
{
    _textBinding?.Dispose();
    _textBinding = new ValueBinding<string>(get, set, subscribe, unsubscribe, () => Text = get());
    // RegisterBinding is internal - use within MewUI assembly
    Text = get();
}

Fluent Extensions

public static class MyButtonExtensions
{
    public static MyButton Text(this MyButton btn, string text) { btn.Text = text; return btn; }
    public static MyButton OnClick(this MyButton btn, Action handler) { btn.Click += (s,e) => handler(); return btn; }
    public static MyButton BindText(this MyButton btn, ObservableValue<string> src)
    {
        btn.SetTextBinding(() => src.Value, v => src.Value = v,
            h => src.Changed += h, h => src.Changed -= h);
        return btn;
    }
}

Input details: See input-handling.md ContentControl pattern: See content-control.md

Weekly Installs
3
GitHub Stars
16
First Seen
Feb 28, 2026
Installed on
opencode3
gemini-cli3
codebuddy3
github-copilot3
codex3
kimi-cli3