Add the ability to interact with subtrees of panes (#11153)

This commit adds the ability to interact with subtrees of panes. 

Have you ever thought that you don't have enough regression testing to
do? Boy do I have the PR for you! This breaks all kinds of assumptions
about what is or is not focused, largely complicated by the fact that a
pane is not a proper control. I did my best to cover as many cases as I
could, but I wouldn't be surprised if there are some things broken that
I am unaware of.

Done:
- Add `parent` and `child` movement directions to move up and down the
  tree respectively
- When a parent pane is selected it will have borders all around it in
  addition to any borders the children have.
- Fix focus, swap, split, zoom, toggle orientation, resize, and move to
  all handle interacting with more than one pane.
- Similarly the actions for font size changing, closing, read-only, clearing
   buffer, and changing color scheme will distribute to all children.
- This technically leaves control focus on the original control in the
  focused subtree because panes aren't proper controls themselves. This
  is also used to make sure we go back down the same path with the
  `child` movement.
- You can zoom a parent pane, and click between different zoomed
  sub-panes and it won't unzoom you until you use moveFocus or another
  action. This wasn't explicitly programmed behavior so it is probably
  buggy (I've quashed a couple at least). It is a natural consequence of
  showing multiple terminals and allowing you to focus a terminal and a
  parent separately, since changing the active pane directly does not
  unzoom. This also means there can be a disconnect between what pane is
  zoomed and what pane is active.

## Validation Steps Performed
Tested focus movement, swapping, moving panes, and zooming.

Closes #10733
This commit is contained in:
Schuyler Rosefield 2021-09-28 15:16:05 -04:00 committed by GitHub
parent 75e2b5fae7
commit 43297315ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 797 additions and 364 deletions

View File

@ -319,7 +319,9 @@
"previous",
"nextInOrder",
"previousInOrder",
"first"
"first",
"parent",
"child"
],
"type": "string"
},
@ -562,7 +564,7 @@
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
"description": "The direction to move focus in, between panes. Direction can be 'previous' to move to the most recently used pane, 'nextInOrder' or 'previousInOrder' to move to the next or previous pane, 'first' to focus the first pane, or 'parent' or 'child' to move up and down the tree."
}
}
}
@ -579,7 +581,7 @@
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane, or 'nextInOrder' or 'previousInOrder' to move to the next or previous pane."
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane, 'nextInOrder' or 'previousInOrder' to move to the next or previous pane, or 'first' to swap with the first pane."
}
}
}

View File

@ -751,7 +751,7 @@ namespace TerminalAppLocalTests
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Move focus. This will cause us to un-zoom.");
Log::Comment(L"Move focus. We should still be zoomed.");
result = RunOnUIThread([&page]() {
// Set up action
MoveFocusArgs args{ FocusDirection::Left };
@ -761,7 +761,7 @@ namespace TerminalAppLocalTests
auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
}
@ -1357,7 +1357,8 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
// And we should have stored a function to revert the change.
VERIFY_ARE_EQUAL(1u, page->_restorePreviewFuncs.size());
});
TestOnUIThread([&page]() {
@ -1383,7 +1384,8 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
// After preview there should be no more restore functions to execute.
VERIFY_ARE_EQUAL(0u, page->_restorePreviewFuncs.size());
});
}
@ -1428,7 +1430,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1451,7 +1452,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be the same as it originally was");
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}
@ -1498,7 +1498,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1522,7 +1521,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1548,7 +1546,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}

View File

