Skip to content

Fixes #4183 - BREAKING CHANGE - Finishes Adornments and Rewrites TabView#4829

Merged
tig merged 193 commits intogui-cs:developfrom
tig:issue-4183-tabview
Apr 7, 2026
Merged

Fixes #4183 - BREAKING CHANGE - Finishes Adornments and Rewrites TabView#4829
tig merged 193 commits intogui-cs:developfrom
tig:issue-4183-tabview

Conversation

@tig
Copy link
Copy Markdown
Collaborator

@tig tig commented Mar 11, 2026

Overview

This PR completes the v2 Adornments implementation (#2994) by replacing the old TabView (~1,500 lines across 6 files of manual drawing code) with a new Tabs class built entirely on the v2 infrastructure: BorderSettings.Tab, SuperViewRendersLineCanvas, overlapped ViewArrangement, and the IValue<View?> pattern.

WindowsTerminal_OUfqSsjg41

The Old TabView Problem

The original TabView contained ~800 lines of manual LineCanvas drawing in TabRow.OnRenderingLineCanvas(), hand-computing line positions, corners, and intersections that the LineCanvas auto-join system was designed to handle automatically. It also had manual viewport calculation, custom mouse/keyboard handling outside the Command system, a separate TabStyle configuration class, and a Tab.View/Tab.DisplayText split.

What Changed

New Tabs class — Any View can be a tab. Add views via View.Add() and the container automatically configures each with BorderSettings.Tab, ViewArrangement.Overlapped, and a Border.Thickness derived from TabDepth and TabSide. The selected tab is the focused one, reported via IValue<View?>.Value. Zero custom line drawing — the entire visual (tab headers, separator lines, T-junctions, corners) is produced by standard Border rendering + LineCanvas auto-joins.

BorderSettings.Tab — A new border mode that draws a tab-style header on one side of any View's border. Configured via Border.TabSide, Border.TabOffset, and Border.TabLength. This is a general-purpose capability — any View can have a tab border, not just views inside a Tabs container.

BorderView tab rendering pipeline — New code in BorderView handles the tab header drawing: computing header rectangles, drawing separator lines with gaps for the selected tab, rendering the title inside the tab header via TabTitleView, and managing line canvas intersections. All done through the existing Border/LineCanvas infrastructure.

Four-side tab supportTabSide supports Top, Bottom, Left, and Right. Tab depth is configurable via TabDepth.

Scroll buttons — When tabs overflow the available space, ScrollButton indicators (◄►/▲▼) appear on the separator line. ScrollOffset provides programmatic scrolling with clamping.

ScrollButton control — Extracted from ScrollBar into a standalone 1×1 button view with directional glyphs and mouse-hold repeat. Used by both ScrollBar and Tabs.

Mouse hold repeat fixMouseHoldRepeaterImpl.RaiseMouseIsHeldDownTick now stops the repeat timer when the view becomes invisible or disabled, preventing runaway timers when scroll buttons are hidden.

Drawing Pipeline Changes

Breaking Changes

  • TabView, Tab, TabRow, TabStyle, TabChangedEventArgs, TabMouseEventArgs are all deleted
  • Replaced by Tabs (the container) — tabs are just View instances added via Add()
  • InsertTab(index, view) for positional insertion; TabCollection for logical-order enumeration
  • Selection via Value property (IValue<View?>) instead of SelectedTab/SelectedTabIndex

Scenarios

  • GraphViewExample refactored to use Tabs — each graph type is a tab instead of cycling via Ctrl+G
  • New TabsExample scenario replacing the old TabViewExample
  • AdornmentsEditor, Notepad, ConfigurationEditor, and other scenarios updated

Fixes

tig and others added 7 commits March 9, 2026 06:31
Remove all old TabView source files (~3,570 lines across 6 files),
old TabView tests, and TabViewExample scenario. Fix compilation errors
in scenarios that referenced deleted types by stubbing out TabView-
dependent scenarios (Notepad, ConfigurationEditor) and replacing
TabView usage with direct views (Editor, Images, AnsiRequests).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add BorderGap record struct and gap lists (TopGaps, BottomGaps,
LeftGaps, RightGaps) to Border. Gaps create exclusion regions in
the border's LineCanvas, allowing TabView to punch openings where
the selected tab connects to the content area.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements the new TabView using modern v2 patterns:
- Tab: Simple View subclass (Visible=false by default, Dim.Fill)
- TabRow: Internal view in Padding that renders tab headers with
  rounded borders, positioned adjacent with 1-cell overlap
- TabView: Orchestrator with Wizard-style show/hide, nullable
  SelectedTabIndex, keyboard commands (Ctrl+Left/Right/Home/End),
  IDesignable support, and CWP SelectedTabChanged event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New scenario for exercising TabView with:
- Demo TabView with 3 example tabs (label, text+button, multi-line)
- Configuration pane: TabsOnBottom toggle, MaxTabTextWidth,
  Add/Remove tab buttons, SelectedTabIndex display, Prev/Next nav
- AdornmentsEditor targeting the demo TabView
- EventLog for monitoring tab change events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use ValueChangingEventArgs.Handled to clamp MaxTabTextWidth
input to 1-100 range instead of non-existent MinValue/MaxValue.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@tig tig marked this pull request as draft March 11, 2026 22:51
tig and others added 22 commits March 11, 2026 21:20
…, disposal fixes

- Change TabView.Border.Thickness to (1,0,1,1) so tab headers form the top
- Add Border.DrawSegmentedVerticalLine for gap-aware vertical line rendering
- Use RightGaps instead of Exclude for correct corner glyphs (╮ not ┤)
- TabRow draws horizontal continuation line from last tab to right edge
- Fix RebuildHeaders to dispose old header views (prevents "Not Disposed" leaks)
- Guard OnSubViewRemoved during disposal to prevent orphaned header creation
- Clear SelectedTabChanged event before base disposal to prevent ObjectDisposedException
- Add disposal tests that fail without fixes (verify Border==null after dispose)
- Update all visual rendering tests for new no-top-border layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sOnBottom

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactored TabViewExample for more adaptive layout using Dim.Fill and Pos.AnchorEnd, and compacted configuration controls. Updated TabView and TabRow to use base.SuperViewRendersLineCanvas = true. Improved command registration with method group syntax. Enhanced Side enum documentation. Updated tabview-rewrite.md to mark navigation and visual polish as partial, added a detailed TabsOnBottom fix plan, and documented additional known issues. Minor code cleanups and formatting improvements.
- Fix text/continuation line overlap in TabRow.UpdateHeaderAppearance()
  by adding Padding.Top=1 compensation for selected header when TabsOnBottom
- Extract visual rendering tests to TabViewVisualTests.cs (28 tests)
- Add 13 new visual regression tests covering tabs-on-bottom, state
  transitions, padding assertions, and edge cases
- Update plan with fix status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements mnemonic (hotkey) selection for TabView tabs by overriding OnHandlingHotKey in Tab. Ensures TabStop is set for tabs and tab headers. Updates TabViewExample and adds comprehensive unit tests for hotkey selection, event raising, and keyboard navigation. Refactors focus management logic for robustness. Improves TabView disposal and focus behavior. Updates documentation and marks hotkey issue as fixed.
- Replace bool TabsOnBottom with Side TabSide enum (Top/Bottom only, Left/Right ignored)
- Add 6 hotkey navigation unit tests proving Tab.OnHandlingHotKey works
- Replace CheckBox with OptionSelector<Side> in TabViewExample, disable Left/Right
- Update all tests and visual tests for TabSide rename
- Add focusable tab headers with hot-normal/hot-focus attribute routing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Make TabRow focusable and add focus event handlers to TabRow/TabView
- Enhance mouse and keyboard navigation for tab headers
- Refactor Padding subview logic and mouse handling
- Set TabView.Padding as a tab stop for accessibility
- Add visual tests for tab scrolling and indicator rendering
- Clean up code and remove unused usings
- Prepares for future tab scrolling support and better accessibility
…iewport scrolling

- TabRow.UpdateContentSizeForScrolling() computes total header width and sets ContentSize
- TabRow.EnsureHeaderVisible() scrolls viewport to reveal selected tab header
- TabView.SelectedTabIndex setter calls EnsureHeaderVisible on tab change
- Rewrite scroll tests to verify content size and forward viewport scrolling
- Skip scroll-back rendering test pending investigation
- Update plan to reflect scrolling progress

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Design plan for implementing tab headers as a first-class Border
capability (BorderSettings.Tab) instead of using a separate TabRow
with dynamically-created header views. Leverages existing
Thickness.Top == 3 rendering and LineCanvas auto-join for the
flowing connected tab style. Includes renderings for all four
sides (Top, Bottom, Left, Right) and a plan for migrating
BorderTests to UnitTestsParallelizable.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major rewrite of tabview-border-based-design.md to propose a new, border-based approach for tabbed interfaces in Terminal.Gui v2. Tabs are now rendered as part of the Border adornment, leveraging a shared LineCanvas for auto-joining borders and eliminating the need for TabRow and dynamic header Views. The document details the new API surface (BorderSettings.Tab, TabOffset, TabWidth), explains rendering for all four sides, and provides implementation phases. Scrolling, focus, and mouse handling are addressed, with ASCII diagrams and tables clarifying the design. The new approach aims to be the reference for compound views in v2.
Add support for transparent borders drawn on top

Introduce transparent border rendering, similar to transparent margins, by drawing borders with the Transparent flag after all content. Refactor border drawing logic and region exclusion to support this feature. Update demo and editors for improved usability and identifier display. Move ShowViewIdentifier to EditorBase and ensure consistent OnViewToEditChanged behavior. Cache clip regions for correct transparent rendering.
# Conflicts:
#	Examples/UICatalog/Scenarios/ConfigurationEditor.cs
#	Examples/UICatalog/Scenarios/Notepad.cs
#	Examples/UICatalog/Scenarios/TabViewExample.cs
#	Terminal.Gui/Views/TabView/TabView.cs
tig and others added 17 commits April 6, 2026 12:11
- BorderView and TitleView now participate in tab navigation via TabStop.
- TitleView can be focused with HotKey; arrow keys are bound for navigation.
- Tabs navigation logic updated: arrow keys move focus between tab titles and content, or switch tabs based on tab side.
- Added FocusContent method to move focus from tab title to content.
- Updated tests to verify new navigation and focus behavior.
Refactor tab header positioning logic and properties (TabSide, TabOffset, TabLength, EffectiveTabLength) from the Border adornment to BorderView, making the view responsible for tab state. Update all usages, tests, and documentation to reference BorderView for tab-related properties. Remove Border.SettingsChanged event in favor of an internal BorderView method. Apply minor code style and formatting improvements for consistency.
Updated Tabs to use Pos.AnchorEnd(TabDepth) for separator positioning when tabs are on the Bottom or Right, improving layout flexibility. Also removed an unnecessary Margin.Thickness assignment in TabsExample.
Adds a TabSpacing property to the Tabs control that controls the gap
between adjacent tab headers. Default is -1 (shared border edge,
preserving current behavior). 0 = edge-to-edge, positive values insert
gaps. Also fixes null-safety in BorderEditor and updates borders.md
property table to reflect BorderView ownership of tab properties.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@tig
Copy link
Copy Markdown
Collaborator Author

tig commented Apr 7, 2026

I've added full keyboard nav support and refactored to improve the look and feel, as well as maintainability of the code.

Pull this down locally and play with it please.

WindowsTerminal_4oDcnlIlpU

I plan on merging this later today.l

@tig tig requested review from BDisp and YourRobotOverlord April 7, 2026 18:40
@tig tig merged commit 953fc1f into gui-cs:develop Apr 7, 2026
@YourRobotOverlord
Copy link
Copy Markdown
Collaborator

I love how configurable these are!

I also feel that the Spacing parameter is a wonderful option for @BDisp's concerns:

Why not consider placing a T at the top of the tabs instead of a space appearing, and using the same procedure for other types of sides?

image

I did notice one unexpected behavior where the Title is not visible and becomes un-clickable when the Side is set to Right or Bottom:
WindowsTerminal_Kp5O3ZW6mi

@BDisp
Copy link
Copy Markdown
Collaborator

BDisp commented Apr 8, 2026

I love this very much too.

image

Copy link
Copy Markdown
Collaborator

@YourRobotOverlord YourRobotOverlord left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tig I'm sorry, I posted my comment after you'd already closed your pr but github is still asking for a review, so the only thing I could find was the new Tab rendering glitch.
There's so much to like in here!
#4829 (comment)

@tig
Copy link
Copy Markdown
Collaborator Author

tig commented Apr 9, 2026

I did notice one unexpected behavior where the Title is not visible and becomes un-clickable when the Side is set to Right or Bottom:

WindowsTerminal_Kp5O3ZW6mi

I can repro this. Happens whenever a subview of the tab exceeds the viewport. Eg.

WindowsTerminal_SS4tMD6DR6

Issue raised here: #4915

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment