mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-20 13:57:43 +00:00
Compare commits
17 Commits
niels9001/
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c47cf8fb | ||
|
|
906c1fe15a | ||
|
|
c67adbdedb | ||
|
|
134a2d5d39 | ||
|
|
47950d2753 | ||
|
|
d3c17f9d6f | ||
|
|
451bf37008 | ||
|
|
5a3ae31453 | ||
|
|
3dd5dadb38 | ||
|
|
724c519788 | ||
|
|
3cc213a2c1 | ||
|
|
0afdc93bef | ||
|
|
6f384c59f4 | ||
|
|
66a828bc02 | ||
|
|
e19e710e03 | ||
|
|
54604076b9 | ||
|
|
ceef4d46ed |
2
.github/actions/spelling/expect/expect.txt
vendored
2
.github/actions/spelling/expect/expect.txt
vendored
@@ -1075,7 +1075,6 @@ NOCONTEXTHELP
|
||||
NOCOPYBITS
|
||||
nodiscard
|
||||
NODUP
|
||||
NODEFAULT
|
||||
noexcepts
|
||||
NOFONT
|
||||
NOHIDDENTEXT
|
||||
@@ -1562,7 +1561,6 @@ SMARTQUOTE
|
||||
SMTO
|
||||
snapcx
|
||||
snapcy
|
||||
SND
|
||||
snk
|
||||
SOLIDBOX
|
||||
Solutiondir
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -344,7 +344,7 @@
|
||||
<TextBlock x:Name="_parentCommandText"
|
||||
Padding="16,4"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind ParentCommandName, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -358,8 +358,8 @@
|
||||
|
||||
<ScrollViewer MaxHeight="200"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<TextBlock Text="{x:Bind ParsedCommandLineText, Mode=OneWay}"
|
||||
TextAlignment="Left"
|
||||
<TextBlock FontStyle="Italic"
|
||||
Text="{x:Bind ParsedCommandLineText, Mode=OneWay}"
|
||||
TextWrapping="Wrap" />
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
@@ -371,6 +371,7 @@
|
||||
Visibility="Collapsed">
|
||||
<TextBlock Padding="12,0"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind NoMatchesText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
|
||||
@@ -152,8 +152,8 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default style: semibold
|
||||
run.FontWeight(FontWeights::SemiBold());
|
||||
// Default style: bold
|
||||
run.FontWeight(FontWeights::Bold());
|
||||
}
|
||||
inlinesCollection.Append(run);
|
||||
|
||||
|
||||
690
src/cascadia/TerminalApp/OverviewPane.cpp
Normal file
690
src/cascadia/TerminalApp/OverviewPane.cpp
Normal file
@@ -0,0 +1,690 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "OverviewPane.h"
|
||||
|
||||
#include "OverviewPane.g.cpp"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml::Input;
|
||||
using namespace winrt::Windows::UI::Xaml::Media;
|
||||
using namespace winrt::Windows::UI::Xaml::Media::Animation;
|
||||
using namespace winrt::Windows::System;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
static constexpr double PreviewCellWidth = 320.0;
|
||||
static constexpr double PreviewCellHeight = 220.0;
|
||||
static constexpr std::chrono::milliseconds EnterAnimDuration{ 400 };
|
||||
static constexpr std::chrono::milliseconds ExitAnimDuration{ 100 };
|
||||
|
||||
OverviewPane::OverviewPane()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Apply the initial background (opaque by default, since _useMica
|
||||
// starts false). Without this the XAML-declared brush stays in place
|
||||
// until someone explicitly calls UseMica().
|
||||
_UpdateBackgroundForMica();
|
||||
|
||||
// Listen for layout passes so we can start the zoom-in animation
|
||||
// once the WrapGrid has measured and positioned its children.
|
||||
PreviewGrid().LayoutUpdated([weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
if (!self->_pendingEnterAnimation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that at least the first cell is laid out
|
||||
auto items = self->PreviewGrid().Items();
|
||||
if (items.Size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto first = items.GetAt(0).try_as<FrameworkElement>();
|
||||
if (!first || first.ActualWidth() <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
self->_pendingEnterAnimation = false;
|
||||
self->_StartEnterZoomAnimation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
OverviewPane::~OverviewPane()
|
||||
{
|
||||
ClearTabContent();
|
||||
}
|
||||
|
||||
void OverviewPane::UpdateTabContent(Windows::Foundation::Collections::IVector<TerminalApp::Tab> tabs, int32_t focusedIndex)
|
||||
{
|
||||
// Clear any previous state
|
||||
ClearTabContent();
|
||||
|
||||
if (!tabs || tabs.Size() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto itemsControl = PreviewGrid();
|
||||
|
||||
// Determine a reference size from the currently visible tab's content.
|
||||
// Inactive tabs have zero ActualWidth/Height since they're not laid out
|
||||
// in the visual tree, but all tabs share the same content area, so the
|
||||
// active tab's size is the right reference for all of them.
|
||||
double referenceWidth = 0;
|
||||
double referenceHeight = 0;
|
||||
if (focusedIndex >= 0 && focusedIndex < static_cast<int32_t>(tabs.Size()))
|
||||
{
|
||||
auto focusedContent = tabs.GetAt(static_cast<uint32_t>(focusedIndex)).Content();
|
||||
if (focusedContent)
|
||||
{
|
||||
referenceWidth = focusedContent.ActualWidth();
|
||||
referenceHeight = focusedContent.ActualHeight();
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < tabs.Size(); i++)
|
||||
{
|
||||
const auto& tab = tabs.GetAt(i);
|
||||
auto cell = _BuildPreviewCell(tab, static_cast<int32_t>(i), referenceWidth, referenceHeight);
|
||||
itemsControl.Items().Append(cell);
|
||||
}
|
||||
|
||||
_selectedIndex = focusedIndex;
|
||||
_UpdateSelection();
|
||||
_PlayEnterAnimation();
|
||||
|
||||
// Focus self for keyboard input
|
||||
Focus(FocusState::Programmatic);
|
||||
}
|
||||
|
||||
void OverviewPane::ClearTabContent()
|
||||
{
|
||||
_pendingEnterAnimation = false;
|
||||
|
||||
// Stop any running exit animation
|
||||
if (_exitContentStoryboard)
|
||||
{
|
||||
_exitContentStoryboard.Completed(_exitAnimationToken);
|
||||
_exitAnimationToken = {};
|
||||
_exitContentStoryboard.Stop();
|
||||
_exitContentStoryboard = nullptr;
|
||||
}
|
||||
|
||||
// Reset the zoom transform to identity
|
||||
auto transform = ContentTransform();
|
||||
transform.ScaleX(1.0);
|
||||
transform.ScaleY(1.0);
|
||||
transform.TranslateX(0.0);
|
||||
transform.TranslateY(0.0);
|
||||
ContentWrapper().Opacity(1.0);
|
||||
|
||||
// Reparent content back to original parents
|
||||
for (auto& entry : _reparentedContent)
|
||||
{
|
||||
if (entry.content)
|
||||
{
|
||||
// Restore original Width/Height (NaN = auto-sizing)
|
||||
entry.content.Width(entry.originalWidth);
|
||||
entry.content.Height(entry.originalHeight);
|
||||
|
||||
// Restore original RenderTransform and origin
|
||||
entry.content.RenderTransform(entry.originalRenderTransform);
|
||||
entry.content.RenderTransformOrigin(entry.originalRenderTransformOrigin);
|
||||
|
||||
_DetachContent(entry.content);
|
||||
|
||||
// Put it back where it came from
|
||||
if (entry.originalParent)
|
||||
{
|
||||
entry.originalParent.Children().Append(entry.content);
|
||||
}
|
||||
}
|
||||
}
|
||||
_reparentedContent.clear();
|
||||
|
||||
const auto itemsControl = PreviewGrid();
|
||||
itemsControl.Items().Clear();
|
||||
}
|
||||
|
||||
int32_t OverviewPane::SelectedIndex() const
|
||||
{
|
||||
return _selectedIndex;
|
||||
}
|
||||
|
||||
void OverviewPane::SelectedIndex(int32_t value)
|
||||
{
|
||||
if (_selectedIndex != value)
|
||||
{
|
||||
_selectedIndex = value;
|
||||
_UpdateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
bool OverviewPane::UseMica() const
|
||||
{
|
||||
return _useMica;
|
||||
}
|
||||
|
||||
void OverviewPane::UseMica(bool value)
|
||||
{
|
||||
if (_useMica != value)
|
||||
{
|
||||
_useMica = value;
|
||||
_UpdateBackgroundForMica();
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPane::_UpdateBackgroundForMica()
|
||||
{
|
||||
auto overlay = BackgroundOverlay();
|
||||
if (_useMica)
|
||||
{
|
||||
// Transparent background — let the Mica backdrop show through
|
||||
overlay.Background(SolidColorBrush{ winrt::Windows::UI::Colors::Transparent() });
|
||||
}
|
||||
else
|
||||
{
|
||||
// Opaque background when Mica is not active.
|
||||
// Use the theme-aware SolidBackgroundFillColorBaseBrush so we
|
||||
// match the correct color for light / dark theme.
|
||||
auto res = Application::Current().Resources();
|
||||
auto brush = res.TryLookup(winrt::box_value(L"SolidBackgroundFillColorBaseBrush"));
|
||||
if (brush)
|
||||
{
|
||||
overlay.Background(brush.as<Brush>());
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.Background(SolidColorBrush{ winrt::Windows::UI::ColorHelper::FromArgb(255, 32, 32, 32) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPane::_OnPreviewKeyDown(const IInspectable& /*sender*/, const KeyRoutedEventArgs& e)
|
||||
{
|
||||
if (e.OriginalKey() != VirtualKey::Tab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto items = PreviewGrid().Items();
|
||||
const auto itemCount = static_cast<int32_t>(items.Size());
|
||||
if (itemCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto shiftPressed = (Windows::UI::Core::CoreWindow::GetForCurrentThread().GetKeyState(VirtualKey::Shift) & Windows::UI::Core::CoreVirtualKeyStates::Down) == Windows::UI::Core::CoreVirtualKeyStates::Down;
|
||||
|
||||
if (shiftPressed)
|
||||
{
|
||||
if (_selectedIndex > 0)
|
||||
{
|
||||
_selectedIndex--;
|
||||
_UpdateSelection();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_selectedIndex < itemCount - 1)
|
||||
{
|
||||
_selectedIndex++;
|
||||
_UpdateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
void OverviewPane::_OnKeyDown(const IInspectable& /*sender*/, const KeyRoutedEventArgs& e)
|
||||
{
|
||||
const auto items = PreviewGrid().Items();
|
||||
const auto itemCount = static_cast<int32_t>(items.Size());
|
||||
if (itemCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto handled = true;
|
||||
switch (e.OriginalKey())
|
||||
{
|
||||
case VirtualKey::Left:
|
||||
if (_selectedIndex > 0)
|
||||
{
|
||||
_selectedIndex--;
|
||||
_UpdateSelection();
|
||||
}
|
||||
break;
|
||||
case VirtualKey::Right:
|
||||
if (_selectedIndex < itemCount - 1)
|
||||
{
|
||||
_selectedIndex++;
|
||||
_UpdateSelection();
|
||||
}
|
||||
break;
|
||||
case VirtualKey::Up:
|
||||
if (_selectedIndex - _columnCount >= 0)
|
||||
{
|
||||
_selectedIndex -= _columnCount;
|
||||
_UpdateSelection();
|
||||
}
|
||||
break;
|
||||
case VirtualKey::Down:
|
||||
if (_selectedIndex + _columnCount < itemCount)
|
||||
{
|
||||
_selectedIndex += _columnCount;
|
||||
_UpdateSelection();
|
||||
}
|
||||
break;
|
||||
case VirtualKey::Enter:
|
||||
_OnItemClicked(_selectedIndex);
|
||||
break;
|
||||
case VirtualKey::Escape:
|
||||
_PlayExitAnimation([weakThis = get_weak()]() {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
self->Dismissed.raise(*self, nullptr);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
e.Handled(handled);
|
||||
}
|
||||
|
||||
void OverviewPane::_OnItemClicked(int32_t index)
|
||||
{
|
||||
_PlayExitAnimation([weakThis = get_weak(), index]() {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
self->TabSelected.raise(*self, winrt::Windows::Foundation::IReference<int32_t>{ index });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OverviewPane::_UpdateSelection()
|
||||
{
|
||||
const auto items = PreviewGrid().Items();
|
||||
const auto itemCount = static_cast<int32_t>(items.Size());
|
||||
|
||||
// Clamp selection
|
||||
_selectedIndex = std::clamp(_selectedIndex, 0, std::max(0, itemCount - 1));
|
||||
|
||||
for (int32_t i = 0; i < itemCount; i++)
|
||||
{
|
||||
if (auto cellElement = items.GetAt(i).try_as<FrameworkElement>())
|
||||
{
|
||||
if (auto border = cellElement.try_as<Border>())
|
||||
{
|
||||
if (i == _selectedIndex)
|
||||
{
|
||||
// Accent-colored border for selected item
|
||||
const auto accentBrush = Application::Current()
|
||||
.Resources()
|
||||
.Lookup(winrt::box_value(L"SystemAccentColor"))
|
||||
.as<winrt::Windows::UI::Color>();
|
||||
border.BorderBrush(SolidColorBrush{ accentBrush });
|
||||
border.BorderThickness(ThicknessHelper::FromUniformLength(2));
|
||||
|
||||
// Scroll into view if needed
|
||||
border.StartBringIntoView();
|
||||
}
|
||||
else
|
||||
{
|
||||
border.BorderBrush(SolidColorBrush{ winrt::Windows::UI::Colors::Transparent() });
|
||||
border.BorderThickness(ThicknessHelper::FromUniformLength(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OverviewPane::_PlayEnterAnimation()
|
||||
{
|
||||
// Hide the content wrapper until the LayoutUpdated callback fires
|
||||
// and we can read cell positions to set up the zoom transform.
|
||||
ContentWrapper().Opacity(0);
|
||||
_pendingEnterAnimation = true;
|
||||
}
|
||||
|
||||
void OverviewPane::_StartEnterZoomAnimation()
|
||||
{
|
||||
// Start the background fade-in together with the zoom so both
|
||||
// animations are visible at the same moment (avoids opacity flash).
|
||||
if (auto bgSb = Resources().Lookup(winrt::box_value(L"BackgroundFadeIn")).try_as<Storyboard>())
|
||||
{
|
||||
bgSb.Begin();
|
||||
}
|
||||
|
||||
auto wrapper = ContentWrapper();
|
||||
auto transform = ContentTransform();
|
||||
|
||||
auto zoomParams = _GetZoomParamsForCell(_selectedIndex);
|
||||
if (!zoomParams)
|
||||
{
|
||||
wrapper.Opacity(1.0);
|
||||
return;
|
||||
}
|
||||
auto [scale, tx, ty] = *zoomParams;
|
||||
|
||||
// Set the initial transform so the focused cell fills the viewport
|
||||
transform.ScaleX(scale);
|
||||
transform.ScaleY(scale);
|
||||
transform.TranslateX(tx);
|
||||
transform.TranslateY(ty);
|
||||
wrapper.Opacity(1.0);
|
||||
|
||||
// Animate from the zoomed-in state to identity (zoom out to grid)
|
||||
Storyboard storyboard;
|
||||
const auto duration = DurationHelper::FromTimeSpan(EnterAnimDuration);
|
||||
CubicEase easing;
|
||||
easing.EasingMode(EasingMode::EaseOut);
|
||||
|
||||
_AddDoubleAnimation(storyboard, transform, L"ScaleX", scale, 1.0, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"ScaleY", scale, 1.0, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"TranslateX", tx, 0.0, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"TranslateY", ty, 0.0, duration, easing);
|
||||
|
||||
storyboard.Begin();
|
||||
}
|
||||
|
||||
void OverviewPane::_PlayExitAnimation(std::function<void()> onComplete)
|
||||
{
|
||||
// Fade out the background overlay
|
||||
if (auto bgSb = Resources().Lookup(winrt::box_value(L"BackgroundFadeOut")).try_as<Storyboard>())
|
||||
{
|
||||
bgSb.Begin();
|
||||
}
|
||||
|
||||
auto zoomParams = _GetZoomParamsForCell(_selectedIndex);
|
||||
if (!zoomParams)
|
||||
{
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete();
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto [scale, tx, ty] = *zoomParams;
|
||||
|
||||
// Animate from the current grid view into the selected cell
|
||||
auto transform = ContentTransform();
|
||||
Storyboard storyboard;
|
||||
const auto duration = DurationHelper::FromTimeSpan(ExitAnimDuration);
|
||||
CubicEase easing;
|
||||
easing.EasingMode(EasingMode::EaseIn);
|
||||
|
||||
_AddDoubleAnimation(storyboard, transform, L"ScaleX", 1.0, scale, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"ScaleY", 1.0, scale, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"TranslateX", 0.0, tx, duration, easing);
|
||||
_AddDoubleAnimation(storyboard, transform, L"TranslateY", 0.0, ty, duration, easing);
|
||||
|
||||
// Revoke any previously registered Completed handler
|
||||
if (_exitContentStoryboard)
|
||||
{
|
||||
_exitContentStoryboard.Completed(_exitAnimationToken);
|
||||
}
|
||||
_exitContentStoryboard = storyboard;
|
||||
_exitAnimationToken = storyboard.Completed([weakThis = get_weak(), onComplete](auto&&, auto&&) {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
if (onComplete)
|
||||
{
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
storyboard.Begin();
|
||||
}
|
||||
|
||||
std::optional<std::tuple<double, double, double>> OverviewPane::_GetZoomParamsForCell(int32_t index)
|
||||
{
|
||||
auto wrapper = ContentWrapper();
|
||||
const auto vpW = wrapper.ActualWidth();
|
||||
const auto vpH = wrapper.ActualHeight();
|
||||
|
||||
if (vpW <= 0 || vpH <= 0)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto items = PreviewGrid().Items();
|
||||
if (index < 0 || index >= static_cast<int32_t>(items.Size()))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto cell = items.GetAt(static_cast<uint32_t>(index)).try_as<FrameworkElement>();
|
||||
if (!cell || cell.ActualWidth() <= 0 || cell.ActualHeight() <= 0)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto cellW = cell.ActualWidth();
|
||||
const auto cellH = cell.ActualHeight();
|
||||
|
||||
// Cell center relative to ContentWrapper (accounts for scroll offset)
|
||||
auto cellTransform = cell.TransformToVisual(wrapper);
|
||||
auto topLeft = cellTransform.TransformPoint({ 0.0f, 0.0f });
|
||||
const auto cellCX = static_cast<double>(topLeft.X) + cellW / 2.0;
|
||||
const auto cellCY = static_cast<double>(topLeft.Y) + cellH / 2.0;
|
||||
|
||||
// Scale so the cell fits the viewport
|
||||
const auto scale = std::min(vpW / cellW, vpH / cellH);
|
||||
|
||||
// With RenderTransformOrigin={0.5,0.5} the scale origin is the
|
||||
// center of ContentWrapper. Translate so the cell center lands
|
||||
// at the viewport center after scaling.
|
||||
const auto translateX = (vpW / 2.0 - cellCX) * scale;
|
||||
const auto translateY = (vpH / 2.0 - cellCY) * scale;
|
||||
|
||||
return std::tuple{ scale, translateX, translateY };
|
||||
}
|
||||
|
||||
FrameworkElement OverviewPane::_BuildPreviewCell(const TerminalApp::Tab& tab, int32_t index, double referenceWidth, double referenceHeight)
|
||||
{
|
||||
// Outer border — serves as the selection indicator
|
||||
Border outerBorder;
|
||||
outerBorder.BorderBrush(SolidColorBrush{ winrt::Windows::UI::Colors::Transparent() });
|
||||
outerBorder.BorderThickness(ThicknessHelper::FromUniformLength(2));
|
||||
outerBorder.CornerRadius({ 8, 8, 8, 8 });
|
||||
outerBorder.Padding(ThicknessHelper::FromUniformLength(4));
|
||||
outerBorder.Margin(ThicknessHelper::FromUniformLength(6));
|
||||
|
||||
// Vertical stack: preview box + title
|
||||
StackPanel cellStack;
|
||||
cellStack.Orientation(Orientation::Vertical);
|
||||
cellStack.HorizontalAlignment(HorizontalAlignment::Center);
|
||||
|
||||
// Preview container with a dark background
|
||||
Border previewBorder;
|
||||
previewBorder.Width(PreviewCellWidth);
|
||||
previewBorder.Height(PreviewCellHeight);
|
||||
previewBorder.CornerRadius({ 6, 6, 6, 6 });
|
||||
auto bgBrush = Application::Current().Resources().TryLookup(winrt::box_value(L"SystemControlBackgroundChromeMediumBrush"));
|
||||
if (bgBrush)
|
||||
{
|
||||
previewBorder.Background(bgBrush.as<Brush>());
|
||||
}
|
||||
else
|
||||
{
|
||||
previewBorder.Background(SolidColorBrush{ winrt::Windows::UI::ColorHelper::FromArgb(255, 30, 30, 30) });
|
||||
}
|
||||
|
||||
// Get the tab's content and reparent it with a ScaleTransform
|
||||
auto tabContent = tab.Content();
|
||||
if (tabContent)
|
||||
{
|
||||
// Save the Width/Height *property* values (likely NaN for auto-sized
|
||||
// elements). These differ from ActualWidth/Height (the rendered size).
|
||||
// We need the property values to restore auto-sizing on exit.
|
||||
const auto origWidthProp = tabContent.Width();
|
||||
const auto origHeightProp = tabContent.Height();
|
||||
const auto origRenderTransform = tabContent.RenderTransform();
|
||||
const auto origRenderTransformOrigin = tabContent.RenderTransformOrigin();
|
||||
|
||||
// Use ActualWidth/Height if the content is currently laid out (active tab).
|
||||
// Inactive tabs aren't in the visual tree and report zero — fall back
|
||||
// to the reference size from the active tab's content area.
|
||||
auto layoutWidth = tabContent.ActualWidth();
|
||||
auto layoutHeight = tabContent.ActualHeight();
|
||||
if (layoutWidth <= 0 || layoutHeight <= 0)
|
||||
{
|
||||
layoutWidth = referenceWidth;
|
||||
layoutHeight = referenceHeight;
|
||||
}
|
||||
|
||||
auto originalParent = VisualTreeHelper::GetParent(tabContent).try_as<Panel>();
|
||||
|
||||
// XAML single-parent rule: remove from current parent first
|
||||
_DetachContent(tabContent);
|
||||
|
||||
// Lock the content to the layout size — this prevents
|
||||
// TermControl from seeing a resize and reflowing its buffer
|
||||
if (layoutWidth > 0 && layoutHeight > 0)
|
||||
{
|
||||
tabContent.Width(layoutWidth);
|
||||
tabContent.Height(layoutHeight);
|
||||
|
||||
// Calculate uniform scale to fit in preview
|
||||
const double previewWidth = PreviewCellWidth;
|
||||
const double previewHeight = PreviewCellHeight;
|
||||
const double scale = std::min(previewWidth / layoutWidth, previewHeight / layoutHeight);
|
||||
|
||||
// RenderTransform is applied AFTER layout — the content still
|
||||
// thinks it's at its original size
|
||||
ScaleTransform scaleTransform;
|
||||
scaleTransform.ScaleX(scale);
|
||||
scaleTransform.ScaleY(scale);
|
||||
tabContent.RenderTransform(scaleTransform);
|
||||
tabContent.RenderTransformOrigin({ 0.0f, 0.0f });
|
||||
|
||||
// Use a Canvas so the content is not constrained by the preview
|
||||
// container's layout. Canvas gives children infinite measure
|
||||
// space and arranges at desired size.
|
||||
Canvas canvas;
|
||||
canvas.Width(previewWidth);
|
||||
canvas.Height(previewHeight);
|
||||
|
||||
// Clip to preview bounds so the scaled content doesn't overflow
|
||||
RectangleGeometry clipGeometry;
|
||||
clipGeometry.Rect({ 0, 0, static_cast<float>(previewWidth), static_cast<float>(previewHeight) });
|
||||
canvas.Clip(clipGeometry);
|
||||
|
||||
canvas.Children().Append(tabContent);
|
||||
|
||||
// Layer the canvas behind a transparent overlay so
|
||||
// pointer events never reach the TermControl content.
|
||||
Grid previewGrid;
|
||||
previewGrid.Children().Append(canvas);
|
||||
|
||||
Border inputOverlay;
|
||||
inputOverlay.Background(SolidColorBrush{ winrt::Windows::UI::Colors::Transparent() });
|
||||
previewGrid.Children().Append(inputOverlay);
|
||||
|
||||
previewBorder.Child(previewGrid);
|
||||
}
|
||||
|
||||
_reparentedContent.push_back({ tabContent, originalParent, origWidthProp, origHeightProp, origRenderTransform, origRenderTransformOrigin });
|
||||
}
|
||||
|
||||
cellStack.Children().Append(previewBorder);
|
||||
|
||||
// Tab title text
|
||||
TextBlock titleBlock;
|
||||
titleBlock.Text(tab.Title());
|
||||
titleBlock.FontSize(14);
|
||||
titleBlock.Foreground(SolidColorBrush{ winrt::Windows::UI::Colors::White() });
|
||||
titleBlock.HorizontalAlignment(HorizontalAlignment::Center);
|
||||
titleBlock.TextTrimming(TextTrimming::CharacterEllipsis);
|
||||
titleBlock.MaxWidth(PreviewCellWidth);
|
||||
titleBlock.Margin({ 0, 6, 0, 0 });
|
||||
|
||||
cellStack.Children().Append(titleBlock);
|
||||
|
||||
outerBorder.Child(cellStack);
|
||||
|
||||
// Click handler
|
||||
outerBorder.PointerPressed([weakThis = get_weak(), index](auto&&, auto&&) {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
self->_selectedIndex = index;
|
||||
self->_UpdateSelection();
|
||||
self->_OnItemClicked(index);
|
||||
}
|
||||
});
|
||||
|
||||
// Hover effect
|
||||
outerBorder.PointerEntered([weakThis = get_weak(), index](auto&&, auto&&) {
|
||||
if (auto self = weakThis.get())
|
||||
{
|
||||
self->_selectedIndex = index;
|
||||
self->_UpdateSelection();
|
||||
}
|
||||
});
|
||||
|
||||
return outerBorder;
|
||||
}
|
||||
|
||||
void OverviewPane::_AddDoubleAnimation(
|
||||
const Storyboard& storyboard,
|
||||
const CompositeTransform& target,
|
||||
const hstring& property,
|
||||
double from,
|
||||
double to,
|
||||
const Duration& duration,
|
||||
const EasingFunctionBase& easing)
|
||||
{
|
||||
DoubleAnimation anim;
|
||||
anim.From(from);
|
||||
anim.To(to);
|
||||
anim.Duration(duration);
|
||||
anim.EasingFunction(easing);
|
||||
Storyboard::SetTarget(anim, target);
|
||||
Storyboard::SetTargetProperty(anim, property);
|
||||
storyboard.Children().Append(anim);
|
||||
}
|
||||
|
||||
void OverviewPane::_DetachContent(const FrameworkElement& content)
|
||||
{
|
||||
// Try removing from various XAML container types
|
||||
if (auto parent = VisualTreeHelper::GetParent(content))
|
||||
{
|
||||
if (auto panel = parent.try_as<Panel>())
|
||||
{
|
||||
uint32_t idx;
|
||||
if (panel.Children().IndexOf(content, idx))
|
||||
{
|
||||
panel.Children().RemoveAt(idx);
|
||||
}
|
||||
}
|
||||
else if (auto border = parent.try_as<Border>())
|
||||
{
|
||||
border.Child(nullptr);
|
||||
}
|
||||
else if (auto contentControl = parent.try_as<ContentControl>())
|
||||
{
|
||||
contentControl.Content(nullptr);
|
||||
}
|
||||
else if (auto viewbox = parent.try_as<Viewbox>())
|
||||
{
|
||||
viewbox.Child(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/cascadia/TerminalApp/OverviewPane.h
Normal file
77
src/cascadia/TerminalApp/OverviewPane.h
Normal file
@@ -0,0 +1,77 @@
|
||||
// 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);
|
||||
|
||||
// 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, double referenceWidth, double referenceHeight);
|
||||
void _DetachContent(const Windows::UI::Xaml::FrameworkElement& content);
|
||||
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{ 3 }; // must match WrapGrid MaximumRowsOrColumns in OverviewPane.xaml
|
||||
bool _pendingEnterAnimation{ false };
|
||||
bool _useMica{ false };
|
||||
void _UpdateBackgroundForMica();
|
||||
winrt::event_token _exitAnimationToken{};
|
||||
Windows::UI::Xaml::Media::Animation::Storyboard _exitContentStoryboard{ nullptr };
|
||||
|
||||
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 };
|
||||
};
|
||||
std::vector<ReparentedEntry> _reparentedContent;
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(OverviewPane);
|
||||
}
|
||||
19
src/cascadia/TerminalApp/OverviewPane.idl
Normal file
19
src/cascadia/TerminalApp/OverviewPane.idl
Normal file
@@ -0,0 +1,19 @@
|
||||
// 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;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.Foundation.IReference<Int32> > TabSelected;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> Dismissed;
|
||||
}
|
||||
}
|
||||
88
src/cascadia/TerminalApp/OverviewPane.xaml
Normal file
88
src/cascadia/TerminalApp/OverviewPane.xaml
Normal file
@@ -0,0 +1,88 @@
|
||||
<!--
|
||||
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"
|
||||
IsTabStop="True"
|
||||
KeyDown="_OnKeyDown"
|
||||
PreviewKeyDown="_OnPreviewKeyDown"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<Storyboard x:Key="BackgroundFadeIn">
|
||||
<DoubleAnimation Duration="0:0:0.2"
|
||||
Storyboard.TargetName="BackgroundOverlay"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="0.0"
|
||||
To="1.0">
|
||||
<DoubleAnimation.EasingFunction>
|
||||
<QuadraticEase EasingMode="EaseOut" />
|
||||
</DoubleAnimation.EasingFunction>
|
||||
</DoubleAnimation>
|
||||
</Storyboard>
|
||||
|
||||
<Storyboard x:Key="BackgroundFadeOut">
|
||||
<DoubleAnimation Duration="0:0:0.15"
|
||||
Storyboard.TargetName="BackgroundOverlay"
|
||||
Storyboard.TargetProperty="Opacity"
|
||||
From="1.0"
|
||||
To="0.0">
|
||||
<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"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="24">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<WrapGrid HorizontalChildrenAlignment="Left"
|
||||
ItemHeight="270"
|
||||
ItemWidth="332"
|
||||
MaximumRowsOrColumns="3"
|
||||
Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -443,13 +443,13 @@
|
||||
<value>Open a new tab</value>
|
||||
</data>
|
||||
<data name="NewPaneRun.Text" xml:space="preserve">
|
||||
<value>Alt + Click to split the current window</value>
|
||||
<value>Alt+Click to split the current window</value>
|
||||
</data>
|
||||
<data name="NewWindowRun.Text" xml:space="preserve">
|
||||
<value>Shift + Click to open a new window</value>
|
||||
<value>Shift+Click to open a new window</value>
|
||||
</data>
|
||||
<data name="ElevatedRun.Text" xml:space="preserve">
|
||||
<value>Ctrl + Click to open as administrator</value>
|
||||
<value>Ctrl+Click to open as administrator</value>
|
||||
</data>
|
||||
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||
<value>Close</value>
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
<TextBlock x:Name="_parentCommandText"
|
||||
Padding="16,4"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind ParentCommandName, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
@@ -179,6 +179,7 @@
|
||||
Visibility="Collapsed">
|
||||
<TextBlock Padding="12,0"
|
||||
VerticalAlignment="Center"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind NoMatchesText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
@@ -227,7 +228,7 @@
|
||||
Visibility="Collapsed">
|
||||
<TextBlock x:Name="_descriptionTitle"
|
||||
FontSize="14"
|
||||
FontWeight="SemiBold"
|
||||
FontWeight="Bold"
|
||||
IsTextSelectionEnabled="True"
|
||||
TextWrapping="WrapWholeWords">
|
||||
<TextBlock.ContextFlyout>
|
||||
|
||||
@@ -235,14 +235,14 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.TextWrapping(WUX::TextWrapping::Wrap);
|
||||
textBlock.TextAlignment(WUX::TextAlignment::Left);
|
||||
textBlock.TextAlignment(WUX::TextAlignment::Center);
|
||||
textBlock.Inlines().Append(titleRun);
|
||||
|
||||
if (!_keyChord.empty())
|
||||
{
|
||||
auto keyChordRun = WUX::Documents::Run();
|
||||
keyChordRun.Text(_keyChord);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
keyChordRun.FontStyle(winrt::Windows::UI::Text::FontStyle::Italic);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(keyChordRun);
|
||||
}
|
||||
|
||||
@@ -1131,6 +1131,13 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
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();
|
||||
if (selectedIndex >= 0 && selectedIndex < gsl::narrow_cast<int32_t>(_tabs.Size()))
|
||||
|
||||
@@ -71,9 +71,11 @@
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Placement="Mouse">
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
<Run x:Uid="NewTabRun" /> <LineBreak /><LineBreak />
|
||||
<Run x:Uid="NewPaneRun" /> <LineBreak />
|
||||
<Run x:Uid="NewWindowRun" />
|
||||
<Run x:Uid="NewTabRun" /> <LineBreak />
|
||||
<Run x:Uid="NewPaneRun"
|
||||
FontStyle="Italic" /> <LineBreak />
|
||||
<Run x:Uid="NewWindowRun"
|
||||
FontStyle="Italic" />
|
||||
</TextBlock>
|
||||
</ToolTip>
|
||||
</ToolTipService.ToolTip>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1295,15 +1295,17 @@ namespace winrt::TerminalApp::implementation
|
||||
newTabRun.Text(RS_(L"NewTabRun/Text"));
|
||||
auto newPaneRun = WUX::Documents::Run();
|
||||
newPaneRun.Text(RS_(L"NewPaneRun/Text"));
|
||||
newPaneRun.FontStyle(FontStyle::Italic);
|
||||
auto newWindowRun = WUX::Documents::Run();
|
||||
newWindowRun.Text(RS_(L"NewWindowRun/Text"));
|
||||
newWindowRun.FontStyle(FontStyle::Italic);
|
||||
auto elevatedRun = WUX::Documents::Run();
|
||||
elevatedRun.Text(RS_(L"ElevatedRun/Text"));
|
||||
elevatedRun.FontStyle(FontStyle::Italic);
|
||||
|
||||
auto textBlock = WUX::Controls::TextBlock{};
|
||||
textBlock.Inlines().Append(newTabRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newPaneRun);
|
||||
textBlock.Inlines().Append(WUX::Documents::LineBreak{});
|
||||
textBlock.Inlines().Append(newWindowRun);
|
||||
@@ -1836,6 +1838,17 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
_DismissOverviewVisuals();
|
||||
}
|
||||
|
||||
if (!_actionDispatch->DoAction(cmd.ActionAndArgs()))
|
||||
{
|
||||
return;
|
||||
@@ -4267,6 +4280,120 @@ 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
_DismissOverviewVisuals();
|
||||
|
||||
if (tabToSelect.has_value())
|
||||
{
|
||||
_SelectTab(tabToSelect.value());
|
||||
}
|
||||
|
||||
_UpdatedSelectedTab(_GetFocusedTab());
|
||||
}
|
||||
|
||||
// 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()
|
||||
{
|
||||
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 = {};
|
||||
|
||||
overview.ClearTabContent();
|
||||
overview.Visibility(WUX::Visibility::Collapsed);
|
||||
}
|
||||
|
||||
_isInOverviewMode = false;
|
||||
}
|
||||
|
||||
bool TerminalPage::OverviewMode() const
|
||||
{
|
||||
return _isInOverviewMode;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the tab split button color when a new tab color is selected
|
||||
// Arguments:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace TerminalApp
|
||||
Boolean FocusMode { get; };
|
||||
Boolean Fullscreen { get; };
|
||||
Boolean AlwaysOnTop { get; };
|
||||
Boolean OverviewMode { get; };
|
||||
|
||||
WindowProperties WindowProperties { get; };
|
||||
void IdentifyWindow();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -71,6 +71,18 @@ 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
|
||||
@@ -263,15 +275,14 @@ namespace winrt::TerminalApp::implementation
|
||||
auto sounds{ _profile.BellSound() };
|
||||
if (sounds && sounds.Size() > 0)
|
||||
{
|
||||
// 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);
|
||||
winrt::hstring soundPath{ sounds.GetAt(rand() % sounds.Size()).Resolved() };
|
||||
winrt::Windows::Foundation::Uri uri{ soundPath };
|
||||
_playBellSound(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto soundAlias = reinterpret_cast<LPCWSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySoundW(soundAlias, nullptr, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
const auto soundAlias = reinterpret_cast<LPCTSTR>(SND_ALIAS_SYSTEMHAND);
|
||||
PlaySound(soundAlias, NULL, SND_ALIAS_ID | SND_ASYNC | SND_SENTRY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,6 +300,33 @@ 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*/)
|
||||
{
|
||||
|
||||
@@ -76,6 +76,9 @@ 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;
|
||||
@@ -93,6 +96,8 @@ 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);
|
||||
|
||||
@@ -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() + _accumulatedFontSizeDelta);
|
||||
const auto sizeChanged = _setFontSizeUnderLock(_settings.FontSize());
|
||||
|
||||
// Update the terminal core with its new Core settings
|
||||
_terminal->UpdateSettings(_settings);
|
||||
@@ -1163,10 +1163,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// - none
|
||||
void ControlCore::ResetFontSize()
|
||||
{
|
||||
if (std::exchange(_accumulatedFontSizeDelta, 0.f) != 0.f)
|
||||
const auto lock = _terminal->LockForWriting();
|
||||
|
||||
if (_setFontSizeUnderLock(_settings.FontSize()))
|
||||
{
|
||||
// No point in doing this if there was no delta.
|
||||
AdjustFontSize(0);
|
||||
_refreshSizeUnderLock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,11 +1177,9 @@ 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(_settings.FontSize() + _accumulatedFontSizeDelta))
|
||||
if (_setFontSizeUnderLock(_desiredFont.GetFontSize() + fontSizeDelta))
|
||||
{
|
||||
_refreshSizeUnderLock();
|
||||
}
|
||||
|
||||
@@ -391,7 +391,6 @@ 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 };
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1334,6 +1334,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
const auto conflictingCmdName{ conflictingCmd.Name() };
|
||||
TextBlock conflictingCommandNameTB{};
|
||||
conflictingCommandNameTB.Text(fmt::format(L"\"{}\"", conflictingCmdName.empty() ? RS_(L"Actions_UnnamedCommandName") : conflictingCmdName));
|
||||
conflictingCommandNameTB.FontStyle(Windows::UI::Text::FontStyle::Italic);
|
||||
|
||||
TextBlock confirmationQuestionTB{};
|
||||
confirmationQuestionTB.Text(RS_(L"Actions_RenameConflictConfirmationQuestion"));
|
||||
|
||||
@@ -19,6 +19,12 @@
|
||||
<ResourceDictionary Source="CommonResources.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<Style x:Key="ItalicDisclaimerStyle"
|
||||
BasedOn="{StaticResource DisclaimerStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="FontStyle" Value="Italic" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CodeBlockStyle"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="FontFamily" Value="Cascadia Mono, Consolas" />
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock x:Uid="NewTabMenuEntry_Separator"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
FontStyle="Italic" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
DataContext="{Binding Mode=OneWay}"
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
FontStyle="Italic"
|
||||
Text="{x:Bind DisplayText, Mode=OneWay}" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
@@ -213,7 +213,7 @@
|
||||
Style="{StaticResource NewTabMenuEntryControlsWrapper}">
|
||||
<TextBlock x:Uid="NewTabMenuEntry_RemainingProfiles"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
FontStyle="Italic" />
|
||||
</ContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
|
||||
@@ -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" };
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -123,7 +123,6 @@ class ScreenBufferTests
|
||||
TEST_METHOD(VtResizePreservingAttributes);
|
||||
|
||||
TEST_METHOD(VtSoftResetCursorPosition);
|
||||
TEST_METHOD(VtSoftResetAltBufferCursorState);
|
||||
|
||||
TEST_METHOD(VtScrollMarginsNewlineColor);
|
||||
|
||||
@@ -1511,30 +1510,6 @@ void ScreenBufferTests::VtSoftResetCursorPosition()
|
||||
VERIFY_ARE_EQUAL(til::point(1, 1), cursor.GetPosition());
|
||||
}
|
||||
|
||||
void ScreenBufferTests::VtSoftResetAltBufferCursorState()
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.LockConsole(); // Lock must be taken to manipulate buffer.
|
||||
auto unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
|
||||
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& stateMachine = si.GetStateMachine();
|
||||
|
||||
Log::Comment(L"Move cursor on the main buffer.");
|
||||
stateMachine.ProcessString(L"\x1b[4;7H");
|
||||
VERIFY_ARE_EQUAL(til::point(6, 3), si.GetTextBuffer().GetCursor().GetPosition());
|
||||
|
||||
Log::Comment(L"Enter alt buffer, soft reset, and return to main buffer.");
|
||||
stateMachine.ProcessString(L"\x1b[?1049h");
|
||||
VERIFY_IS_TRUE(gci.GetActiveOutputBuffer()._IsAltBuffer());
|
||||
stateMachine.ProcessString(L"\x1b[!p");
|
||||
stateMachine.ProcessString(L"\x1b[?1049l");
|
||||
VERIFY_IS_FALSE(gci.GetActiveOutputBuffer()._IsAltBuffer());
|
||||
|
||||
Log::Comment(L"Returning from alt buffer should restore the main cursor position.");
|
||||
VERIFY_ARE_EQUAL(til::point(6, 3), gci.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition());
|
||||
}
|
||||
|
||||
void ScreenBufferTests::VtScrollMarginsNewlineColor()
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
@@ -3002,15 +3002,17 @@ void AdaptDispatch::SoftReset()
|
||||
SetGraphicsRendition({}); // Normal rendition.
|
||||
SetCharacterProtectionAttribute({}); // Default (unprotected)
|
||||
|
||||
// Reset only the active saved cursor state.
|
||||
// This matches xterm behavior when DECSTR is processed while using
|
||||
// the alternate screen buffer (GH#19918).
|
||||
_savedCursorState.at(_usingAltBuffer ? 1 : 0) = {};
|
||||
// Reset the saved cursor state.
|
||||
// Note that XTerm only resets the main buffer state, but that
|
||||
// seems likely to be a bug. Most other terminals reset both.
|
||||
_savedCursorState.at(0) = {}; // Main buffer
|
||||
_savedCursorState.at(1) = {}; // Alt buffer
|
||||
|
||||
// The TerminalOutput state in this buffer must be reset to
|
||||
// The TerminalOutput state in these buffers must be reset to
|
||||
// the same state as the _termOutput instance, which is not
|
||||
// necessarily equivalent to a full reset.
|
||||
_savedCursorState.at(_usingAltBuffer ? 1 : 0).TermOutput = _termOutput;
|
||||
_savedCursorState.at(0).TermOutput = _termOutput;
|
||||
_savedCursorState.at(1).TermOutput = _termOutput;
|
||||
|
||||
// Soft reset the Sixel parser if in use.
|
||||
if (_sixelParser)
|
||||
|
||||
Reference in New Issue
Block a user