@ -67,41 +67,17 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_EndPreviewColorScheme()
{
// Get the focused control
if (const auto& activeControl{ _GetActiveControl() })
for (const auto& f : _restorePreviewFuncs)
{
// Get the runtime settings of the focused control
const auto& controlSettings{ activeControl.Settings().as<TerminalSettings>() };
// Get the control's root settings, the ones that we actually
// assigned to it.
auto parentSettings{ controlSettings.GetParent() };
while (parentSettings.GetParent() != nullptr)
{
parentSettings = parentSettings.GetParent();
}
// If the root settings are the same as the ones we stashed,
// then reset the parent of the runtime settings to the stashed
// settings. This condition might be false if the settings
// hot-reloaded while the palette was open. In that case, we
// don't want to reset the settings to what they were _before_
// the hot-reload.
if (_originalSettings == parentSettings)
{
// Set the original settings as the parent of the control's settings
activeControl.Settings().as<TerminalSettings>().SetParent(_originalSettings);
}
activeControl.UpdateSettings();
f();
}
_originalSettings = nullptr;
_restorePreviewFuncs.clear();
}
// Method Description:
// - Preview handler for the SetColorScheme action.
// - This method will stash the settings of the current control in
// _originalSettings. Then it will create a new TerminalSettings object
// - This method will stash functions to reset the settings of the selected controls in
// _restorePreviewFuncs. Then it will create a new TerminalSettings object
// with only the properties from the ColorScheme set. It'll _insert_ a
// TerminalSettings between the control's root settings (built from
// CascadiaSettings) and the control's runtime settings. That'll cause the
@ -112,33 +88,63 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_PreviewColorScheme(const Settings::Model::SetColorSchemeArgs& args)
{
// Get the focused control
if (const auto& activeControl{ _GetActiveControl() })
if (const auto& scheme{ _settings.GlobalSettings().ColorSchemes().TryLookup(args.SchemeName()) })
{
if (const auto& scheme{ _settings.GlobalSettings().ColorSchemes().TryLookup(args.SchemeName()) })
{
// Clear the saved preview funcs because we don't need to add a restore each time
// the preview color changes, we only need to be able to restore the last one.
_restorePreviewFuncs.clear();
_ApplyToActiveControls([&](const auto& control) {
// Get the settings of the focused control and stash them
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
const auto& controlSettings = control.Settings().as<TerminalSettings>();
// Make sure to recurse up to the root - if you're doing
// this while you're currently previewing a SetColorScheme
// action, then the parent of the control's settings is _the
// last preview TerminalSettings we inserted! We don't want
// to save that one!
_originalSettings = controlSettings.GetParent();
while (_originalSettings.GetParent() != nullptr)
auto originalSettings = controlSettings.GetParent();
while (originalSettings.GetParent() != nullptr)
{
_originalSettings = _originalSettings.GetParent();
originalSettings = originalSettings.GetParent();
}
// Create a new child for those settings
TerminalSettingsCreateResult fake{ _originalSettings };
TerminalSettingsCreateResult fake{ originalSettings };
const auto& childStruct = TerminalSettings::CreateWithParent(fake);
// Modify the child to have the applied color scheme
childStruct.DefaultSettings().ApplyColorScheme(scheme);
// Insert that new child as the parent of the control's settings
controlSettings.SetParent(childStruct.DefaultSettings());
activeControl.UpdateSettings();
}
control.UpdateSettings();
// Take a copy of the inputs, since they are pointers anyways.
_restorePreviewFuncs.emplace_back([=]() {
// Get the runtime settings of the focused control
const auto& controlSettings{ control.Settings().as<TerminalSettings>() };
// Get the control's root settings, the ones that we actually
// assigned to it.
auto parentSettings{ controlSettings.GetParent() };
while (parentSettings.GetParent() != nullptr)
{
parentSettings = parentSettings.GetParent();
}
// If the root settings are the same as the ones we stashed,
// then reset the parent of the runtime settings to the stashed
// settings. This condition might be false if the settings
// hot-reloaded while the palette was open. In that case, we
// don't want to reset the settings to what they were _before_
// the hot-reload.
if (originalSettings == parentSettings)
{
// Set the original settings as the parent of the control's settings
control.Settings().as<TerminalSettings>().SetParent(originalSettings);
}
control.UpdateSettings();
});
});
}
}

View File

