Skip to content

Host settings as a tab and match the Windows Terminal settings UI - #5

Merged
Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
masterfrom
copilot/settings-ui-avalonia-devtools-review
Sep 5, 2026
Merged

Host settings as a tab and match the Windows Terminal settings UI#5
Marc-André Moreau (mamoreau-devolutions) merged 2 commits into
masterfrom
copilot/settings-ui-avalonia-devtools-review

Conversation

@mamoreau-devolutions

Copy link
Copy Markdown
Contributor

Windows Terminal shows its settings as a tab inside the main window; ours opened a separate window that also looked noticeably different. This moves the editor into a tab and aligns the visuals with the real thing.

Before After
Hosting separate Window tab in the main window (gear icon, reused/focused on reopen)
Page background #0C0C0C (terminal black) #282828
Cards #333333 with a bright #3D3D3D border, 68px tall, 4px gaps borderless #333333, 62px tall, 6px gaps
Nav 280px, search-only header 320px, hamburger, profiles listed, "Add a new profile"
Footer Open JSON / Reset / status / diagnostics / Save / Discard Open JSON left, accent Save + Discard changes right
Toggles 3 rows showed On/Off every row shows On/Off
Enum settings free-text boxes combo boxes

How it works

SettingsView (new UserControl) holds the whole editor; SettingsWindow now wraps that same view, so the standalone entry point still works. TerminalTab gains CustomContent (plus an IsSettingsTab predicate) so a tab can host non-terminal content, and MainWindow.OpenSettingsTab() creates or focuses the single settings tab.

Settings-tab guards

The settings tab carries a placeholder pane, so terminal-only paths are guarded. Two of these were user-visible bugs:

  • Tearing off or duplicating the settings tab captured the placeholder session and spawned a real PowerShell session in a tab titled "Settings".
  • Ctrl+V was consumed by the window-level key handler and then dropped, so paste was dead in every settings text box.

Also guarded: restore-last-closed (the settings tab is no longer remembered), movePane, layout capture, and new-tab sizing. ActiveControl returns null on the settings tab, which disables the ~30 control-scope actions in one place.

Separately, AddProfile now commits pending JSON editor buffers first instead of silently discarding unsaved Actions/keybindings/environment edits.

Visual parity

Metrics were measured pixel-by-pixel against a maximized Windows Terminal capture (window 1113px wide):

  • nav items span x=7..315 (ours 9..312), cards span x=337..1095 (ours 337..1096)
  • first card at y=85 with the title 11px below the content top (ours 82 / 12px)
  • card height 62px and gaps 6px in both

One deliberate difference: WT's capture uses grayscale text antialiasing while we render with subpixel AA, so glyph edges show faint colour fringing. That's a font-rendering mode, invisible at normal viewing distance, so text rendering was left alone.

Testing

  • dotnet test Devolutions.Terminal.slnx13 projects, 825 tests, 0 failures
  • New coverage: settings tab is created/reused, a closed settings tab is not restored as a terminal, paste/duplicate are unavailable on the settings tab, SettingsToggle state/binding round-trip, and AddProfile preserving vs. rejecting JSON edits
  • The headless UI test app now loads FluentTheme so real control templates apply
  • Manual checks via the Avalonia DevTools MCP: tab switching preserves state, close/reopen work, and Ctrl+V pastes in settings and still pastes in the terminal

Windows Terminal shows its settings as a tab inside the main window, and our
settings editor was a separate window with a noticeably different look. Move the
editor into a tab and align its visuals with the real thing.

Extract the editor into a SettingsView UserControl so it can be hosted anywhere;
SettingsWindow now wraps that same view. TerminalTab gains CustomContent (and the
IsSettingsTab predicate) so a tab can host non-terminal content, and
OpenSettingsTab creates or focuses the single settings tab.

The settings tab carries a placeholder pane, so guard the terminal-only paths that
would otherwise act on it: ActiveControl returns null there (disabling the
control-scope actions), and paste, duplicate, tear-off, restore-last-closed,
movePane, layout capture and new-tab sizing all skip it. Without these, tearing
off or duplicating the settings tab spawned a bogus PowerShell session, and Ctrl+V
was swallowed before reaching the settings text boxes.

Visuals are measured against a maximized Windows Terminal capture: page/nav
#282828 with #333333 borderless cards, 320px nav, 62px rows with 6px gaps, cards
spanning the same x range, a hamburger, profiles listed in the nav, and a footer
with Open JSON file on the left plus an accent Save and Discard changes on the
right. Enum-ish settings become combo boxes and every toggle row shows its On/Off
state through the new SettingsToggle control.

AddProfile now commits pending JSON editor buffers first instead of silently
discarding unsaved Actions, keybindings and environment edits.

The headless UI test application loads FluentTheme so real control templates apply.
An activation that closes the final tab also closes the window, and ActivateAsync
then called Show() on it, throwing "Cannot re-show a closed window". Track the
closed state and skip the show/activate step.

This surfaced on Linux and macOS CI, where the placeholder terminal tab fails to
launch, so the settings tab is the only tab and closing it closes the window.
Assert the reopen-stack invariant through the tab collection instead of a second
activation, and cover the closed-window path with a regression test.
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) merged commit b273dc1 into master Sep 5, 2026
24 checks passed
@mamoreau-devolutions
Marc-André Moreau (mamoreau-devolutions) deleted the copilot/settings-ui-avalonia-devtools-review branch September 5, 2026 22:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant