PRE-MERGE #20068 Add per-pane title header for split panes (#4717)

This commit is contained in:
Carlos Zamora
2026-05-13 14:23:17 -07:00
9 changed files with 145 additions and 6 deletions

View File

@@ -31,10 +31,14 @@ Pane::Pane(IPaneContent content, const bool lastFocused) :
_lastActive{ lastFocused }
{
_setPaneContent(std::move(content));
_root.Children().Append(_borderFirst);
_CreatePaneHeader();
const auto& control{ _content.GetRoot() };
_borderFirst.Child(control);
// Set up leaf layout: header in _root row 0, content in _borderFirst row 1.
// The TermControl stays as the direct child of _borderFirst (no Grid wrapper)
// so the SwapChainPanel renders correctly.
_SetupLeafLayout(control);
// Register an event with the control to have it inform us when it gains focus.
if (control)
@@ -1232,6 +1236,12 @@ void Pane::UpdateVisuals()
const auto& brush{ _ComputeBorderColor() };
_borderFirst.BorderBrush(brush);
_borderSecond.BorderBrush(brush);
// Update pane header color to match focus state
if (_paneHeaderBorder && _paneHeaderBorder.Visibility() == winrt::Windows::UI::Xaml::Visibility::Visible)
{
_paneHeaderBorder.Background(brush);
}
}
// Method Description:
@@ -1454,9 +1464,9 @@ void Pane::_CloseChild(const bool closeFirst)
_root.RowDefinitions().Clear();
// Reattach the TermControl to our grid.
_root.Children().Append(_borderFirst);
_CreatePaneHeader();
const auto& control{ _content.GetRoot() };
_borderFirst.Child(control);
_SetupLeafLayout(control);
// Make sure to set our _splitState before focusing the control. If you
// fail to do this, when the tab handles the GotFocus event and asks us
@@ -1759,7 +1769,92 @@ void Pane::_setPaneContent(IPaneContent content)
}
// Method Description:
// - Sets up row/column definitions for this pane. There are three total
// - Creates the pane header UI elements (title bar shown above the content).
// The header is initially collapsed and only shown via ShowPaneHeaders().
void Pane::_CreatePaneHeader()
{
namespace WUX = winrt::Windows::UI::Xaml;
_paneHeaderText = Controls::TextBlock{};
_paneHeaderText.FontSize(12);
_paneHeaderText.Padding({ 8, 2, 8, 2 });
_paneHeaderText.IsTextSelectionEnabled(false);
_paneHeaderText.TextTrimming(WUX::TextTrimming::CharacterEllipsis);
if (_content)
{
_paneHeaderText.Text(_content.Title());
_titleChangedRevoker = _content.TitleChanged(winrt::auto_revoke, [this](auto&&, auto&&) {
_paneHeaderBorder.Dispatcher().RunAsync(
winrt::Windows::UI::Core::CoreDispatcherPriority::Normal,
[this]() {
if (_content && _paneHeaderText)
{
_paneHeaderText.Text(_content.Title());
}
});
});
}
_paneHeaderBorder = Controls::Border{};
_paneHeaderBorder.Padding({ 0, 0, 0, 0 });
_paneHeaderBorder.Child(_paneHeaderText);
_paneHeaderBorder.Visibility(WUX::Visibility::Collapsed);
}
// Method Description:
// - Sets up the leaf pane layout in _root: a header row (auto-sized) and a
// content row (star-sized). The TermControl stays as the direct child of
// _borderFirst so the SwapChainPanel renders correctly.
void Pane::_SetupLeafLayout(const winrt::Windows::UI::Xaml::UIElement& control)
{
auto headerRow = Controls::RowDefinition{};
headerRow.Height(GridLengthHelper::Auto());
auto contentRow = Controls::RowDefinition{};
contentRow.Height(GridLengthHelper::FromValueAndType(1, GridUnitType::Star));
_root.RowDefinitions().Append(headerRow);
_root.RowDefinitions().Append(contentRow);
Controls::Grid::SetRow(_paneHeaderBorder, 0);
Controls::Grid::SetRow(_borderFirst, 1);
_root.Children().Append(_paneHeaderBorder);
_root.Children().Append(_borderFirst);
if (control)
{
_borderFirst.Child(control);
}
}
// Method Description:
// - Show or hide the pane header title bar on all leaf panes in the tree.
// Called by Tab when the number of panes changes.
void Pane::ShowPaneHeaders(bool show)
{
if (_IsLeaf())
{
if (_paneHeaderBorder)
{
namespace WUX = winrt::Windows::UI::Xaml;
_paneHeaderBorder.Visibility(show ? WUX::Visibility::Visible : WUX::Visibility::Collapsed);
if (show)
{
const auto& brush = _ComputeBorderColor();
_paneHeaderBorder.Background(brush);
_paneHeaderText.Foreground(winrt::Windows::UI::Xaml::Media::SolidColorBrush(winrt::Windows::UI::Colors::White()));
}
}
}
else
{
_firstChild->ShowPaneHeaders(show);
_secondChild->ShowPaneHeaders(show);
}
}
// Method Description:
// - Sets up row/column definitions for this pane.There are three total
// row/cols. The middle one is for the separator. The first and third are for
// each of the child panes, and are given a size in pixels, based off the
// available space, and the percent of the space they respectively consume,
@@ -2325,6 +2420,10 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
_root.RowDefinitions().Clear();
_CreateRowColDefinitions();
// Reset Grid.Row on _borderFirst — it may have been set to row 1 in the
// leaf layout (header=row0, content=row1).
Controls::Grid::SetRow(_borderFirst, 0);
_borderFirst.Child(_firstChild->GetRootElement());
_borderSecond.Child(_secondChild->GetRootElement());