@ -377,11 +377,10 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<AdjustFontSizeArgs>())
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.AdjustFontSize(realArgs.Delta());
args.Handled(true);
}
const auto res = _ApplyToActiveControls([&](auto& control) {
control.AdjustFontSize(realArgs.Delta());
});
args.Handled(res);
}
}
@ -395,21 +394,19 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.ResetFontSize();
args.Handled(true);
}
const auto res = _ApplyToActiveControls([](auto& control) {
control.ResetFontSize();
});
args.Handled(res);
}
void TerminalPage::_HandleToggleShaderEffects(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.ToggleShaderEffects();
args.Handled(true);
}
const auto res = _ApplyToActiveControls([](auto& control) {
control.ToggleShaderEffects();
});
args.Handled(res);
}
void TerminalPage::_HandleToggleFocusMode(const IInspectable& /*sender*/,
@ -452,37 +449,33 @@ namespace winrt::TerminalApp::implementation
args.Handled(false);
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
{
if (const auto activeTab{ _GetFocusedTabImpl() })
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
{
if (auto activeControl = activeTab->GetActiveTerminalControl())
{
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
const auto res = _ApplyToActiveControls([&](auto& control) {
// Start by getting the current settings of the control
auto controlSettings = control.Settings().as<TerminalSettings>();
auto parentSettings = controlSettings;
// Those are the _runtime_ settings however. What we
// need to do is:
//
// 1. Blow away any colors set in the runtime settings.
// 2. Apply the color scheme to the parent settings.
//
// 1 is important to make sure that the effects of
// something like `colortool` are cleared when setting
// the scheme.
if (controlSettings.GetParent() != nullptr)
{
// Start by getting the current settings of the control
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
auto parentSettings = controlSettings;
// Those are the _runtime_ settings however. What we
// need to do is:
//
// 1. Blow away any colors set in the runtime settings.
// 2. Apply the color scheme to the parent settings.
//
// 1 is important to make sure that the effects of
// something like `colortool` are cleared when setting
// the scheme.
if (controlSettings.GetParent() != nullptr)
{
parentSettings = controlSettings.GetParent();
}
// ApplyColorScheme(nullptr) will clear the old color scheme.
controlSettings.ApplyColorScheme(nullptr);
parentSettings.ApplyColorScheme(scheme);
activeControl.UpdateSettings();
args.Handled(true);
parentSettings = controlSettings.GetParent();
}
}
// ApplyColorScheme(nullptr) will clear the old color scheme.
controlSettings.ApplyColorScheme(nullptr);
parentSettings.ApplyColorScheme(scheme);
control.UpdateSettings();
});
args.Handled(res);
}
}
}
@ -896,11 +889,10 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
{
if (const auto termControl{ _GetActiveControl() })
{
termControl.ClearBuffer(realArgs.Clear());
args.Handled(true);
}
const auto res = _ApplyToActiveControls([&](auto& control) {
control.ClearBuffer(realArgs.Clear());
});
args.Handled(res);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,8 @@ enum class Borders : int
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
Right = 0x8
Right = 0x8,
All = 0xF
};
DEFINE_ENUM_FLAG_OPERATORS(Borders);
@ -58,7 +59,14 @@ public:
const winrt::Microsoft::Terminal::Control::TermControl& control,
const bool lastFocused = false);
Pane(std::shared_ptr<Pane> first,
std::shared_ptr<Pane> second,
const SplitState splitType,
const float splitPosition,
const bool lastFocused = false);
std::shared_ptr<Pane> GetActivePane();
winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl();
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
@ -142,25 +150,43 @@ public:
// - true if the predicate returned true on any pane.
template<typename F>
//requires std::predicate<F, std::shared_ptr<Pane>>
bool WalkTree(F f)
auto WalkTree(F f) -> decltype(f(shared_from_this()))
{
if (f(shared_from_this()))
{
return true;
}
using R = std::invoke_result_t<F, std::shared_ptr<Pane>>;
static constexpr auto IsVoid = std::is_void_v<R>;
if (!_IsLeaf())
if constexpr (IsVoid)
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
f(shared_from_this());
if (!_IsLeaf())
{
_firstChild->WalkTree(f);
_secondChild->WalkTree(f);
}
}
else
{
if (f(shared_from_this()))
{
return true;
}
return false;
if (!_IsLeaf())
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
}
return false;
}
}
void CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
using gotFocusArgs = winrt::delegate<std::shared_ptr<Pane>, winrt::Windows::UI::Xaml::FocusState>;
DECLARE_EVENT(GotFocus, _GotFocusHandlers, gotFocusArgs);
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate<std::shared_ptr<Pane>>);
@ -173,7 +199,8 @@ private:
struct LayoutSizeNode;
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Border _border{};
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
@ -185,6 +212,7 @@ private:
float _desiredSplitPosition;
std::optional<uint32_t> _id;
std::weak_ptr<Pane> _parentChildPath{};
bool _lastActive{ false };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
@ -205,6 +233,7 @@ private:
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;
void _SetupChildCloseHandlers();
bool _HasChild(const std::shared_ptr<Pane> child);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
@ -232,6 +261,7 @@ private:
void _CloseChild(const bool closeFirst, const bool isDetaching);
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
void _Focus();
void _FocusFirstChild();
void _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _ControlWarningBellHandler(winrt::Windows::Foundation::IInspectable const& sender,

View File

@ -190,6 +190,9 @@
<data name="CloseWindowWarningTitle" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
<data name="MultiplePanes" xml:space="preserve">
<value>Multiple panes</value>
</data>
<data name="TabCloseSubMenu" xml:space="preserve">
<value>Close...</value>
</data>

View File

@ -736,31 +736,32 @@ namespace winrt::TerminalApp::implementation
{
_UnZoomIfNeeded();
auto pane = terminalTab->GetActivePane();
if (const auto pane{ terminalTab->GetActivePane() })
{
if (const auto control{ pane->GetTerminalControl() })
if (pane->ContainsReadOnly())
{
if (control.ReadOnly())
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
// If the user didn't explicitly click on close tab - leave
if (warningResult != ContentDialogResult::Primary)
{
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
// If the user didn't explicitly click on close tab - leave
if (warningResult != ContentDialogResult::Primary)
{
co_return;
}
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
if (control.ReadOnly())
{
control.ToggleReadOnly();
}
co_return;
}
pane->Close();
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
pane->WalkTree([](auto p) {
if (const auto control{ p->GetTerminalControl() })
{
if (control.ReadOnly())
{
control.ToggleReadOnly();
}
}
return false;
});
}
pane->Close();
}
}
else if (auto index{ _GetFocusedTabIndex() })

View File

@ -262,6 +262,26 @@ namespace winrt::TerminalApp::implementation
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
template<typename F>
bool _ApplyToActiveControls(F f)
{
if (const auto tab{ _GetFocusedTabImpl() })
{
if (const auto activePane = tab->GetActivePane())
{
activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
f(control);
}
});
return true;
}
}
return false;
}
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
TerminalApp::TabBase _GetFocusedTab() const noexcept;
@ -365,7 +385,7 @@ namespace winrt::TerminalApp::implementation
void _EndPreviewColorScheme();
void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr };
std::vector<std::function<void()>> _restorePreviewFuncs{};
HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);

