Compare commits

...

3 Commits

Author SHA1 Message Date
Mike Griese
f448beaa73 Add an "Tab Overview" pane to the Terminal
This is a new action we're adding to allow the user to quickly view all
of their open tabs at a glance.

When the user performs the `toggleOverview` action, we'll create a tab
overview control, which handles all the animations. We'll set that
OverviewPane as the content of the TerminalPage, and then we'll use
XAML translations and transforms to make all the other TermControls
children of that overviewpane.

This lets us render a bunch of live previews of the tabs all at once!
neat!

full disclosure: lots of this XAML animation code inside of OverviewPane
was written by AI. Sorry, but animations are kinda just clanker work,
and I'd never get it to look like that myself.

The Overview pane will update it's layout to try and maximize how big
the previews are. It'll try to lay out the previews so that we fit as
many as we can, up to 4 in a row, but also it'll try to intelligently
lay them out in a 2x2 grid if there's only 4 (for example).

Closes: #15385
2026-05-20 16:17:11 -05:00
Dustin L. Howett
8fe6c21ef8 Keep the font size delta across settings reloads (#20230)
This is a trivial fix for an issue we get a somewhat outsized number of
complaints about.

When the user has adjusted the font size in any direction, we'll just
keep track of the magnitude and apply it every time the settings change.

Yes, this means that if you zoom once and then change your real font
size it's going to zoom even more.

That probably doesn't matter.

Refs #11522
2026-05-15 13:56:48 -05:00
aarushi singh
abeac1b135 Use PlaySoundW for profile bell sounds (#20031)
We believed that this would fix an issue on Windows 10, where the volume
mixer would forget Windows Terminal after every relaunch. It turns out
that it does not.

Still, this code is much more concise and doesn't require yet another
WinRT object. Story of our lives.

Refs #17733
2026-05-15 20:51:01 +02:00
23 changed files with 1628 additions and 55 deletions

View File

@@ -1075,6 +1075,7 @@ NOCONTEXTHELP
NOCOPYBITS
nodiscard
NODUP
NODEFAULT
noexcepts
NOFONT
NOHIDDENTEXT
@@ -1561,6 +1562,7 @@ SMARTQUOTE
SMTO
snapcx
snapcy
SND
snk
SOLIDBOX
Solutiondir

View File

@@ -475,6 +475,7 @@
"toggleBlockSelection",
"toggleFocusMode",
"toggleFullscreen",
"toggleOverview",
"togglePaneZoom",
"toggleReadOnlyMode",
"toggleShaderEffects",

View File

@@ -604,6 +604,13 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
void TerminalPage::_HandleToggleOverview(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
ToggleOverview();
args.Handled(true);
}
void TerminalPage::_HandleSetFocusMode(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <limits>
#include <optional>
#include "OverviewPane.g.h"
namespace winrt::TerminalApp::implementation
{
struct OverviewPane : OverviewPaneT<OverviewPane>
{
OverviewPane();
~OverviewPane();
void UpdateTabContent(Windows::Foundation::Collections::IVector<TerminalApp::Tab> tabs, int32_t focusedIndex);
void ClearTabContent();
int32_t SelectedIndex() const;
void SelectedIndex(int32_t value);
bool UseMica() const;
void UseMica(bool value);
hstring ControlName() const;
// Events
til::typed_event<Windows::Foundation::IInspectable, Windows::Foundation::IReference<int32_t>> TabSelected;
til::typed_event<> Dismissed;
private:
friend struct OverviewPaneT<OverviewPane>; // for Xaml to bind events
void _OnKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _OnPreviewKeyDown(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
void _OnItemClicked(int32_t index);
void _UpdateSelection();
void _PlayEnterAnimation();
void _PlayExitAnimation(std::function<void()> onComplete = nullptr);
void _StartEnterZoomAnimation();
std::optional<std::tuple<double, double, double>> _GetZoomParamsForCell(int32_t index);
Windows::UI::Xaml::FrameworkElement _BuildPreviewCell(const TerminalApp::Tab& tab, int32_t index, int32_t totalCount, double previewWidth, double previewHeight, double referenceWidth, double referenceHeight);
void _DetachContent(const Windows::UI::Xaml::FrameworkElement& content);
struct AdaptiveLayout
{
double cellWidth{ 0.0 };
double cellHeight{ 0.0 };
double previewWidth{ 0.0 };
double previewHeight{ 0.0 };
int32_t columnCount{ 1 };
};
AdaptiveLayout _ComputeAdaptiveLayout(int32_t tabCount, double viewportWidth, double viewportHeight, double aspect) const;
void _ApplyAdaptiveLayout();
static void _AddDoubleAnimation(
const Windows::UI::Xaml::Media::Animation::Storyboard& storyboard,
const Windows::UI::Xaml::Media::CompositeTransform& target,
const hstring& property,
double from,
double to,
const Windows::UI::Xaml::Duration& duration,
const Windows::UI::Xaml::Media::Animation::EasingFunctionBase& easing);
int32_t _selectedIndex{ 0 };
int32_t _columnCount{ 1 }; // updated at UpdateTabContent time to match the adaptive layout
bool _pendingEnterAnimation{ false };
bool _pendingAdaptiveLayout{ false };
// Homogeneous-aspect assumption: every cell uses the active tab's
// content aspect ratio. Per-tab aspect (e.g. one tab with a Settings
// page that's narrow + tall, another with a TermControl that's wide)
// would require a different layout pass — cells could no longer
// share a single ItemWidth/ItemHeight on the WrapGrid.
double _referenceAspect{ 16.0 / 10.0 };
double _lastAppliedViewportWidth{ 0.0 };
double _lastAppliedViewportHeight{ 0.0 };
bool _useMica{ false };
void _UpdateBackgroundForMica();
winrt::event_token _exitAnimationToken{};
Windows::UI::Xaml::Media::Animation::Storyboard _exitContentStoryboard{ nullptr };
// Subscriptions on our own owned children. Stored so the destructor
// can explicitly revoke them before ClearTabContent runs — matches
// the token-revoke pattern used in TerminalPage for overview events.
winrt::event_token _layoutUpdatedToken{};
winrt::event_token _sizeChangedToken{};
// Per-cell state for the adaptive layout pass. INVARIANT: there is
// exactly one ReparentedEntry per cell in PreviewGrid().Items(), in
// cell order. When the source tab had no live content,
// `content` / `previewCanvas` are null and `layoutWidth` /
// `layoutHeight` are zero — the cell still gets a previewBorder
// + titleBlock sized by _ApplyAdaptiveLayout, but the
// ScaleTransform / reparent path is skipped. _ApplyAdaptiveLayout
// and ClearTabContent both null-check before touching content.
struct ReparentedEntry
{
Windows::UI::Xaml::FrameworkElement content{ nullptr };
Windows::UI::Xaml::Controls::Panel originalParent{ nullptr };
double originalWidth{ std::numeric_limits<double>::quiet_NaN() };
double originalHeight{ std::numeric_limits<double>::quiet_NaN() };
Windows::UI::Xaml::Media::Transform originalRenderTransform{ nullptr };
Windows::Foundation::Point originalRenderTransformOrigin{ 0.0f, 0.0f };
// Per-cell handles for adaptive resizing after the first layout pass.
Windows::UI::Xaml::Controls::Border previewBorder{ nullptr };
Windows::UI::Xaml::Controls::TextBlock titleBlock{ nullptr };
Windows::UI::Xaml::Controls::Canvas previewCanvas{ nullptr };
double layoutWidth{ 0.0 };
double layoutHeight{ 0.0 };
};
std::vector<ReparentedEntry> _reparentedContent;
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(OverviewPane);
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import "Tab.idl";
namespace TerminalApp
{
[default_interface] runtimeclass OverviewPane : Windows.UI.Xaml.Controls.UserControl
{
OverviewPane();
void UpdateTabContent(Windows.Foundation.Collections.IVector<Tab> tabs, Int32 focusedIndex);
void ClearTabContent();
Int32 SelectedIndex;
Boolean UseMica;
String ControlName { get; };
event Windows.Foundation.TypedEventHandler<Object, Windows.Foundation.IReference<Int32> > TabSelected;
event Windows.Foundation.TypedEventHandler<Object, Object> Dismissed;
}
}

View File

@@ -0,0 +1,90 @@
<!--
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
the MIT License. See LICENSE in the project root for license information.
-->
<UserControl x:Class="TerminalApp.OverviewPane"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:TerminalApp"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
AllowFocusOnInteraction="True"
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
IsTabStop="True"
KeyDown="_OnKeyDown"
PreviewKeyDown="_OnPreviewKeyDown"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<Storyboard x:Key="BackgroundFadeIn">
<DoubleAnimation Storyboard.TargetName="BackgroundOverlay"
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<Storyboard x:Key="BackgroundFadeOut">
<DoubleAnimation Storyboard.TargetName="BackgroundOverlay"
Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.0"
Duration="0:0:0.15">
<DoubleAnimation.EasingFunction>
<QuadraticEase EasingMode="EaseIn" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</ResourceDictionary>
</UserControl.Resources>
<Grid x:Name="RootGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- Semi-transparent dark overlay -->
<Border x:Name="BackgroundOverlay"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{ThemeResource SmokeFillColorDefaultBrush}" />
<!-- Zoom-animated wrapper around the scrollable content -->
<Grid x:Name="ContentWrapper"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
RenderTransformOrigin="0.5,0.5">
<Grid.RenderTransform>
<CompositeTransform x:Name="ContentTransform" />
</Grid.RenderTransform>
<ScrollViewer x:Name="PreviewScrollViewer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<!-- WrapGrid-style layout using ItemsControl with WrapGrid panel -->
<ItemsControl x:Name="PreviewGrid"
Margin="12"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!-- ItemWidth, ItemHeight, and MaximumRowsOrColumns are owned by -->
<!-- OverviewPane.cpp (_ComputeAdaptiveLayout / _ApplyAdaptiveLayout) — -->
<!-- they are reassigned on every UpdateTabContent and on every resize. -->
<WrapGrid HorizontalChildrenAlignment="Center"
Orientation="Horizontal"
VerticalChildrenAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
</Grid>
</UserControl>

View File

@@ -606,6 +606,10 @@
<data name="CommandPaletteControlName" xml:space="preserve">
<value>Command palette</value>
</data>
<data name="OverviewPaneControlName" xml:space="preserve">
<value>Tab Overview</value>
<comment>The name announced by assistive technologies (e.g. Narrator) when the tab overview pane receives focus.</comment>
</data>
<data name="TabSwitcherControlName" xml:space="preserve">
<value>Tab switcher</value>
</data>

View File

@@ -1065,6 +1065,7 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_UpdatedSelectedTab(const winrt::TerminalApp::Tab& tab)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _UpdatedSelectedTab enter, tab={}\n"), static_cast<bool>(tab)).c_str());
// Unfocus all the tabs.
for (const auto& tab : _tabs)
{
@@ -1074,7 +1075,12 @@ namespace winrt::TerminalApp::implementation
try
{
_tabContent.Children().Clear();
auto content = tab ? tab.Content() : nullptr;
const bool hasContent = static_cast<bool>(content);
const bool hasParent = hasContent && static_cast<bool>(WUX::Media::VisualTreeHelper::GetParent(content));
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _UpdatedSelectedTab: about to Append, hasContent={} hasParent={}\n"), hasContent, hasParent).c_str());
_tabContent.Children().Append(tab.Content());
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: Append succeeded\n");
// GH#7409: If the tab switcher is open, then we _don't_ want to
// automatically focus the new tab here. The tab switcher wants
@@ -1089,6 +1095,7 @@ namespace winrt::TerminalApp::implementation
const auto p = CommandPaletteElement();
if (!p || p.Visibility() != Visibility::Visible)
{
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: calling tab.Focus(Programmatic)\n");
tab.Focus(FocusState::Programmatic);
_UpdateMRUTab(tab);
_updateAllTabCloseButtons();
@@ -1109,8 +1116,10 @@ namespace winrt::TerminalApp::implementation
}
_adjustProcessPriorityThrottled->Run();
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: try-block finished cleanly\n");
}
CATCH_LOG();
OutputDebugStringW(L"[OV] _UpdatedSelectedTab: exiting\n");
}
void TerminalPage::_UpdateBackground(const winrt::Microsoft::Terminal::Settings::Model::Profile& profile)
@@ -1129,10 +1138,19 @@ namespace winrt::TerminalApp::implementation
// - eventArgs: the event's constituent arguments
void TerminalPage::_OnTabSelectionChanged(const IInspectable& sender, const WUX::Controls::SelectionChangedEventArgs& /*eventArgs*/)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _OnTabSelectionChanged: _rearranging={} _removing={} _isInOverviewMode={}\n"), _rearranging, _removing, _isInOverviewMode).c_str());
if (!_rearranging && !_removing)
{
// If the user clicked a tab in the tab row while the overview is
// open, the overview is still holding that tab's Content reparented
// into one of its preview cells. Tear down the overview visuals
// first so _UpdatedSelectedTab can mount the content into the
// active content area without hitting the XAML single-parent rule.
_DismissOverviewVisuals();
auto tabView = sender.as<MUX::Controls::TabView>();
auto selectedIndex = tabView.SelectedIndex();
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _OnTabSelectionChanged: tabView.SelectedIndex={}\n"), selectedIndex).c_str());
if (selectedIndex >= 0 && selectedIndex < gsl::narrow_cast<int32_t>(_tabs.Size()))
{
const auto tab{ _tabs.GetAt(selectedIndex) };

View File

@@ -71,6 +71,9 @@
<Page Include="CommandPalette.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="OverviewPane.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="SuggestionsControl.xaml">
<SubType>Designer</SubType>
</Page>
@@ -132,6 +135,9 @@
<ClInclude Include="CommandPalette.h">
<DependentUpon>CommandPalette.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="OverviewPane.h">
<DependentUpon>OverviewPane.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="FilteredCommand.h" />
<ClInclude Include="Pane.h" />
<ClInclude Include="../fzf/fzf.h" />
@@ -243,6 +249,9 @@
<ClCompile Include="CommandPalette.cpp">
<DependentUpon>CommandPalette.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="OverviewPane.cpp">
<DependentUpon>OverviewPane.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="FilteredCommand.cpp" />
<ClCompile Include="Pane.cpp" />
<ClCompile Include="Pane.LayoutSizeNode.cpp" />
@@ -354,6 +363,10 @@
<DependentUpon>CommandPalette.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="OverviewPane.idl">
<DependentUpon>OverviewPane.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="SuggestionsControl.idl">
<DependentUpon>SuggestionsControl.xaml</DependentUpon>
<SubType>Code</SubType>

View File

@@ -1790,6 +1790,7 @@ namespace winrt::TerminalApp::implementation
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
const auto modifiers = _GetPressedModifierKeys();
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_KeyDownHandler vkey={} handled={} overviewMode={}\n"), static_cast<uint32_t>(vkey), e.Handled(), _isInOverviewMode).c_str());
// GH#11076:
// For some weird reason we sometimes receive a WM_KEYDOWN
@@ -1835,8 +1836,22 @@ namespace winrt::TerminalApp::implementation
});
if (!cmd)
{
OutputDebugStringW(L"[OV] TP::_KeyDownHandler: no cmd bound, returning\n");
return;
}
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_KeyDownHandler: cmd found, action={}\n"), static_cast<uint32_t>(cmd.ActionAndArgs().Action())).c_str());
// If the overview pane is open, most actions mutate tab state whose
// Content() is currently reparented under the overview. Tear down the
// overview's visuals first so the content is back under its original
// parent before the action runs. ToggleOverview is the exception — it
// handles its own exit.
if (_isInOverviewMode &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleOverview)
{
OutputDebugStringW(L"[OV] TP::_KeyDownHandler: dismissing overview before action dispatch\n");
_DismissOverviewVisuals();
}
if (!_actionDispatch->DoAction(cmd.ActionAndArgs()))
{
@@ -4269,6 +4284,128 @@ namespace winrt::TerminalApp::implementation
AlwaysOnTopChanged.raise(*this, nullptr);
}
void TerminalPage::ToggleOverview()
{
if (_isInOverviewMode)
{
_ExitOverview(std::nullopt);
}
else
{
_EnterOverview();
}
}
void TerminalPage::_EnterOverview()
{
_isInOverviewMode = true;
// Use FindName to lazily load the OverviewPane (same pattern as CommandPalette)
auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>();
if (!overview)
{
return;
}
const auto focusedIdx = _GetFocusedTabIndex();
const auto idx = focusedIdx.has_value() ? static_cast<int32_t>(focusedIdx.value()) : 0;
// Wire up events to handle tab selection and dismissal
_overviewTabSelectedToken = overview.TabSelected([weakThis = get_weak()](auto&&, const auto& args) {
if (auto self = weakThis.get())
{
if (args)
{
self->_ExitOverview(static_cast<uint32_t>(args.Value()));
}
}
});
_overviewDismissedToken = overview.Dismissed([weakThis = get_weak()](auto&&, auto&&) {
if (auto self = weakThis.get())
{
self->_ExitOverview(std::nullopt);
}
});
// _tabs is already IObservableVector<Tab>, which inherits from IVector<Tab>
const auto theme = _settings.GlobalSettings().CurrentTheme();
const auto windowTheme = theme ? theme.Window() : nullptr;
overview.UseMica(windowTheme ? windowTheme.UseMica() : false);
overview.UpdateTabContent(_tabs, idx);
overview.Visibility(WUX::Visibility::Visible);
overview.Focus(WUX::FocusState::Programmatic);
}
void TerminalPage::_ExitOverview(const std::optional<uint32_t>& selectedIndex)
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview enter, selectedIndex.has_value={} value={}\n"), selectedIndex.has_value(), selectedIndex.value_or(0xFFFFFFFFu)).c_str());
auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>();
// Determine which tab to switch to. Prefer the explicitly passed index
// (from TabSelected event), but fall back to the overview pane's
// current selection — this covers ToggleOverview and Dismiss paths so
// the user always lands on whichever tab they navigated to.
std::optional<uint32_t> tabToSelect = selectedIndex;
if (!tabToSelect.has_value() && overview)
{
const auto overviewIdx = overview.SelectedIndex();
if (overviewIdx >= 0 && overviewIdx < static_cast<int32_t>(_tabs.Size()))
{
tabToSelect = static_cast<uint32_t>(overviewIdx);
}
}
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview before _DismissOverviewVisuals, tabToSelect.has_value={} value={}\n"), tabToSelect.has_value(), tabToSelect.value_or(0xFFFFFFFFu)).c_str());
_DismissOverviewVisuals();
if (tabToSelect.has_value())
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] TP::_ExitOverview calling _SelectTab({})\n"), tabToSelect.value()).c_str());
_SelectTab(tabToSelect.value());
}
OutputDebugStringW(L"[OV] TP::_ExitOverview calling _UpdatedSelectedTab(_GetFocusedTab())\n");
_UpdatedSelectedTab(_GetFocusedTab());
OutputDebugStringW(L"[OV] TP::_ExitOverview returning\n");
}
// Method Description:
// - Tears down the overview's reparented content and hides the overlay,
// without changing tab selection. Safe to call when not in overview mode.
// - Used by both _ExitOverview (which then selects a tab) and by
// _OnTabSelectionChanged (where the TabView has already updated the
// selection and we just need to release the reparented content before
// _UpdatedSelectedTab tries to mount it back into the content area).
void TerminalPage::_DismissOverviewVisuals()
{
OutputDebugStringW(fmt::format(FMT_COMPILE(L"[OV] _DismissOverviewVisuals enter, _isInOverviewMode={}\n"), _isInOverviewMode).c_str());
if (!_isInOverviewMode)
{
return;
}
if (auto overview = FindName(L"OverviewPaneElement").try_as<OverviewPane>())
{
// Revoke event handlers to avoid stale callbacks
overview.TabSelected(_overviewTabSelectedToken);
overview.Dismissed(_overviewDismissedToken);
_overviewTabSelectedToken = {};
_overviewDismissedToken = {};
OutputDebugStringW(L"[OV] _DismissOverviewVisuals calling overview.ClearTabContent()\n");
overview.ClearTabContent();
overview.Visibility(WUX::Visibility::Collapsed);
}
_isInOverviewMode = false;
OutputDebugStringW(L"[OV] _DismissOverviewVisuals exit\n");
}
bool TerminalPage::OverviewMode() const
{
return _isInOverviewMode;
}
// Method Description:
// - Sets the tab split button color when a new tab color is selected
// Arguments:

View File

@@ -138,9 +138,11 @@ namespace winrt::TerminalApp::implementation
void ToggleFocusMode();
void ToggleFullscreen();
void ToggleAlwaysOnTop();
void ToggleOverview();
bool FocusMode() const;
bool Fullscreen() const;
bool AlwaysOnTop() const;
bool OverviewMode() const;
bool ShowTabsFullscreen() const;
void SetShowTabsFullscreen(bool newShowTabsFullscreen);
void SetFullscreen(bool);
@@ -250,6 +252,9 @@ namespace winrt::TerminalApp::implementation
TerminalApp::Tab _settingsTab{ nullptr };
bool _isInFocusMode{ false };
bool _isInOverviewMode{ false };
winrt::event_token _overviewTabSelectedToken{};
winrt::event_token _overviewDismissedToken{};
bool _isFullscreen{ false };
bool _isMaximized{ false };
bool _isAlwaysOnTop{ false };
@@ -375,6 +380,9 @@ namespace winrt::TerminalApp::implementation
void _SelectNextTab(const bool bMoveRight, const Windows::Foundation::IReference<Microsoft::Terminal::Settings::Model::TabSwitcherMode>& customTabSwitcherMode);
bool _SelectTab(uint32_t tabIndex);
void _EnterOverview();
void _ExitOverview(const std::optional<uint32_t>& selectedIndex);
void _DismissOverviewVisuals();
bool _MoveFocus(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const Microsoft::Terminal::Settings::Model::MovePaneArgs args);

View File

@@ -61,6 +61,7 @@ namespace TerminalApp
Boolean FocusMode { get; };
Boolean Fullscreen { get; };
Boolean AlwaysOnTop { get; };
Boolean OverviewMode { get; };
WindowProperties WindowProperties { get; };
void IdentifyWindow();

View File

@@ -155,6 +155,12 @@
</TextBlock>
</ContentDialog>
<local:OverviewPane x:Name="OverviewPaneElement"
Grid.Row="2"
x:Load="False"
PreviewKeyDown="_KeyDownHandler"
Visibility="Collapsed" />
<local:CommandPalette x:Name="CommandPaletteElement"
Grid.Row="2"
VerticalAlignment="Stretch"

View File

@@ -71,18 +71,6 @@ namespace winrt::TerminalApp::implementation
_removeControlEvents();
_control.Close();
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
}
winrt::hstring TerminalPaneContent::Icon() const
@@ -275,14 +263,15 @@ namespace winrt::TerminalApp::implementation
auto sounds{ _profile.BellSound() };
if (sounds && sounds.Size() > 0)
{
winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
winrt::Windows::Foundation::Uri uri{ soundPath };
_playBellSound(uri);
// Sound paths are resolved and validated by CascadiaSettings
// before we reach this point.
auto soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
PlaySoundW(soundPath.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_SENTRY | SND_NODEFAULT);
}
else
{
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
const auto soundAlias = reinterpret_cast<LPCWSTR>(SND_ALIAS_SYSTEMHAND);
PlaySoundW(soundAlias, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
}
}
@@ -300,33 +289,6 @@ namespace winrt::TerminalApp::implementation
}
}
safe_void_coroutine TerminalPaneContent::_playBellSound(winrt::Windows::Foundation::Uri uri)
{
auto weakThis{ get_weak() };
co_await wil::resume_foreground(_control.Dispatcher());
if (auto pane{ weakThis.get() })
{
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
if (_bellPlayer)
{
const auto source{ winrt::Windows::Media::Core::MediaSource::CreateFromUri(uri) };
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
}
}
}
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{

View File

@@ -76,9 +76,6 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<TerminalSettingsCache> _cache{};
bool _isDefTermSession{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
bool _bellPlayerCreated{ false };
struct ControlEventTokens
{
winrt::Microsoft::Terminal::Control::TermControl::ConnectionStateChanged_revoker _ConnectionStateChanged;
@@ -96,8 +93,6 @@ namespace winrt::TerminalApp::implementation
void _setupControlEvents();
void _removeControlEvents();
safe_void_coroutine _playBellSound(winrt::Windows::Foundation::Uri uri);
safe_void_coroutine _controlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _controlWarningBellHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& e);

View File

@@ -925,7 +925,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Manually turn off acrylic if they turn off transparency.
_runtimeUseAcrylic = _settings.Opacity() < 1.0 && _settings.UseAcrylic();
const auto sizeChanged = _setFontSizeUnderLock(_settings.FontSize());
const auto sizeChanged = _setFontSizeUnderLock(_settings.FontSize() + _accumulatedFontSizeDelta);
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(_settings);
@@ -1163,11 +1163,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - none
void ControlCore::ResetFontSize()
{
const auto lock = _terminal->LockForWriting();
if (_setFontSizeUnderLock(_settings.FontSize()))
if (std::exchange(_accumulatedFontSizeDelta, 0.f) != 0.f)
{
_refreshSizeUnderLock();
// No point in doing this if there was no delta.
AdjustFontSize(0);
}
}
@@ -1177,9 +1176,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - fontSizeDelta: The amount to increase or decrease the font size by.
void ControlCore::AdjustFontSize(float fontSizeDelta)
{
_accumulatedFontSizeDelta += fontSizeDelta;
const auto lock = _terminal->LockForWriting();
if (_setFontSizeUnderLock(_desiredFont.GetFontSize() + fontSizeDelta))
if (_setFontSizeUnderLock(_settings.FontSize() + _accumulatedFontSizeDelta))
{
_refreshSizeUnderLock();
}

View File

@@ -391,6 +391,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool _colorGlyphs = true;
CSSLengthPercentage _cellWidth;
CSSLengthPercentage _cellHeight;
float _accumulatedFontSizeDelta = 0.f; // Preserved across reloads to prevent user zoom from being overwritten.
// Rendering stuff.
winrt::handle _lastSwapChainHandle{ nullptr };

View File

@@ -2906,6 +2906,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else
{
// Yes, this is reachable: when a pane is split, the new TermControl
// is added to the XAML tree before its Loaded event fires, so
// _initializedTerminal is still false when layout queries MinimumSize().
// Return a small fallback — the real size will be used once initialized.
return { 10, 10 };
}
}

View File

@@ -57,6 +57,7 @@ static constexpr std::string_view SuggestionsKey{ "showSuggestions" };
static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" };
static constexpr std::string_view SetFocusModeKey{ "setFocusMode" };
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
static constexpr std::string_view ToggleOverviewKey{ "toggleOverview" };
static constexpr std::string_view SetFullScreenKey{ "setFullScreen" };
static constexpr std::string_view SetMaximizedKey{ "setMaximized" };
static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" };

View File

@@ -59,6 +59,7 @@
ON_ALL_ACTIONS(ToggleShaderEffects) \
ON_ALL_ACTIONS(ToggleFocusMode) \
ON_ALL_ACTIONS(ToggleFullscreen) \
ON_ALL_ACTIONS(ToggleOverview) \
ON_ALL_ACTIONS(ToggleAlwaysOnTop) \
ON_ALL_ACTIONS(OpenSettings) \
ON_ALL_ACTIONS(SetFocusMode) \

View File

@@ -466,6 +466,9 @@
<value>Toggle focus mode</value>
<comment>"Focus mode" is a mode with minimal UI elements, for a distraction-free experience</comment>
</data>
<data name="ToggleOverviewCommandKey" xml:space="preserve">
<value>Toggle overview mode</value>
</data>
<data name="EnableFocusModeCommandKey" xml:space="preserve">
<value>Enable focus mode</value>
</data>

View File

@@ -517,6 +517,7 @@
{ "command": "closeWindow", "id": "Terminal.CloseWindow" },
{ "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" },
{ "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" },
{ "command": "toggleOverview", "id": "Terminal.ToggleOverview" },
{ "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" },
{ "command": "openNewTabDropdown", "id": "Terminal.OpenNewTabDropdown" },
{ "command": { "action": "openSettings", "target": "settingsUI" }, "id": "Terminal.OpenSettingsUI" },