View File

@@ -150,6 +150,7 @@ public:
bool ContainsReadOnly() const;
void EnableBroadcast(bool enabled);
void ShowPaneHeaders(bool show);
void BroadcastKey(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const WORD vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
void BroadcastChar(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const wchar_t vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers);
void BroadcastString(const winrt::Microsoft::Terminal::Control::TermControl& sourceControl, const winrt::hstring& text);
@@ -235,6 +236,11 @@ private:
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
// Per-pane title header (visible when there are split panes)
winrt::Windows::UI::Xaml::Controls::Border _paneHeaderBorder{ nullptr };
winrt::Windows::UI::Xaml::Controls::TextBlock _paneHeaderText{ nullptr };
winrt::TerminalApp::IPaneContent::TitleChanged_revoker _titleChangedRevoker;
PaneResources _themeResources;
#pragma region Properties that need to be transferred between child / parent panes upon splitting / closing
@@ -266,6 +272,8 @@ private:
void _SetupChildCloseHandlers();
winrt::TerminalApp::IPaneContent _takePaneContent();
void _setPaneContent(winrt::TerminalApp::IPaneContent content);
void _CreatePaneHeader();
void _SetupLeafLayout(const winrt::Windows::UI::Xaml::UIElement& control);
bool _HasChild(const std::shared_ptr<Pane> child);
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const;

View File

@@ -361,6 +361,10 @@ namespace winrt::TerminalApp::implementation
// The tabWidthMode may have changed, update the header control accordingly
_UpdateHeaderControlMaxWidth();
// Refresh pane header visibility based on the current setting
const auto showHeaders = settings.GlobalSettings().ShowPaneHeaders() && _rootPane->GetLeafPaneCount() > 1;
_rootPane->ShowPaneHeaders(showHeaders);
// Update the settings on all our panes.
_rootPane->WalkTree([&](const auto& pane) {
pane->UpdateSettings(settings);
@@ -646,6 +650,14 @@ namespace winrt::TerminalApp::implementation
// After split, Close Pane Menu Item should be visible
_closePaneMenuItem.Visibility(WUX::Visibility::Visible);
// Show pane headers now that we have multiple panes (if the setting is enabled)
try
{
const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() };
_rootPane->ShowPaneHeaders(settings.GlobalSettings().ShowPaneHeaders());
}
CATCH_LOG();
// The active pane has an id if it is a leaf
if (activePaneId)
{
@@ -1337,6 +1349,7 @@ namespace winrt::TerminalApp::implementation
if (_rootPane->GetLeafPaneCount() == 1)
{
_closePaneMenuItem.Visibility(WUX::Visibility::Collapsed);
_rootPane->ShowPaneHeaders(false);
}
_RecalculateAndApplyReadOnly();

View File

@@ -68,6 +68,13 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.InvertedDisableAnimations, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
<!-- Show pane headers -->
<local:SettingContainer x:Name="ShowPaneHeaders"
x:Uid="Globals_ShowPaneHeaders">
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowPaneHeaders, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
</StackPanel>
</local:SettingContainer>

View File

@@ -33,6 +33,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, AlwaysShowTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowTabsFullscreen);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowPaneHeaders);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowTabsInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, UseAcrylicInTabRow);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(_GlobalSettings, ShowTitleInTitlebar);

View File

@@ -27,6 +27,7 @@ namespace Microsoft.Terminal.Settings.Editor
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, AlwaysShowTabs);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowTabsFullscreen);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowPaneHeaders);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowTabsInTitlebar);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, UseAcrylicInTabRow);
PERMANENT_OBSERVABLE_PROJECTED_SETTING(Boolean, ShowTitleInTitlebar);

View File

@@ -2685,6 +2685,14 @@
<value>When enabled, the tab bar will be visible when the app is full screen.</value>
<comment>A description for what the "show tabs in full screen" setting does.</comment>
</data>
<data name="Globals_ShowPaneHeaders.Header" xml:space="preserve">
<value>Show pane title headers</value>
<comment>Header for a control to toggle if the app should show title headers above each pane when multiple panes are open.</comment>
</data>
<data name="Globals_ShowPaneHeaders.HelpText" xml:space="preserve">
<value>When enabled, a title header is shown above each pane when multiple panes are open.</value>
<comment>A description for what the "show pane headers" setting does. Presented near "Globals_ShowPaneHeaders.Header".</comment>
</data>
<data name="Profile_PathTranslationStyle.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Path translation</value>
<comment>Name for a control to select how file and directory paths are translated.</comment>

View File

@@ -66,6 +66,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Int32, InitialCols);
INHERITABLE_SETTING(Boolean, AlwaysShowTabs);
INHERITABLE_SETTING(Boolean, ShowTabsFullscreen);
INHERITABLE_SETTING(Boolean, ShowPaneHeaders);
INHERITABLE_SETTING(NewTabPosition, NewTabPosition);
INHERITABLE_SETTING(Boolean, ShowTitleInTitlebar);
INHERITABLE_SETTING(ConfirmOnClose, ConfirmOnClose);

View File

@@ -72,7 +72,8 @@ Author(s):
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
X(bool, AllowHeadless, "compatibility.allowHeadless", false) \
X(hstring, SearchWebDefaultQueryUrl, "searchWebDefaultQueryUrl", L"https://www.bing.com/search?q=%22%s%22") \
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false)
X(bool, ShowTabsFullscreen, "showTabsFullscreen", false) \
X(bool, ShowPaneHeaders, "showPaneHeaders", true)
// Also add these settings to:
// * Profile.idl