View File

@ -67,8 +67,11 @@ namespace winrt::TerminalApp::implementation
_rootPane->FocusPane(firstId);
_activePane = _rootPane->GetActivePane();
}
// Set the active control
_mruPanes.insert(_mruPanes.begin(), _activePane->Id().value());
// If the focused pane is a leaf, add it to the MRU panes
if (const auto id = _activePane->Id())
{
_mruPanes.insert(_mruPanes.begin(), id.value());
}
_Setup();
}
@ -180,8 +183,8 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Returns nullptr if no children of this tab were the last control to be
// focused, or the TermControl that _was_ the last control to be focused (if
// there was one).
// focused, the active control of the current pane, or the last active child control
// of the active pane if it is a parent.
// - This control might not currently be focused, if the tab itself is not
// currently focused.
// Arguments:
@ -193,7 +196,7 @@ namespace winrt::TerminalApp::implementation
{
if (_activePane)
{
return _activePane->GetTerminalControl();
return _activePane->GetLastFocusedTerminalControl();
}
return nullptr;
}
@ -390,6 +393,10 @@ namespace winrt::TerminalApp::implementation
{
return _runtimeTabText;
}
if (!_activePane->_IsLeaf())
{
return RS_(L"MultiplePanes");
}
const auto lastFocusedControl = GetActiveTerminalControl();
return lastFocusedControl ? lastFocusedControl.Title() : L"";
}
@ -514,19 +521,14 @@ namespace winrt::TerminalApp::implementation
// either the first or second child, but this will always return the
// original pane first.
auto [original, newPane] = _activePane->Split(splitType, splitSize, profile, control);
// The active pane has an id if it is a leaf
if (activePaneId)
{
original->Id(activePaneId.value());
newPane->Id(_nextPaneId);
++_nextPaneId;
}
else
{
original->Id(_nextPaneId);
++_nextPaneId;
newPane->Id(_nextPaneId);
++_nextPaneId;
}
newPane->Id(_nextPaneId);
++_nextPaneId;
_activePane = original;
// Add a event handlers to the new panes' GotFocus event. When the pane
@ -551,8 +553,8 @@ namespace winrt::TerminalApp::implementation
// - The removed pane, if the remove succeeded.
std::shared_ptr<Pane> TerminalTab::DetachPane()
{
// if we only have one pane, remove it entirely
// and close this tab
// if we only have one pane, or the focused pane is the root, remove it
// entirely and close this tab
if (_rootPane == _activePane)
{
return DetachRoot();
@ -627,16 +629,12 @@ namespace winrt::TerminalApp::implementation
// Add the new pane as an automatic split on the active pane.
auto first = _activePane->AttachPane(pane, SplitDirection::Automatic);
// under current assumptions this condition should always be true.
// This will be true if the original _activePane is a leaf pane.
// If it is a parent pane then we don't want to set an ID on it.
if (previousId)
{
first->Id(previousId.value());
}
else
{
first->Id(_nextPaneId);
++_nextPaneId;
}
// Update with event handlers on the new child.
_activePane = first;
@ -712,7 +710,10 @@ namespace winrt::TerminalApp::implementation
// throughout the entire tree.
if (const auto newFocus = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
// Mark that we want the active pane to changed
_changingActivePane = true;
const auto res = _rootPane->FocusPane(newFocus);
_changingActivePane = false;
if (_zoomedPane)
{
@ -735,11 +736,22 @@ namespace winrt::TerminalApp::implementation
// - true if two panes were swapped.
bool TerminalTab::SwapPane(const FocusDirection& direction)
{
// You cannot swap panes with the parent/child pane because of the
// circular reference.
if (direction == FocusDirection::Parent || direction == FocusDirection::Child)
{
return false;
}
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto neighbor = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
return _rootPane->SwapPanes(_activePane, neighbor);
// SwapPanes will refocus the terminal to make sure that it has focus
// even after moving.
_changingActivePane = true;
const auto res = _rootPane->SwapPanes(_activePane, neighbor);
_changingActivePane = false;
return res;
}
return false;
@ -747,7 +759,10 @@ namespace winrt::TerminalApp::implementation
bool TerminalTab::FocusPane(const uint32_t id)
{
return _rootPane->FocusPane(id);
_changingActivePane = true;
const auto res = _rootPane->FocusPane(id);
_changingActivePane = false;
return res;
}
// Method Description:
@ -1040,7 +1055,7 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() };
std::weak_ptr<Pane> weakPane{ pane };
auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender, WUX::FocusState focus) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
@ -1048,8 +1063,20 @@ namespace winrt::TerminalApp::implementation
{
if (sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
auto senderIsChild = tab->_activePane->_HasChild(sender);
// Only move focus if we the program moved focus, or the
// user moved with their mouse. This is a problem because a
// pane isn't a control itself, and if we have the parent
// focused we are fine if the terminal control is focused,
// but we don't want to update the active pane.
if (!senderIsChild ||
(focus == WUX::FocusState::Programmatic && tab->_changingActivePane) ||
focus == WUX::FocusState::Pointer)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
}
}
tab->_focusState = WUX::FocusState::Programmatic;
// This tab has gained focus, remove the bell indicator if it is active
@ -1084,8 +1111,19 @@ namespace winrt::TerminalApp::implementation
tab->Content(tab->_rootPane->GetRootElement());
tab->ExitZoom();
}
if (auto pane = weakPane.lock())
{
// When a parent pane is selected, but one of its children
// close out under it we still need to update title/focus information
// but the GotFocus handler will rightly see that the _activePane
// did not actually change. Triggering
if (pane != tab->_activePane && !tab->_activePane->_IsLeaf())
{
co_await winrt::resume_foreground(tab->Content().Dispatcher());
tab->_UpdateActivePane(tab->_activePane);
}
for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i)
{
if (*i == pane->Id())
@ -1303,11 +1341,13 @@ namespace winrt::TerminalApp::implementation
// - The tab's color, if any
std::optional<winrt::Windows::UI::Color> TerminalTab::GetTabColor()
{
const auto currControlColor{ GetActiveTerminalControl().TabColor() };
std::optional<winrt::Windows::UI::Color> controlTabColor;
if (currControlColor != nullptr)
if (const auto& control = GetActiveTerminalControl())
{
controlTabColor = currControlColor.Value();
if (const auto color = control.TabColor())
{
controlTabColor = color.Value();
}
}
// A Tab's color will be the result of layering a variety of sources,
@ -1598,6 +1638,11 @@ namespace winrt::TerminalApp::implementation
void TerminalTab::EnterZoom()
{
// Clear the content first, because with parent focusing it is possible
// to zoom the root pane, but setting the content will not trigger the
// property changed event since it is the same and you would end up with
// an empty tab.
Content(nullptr);
_zoomedPane = _activePane;
_rootPane->Maximize(_zoomedPane);
// Update the tab header to show the magnifying glass
@ -1606,6 +1651,7 @@ namespace winrt::TerminalApp::implementation
}
void TerminalTab::ExitZoom()
{
Content(nullptr);
_rootPane->Restore(_zoomedPane);
_zoomedPane = nullptr;
// Update the tab header to hide the magnifying glass
@ -1620,13 +1666,34 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Toggle read-only mode on the active pane
// - If a parent pane is selected, this will ensure that all children have
// the same read-only status.
void TerminalTab::TogglePaneReadOnly()
{
auto control = GetActiveTerminalControl();
if (control)
{
control.ToggleReadOnly();
}
auto hasReadOnly = false;
auto allReadOnly = true;
_activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
hasReadOnly |= control.ReadOnly();
allReadOnly &= control.ReadOnly();
}
});
_activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
// If all controls have the same read only state then just toggle
if (allReadOnly || !hasReadOnly)
{
control.ToggleReadOnly();
}
// otherwise set to all read only.
else if (!control.ReadOnly())
{
control.ToggleReadOnly();
}
}
});
}
// Method Description:

View File

@ -139,6 +139,7 @@ namespace winrt::TerminalApp::implementation
bool _receivedKeyDown{ false };
bool _iconHidden{ false };
bool _changingActivePane{ false };
winrt::hstring _runtimeTabText{};
bool _inRename{ false };

View File

@ -469,6 +469,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_renderEngine->ToggleShaderEffects();
}
// Always redraw after toggling effects. This way even if the control
// does not have focus it will update immediately.
_renderer->TriggerRedrawAll();
}
// Method Description:

View File

@ -298,6 +298,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_(L"MoveFocusPreviousInOrder");
case FocusDirection::First:
return RS_(L"MoveFocusFirstPane");
case FocusDirection::Parent:
return RS_(L"MoveFocusParentPane");
case FocusDirection::Child:
return RS_(L"MoveFocusChildPane");
}
return winrt::hstring{

View File

@ -38,7 +38,9 @@ namespace Microsoft.Terminal.Settings.Model
Previous,
PreviousInOrder,
NextInOrder,
First
First,
Parent,
Child
};
enum SplitDirection

View File

@ -258,6 +258,12 @@
<data name="MoveFocusFirstPane" xml:space="preserve">
<value>Move focus to the first pane</value>
</data>
<data name="MoveFocusParentPane" xml:space="preserve">
<value>Move focus to the parent pane</value>
</data>
<data name="MoveFocusChildPane" xml:space="preserve">
<value>Move focus to the child pane</value>
</data>
<data name="SwapPaneCommandKey" xml:space="preserve">
<value>Swap pane</value>
</data>

View File

@ -373,7 +373,7 @@ struct IntAsFloatPercentConversionTrait : ::Microsoft::Terminal::Settings::Model
// Possible FocusDirection values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FocusDirection)
{
JSON_MAPPINGS(8) = {
JSON_MAPPINGS(10) = {
pair_type{ "left", ValueType::Left },
pair_type{ "right", ValueType::Right },
pair_type{ "up", ValueType::Up },
@ -382,6 +382,8 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FocusDirection)
pair_type{ "previousInOrder", ValueType::PreviousInOrder },
pair_type{ "nextInOrder", ValueType::NextInOrder },
pair_type{ "first", ValueType::First },
pair_type{ "parent", ValueType::Parent },
pair_type{ "child", ValueType::Child },
};
};

View File

@ -354,6 +354,8 @@
{ "command": { "action": "moveFocus", "direction": "previousInOrder" } },
{ "command": { "action": "moveFocus", "direction": "nextInOrder" } },
{ "command": { "action": "moveFocus", "direction": "first" } },
{ "command": { "action": "moveFocus", "direction": "parent" } },
{ "command": { "action": "moveFocus", "direction": "child" } },
{ "command": { "action": "swapPane", "direction": "down" } },
{ "command": { "action": "swapPane", "direction": "left" } },
{ "command": { "action": "swapPane", "direction": "right" } },

View File

@ -247,6 +247,7 @@ bool DxEngine::_HasTerminalEffects() const noexcept
void DxEngine::ToggleShaderEffects()
{
_terminalEffectsEnabled = !_terminalEffectsEnabled;
_recreateDeviceRequested = true;
LOG_IF_FAILED(InvalidateAll());
}