From 43297315ba4cc590648b35628d8a38d11af35c33 Mon Sep 17 00:00:00 2001 From: Schuyler Rosefield Date: Tue, 28 Sep 2021 15:16:05 -0400 Subject: [PATCH 01/11] 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 --- doc/cascadia/profiles.schema.json | 8 +- .../LocalTests_TerminalApp/TabTests.cpp | 15 +- .../TerminalApp/ActionPreviewHandlers.cpp | 86 ++- .../TerminalApp/AppActionHandlers.cpp | 88 +-- src/cascadia/TerminalApp/Pane.cpp | 688 +++++++++++++----- src/cascadia/TerminalApp/Pane.h | 52 +- .../Resources/en-US/Resources.resw | 3 + src/cascadia/TerminalApp/TabManagement.cpp | 37 +- src/cascadia/TerminalApp/TerminalPage.h | 22 +- src/cascadia/TerminalApp/TerminalTab.cpp | 137 +++- src/cascadia/TerminalApp/TerminalTab.h | 1 + src/cascadia/TerminalControl/ControlCore.cpp | 3 + .../TerminalSettingsModel/ActionArgs.cpp | 4 + .../TerminalSettingsModel/ActionArgs.idl | 4 +- .../Resources/en-US/Resources.resw | 6 + .../TerminalSettingsSerializationHelpers.h | 4 +- .../TerminalSettingsModel/defaults.json | 2 + src/renderer/dx/DxRenderer.cpp | 1 + 18 files changed, 797 insertions(+), 364 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 293b3dd07..0a399f17d 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -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." } } } diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 4e3da70aa..ecb40c3eb 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -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); }); } diff --git a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp index f09c97553..3fc4ff2ae 100644 --- a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp +++ b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp @@ -67,41 +67,17 @@ namespace winrt::TerminalApp::implementation // - 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() }; - - // 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().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 // - 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(); + const auto& controlSettings = control.Settings().as(); // 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() }; + + // 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().SetParent(originalSettings); + } + + control.UpdateSettings(); + }); + }); } } diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index d99726b39..ae79289fa 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -377,11 +377,10 @@ namespace winrt::TerminalApp::implementation { if (const auto& realArgs = args.ActionArgs().try_as()) { - 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()) { - 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(); + 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(); - 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()) { - 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); } } } diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 310d7ca47..82bc861b4 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -39,8 +39,8 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo _lastActive{ lastFocused }, _profile{ profile } { - _root.Children().Append(_border); - _border.Child(_control); + _root.Children().Append(_borderFirst); + _borderFirst.Child(_control); _connectionStateChangedToken = _control.ConnectionStateChanged({ this, &Pane::_ControlConnectionStateChangedHandler }); _warningBellToken = _control.WarningBell({ this, &Pane::_ControlWarningBellHandler }); @@ -61,7 +61,52 @@ Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFo // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to // Colors::Transparent! The border won't get Tapped events, and they'll fall // through to something else. - _border.Tapped([this](auto&, auto& e) { + _borderFirst.Tapped([this](auto&, auto& e) { + _FocusFirstChild(); + e.Handled(true); + }); + _borderSecond.Tapped([this](auto&, auto& e) { + _FocusFirstChild(); + e.Handled(true); + }); +} + +Pane::Pane(std::shared_ptr first, + std::shared_ptr second, + const SplitState splitState, + const float splitPosition, + const bool lastFocused) : + _firstChild{ first }, + _secondChild{ second }, + _splitState{ splitState }, + _desiredSplitPosition{ splitPosition }, + _lastActive{ lastFocused } +{ + _CreateRowColDefinitions(); + _borderFirst.Child(_firstChild->GetRootElement()); + _borderSecond.Child(_secondChild->GetRootElement()); + + // Use the unfocused border color as the pane background, so an actual color + // appears behind panes as we animate them sliding in. + _root.Background(s_unfocusedBorderBrush); + + _root.Children().Append(_borderFirst); + _root.Children().Append(_borderSecond); + + _ApplySplitDefinitions(); + + // Register event handlers on our children to handle their Close events + _SetupChildCloseHandlers(); + + // When our border is tapped, make sure to transfer focus to our control. + // LOAD-BEARING: This will NOT work if the border's BorderBrush is set to + // Colors::Transparent! The border won't get Tapped events, and they'll fall + // through to something else. + _borderFirst.Tapped([this](auto&, auto& e) { + _FocusFirstChild(); + e.Handled(true); + }); + _borderSecond.Tapped([this](auto&, auto& e) { _FocusFirstChild(); e.Handled(true); }); @@ -330,18 +375,18 @@ bool Pane::ResizePane(const ResizeDirection& direction) return false; } - // Check if either our first or second child is the currently focused leaf. + // Check if either our first or second child is the currently focused pane. // If it is, and the requested resize direction matches our separator, then // we're the pane that needs to adjust its separator. // If our separator is the wrong direction, then we can't handle it. - const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive; - const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; + const bool firstIsFocused = _firstChild->_lastActive; + const bool secondIsFocused = _secondChild->_lastActive; if (firstIsFocused || secondIsFocused) { return _Resize(direction); } - // If neither of our children were the focused leaf, then recurse into + // If neither of our children were the focused pane, then recurse into // our children and see if they can handle the resize. // For each child, if it has a focused descendant, try having that child // handle the resize. @@ -390,6 +435,36 @@ std::shared_ptr Pane::NavigateDirection(const std::shared_ptr source return nullptr; } + // Check if moving up or down the tree + if (direction == FocusDirection::Parent) + { + if (const auto parent = _FindParentOfPane(sourcePane)) + { + // Keep a reference to which child we came from + parent->_parentChildPath = sourcePane->weak_from_this(); + + return parent; + } + return nullptr; + } + + if (direction == FocusDirection::Child) + { + if (!sourcePane->_IsLeaf()) + { + auto child = sourcePane->_firstChild; + // If we've recorded path try to go back down it + if (const auto prevFocus = sourcePane->_parentChildPath.lock()) + { + child = prevFocus; + } + // clean up references + sourcePane->_parentChildPath.reset(); + return child; + } + return nullptr; + } + // Previous movement relies on the last used panes if (direction == FocusDirection::Previous) { @@ -413,6 +488,7 @@ std::shared_ptr Pane::NavigateDirection(const std::shared_ptr source return PreviousPane(sourcePane); } + // Fixed movement if (direction == FocusDirection::First) { std::shared_ptr firstPane = nullptr; @@ -482,6 +558,11 @@ std::shared_ptr Pane::NextPane(const std::shared_ptr targetPane) bool foundTarget = false; auto foundNext = WalkTree([&](auto pane) { + // If we are a parent pane we don't want to move to one of our children + if (foundTarget && targetPane->_HasChild(pane)) + { + return false; + } // In case the target pane is the last pane in the tree, keep a reference // to the first leaf so we can wrap around. if (firstLeaf == nullptr && pane->_IsLeaf()) @@ -612,6 +693,12 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) return false; } + // Similarly don't swap if we have a circular reference + if (first->_HasChild(second) || second->_HasChild(first)) + { + return false; + } + std::unique_lock lock{ _createCloseLock }; // Recurse through the tree to find the parent panes of each pane that is @@ -625,8 +712,10 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) // after the pointers were found but before we reached this function. if (firstParent && secondParent) { - // Swap size/display information of the two panes. - std::swap(first->_borders, second->_borders); + // Before we swap anything get the borders for the parents so that + // it can be propagated to the swapped child. + firstParent->_borders = firstParent->_GetCommonBorders(); + secondParent->_borders = secondParent->_GetCommonBorders(); // Replace the old child with new one, and revoke appropriate event // handlers. @@ -644,32 +733,30 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) } // Clear now to ensure that we can add the child's grid to us later parent->_root.Children().Clear(); + parent->_borderFirst.Child(nullptr); + parent->_borderSecond.Child(nullptr); }; // Make sure that the right event handlers are set, and the children // are placed in the appropriate locations in the grid. auto updateParent = [](auto& parent) { + // just always revoke the old helpers since we are making new ones. + parent->_firstChild->Closed(parent->_firstClosedToken); + parent->_secondChild->Closed(parent->_secondClosedToken); parent->_SetupChildCloseHandlers(); parent->_root.Children().Clear(); - parent->_root.Children().Append(parent->_firstChild->GetRootElement()); - parent->_root.Children().Append(parent->_secondChild->GetRootElement()); - // Make sure they have the correct borders, and also that they are - // placed in the right location in the grid. - // This mildly reproduces ApplySplitDefinitions, but is different in - // that it does not want to utilize the parent's border to set child - // borders. - if (parent->_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(parent->_firstChild->GetRootElement(), 0); - Controls::Grid::SetColumn(parent->_secondChild->GetRootElement(), 1); - } - else if (parent->_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(parent->_firstChild->GetRootElement(), 0); - Controls::Grid::SetRow(parent->_secondChild->GetRootElement(), 1); - } - parent->_firstChild->_UpdateBorders(); - parent->_secondChild->_UpdateBorders(); + parent->_borderFirst.Child(nullptr); + parent->_borderSecond.Child(nullptr); + parent->_borderFirst.Child(parent->_firstChild->GetRootElement()); + parent->_borderSecond.Child(parent->_secondChild->GetRootElement()); + + parent->_root.Children().Append(parent->_borderFirst); + parent->_root.Children().Append(parent->_borderSecond); + + // reset split definitions to clear any set row/column + parent->_root.ColumnDefinitions().Clear(); + parent->_root.RowDefinitions().Clear(); + parent->_CreateRowColDefinitions(); }; // If the firstParent and secondParent are the same, then we are just @@ -681,6 +768,7 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) std::swap(firstParent->_firstChild, firstParent->_secondChild); updateParent(firstParent); + firstParent->_ApplySplitDefinitions(); } else { @@ -690,11 +778,45 @@ bool Pane::SwapPanes(std::shared_ptr first, std::shared_ptr second) replaceChild(secondParent, second, first); updateParent(firstParent); updateParent(secondParent); + + // If one of the two parents is a child of the other we only want + // to apply the split definitions to the greatest parent to make + // sure that all panes get the correct borders. if this is not done + // and the ordering happens to be bad one parent's children will lose + // a border. + if (firstParent->_HasChild(secondParent)) + { + firstParent->_ApplySplitDefinitions(); + } + else if (secondParent->_HasChild(firstParent)) + { + secondParent->_ApplySplitDefinitions(); + } + else + { + firstParent->_ApplySplitDefinitions(); + secondParent->_ApplySplitDefinitions(); + } } - // For now the first pane is always the focused pane, so re-focus to - // make sure the cursor is still in the terminal since the root was moved. - first->_FocusFirstChild(); + // Refocus the last pane if there was a pane focused + first->WalkTree([](auto p) { + if (p->_lastActive) + { + p->_Focus(); + return true; + } + return false; + }); + + second->WalkTree([](auto p) { + if (p->_lastActive) + { + p->_Focus(); + return true; + } + return false; + }); return true; } @@ -796,13 +918,13 @@ std::pair Pane::_GetOffsetsForPane(const PaneP if (_splitState == SplitState::Horizontal) { - secondOffset.y += (1 - _desiredSplitPosition) * parentOffset.scaleY; + secondOffset.y += _desiredSplitPosition * parentOffset.scaleY; firstOffset.scaleY *= _desiredSplitPosition; secondOffset.scaleY *= (1 - _desiredSplitPosition); } else { - secondOffset.x += (1 - _desiredSplitPosition) * parentOffset.scaleX; + secondOffset.x += _desiredSplitPosition * parentOffset.scaleX; firstOffset.scaleX *= _desiredSplitPosition; secondOffset.scaleX *= (1 - _desiredSplitPosition); } @@ -1024,10 +1146,15 @@ void Pane::_ControlWarningBellHandler(const winrt::Windows::Foundation::IInspect // - // Return Value: // - -void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& /* sender */, +void Pane::_ControlGotFocusHandler(winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const& /* args */) { - _GotFocusHandlers(shared_from_this()); + FocusState f = FocusState::Programmatic; + if (const auto o = sender.try_as()) + { + f = o.FocusState(); + } + _GotFocusHandlers(shared_from_this(), f); } // Event Description: @@ -1085,8 +1212,8 @@ Controls::Grid Pane::GetRootElement() // Method Description: // - If this is the last focused pane, returns itself. Returns nullptr if this -// is a leaf and it's not focused. If it's a parent, it returns nullptr if no -// children of this pane were the last pane to be focused, or the Pane that +// is a leaf and it's not focused. If it's a parent, it returns nullptr if it nor +// any children of this pane were the last pane to be focused, or the Pane that // _was_ the last pane to be focused (if there was one). // - This Pane's control might not currently be focused, if the tab itself is // not currently focused. @@ -1095,9 +1222,13 @@ Controls::Grid Pane::GetRootElement() // `_lastActive`, else returns this std::shared_ptr Pane::GetActivePane() { + if (_lastActive) + { + return shared_from_this(); + } if (_IsLeaf()) { - return _lastActive ? shared_from_this() : nullptr; + return nullptr; } auto firstFocused = _firstChild->GetActivePane(); @@ -1109,7 +1240,38 @@ std::shared_ptr Pane::GetActivePane() } // Method Description: -// - Gets the TermControl of this pane. If this Pane is not a leaf, this will return nullptr. +// - Gets the TermControl of this pane. If this Pane is not a leaf but is +// focused, this will return the control of the last leaf pane that had focus. +// Otherwise, this will return the control of the first child of this pane. +// Arguments: +// - +// Return Value: +// - nullptr if this Pane is an unfocused parent, otherwise the TermControl of this Pane. +TermControl Pane::GetLastFocusedTerminalControl() +{ + if (!_IsLeaf()) + { + if (_lastActive) + { + std::shared_ptr pane = shared_from_this(); + while (const auto p = pane->_parentChildPath.lock()) + { + if (p->_IsLeaf()) + { + return p->_control; + } + pane = p; + } + // We didn't find our child somehow, they might have closed under us. + } + return _firstChild->GetLastFocusedTerminalControl(); + } + return _control; +} + +// Method Description: +// - Gets the TermControl of this pane. If this Pane is not a leaf this will +// return the nullptr; // Arguments: // - // Return Value: @@ -1139,7 +1301,7 @@ void Pane::ClearActive() // Method Description: // - Sets the "Active" state on this Pane. Only one Pane in a tree of Panes -// should be "active", and that pane should be a leaf. +// should be "active". // - Updates our visuals to match our new state, including highlighting our borders. // Arguments: // - @@ -1201,7 +1363,7 @@ bool Pane::_HasFocusedChild() const noexcept // We're intentionally making this one giant expression, so the compiler // will skip the following lookups if one of the lookups before it returns // true - return (_control && _lastActive) || + return (_lastActive) || (_firstChild && _firstChild->_HasFocusedChild()) || (_secondChild && _secondChild->_HasFocusedChild()); } @@ -1215,7 +1377,30 @@ bool Pane::_HasFocusedChild() const noexcept // - void Pane::UpdateVisuals() { - _border.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); + // If we are the focused pane, but not a leaf we should add borders + if (!_IsLeaf()) + { + _UpdateBorders(); + } + _borderFirst.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); + _borderSecond.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush); +} + +// Method Description: +// - Focus the current pane. Also trigger focus on the control, or if not a leaf +// the control belonging to the last focused leaf. +// This makes sure that focus exists within the tab (since panes aren't proper controls) +// Arguments: +// - +// Return Value: +// - +void Pane::_Focus() +{ + _GotFocusHandlers(shared_from_this(), FocusState::Programmatic); + if (const auto& control = GetLastFocusedTerminalControl()) + { + control.Focus(FocusState::Programmatic); + } } // Method Description: @@ -1246,10 +1431,7 @@ void Pane::_FocusFirstChild() // // `wtd -w 0 mf down ; sp` // `wtd -w 0 fp -t 1 ; sp` - - _GotFocusHandlers(shared_from_this()); - - _control.Focus(FocusState::Programmatic); + _Focus(); } else { @@ -1305,7 +1487,7 @@ std::shared_ptr Pane::AttachPane(std::shared_ptr pane, SplitDirectio pane->WalkTree([](auto p) { if (p->_lastActive) { - p->_FocusFirstChild(); + p->_Focus(); return true; } return false; @@ -1345,8 +1527,10 @@ std::shared_ptr Pane::DetachPane(std::shared_ptr pane) // other child. _CloseChild(isFirstChild, true); + // Update the borders on this pane and any children to match if we have + // no parent. detached->_borders = Borders::None; - detached->_UpdateBorders(); + detached->_ApplySplitDefinitions(); // Trigger the detached event on each child detached->WalkTree([](auto pane) { @@ -1397,15 +1581,23 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) auto closedChildClosedToken = closeFirst ? _firstClosedToken : _secondClosedToken; auto remainingChildClosedToken = closeFirst ? _secondClosedToken : _firstClosedToken; + // If we were a parent pane, and we pointed into the now closed child + // clear it. We will set it to something else later if + bool usedToFocusClosedChildsTerminal = false; + if (const auto prev = _parentChildPath.lock()) + { + if (closedChild == prev) + { + _parentChildPath.reset(); + usedToFocusClosedChildsTerminal = true; + } + } + // If the only child left is a leaf, that means we're a leaf now. if (remainingChild->_IsLeaf()) { - // When the remaining child is a leaf, that means both our children were - // previously leaves, and the only difference in their borders is the - // border that we gave them. Take a bitwise AND of those two children to - // remove that border. Other borders the children might have, they - // inherited from us, so the flag will be set for both children. - _borders = _firstChild->_borders & _secondChild->_borders; + // Find what borders need to persist after we close the child + _borders = _GetCommonBorders(); // take the control, profile and id of the pane that _wasn't_ closed. _control = remainingChild->_control; @@ -1426,8 +1618,14 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // handlers since it is just getting moved. if (!isDetaching) { - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); + closedChild->WalkTree([](auto p) { + if (p->_IsLeaf()) + { + p->_control.ConnectionStateChanged(p->_connectionStateChangedToken); + p->_control.WarningBell(p->_warningBellToken); + } + return false; + }); } closedChild->Closed(closedChildClosedToken); @@ -1435,24 +1633,25 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) remainingChild->_control.ConnectionStateChanged(remainingChild->_connectionStateChangedToken); remainingChild->_control.WarningBell(remainingChild->_warningBellToken); - // If either of our children was focused, we want to take that focus from - // them. - _lastActive = _firstChild->_lastActive || _secondChild->_lastActive; + // If we or either of our children was focused, we want to take that + // focus from them. + _lastActive = _lastActive || _firstChild->_lastActive || _secondChild->_lastActive; // Remove all the ui elements of the remaining child. This'll make sure // we can re-attach the TermControl to our Grid. remainingChild->_root.Children().Clear(); - remainingChild->_border.Child(nullptr); + remainingChild->_borderFirst.Child(nullptr); // Reset our UI: _root.Children().Clear(); - _border.Child(nullptr); + _borderFirst.Child(nullptr); + _borderSecond.Child(nullptr); _root.ColumnDefinitions().Clear(); _root.RowDefinitions().Clear(); // Reattach the TermControl to our grid. - _root.Children().Append(_border); - _border.Child(_control); + _root.Children().Append(_borderFirst); + _borderFirst.Child(_control); // Make sure to set our _splitState before focusing the control. If you // fail to do this, when the tab handles the GotFocus event and asks us @@ -1466,7 +1665,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // If we're inheriting the "last active" state from one of our children, // focus our control now. This should trigger our own GotFocus event. - if (_lastActive) + if (usedToFocusClosedChildsTerminal || _lastActive) { _control.Focus(FocusState::Programmatic); @@ -1478,7 +1677,7 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // the control. Because Tab is relying on GotFocus to know who the // active pane in the tree is, without this call, _no one_ will be // the active pane any longer. - _GotFocusHandlers(shared_from_this()); + _GotFocusHandlers(shared_from_this(), FocusState::Programmatic); } _UpdateBorders(); @@ -1509,13 +1708,20 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) closedChild->Closed(closedChildClosedToken); if (!isDetaching) { - closedChild->_control.ConnectionStateChanged(closedChild->_connectionStateChangedToken); - closedChild->_control.WarningBell(closedChild->_warningBellToken); + closedChild->WalkTree([](auto p) { + if (p->_IsLeaf()) + { + p->_control.ConnectionStateChanged(p->_connectionStateChangedToken); + p->_control.WarningBell(p->_warningBellToken); + } + return false; + }); } // Reset our UI: _root.Children().Clear(); - _border.Child(nullptr); + _borderFirst.Child(nullptr); + _borderSecond.Child(nullptr); _root.ColumnDefinitions().Clear(); _root.RowDefinitions().Clear(); @@ -1539,20 +1745,44 @@ void Pane::_CloseChild(const bool closeFirst, const bool isDetaching) // Remove the child's UI elements from the child's grid, so we can // attach them to us instead. remainingChild->_root.Children().Clear(); - remainingChild->_border.Child(nullptr); + remainingChild->_borderFirst.Child(nullptr); + remainingChild->_borderSecond.Child(nullptr); - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); + _borderFirst.Child(_firstChild->GetRootElement()); + _borderSecond.Child(_secondChild->GetRootElement()); + + _root.Children().Append(_borderFirst); + _root.Children().Append(_borderSecond); // Propagate the new borders down to the children. _borders = remainingBorders; _ApplySplitDefinitions(); - // If the closed child was focused, transfer the focus to it's first sibling. - if (closedChild->_lastActive) + // If our child had focus and closed, just transfer to the first remaining + // child + if (closedChild->_HasFocusedChild()) { _FocusFirstChild(); } + // We might not have focus currently, but if our parent does then we + // want to make sure we have a valid path to one of our children. + // We should only update the path if our other child doesn't have focus itself. + else if (usedToFocusClosedChildsTerminal && !_secondChild->_HasFocusedChild()) + { + // update our path to our first remaining leaf + _parentChildPath = _firstChild; + _firstChild->WalkTree([](auto p) { + if (p->_IsLeaf()) + { + return true; + } + p->_parentChildPath = p->_firstChild; + return false; + }); + // This will focus the first terminal, and will set that leaf pane + // to the active pane if we nor one of our parents is not itself focused. + _FocusFirstChild(); + } // Release the pointers that the child was holding. remainingChild->_firstChild = nullptr; @@ -1602,16 +1832,18 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) }; // Remove both children from the grid - _root.Children().Clear(); - // Add the remaining child back to the grid, in the right place. - _root.Children().Append(remainingChild->GetRootElement()); + _borderFirst.Child(nullptr); + _borderSecond.Child(nullptr); + if (_splitState == SplitState::Vertical) { - Controls::Grid::SetColumn(remainingChild->GetRootElement(), closeFirst ? 1 : 0); + Controls::Grid::SetColumn(_borderFirst, 0); + Controls::Grid::SetColumn(_borderSecond, 1); } else if (_splitState == SplitState::Horizontal) { - Controls::Grid::SetRow(remainingChild->GetRootElement(), closeFirst ? 1 : 0); + Controls::Grid::SetRow(_borderFirst, 0); + Controls::Grid::SetRow(_borderSecond, 1); } // Create the dummy grid. This grid will be the one we actually animate, @@ -1623,17 +1855,9 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst) // It should be the size of the closed pane. dummyGrid.Width(removedOriginalSize.Width); dummyGrid.Height(removedOriginalSize.Height); - // Put it where the removed child is - if (_splitState == SplitState::Vertical) - { - Controls::Grid::SetColumn(dummyGrid, closeFirst ? 0 : 1); - } - else if (_splitState == SplitState::Horizontal) - { - Controls::Grid::SetRow(dummyGrid, closeFirst ? 0 : 1); - } - // Add it to the tree - _root.Children().Append(dummyGrid); + + _borderFirst.Child(closeFirst ? dummyGrid : remainingChild->GetRootElement()); + _borderSecond.Child(closeFirst ? remainingChild->GetRootElement() : dummyGrid); // Set up the rows/cols as auto/auto, so they'll only use the size of // the elements in the grid. @@ -1772,9 +1996,9 @@ void Pane::_UpdateBorders() double top = 0, bottom = 0, left = 0, right = 0; Thickness newBorders{ 0 }; - if (_zoomed) + // Zoomed panes, and focused parents should have full borders + if (_zoomed || (!_IsLeaf() && _lastActive)) { - // When the pane is zoomed, manually show all the borders around the window. top = bottom = right = left = PaneBorderSize; } else @@ -1796,11 +2020,31 @@ void Pane::_UpdateBorders() right = PaneBorderSize; } } - _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); + + if (_IsLeaf()) + { + _borderFirst.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); + } + else + { + // If we are not a leaf we don't want to duplicate the shared border + // between our children. + if (_splitState == SplitState::Vertical) + { + _borderFirst.BorderThickness(ThicknessHelper::FromLengths(left, top, 0, bottom)); + _borderSecond.BorderThickness(ThicknessHelper::FromLengths(0, top, right, bottom)); + } + else + { + _borderFirst.BorderThickness(ThicknessHelper::FromLengths(left, top, right, 0)); + _borderSecond.BorderThickness(ThicknessHelper::FromLengths(left, 0, right, bottom)); + } + } } // Method Description: // - Find the borders for the leaf pane, or the shared borders for child panes. +// - This deliberately ignores if a focused parent has borders. // Arguments: // - // Return Value: @@ -1827,8 +2071,8 @@ void Pane::_ApplySplitDefinitions() { if (_splitState == SplitState::Vertical) { - Controls::Grid::SetColumn(_firstChild->GetRootElement(), 0); - Controls::Grid::SetColumn(_secondChild->GetRootElement(), 1); + Controls::Grid::SetColumn(_borderFirst, 0); + Controls::Grid::SetColumn(_borderSecond, 1); _firstChild->_borders = _borders | Borders::Right; _secondChild->_borders = _borders | Borders::Left; @@ -1839,8 +2083,8 @@ void Pane::_ApplySplitDefinitions() } else if (_splitState == SplitState::Horizontal) { - Controls::Grid::SetRow(_firstChild->GetRootElement(), 0); - Controls::Grid::SetRow(_secondChild->GetRootElement(), 1); + Controls::Grid::SetRow(_borderFirst, 0); + Controls::Grid::SetRow(_borderSecond, 1); _firstChild->_borders = _borders | Borders::Bottom; _secondChild->_borders = _borders | Borders::Top; @@ -1894,6 +2138,7 @@ void Pane::_SetupEntranceAnimation() auto setupAnimation = [&](const auto& size, const bool isFirstChild) { auto child = isFirstChild ? _firstChild : _secondChild; auto childGrid = child->_root; + // If we are splitting a parent pane this may be null auto control = child->_control; // Build up our animation: // * it'll take as long as our duration (200ms) @@ -1949,16 +2194,22 @@ void Pane::_SetupEntranceAnimation() // the parent pane, otherwise use the bottom/right. This is always // the "outside" of the parent pane. childGrid.HorizontalAlignment(isFirstChild ? HorizontalAlignment::Left : HorizontalAlignment::Right); - control.HorizontalAlignment(HorizontalAlignment::Left); - control.Width(isFirstChild ? totalSize : size); + if (control) + { + control.HorizontalAlignment(HorizontalAlignment::Left); + control.Width(isFirstChild ? totalSize : size); + } // When the animation is completed, undo the trickiness from before, to // restore the controls to the behavior they'd usually have. animation.Completed([childGrid, control, root = _secondChild->_root](auto&&, auto&&) { - control.Width(NAN); childGrid.Width(NAN); childGrid.HorizontalAlignment(HorizontalAlignment::Stretch); - control.HorizontalAlignment(HorizontalAlignment::Stretch); + if (control) + { + control.Width(NAN); + control.HorizontalAlignment(HorizontalAlignment::Stretch); + } root.Background(nullptr); }); } @@ -1968,16 +2219,22 @@ void Pane::_SetupEntranceAnimation() // the parent pane, otherwise use the bottom/right. This is always // the "outside" of the parent pane. childGrid.VerticalAlignment(isFirstChild ? VerticalAlignment::Top : VerticalAlignment::Bottom); - control.VerticalAlignment(VerticalAlignment::Top); - control.Height(isFirstChild ? totalSize : size); + if (control) + { + control.VerticalAlignment(VerticalAlignment::Top); + control.Height(isFirstChild ? totalSize : size); + } // When the animation is completed, undo the trickiness from before, to // restore the controls to the behavior they'd usually have. animation.Completed([childGrid, control, root = _secondChild->_root](auto&&, auto&&) { - control.Height(NAN); childGrid.Height(NAN); childGrid.VerticalAlignment(VerticalAlignment::Stretch); - control.VerticalAlignment(VerticalAlignment::Stretch); + if (control) + { + control.Height(NAN); + control.VerticalAlignment(VerticalAlignment::Stretch); + } root.Background(nullptr); }); } @@ -2025,41 +2282,38 @@ std::optional Pane::PreCalculateCanSplit(const std::shared_ptr targe const float splitSize, const winrt::Windows::Foundation::Size availableSpace) const { - if (_IsLeaf()) + if (target.get() == this) { - if (target.get() == this) + const auto firstPercent = 1.0f - splitSize; + const auto secondPercent = splitSize; + // If this pane is a leaf, and it's the pane we're looking for, use + // the available space to calculate which direction to split in. + const Size minSize = _GetMinSize(); + + if (splitType == SplitDirection::Left || splitType == SplitDirection::Right) { - const auto firstPrecent = 1.0f - splitSize; - const auto secondPercent = splitSize; - // If this pane is a leaf, and it's the pane we're looking for, use - // the available space to calculate which direction to split in. - const Size minSize = _GetMinSize(); + const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize; + const auto newFirstWidth = widthMinusSeparator * firstPercent; + const auto newSecondWidth = widthMinusSeparator * secondPercent; - if (splitType == SplitDirection::Left || splitType == SplitDirection::Right) - { - const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize; - const auto newFirstWidth = widthMinusSeparator * firstPrecent; - const auto newSecondWidth = widthMinusSeparator * secondPercent; - - return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width }; - } - - else if (splitType == SplitDirection::Up || splitType == SplitDirection::Down) - { - const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize; - const auto newFirstHeight = heightMinusSeparator * firstPrecent; - const auto newSecondHeight = heightMinusSeparator * secondPercent; - - return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height }; - } + return { newFirstWidth > minSize.Width && newSecondWidth > minSize.Width }; } - else + + else if (splitType == SplitDirection::Up || splitType == SplitDirection::Down) { - // If this pane is _any other leaf_, then just return nullopt, to - // indicate that the `target` Pane is not down this branch. - return std::nullopt; + const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize; + const auto newFirstHeight = heightMinusSeparator * firstPercent; + const auto newSecondHeight = heightMinusSeparator * secondPercent; + + return { newFirstHeight > minSize.Height && newSecondHeight > minSize.Height }; } } + else if (_IsLeaf()) + { + // If this pane is _any other leaf_, then just return nullopt, to + // indicate that the `target` Pane is not down this branch. + return std::nullopt; + } else { // If this pane is a parent, calculate how much space our children will @@ -2103,13 +2357,13 @@ std::pair, std::shared_ptr> Pane::Split(SplitDirecti const Profile& profile, const TermControl& control) { - if (!_IsLeaf()) + if (!_lastActive) { - if (_firstChild->_HasFocusedChild()) + if (_firstChild && _firstChild->_HasFocusedChild()) { return _firstChild->Split(splitType, splitSize, profile, control); } - else if (_secondChild->_HasFocusedChild()) + else if (_secondChild && _secondChild->_HasFocusedChild()) { return _secondChild->Split(splitType, splitSize, profile, control); } @@ -2135,11 +2389,11 @@ bool Pane::ToggleSplitOrientation() return false; } - // Check if either our first or second child is the currently focused leaf. - // If they are then switch the split orientation on the current pane. + // If a parent pane is focused, or if one of its children are a leaf and is + // focused then switch the split orientation on the current pane. const bool firstIsFocused = _firstChild->_IsLeaf() && _firstChild->_lastActive; const bool secondIsFocused = _secondChild->_IsLeaf() && _secondChild->_lastActive; - if (firstIsFocused || secondIsFocused) + if (_lastActive || firstIsFocused || secondIsFocused) { // Switch the split orientation _splitState = _splitState == SplitState::Horizontal ? SplitState::Vertical : SplitState::Horizontal; @@ -2207,46 +2461,66 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect // modify our tree std::unique_lock lock{ _createCloseLock }; - // revoke our handler - the child will take care of the control now. - _control.ConnectionStateChanged(_connectionStateChangedToken); - _connectionStateChangedToken.value = 0; - _control.WarningBell(_warningBellToken); - _warningBellToken.value = 0; + if (_IsLeaf()) + { + // revoke our handler - the child will take care of the control now. + _control.ConnectionStateChanged(_connectionStateChangedToken); + _connectionStateChangedToken.value = 0; + _control.WarningBell(_warningBellToken); + _warningBellToken.value = 0; - // Remove our old GotFocus handler from the control. We don't what the - // control telling us that it's now focused, we want it telling its new - // parent. - _gotFocusRevoker.revoke(); - _lostFocusRevoker.revoke(); - - _splitState = actualSplitType; - _desiredSplitPosition = 1.0f - splitSize; + // Remove our old GotFocus handler from the control. We don't want the + // control telling us that it's now focused, we want it telling its new + // parent. + _gotFocusRevoker.revoke(); + _lostFocusRevoker.revoke(); + } // Remove any children we currently have. We can't add the existing // TermControl to a new grid until we do this. _root.Children().Clear(); - _border.Child(nullptr); + _borderFirst.Child(nullptr); + _borderSecond.Child(nullptr); - // Create two new Panes - // Move our control, guid into the first one. - // Move the new guid, control into the second. - _firstChild = std::make_shared(_profile, _control); - _firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected); + // Create a new pane from ourself + if (!_IsLeaf()) + { + // Since we are a parent we don't have borders normally, + // so set them temporarily for when we update our split definition. + _borders = _GetCommonBorders(); + _firstChild->Closed(_firstClosedToken); + _secondChild->Closed(_secondClosedToken); + // If we are not a leaf we should create a new pane that contains our children + auto first = std::make_shared(_firstChild, _secondChild, _splitState, _desiredSplitPosition); + _firstChild = first; + } + else + { + // Move our control, guid into the first one. + _firstChild = std::make_shared(_profile, _control); + _firstChild->_connectionState = std::exchange(_connectionState, ConnectionState::NotConnected); + _profile = nullptr; + _control = { nullptr }; + } + + _splitState = actualSplitType; + _desiredSplitPosition = 1.0f - splitSize; _secondChild = newPane; - // If we want the new pane to be the first child, swap the children if (splitType == SplitDirection::Up || splitType == SplitDirection::Left) { std::swap(_firstChild, _secondChild); } - _profile = nullptr; - _control = { nullptr }; - + _root.ColumnDefinitions().Clear(); + _root.RowDefinitions().Clear(); _CreateRowColDefinitions(); - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); + _borderFirst.Child(_firstChild->GetRootElement()); + _borderSecond.Child(_secondChild->GetRootElement()); + + _root.Children().Append(_borderFirst); + _root.Children().Append(_borderSecond); _ApplySplitDefinitions(); @@ -2281,12 +2555,9 @@ std::pair, std::shared_ptr> Pane::_Split(SplitDirect // - void Pane::Maximize(std::shared_ptr zoomedPane) { - if (_IsLeaf()) - { - _zoomed = (zoomedPane == shared_from_this()); - _UpdateBorders(); - } - else + _zoomed = (zoomedPane == shared_from_this()); + _UpdateBorders(); + if (!_IsLeaf()) { if (zoomedPane == _firstChild || zoomedPane == _secondChild) { @@ -2294,6 +2565,8 @@ void Pane::Maximize(std::shared_ptr zoomedPane) // tree. Easy way: just remove both children. We'll re-attach both // when we un-zoom. _root.Children().Clear(); + _borderFirst.Child(nullptr); + _borderSecond.Child(nullptr); } // Always recurse into both children. If the (un)zoomed pane was one of @@ -2316,20 +2589,21 @@ void Pane::Maximize(std::shared_ptr zoomedPane) // - void Pane::Restore(std::shared_ptr zoomedPane) { - if (_IsLeaf()) - { - _zoomed = false; - _UpdateBorders(); - } - else + _zoomed = false; + _UpdateBorders(); + if (!_IsLeaf()) { if (zoomedPane == _firstChild || zoomedPane == _secondChild) { // When we're un-zooming the pane, we'll need to re-add it to our UI // tree where it originally belonged. easy way: just re-add both. _root.Children().Clear(); - _root.Children().Append(_firstChild->GetRootElement()); - _root.Children().Append(_secondChild->GetRootElement()); + + _borderFirst.Child(_firstChild->GetRootElement()); + _borderSecond.Child(_secondChild->GetRootElement()); + + _root.Children().Append(_borderFirst); + _root.Children().Append(_borderSecond); } // Always recurse into both children. If the (un)zoomed pane was one of @@ -2366,6 +2640,8 @@ void Pane::Id(uint32_t id) noexcept // - The ID of the pane we want to focus bool Pane::FocusPane(const uint32_t id) { + // Always clear the parent child path if we are focusing a leaf + _parentChildPath.reset(); if (_IsLeaf() && id == _id) { // Make sure to use _FocusFirstChild here - that'll properly update the @@ -2385,23 +2661,23 @@ bool Pane::FocusPane(const uint32_t id) } // Method Description: // - Focuses the given pane if it is in the tree. -// This deliberately mirrors FocusPane(id) instead of just calling -// _FocusFirstChild directly. +// - This is different than FocusPane(id) in that it allows focusing +// panes that are not leaves. // Arguments: // - the pane to focus // Return Value: // - true if focus was set bool Pane::FocusPane(const std::shared_ptr pane) { - if (_IsLeaf() && this == pane.get()) + if (this == pane.get()) { - // Make sure to use _FocusFirstChild here - that'll properly update the - // focus if we're in startup. - _FocusFirstChild(); + _Focus(); return true; } else { + // clear the parent child path if we are not the pane being focused. + _parentChildPath.reset(); if (_firstChild && _secondChild) { return _firstChild->FocusPane(pane) || @@ -2411,6 +2687,27 @@ bool Pane::FocusPane(const std::shared_ptr pane) return false; } +// Method Description: +// - Check if this pane contains the the argument as a child anywhere along the tree. +// Arguments: +// - child: the child to search for. +// Return Value: +// - true if the child was found. +bool Pane::_HasChild(const std::shared_ptr child) +{ + if (_IsLeaf()) + { + return false; + } + + if (_firstChild == child || _secondChild == child) + { + return true; + } + + return _firstChild->_HasChild(child) || _secondChild->_HasChild(child); +} + // Method Description: // - Recursive function that finds a pane with the given ID // Arguments: @@ -2906,20 +3203,17 @@ int Pane::GetLeafPaneCount() const noexcept std::optional Pane::PreCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size availableSpace) const { - if (_IsLeaf()) + if (target.get() == this) { - if (target.get() == this) - { - //If this pane is a leaf, and it's the pane we're looking for, use - //the available space to calculate which direction to split in. - return availableSpace.Width > availableSpace.Height ? SplitDirection::Right : SplitDirection::Down; - } - else - { - // If this pane is _any other leaf_, then just return nullopt, to - // indicate that the `target` Pane is not down this branch. - return std::nullopt; - } + // If this pane is the pane we are looking for, use the available space + // to calculate which direction to split in. + return availableSpace.Width > availableSpace.Height ? SplitDirection::Right : SplitDirection::Down; + } + else if (_IsLeaf()) + { + // If this pane is _any other leaf_, then just return nullopt, to + // indicate that the `target` Pane is not down this branch. + return std::nullopt; } else { @@ -2971,7 +3265,7 @@ void Pane::CollectTaskbarStates(std::vector& s } } -DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate>); +DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, Pane::gotFocusArgs); DEFINE_EVENT(Pane, LostFocus, _LostFocusHandlers, winrt::delegate>); DEFINE_EVENT(Pane, PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); DEFINE_EVENT(Pane, Detached, _PaneDetachedHandlers, winrt::delegate>); diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index ab1309f40..9892a6e8a 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -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 first, + std::shared_ptr second, + const SplitState splitType, + const float splitPosition, + const bool lastFocused = false); + std::shared_ptr 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 //requires std::predicate> - 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>; + static constexpr auto IsVoid = std::is_void_v; - 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& states); WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); - DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); + + using gotFocusArgs = winrt::delegate, winrt::Windows::UI::Xaml::FocusState>; + + DECLARE_EVENT(GotFocus, _GotFocusHandlers, gotFocusArgs); DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate>); DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate>); @@ -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 _id; + std::weak_ptr _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 child); std::pair, std::shared_ptr> _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, diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 272fa9df6..55e51abbb 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -190,6 +190,9 @@ Do you want to close all tabs? + + Multiple panes + Close... diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index e5d1cdc46..a339b2bfe 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -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() }) diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 8f776a2d7..26332453a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -262,6 +262,26 @@ namespace winrt::TerminalApp::implementation bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction); bool _MovePane(const uint32_t tabIdx); + template + 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 _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> _restorePreviewFuncs{}; HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection); void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index afb637810..8ef1847ba 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -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 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 weakPane{ pane }; - auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr sender) { + auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr 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 TerminalTab::GetTabColor() { - const auto currControlColor{ GetActiveTerminalControl().TabColor() }; std::optional 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: diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h index 3dbeaa5aa..4c071c0b0 100644 --- a/src/cascadia/TerminalApp/TerminalTab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -139,6 +139,7 @@ namespace winrt::TerminalApp::implementation bool _receivedKeyDown{ false }; bool _iconHidden{ false }; + bool _changingActivePane{ false }; winrt::hstring _runtimeTabText{}; bool _inRename{ false }; diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 1e41fdb5c..534862698 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -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: diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp index bf60db47e..d713825cd 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.cpp @@ -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{ diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index 2ea94e84d..457feefce 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -38,7 +38,9 @@ namespace Microsoft.Terminal.Settings.Model Previous, PreviousInOrder, NextInOrder, - First + First, + Parent, + Child }; enum SplitDirection diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 3ced7eb85..5538d5d22 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -258,6 +258,12 @@ Move focus to the first pane + + Move focus to the parent pane + + + Move focus to the child pane + Swap pane diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h index a4503efb7..da934e464 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettingsSerializationHelpers.h @@ -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 }, }; }; diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index d6b45016f..fb2078966 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -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" } }, diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index de33cd00c..fc8111a63 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -247,6 +247,7 @@ bool DxEngine::_HasTerminalEffects() const noexcept void DxEngine::ToggleShaderEffects() { _terminalEffectsEnabled = !_terminalEffectsEnabled; + _recreateDeviceRequested = true; LOG_IF_FAILED(InvalidateAll()); } From 3b3b72e9cf69d9f26b5763f366510076ba7acb01 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Tue, 28 Sep 2021 14:43:51 -0500 Subject: [PATCH 02/11] Replace `null` with `"null"` for types in the schema (#11350) --- doc/cascadia/profiles.schema.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 0a399f17d..8b441d990 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -110,7 +110,7 @@ "description": "Sets the file location of the image to draw over the window background when unfocused.", "oneOf": [ { - "type": ["string", null] + "type": ["string", "null"] }, { "enum": [ @@ -531,13 +531,13 @@ "type": "integer", "default": 0, "description": "Which tab to switch to, with the first being 0" + } } } - } - ], - "required": [ "index" ] - }, - "MovePaneAction": { + ], + "required": [ "index" ] + }, + "MovePaneAction": { "description": "Arguments corresponding to a Move Pane Action", "allOf": [ { "$ref": "#/definitions/ShortcutAction" }, @@ -1444,7 +1444,7 @@ "description": "Sets the file location of the image to draw over the window background.", "oneOf": [ { - "type": ["string", null] + "type": ["string", "null"] }, { "enum": [ From 856f8764ce10cbd17962d619686be7969c262615 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 29 Sep 2021 12:23:38 +0200 Subject: [PATCH 03/11] Fix compatibility issues with profiles.schema.json (#11360) This fixes two issues with profiles.schema.json: * The `$schema` should not end in a `#` * `$defs` is the official reserved keyword for schema re-use See: http://json-schema.org/draft/2020-12/json-schema-core.html ## PR Checklist * [x] I work here * [x] Tests added/passed * [x] Schema updated ## Validation Steps Performed The previous schema didn't pass https://jschon.dev/, the new schema does. --- doc/cascadia/profiles.schema.json | 787 +++++++++++++++++++++--------- 1 file changed, 569 insertions(+), 218 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 8b441d990..ba3387d84 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -1,8 +1,8 @@ { "$id": "https://github.com/microsoft/terminal/blob/main/doc/cascadia/profiles.schema.json", - "$schema": "https://json-schema.org/draft/2020-12/schema#", + "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Microsoft's Windows Terminal Settings Profile Schema", - "definitions": { + "$defs": { "KeyChordSegment": { "pattern": "^(?:(?:ctrl|alt|shift|win)\\+)*(?:app|backspace|browser_(?:back|forward|refresh|stop|search|favorites|home)|comma|delete|down|end|enter|esc|escape|home|insert|left|menu|minus|pagedown|pageup|period|pgdn|pgup|plus|right|space|tab|up|f(?:1\\d?|2[0-4]?|[3-9])|numpad\\d|numpad_(?:\\d|add|decimal|divide|minus|multiply|period|plus|subtract)|(?:vk|sc)\\((?:[1-9]|1?\\d{2}|2[0-4]\\d|25[0-5])\\)|[^\\s+])(?:\\+(?:ctrl|alt|shift|win))*$", "type": "string", @@ -61,28 +61,42 @@ "type": "string" }, "foreground": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": "#cccccc", "description": "Sets the text color when unfocused. Overrides \"foreground\" from the color scheme. Uses hex color format: \"#rrggbb\".", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "background": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": "#0c0c0c", "description": "Sets the background color of the text when unfocused. Overrides \"background\" from the color scheme. Uses hex color format: \"#rrggbb\".", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "selectionBackground": { "oneOf": [ - {"$ref": "#/definitions/Color"}, - { "type": "null" } + { + "$ref": "#/$defs/Color" + }, + { + "type": "null" + } ], "description": "Sets the background color of selected text when unfocused. Overrides selectionBackground set in the color scheme. Uses hex color format: \"#rrggbb\"." }, "cursorColor": { "oneOf": [ - { "$ref": "#/definitions/Color" }, - {"type": "null"} + { + "$ref": "#/$defs/Color" + }, + { + "type": "null" + } ], "description": "Sets the color of the cursor when unfocused. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"." }, @@ -103,14 +117,20 @@ "description": "Sets the percentage height of the cursor (when unfocused) starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.", "maximum": 100, "minimum": 1, - "type": ["integer","null"], + "type": [ + "integer", + "null" + ], "default": 25 }, "backgroundImage": { "description": "Sets the file location of the image to draw over the window background when unfocused.", "oneOf": [ { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, { "enum": [ @@ -118,7 +138,10 @@ ] } ], - "type": [ "string", "null" ] + "type": [ + "string", + "null" + ] }, "backgroundImageOpacity": { "default": 1.0, @@ -220,7 +243,9 @@ "description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.", "type": "object", "patternProperties": { - "^(([A-Za-z0-9]){4})$": { "type": "integer" } + "^(([A-Za-z0-9]){4})$": { + "type": "integer" + } }, "additionalProperties": false }, @@ -228,7 +253,9 @@ "description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.", "type": "object", "patternProperties": { - "^([A-Za-z]{4})$": { "type": "number" } + "^([A-Za-z]{4})$": { + "type": "number" + } }, "additionalProperties": false } @@ -422,7 +449,7 @@ "description": "The index of the profile in the new tab dropdown (starting at 0)" }, "tabColor": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": null, "description": "If provided, will set the tab's color to the given value" }, @@ -438,9 +465,11 @@ }, "type": "object" }, - "SwitchToAdjacentTabArgs" : { + "SwitchToAdjacentTabArgs": { "oneOf": [ - { "type": "null" }, + { + "type": "null" + }, { "enum": [ "mru", @@ -455,7 +484,7 @@ "properties": { "action": { "description": "The action to execute", - "$ref": "#/definitions/ShortcutActionName" + "$ref": "#/$defs/ShortcutActionName" } }, "required": [ @@ -466,10 +495,15 @@ "AdjustFontSizeAction": { "description": "Arguments corresponding to an Adjust Font Size Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "adjustFontSize" }, + "action": { + "type": "string", + "pattern": "adjustFontSize" + }, "delta": { "type": "integer", "default": 0, @@ -478,15 +512,22 @@ } } ], - "required": [ "delta" ] + "required": [ + "delta" + ] }, "CopyAction": { "description": "Arguments corresponding to a Copy Text Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "copy" }, + "action": { + "type": "string", + "pattern": "copy" + }, "singleLine": { "type": "boolean", "default": false, @@ -497,7 +538,7 @@ "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied. Not setting this value inherits the behavior of the `copyFormatting` global setting.", "oneOf": [ { - "$ref": "#/definitions/CopyFormat" + "$ref": "#/$defs/CopyFormat" }, { "type": "null" @@ -511,11 +552,18 @@ "NewTabAction": { "description": "Arguments corresponding to a New Tab Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, - { "$ref": "#/definitions/NewTerminalArgs" }, + { + "$ref": "#/$defs/ShortcutAction" + }, + { + "$ref": "#/$defs/NewTerminalArgs" + }, { "properties": { - "action": { "type":"string", "pattern": "newTab" } + "action": { + "type": "string", + "pattern": "newTab" + } } } ] @@ -523,10 +571,15 @@ "SwitchToTabAction": { "description": "Arguments corresponding to a Switch To Tab Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "switchToTab" }, + "action": { + "type": "string", + "pattern": "switchToTab" + }, "index": { "type": "integer", "default": 0, @@ -535,15 +588,22 @@ } } ], - "required": [ "index" ] + "required": [ + "index" + ] }, "MovePaneAction": { "description": "Arguments corresponding to a Move Pane Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "movePane" }, + "action": { + "type": "string", + "pattern": "movePane" + }, "index": { "type": "integer", "default": 0, @@ -552,68 +612,94 @@ } } ], - "required": [ "index" ] + "required": [ + "index" + ] }, "MoveFocusAction": { "description": "Arguments corresponding to a Move Focus Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "moveFocus" }, + "action": { + "type": "string", + "pattern": "moveFocus" + }, "direction": { - "$ref": "#/definitions/FocusDirection", + "$ref": "#/$defs/FocusDirection", "default": "left", "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." } } } ], - "required": [ "direction" ] + "required": [ + "direction" + ] }, "SwapPaneAction": { "description": "Arguments corresponding to a Swap Pane Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "swapPane" }, + "action": { + "type": "string", + "pattern": "swapPane" + }, "direction": { - "$ref": "#/definitions/FocusDirection", + "$ref": "#/$defs/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, 'nextInOrder' or 'previousInOrder' to move to the next or previous pane, or 'first' to swap with the first pane." } } } ], - "required": [ "direction" ] + "required": [ + "direction" + ] }, "ResizePaneAction": { "description": "Arguments corresponding to a Resize Pane Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "resizePane" }, + "action": { + "type": "string", + "pattern": "resizePane" + }, "direction": { - "$ref": "#/definitions/ResizeDirection", + "$ref": "#/$defs/ResizeDirection", "default": "left", "description": "The direction to move the pane separator in." } } } ], - "required": [ "direction" ] + "required": [ + "direction" + ] }, "SendInputAction": { "description": "Arguments corresponding to a Send Input Action", "allOf": [ { - "$ref": "#/definitions/ShortcutAction" + "$ref": "#/$defs/ShortcutAction" }, { "properties": { - "action": { "type": "string", "pattern": "sendInput" }, + "action": { + "type": "string", + "pattern": "sendInput" + }, "input": { "type": "string", "default": "", @@ -622,18 +708,27 @@ } } ], - "required": [ "input" ] + "required": [ + "input" + ] }, "SplitPaneAction": { "description": "Arguments corresponding to a Split Pane Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, - { "$ref": "#/definitions/NewTerminalArgs" }, + { + "$ref": "#/$defs/ShortcutAction" + }, + { + "$ref": "#/$defs/NewTerminalArgs" + }, { "properties": { - "action": { "type": "string", "pattern": "splitPane" }, + "action": { + "type": "string", + "pattern": "splitPane" + }, "split": { - "$ref": "#/definitions/SplitDirection", + "$ref": "#/$defs/SplitDirection", "default": "auto", "description": "The orientation to split the pane in. Possible values:\n -\"auto\" (splits pane based on remaining space)\n -\"up\" (think [-] and above)\n -\"down\" (think [-] and below)\n -\"left\" (think [|] and to the left)\n -\"right\"(think [|] and to the right)" }, @@ -656,11 +751,14 @@ "description": "Arguments corresponding to a Open Settings Action", "allOf": [ { - "$ref": "#/definitions/ShortcutAction" + "$ref": "#/$defs/ShortcutAction" }, { "properties": { - "action": { "type": "string", "pattern": "openSettings" }, + "action": { + "type": "string", + "pattern": "openSettings" + }, "target": { "type": "string", "default": "settingsUI", @@ -670,7 +768,6 @@ "defaultsFile", "allFiles", "settingsUI" - ] } } @@ -680,12 +777,17 @@ "SetTabColorAction": { "description": "Arguments corresponding to a Set Tab Color Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "setTabColor" }, + "action": { + "type": "string", + "pattern": "setTabColor" + }, "color": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": null, "description": "If provided, will set the tab's color to the given value. If omitted, will reset the tab's color." } @@ -696,10 +798,15 @@ "SetColorSchemeAction": { "description": "Arguments corresponding to a Set Color Scheme Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "setColorScheme" }, + "action": { + "type": "string", + "pattern": "setColorScheme" + }, "colorScheme": { "type": "string", "default": "", @@ -708,15 +815,22 @@ } } ], - "required": [ "colorScheme" ] + "required": [ + "colorScheme" + ] }, "WtAction": { "description": "Arguments corresponding to a wt Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "wt" }, + "action": { + "type": "string", + "pattern": "wt" + }, "commandline": { "type": "string", "default": "", @@ -725,19 +839,30 @@ } } ], - "required": [ "commandline" ] + "required": [ + "commandline" + ] }, "CloseOtherTabsAction": { "description": "Arguments for a closeOtherTabs action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "closeOtherTabs" }, + "action": { + "type": "string", + "pattern": "closeOtherTabs" + }, "index": { "oneOf": [ - { "type": "integer" }, - { "type": "null" } + { + "type": "integer" + }, + { + "type": "null" + } ], "default": null, "description": "Close the tabs other than the one at this index. If no index is provided, use the focused tab's index." @@ -749,14 +874,23 @@ "CloseTabsAfterAction": { "description": "Arguments for a closeTabsAfter action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "closeTabsAfter" }, + "action": { + "type": "string", + "pattern": "closeTabsAfter" + }, "index": { "oneOf": [ - { "type": "integer" }, - { "type": "null" } + { + "type": "integer" + }, + { + "type": "null" + } ], "default": null, "description": "Close the tabs following the tab at this index. If no index is provided, use the focused tab's index." @@ -768,14 +902,23 @@ "CloseTabAction": { "description": "Arguments for a closeTab action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "closeTab" }, + "action": { + "type": "string", + "pattern": "closeTab" + }, "index": { "oneOf": [ - { "type": "integer" }, - { "type": "null" } + { + "type": "integer" + }, + { + "type": "null" + } ], "default": null, "description": "Close the tab at this index. If no index is provided, use the focused tab's index." @@ -787,12 +930,20 @@ "ScrollUpAction": { "description": "Arguments for a scrollUp action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "scrollUp" }, + "action": { + "type": "string", + "pattern": "scrollUp" + }, "rowsToScroll": { - "type": ["integer", "null"], + "type": [ + "integer", + "null" + ], "default": null, "description": "Scroll up rowsToScroll lines. If no value is provided, use the system-level defaults." } @@ -803,12 +954,20 @@ "ScrollDownAction": { "description": "Arguments for a scrollDown action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "scrollDown" }, + "action": { + "type": "string", + "pattern": "scrollDown" + }, "rowsToScroll": { - "type": ["integer", "null"], + "type": [ + "integer", + "null" + ], "default": null, "description": "Scroll down rowsToScroll lines. If no value is provided, use the system-level defaults." } @@ -819,28 +978,40 @@ "MoveTabAction": { "description": "Arguments for moving a tab", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "moveTab" }, + "action": { + "type": "string", + "pattern": "moveTab" + }, "direction": { - "$ref": "#/definitions/MoveTabDirection", + "$ref": "#/$defs/MoveTabDirection", "description": "The direction to move the tab" } } } ], - "required": [ "direction" ] + "required": [ + "direction" + ] }, "MultipleActionsAction": { "description": "Arguments for the multiple actions command", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "multipleActions" }, - "actions" : { - "$ref": "#/definitions/ShortcutAction", + "action": { + "type": "string", + "pattern": "multipleActions" + }, + "actions": { + "$ref": "#/$defs/ShortcutAction", "type": "array", "minItems": 1, "description": "A list of other actions." @@ -848,17 +1019,24 @@ } } ], - "required": [ "actions" ] + "required": [ + "actions" + ] }, "CommandPaletteAction": { "description": "Arguments for a commandPalette action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "commandPalette" }, + "action": { + "type": "string", + "pattern": "commandPalette" + }, "launchMode": { - "$ref": "#/definitions/CommandPaletteLaunchMode", + "$ref": "#/$defs/CommandPaletteLaunchMode", "default": "action", "description": "Toggle command palette in either action or command line mode. If no value is provided, the palette will launch in action mode." } @@ -869,28 +1047,42 @@ "FindMatchAction": { "description": "Arguments corresponding to a Find Match Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "findMatch" }, + "action": { + "type": "string", + "pattern": "findMatch" + }, "direction": { - "$ref": "#/definitions/FindMatchDirection", + "$ref": "#/$defs/FindMatchDirection", "default": "prev", "description": "The direction to search in. \"prev\" will search upwards in the buffer, and \"next\" will search downwards." } } } ], - "required": [ "direction" ] + "required": [ + "direction" + ] }, "NewWindowAction": { "description": "Arguments corresponding to a New Window Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, - { "$ref": "#/definitions/NewTerminalArgs" }, + { + "$ref": "#/$defs/ShortcutAction" + }, + { + "$ref": "#/$defs/NewTerminalArgs" + }, { "properties": { - "action": { "type":"string", "pattern": "newWindow" } + "action": { + "type": "string", + "pattern": "newWindow" + } } } ] @@ -898,12 +1090,17 @@ "PrevTabAction": { "description": "Arguments corresponding to a Previous Tab Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type":"string", "pattern": "prevTab" }, + "action": { + "type": "string", + "pattern": "prevTab" + }, "tabSwitcherMode": { - "$ref": "#/definitions/SwitchToAdjacentTabArgs", + "$ref": "#/$defs/SwitchToAdjacentTabArgs", "default": null, "description": "Move to the previous tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one." } @@ -914,12 +1111,17 @@ "NextTabAction": { "description": "Arguments corresponding to a Next Tab Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type":"string", "pattern": "nextTab" }, + "action": { + "type": "string", + "pattern": "nextTab" + }, "tabSwitcherMode": { - "$ref": "#/definitions/SwitchToAdjacentTabArgs", + "$ref": "#/$defs/SwitchToAdjacentTabArgs", "default": null, "description": "Move to the next tab using \"tabSwitcherMode\". If no mode is provided, use the one globally defined one." } @@ -930,10 +1132,15 @@ "RenameTabAction": { "description": "Arguments corresponding to a renameTab Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "renameTab" }, + "action": { + "type": "string", + "pattern": "renameTab" + }, "title": { "type": "string", "default": "", @@ -946,10 +1153,15 @@ "RenameWindowAction": { "description": "Arguments corresponding to a renameWindow Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "renameWindow" }, + "action": { + "type": "string", + "pattern": "renameWindow" + }, "name": { "type": "string", "default": "", @@ -962,10 +1174,15 @@ "FocusPaneAction": { "description": "Arguments corresponding to a focusPane Action", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "focusPane" }, + "action": { + "type": "string", + "pattern": "focusPane" + }, "id": { "type": "string", "default": "", @@ -978,10 +1195,15 @@ "GlobalSummonAction": { "description": "This is a special action that works globally in the OS, rather than only in the context of the terminal window. When pressed, this action will summon the terminal window.", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "globalSummon" }, + "action": { + "type": "string", + "pattern": "globalSummon" + }, "desktop": { "type": "string", "default": "toCurrent", @@ -1024,10 +1246,15 @@ "QuakeModeAction": { "description": "This action is a special variation of the globalSummon action. It specifically summons a window called \"_quake\". If you would like to change the behavior of the quakeMode action, we recommended creating a new globalSummon entry.", "allOf": [ - { "$ref": "#/definitions/ShortcutAction" }, + { + "$ref": "#/$defs/ShortcutAction" + }, { "properties": { - "action": { "type": "string", "pattern": "quakeMode" } + "action": { + "type": "string", + "pattern": "quakeMode" + } } } ] @@ -1037,56 +1264,120 @@ "properties": { "command": { "description": "The action executed when the associated key bindings are pressed.", - "oneOf": [ - { "$ref": "#/definitions/AdjustFontSizeAction" }, - { "$ref": "#/definitions/CopyAction" }, - { "$ref": "#/definitions/ShortcutActionName" }, - { "$ref": "#/definitions/NewTabAction" }, - { "$ref": "#/definitions/SwitchToTabAction" }, - { "$ref": "#/definitions/MoveFocusAction" }, - { "$ref": "#/definitions/MovePaneAction" }, - { "$ref": "#/definitions/SwapPaneAction" }, - { "$ref": "#/definitions/ResizePaneAction" }, - { "$ref": "#/definitions/SendInputAction" }, - { "$ref": "#/definitions/SplitPaneAction" }, - { "$ref": "#/definitions/OpenSettingsAction" }, - { "$ref": "#/definitions/SetTabColorAction" }, - { "$ref": "#/definitions/SetColorSchemeAction" }, - { "$ref": "#/definitions/WtAction" }, - { "$ref": "#/definitions/CloseOtherTabsAction" }, - { "$ref": "#/definitions/CloseTabsAfterAction" }, - { "$ref": "#/definitions/CloseTabAction" }, - { "$ref": "#/definitions/ScrollUpAction" }, - { "$ref": "#/definitions/ScrollDownAction" }, - { "$ref": "#/definitions/MoveTabAction" }, - { "$ref": "#/definitions/FindMatchAction" }, - { "$ref": "#/definitions/NewWindowAction" }, - { "$ref": "#/definitions/NextTabAction" }, - { "$ref": "#/definitions/PrevTabAction" }, - { "$ref": "#/definitions/RenameTabAction" }, - { "$ref": "#/definitions/RenameWindowAction" }, - { "$ref": "#/definitions/FocusPaneAction" }, - { "$ref": "#/definitions/GlobalSummonAction" }, - { "$ref": "#/definitions/QuakeModeAction" }, - { "type": "null" } - ] + "oneOf": [ + { + "$ref": "#/$defs/AdjustFontSizeAction" + }, + { + "$ref": "#/$defs/CopyAction" + }, + { + "$ref": "#/$defs/ShortcutActionName" + }, + { + "$ref": "#/$defs/NewTabAction" + }, + { + "$ref": "#/$defs/SwitchToTabAction" + }, + { + "$ref": "#/$defs/MoveFocusAction" + }, + { + "$ref": "#/$defs/MovePaneAction" + }, + { + "$ref": "#/$defs/SwapPaneAction" + }, + { + "$ref": "#/$defs/ResizePaneAction" + }, + { + "$ref": "#/$defs/SendInputAction" + }, + { + "$ref": "#/$defs/SplitPaneAction" + }, + { + "$ref": "#/$defs/OpenSettingsAction" + }, + { + "$ref": "#/$defs/SetTabColorAction" + }, + { + "$ref": "#/$defs/SetColorSchemeAction" + }, + { + "$ref": "#/$defs/WtAction" + }, + { + "$ref": "#/$defs/CloseOtherTabsAction" + }, + { + "$ref": "#/$defs/CloseTabsAfterAction" + }, + { + "$ref": "#/$defs/CloseTabAction" + }, + { + "$ref": "#/$defs/ScrollUpAction" + }, + { + "$ref": "#/$defs/ScrollDownAction" + }, + { + "$ref": "#/$defs/MoveTabAction" + }, + { + "$ref": "#/$defs/FindMatchAction" + }, + { + "$ref": "#/$defs/NewWindowAction" + }, + { + "$ref": "#/$defs/NextTabAction" + }, + { + "$ref": "#/$defs/PrevTabAction" + }, + { + "$ref": "#/$defs/RenameTabAction" + }, + { + "$ref": "#/$defs/RenameWindowAction" + }, + { + "$ref": "#/$defs/FocusPaneAction" + }, + { + "$ref": "#/$defs/GlobalSummonAction" + }, + { + "$ref": "#/$defs/QuakeModeAction" + }, + { + "type": "null" + } + ] }, "keys": { "description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key", "oneOf": [ { - "$ref": "#/definitions/KeyChordSegment" + "$ref": "#/$defs/KeyChordSegment" }, { "items": { - "$ref": "#/definitions/KeyChordSegment" + "$ref": "#/$defs/KeyChordSegment" }, "minItems": 1, "type": "array" } ] }, - "icon": { "$ref": "#/definitions/Icon" }, + "icon": { + "$ref": "#/$defs/Icon" + }, "name": { "description": "The name that will appear in the command palette. If one isn't provided, the terminal will attempt to automatically generate a name.\nIf name is a string, it will be the name of the command.\nIf name is a object, the key property of the object will be used to lookup a localized string resource for the command", "properties": { @@ -1111,15 +1402,24 @@ "commands": { "description": "List of commands to execute", "items": { - "$ref": "#/definitions/Keybinding/properties/command" + "$ref": "#/$defs/Keybinding/properties/command" }, "minItems": 1, "type": "array" } }, "anyOf": [ - {"required": ["name","commands"]}, - {"required": ["command"]} + { + "required": [ + "name", + "commands" + ] + }, + { + "required": [ + "command" + ] + } ], "type": "object" }, @@ -1160,7 +1460,7 @@ "copyFormatting": { "default": true, "description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.", - "$ref": "#/definitions/CopyFormat" + "$ref": "#/$defs/CopyFormat" }, "trimBlockSelection": { "default": false, @@ -1198,7 +1498,7 @@ "disabledProfileSources": { "description": "Disables all the dynamic profile generators in this list, preventing them from adding their profiles to the list of profiles on startup.", "items": { - "$ref": "#/definitions/DynamicProfileSource" + "$ref": "#/$defs/DynamicProfileSource" }, "type": "array" }, @@ -1222,7 +1522,7 @@ "type": "integer" }, "initialPosition": { - "$ref": "#/definitions/Coordinates", + "$ref": "#/$defs/Coordinates", "description": "The position of the top left corner of the window upon first load. On a system with multiple displays, these coordinates are relative to the top left of the primary display. If \"launchMode\" is set to \"maximized\" (or \"maximizedFocus\"), the window will be maximized on the monitor specified by those coordinates." }, "initialRows": { @@ -1263,7 +1563,10 @@ "description": "This parameter once allowed you to override the systemwide \"choose how many lines to scroll at one time\" setting. It no longer does so. However, you can customize the number of lines to scroll in \"scrollUp\" and \"scrollDown\" bindings.", "maximum": 999, "minimum": 0, - "type": [ "integer", "string" ], + "type": [ + "integer", + "string" + ], "deprecated": true }, "minimizeToNotificationArea": { @@ -1286,18 +1589,18 @@ "description": "When set to true, the tab row will have an acrylic background with 50% opacity.", "type": "boolean" }, - "actions": { - "description": "Properties are specific to each custom action.", - "items": { - "$ref": "#/definitions/Keybinding" - }, - "type": "array" - }, + "actions": { + "description": "Properties are specific to each custom action.", + "items": { + "$ref": "#/$defs/Keybinding" + }, + "type": "array" + }, "keybindings": { "description": "[deprecated] Use actions instead.", "deprecated": true, "items": { - "$ref": "#/definitions/Keybinding" + "$ref": "#/$defs/Keybinding" }, "type": "array" }, @@ -1425,26 +1728,38 @@ "type": "string" }, "background": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": "#0c0c0c", "description": "Sets the background color of the text. Overrides \"background\" from the color scheme. Uses hex color format: \"#rrggbb\".", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "unfocusedAppearance": { - "$ref": "#/definitions/AppearanceConfig", + "$ref": "#/$defs/AppearanceConfig", "description": "Sets the appearance of the terminal when it is unfocused.", - "type": ["object", "null"] + "type": [ + "object", + "null" + ] }, "font": { - "$ref": "#/definitions/FontConfig", + "$ref": "#/$defs/FontConfig", "description": "Sets the font options of the terminal.", - "type": ["object", "null"] + "type": [ + "object", + "null" + ] }, "backgroundImage": { "description": "Sets the file location of the image to draw over the window background.", "oneOf": [ { - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, { "enum": [ @@ -1452,7 +1767,10 @@ ] } ], - "type": [ "string", "null" ] + "type": [ + "string", + "null" + ] }, "backgroundImageAlignment": { "default": "center", @@ -1491,7 +1809,7 @@ "bellStyle": { "default": "audible", "description": "Controls what happens when the application emits a BEL character. When set to \"all\", the Terminal will play a sound, flash the taskbar icon (if the terminal window is not in focus) and flash the window. An array of specific behaviors can also be used. Supported array values include `audible`, `window` and `taskbar`. When set to \"none\", nothing will happen.", - "$ref": "#/definitions/BellStyle" + "$ref": "#/$defs/BellStyle" }, "closeOnExit": { "default": "graceful", @@ -1521,8 +1839,12 @@ }, "cursorColor": { "oneOf": [ - { "$ref": "#/definitions/Color" }, - {"type": "null"} + { + "$ref": "#/$defs/Color" + }, + { + "type": "null" + } ], "description": "Sets the color of the cursor. Overrides the cursor color from the color scheme. Uses hex color format: \"#rrggbb\"." }, @@ -1530,7 +1852,10 @@ "description": "Sets the percentage height of the cursor starting from the bottom. Only works when cursorShape is set to \"vintage\". Accepts values from 1-100.", "maximum": 100, "minimum": 1, - "type": ["integer","null"], + "type": [ + "integer", + "null" + ], "default": 25 }, "cursorShape": { @@ -1596,13 +1921,16 @@ "deprecated": true }, "foreground": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": "#cccccc", "description": "Sets the text color. Overrides \"foreground\" from the color scheme. Uses hex color format: \"#rrggbb\".", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "guid": { - "$ref": "#/definitions/ProfileGuid", + "$ref": "#/$defs/ProfileGuid", "description": "Unique identifier of the profile. Written in registry format: \"{00000000-0000-0000-0000-000000000000}\"." }, "hidden": { @@ -1616,7 +1944,9 @@ "minimum": -1, "type": "integer" }, - "icon":{ "$ref": "#/definitions/Icon" }, + "icon": { + "$ref": "#/$defs/Icon" + }, "name": { "description": "Name of the profile. Displays in the dropdown menu.", "minLength": 1, @@ -1653,8 +1983,12 @@ }, "selectionBackground": { "oneOf": [ - {"$ref": "#/definitions/Color"}, - { "type": "null" } + { + "$ref": "#/$defs/Color" + }, + { + "type": "null" + } ], "description": "Sets the background color of selected text. Overrides selectionBackground set in the color scheme. Uses hex color format: \"#rrggbb\"." }, @@ -1670,7 +2004,10 @@ }, "source": { "description": "Stores the name of the profile generator that originated this profile.", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "startingDirectory": { "description": "The directory the shell starts in when it is loaded.", @@ -1682,13 +2019,19 @@ "default": false }, "tabColor": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color of the profile's tab. Using the tab color picker will override this color.", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "tabTitle": { "description": "If set, will replace the name as the title to pass to the shell on startup. Some shells (like bash) may choose to ignore this initial value, while others (cmd, powershell) may use this value over the lifetime of the application.", - "type": ["string", "null"] + "type": [ + "string", + "null" + ] }, "useAcrylic": { "default": false, @@ -1701,7 +2044,7 @@ "ProfileList": { "description": "A list of profiles and the properties specific to each.", "items": { - "$ref": "#/definitions/Profile", + "$ref": "#/$defs/Profile", "required": [ "guid", "name" @@ -1713,11 +2056,11 @@ "description": "A list of profiles and default settings that apply to all of them", "properties": { "list": { - "$ref": "#/definitions/ProfileList" + "$ref": "#/$defs/ProfileList" }, "defaults": { "description": "The default settings that apply to every profile.", - "$ref": "#/definitions/Profile" + "$ref": "#/$defs/Profile" } }, "type": "object" @@ -1733,84 +2076,84 @@ "type": "string" }, "background": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the background color of the color scheme." }, "black": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI black." }, "blue": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI blue." }, "brightBlack": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright black." }, "brightBlue": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright blue." }, "brightCyan": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright cyan." }, "brightGreen": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright green." }, "brightPurple": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright purple." }, "brightRed": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright red." }, "brightWhite": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright white." }, "brightYellow": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI bright yellow." }, "cursorColor": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "default": "#FFFFFF", "description": "Sets the cursor color of the color scheme." }, "cyan": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI cyan." }, "foreground": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the foreground color of the color scheme." }, "green": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI green." }, "purple": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI purple." }, "red": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI red." }, "selectionBackground": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the selection background color of the color scheme." }, "white": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI white." }, "yellow": { - "$ref": "#/definitions/Color", + "$ref": "#/$defs/Color", "description": "Sets the color used as ANSI yellow." } }, @@ -1820,17 +2163,25 @@ } }, "allOf": [ - { "$ref": "#/definitions/Globals" }, + { + "$ref": "#/$defs/Globals" + }, { "additionalItems": true, "properties": { "profiles": { "oneOf": [ - { "$ref": "#/definitions/ProfileList" }, - { "$ref": "#/definitions/ProfilesObject" } + { + "$ref": "#/$defs/ProfileList" + }, + { + "$ref": "#/$defs/ProfilesObject" + } ] }, - "schemes": { "$ref": "#/definitions/SchemeList" } + "schemes": { + "$ref": "#/$defs/SchemeList" + } }, "required": [ "profiles", From 69391128201add56e124ec8363b4b5610ed3a7d0 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 29 Sep 2021 05:24:46 -0500 Subject: [PATCH 04/11] Minor typos in 1.12 features in SUI (#11362) * [x] Fixes a bunch of the checkboxes in #11352 * [x] Fixes one of the boxes in #11353 * [x] The opacity warning -> error gibberish was fixed with the change to `DeserializationError` - `asCString` only works if the `JsonValue` is a string already. --- .../TerminalSettingsEditor/Resources/en-US/Resources.resw | 4 ++-- src/cascadia/TerminalSettingsModel/JsonUtils.h | 2 +- .../TerminalSettingsModel/Resources/en-US/Resources.resw | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index bf0e85ee8..c6126240d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -496,7 +496,7 @@ A description for what the "acrylic opacity" setting does. Presented near "Profile_AcrylicOpacity.Header". - Background Opacity + Background opacity Header for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque. @@ -1227,7 +1227,7 @@ Header for a control to how text is formatted - Intense Text Style + Intense text style Header for a control to select how "intense" text is formatted (bold, bright, both or none) diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index cc72ce143..cea006b2b 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -101,7 +101,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils { public: DeserializationError(const Json::Value& value) : - runtime_error(std::string("failed to deserialize ") + (value.isNull() ? "" : value.asCString())), + runtime_error(std::string("failed to deserialize ") + (value.isNull() ? "" : value.asString())), jsonValue{ value } {} void SetKey(std::string_view newKey) diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 5538d5d22..8bbbbfb7f 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -433,7 +433,7 @@ Break into the debugger - Open Settings... + Open settings... Rename window to "{0}" @@ -456,15 +456,15 @@ {0} will be replaced with a user-specified number - Clear Buffer + Clear buffer A command to clear the entirety of the Terminal output buffer - Clear Viewport + Clear viewport A command to clear the active viewport of the Terminal - Clear Scrollback + Clear scrollback A command to clear the part of the buffer above the viewport From c0574f5eced0ae0aab1a4ddfa92bce62b38c4065 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 29 Sep 2021 05:26:20 -0500 Subject: [PATCH 05/11] Remove the fallback to 50% opacity when only `useAcrylic` is set (#11363) This logic was seemingly redundant. There's two cases I'm looking at here: #### Case 1 ```jsonc "defaults": { "opacity": 35 }, "list": [ { "commandline": "cmd.exe", "name": "Command Prompt" }, ``` In this case, we wouldn't set the `TerminalSettings` Opacity to .35, we'd set it to 1.0, because the profile didn't have an `opactity`. #### Case 2 ```jsonc "defaults": { "useAcrylic": true }, "list": [ { "commandline": "cmd.exe", "name": "Command Prompt" }, ``` In this case we still want to have an acrylic effect. Previously, we'd default this effect to 50% opaque. I'm not sure that we can actually get that anymore. BUT it turns out, we _can_ have 100% opacity and HostBackdropAcrylic. It is very subtle, but is maybe something we should be allowing anyways. It kinda looks like: ![image](https://user-images.githubusercontent.com/18356694/135168469-35d1f55b-58d1-4ee3-a717-76000c2574b9.png) * [x] Fixes #11355 * [x] Regressed in #11180 * [x] I work here --- .../TerminalSettingsModel/TerminalSettings.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 3d7b2f11e..187b3cedb 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -202,15 +202,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold); _IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright); - // If the user set an opacity, then just use that. Otherwise, change the - // default value based off of whether useAcrylic was set or not. If they - // want acrylic, then default to 50%. Otherwise, default to 100% (fully - // opaque) - _Opacity = appearance.HasOpacity() ? - appearance.Opacity() : - UseAcrylic() ? - .5 : - 1.0; + _Opacity = appearance.Opacity(); } // Method Description: From dacff61f8862fa7c28f0244e74555cb2658455ad Mon Sep 17 00:00:00 2001 From: James Holderness Date: Wed, 29 Sep 2021 11:48:32 +0100 Subject: [PATCH 06/11] Use the til::enumset type for the GridLines enum in the renderers (#11345) ## Summary of the Pull Request This replaces the `GridLines` enum in the renderers with a `til::enumset` type, avoiding the need for the various `WI_IsFlagSet` macros and flag operators. ## References This is followup to PR #10492 which introduced the `enumset` class. ## PR Checklist * [ ] Closes #xxx * [x] CLA signed. * [ ] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. ## Validation Steps Performed I've manually confirmed that all the different gridlines are still rendering correctly in both the GDI and DX renderers. --- src/interactivity/onecore/BgfxEngine.cpp | 2 +- src/interactivity/onecore/BgfxEngine.hpp | 2 +- src/renderer/base/renderer.cpp | 28 ++++++++++++------------ src/renderer/base/renderer.hpp | 2 +- src/renderer/dx/DxRenderer.cpp | 24 ++++++++++---------- src/renderer/dx/DxRenderer.hpp | 2 +- src/renderer/gdi/gdirenderer.hpp | 2 +- src/renderer/gdi/paint.cpp | 16 +++++++------- src/renderer/inc/IRenderEngine.hpp | 25 ++++++++++----------- src/renderer/uia/UiaRenderer.cpp | 2 +- src/renderer/uia/UiaRenderer.hpp | 2 +- src/renderer/vt/paint.cpp | 2 +- src/renderer/vt/vtrenderer.hpp | 2 +- src/renderer/wddmcon/WddmConRenderer.cpp | 2 +- src/renderer/wddmcon/WddmConRenderer.hpp | 2 +- 15 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp index dcd3f923e..ef9e95112 100644 --- a/src/interactivity/onecore/BgfxEngine.cpp +++ b/src/interactivity/onecore/BgfxEngine.cpp @@ -166,7 +166,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid CATCH_RETURN(); } -[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLines const /*lines*/, +[[nodiscard]] HRESULT BgfxEngine::PaintBufferGridLines(GridLineSet const /*lines*/, COLORREF const /*color*/, size_t const /*cchLine*/, COORD const /*coordTarget*/) noexcept diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp index 2b4e787a3..4a01837d9 100644 --- a/src/interactivity/onecore/BgfxEngine.hpp +++ b/src/interactivity/onecore/BgfxEngine.hpp @@ -53,7 +53,7 @@ namespace Microsoft::Console::Render const COORD coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp index 9654df2aa..13a72d4ba 100644 --- a/src/renderer/base/renderer.cpp +++ b/src/renderer/base/renderer.cpp @@ -898,50 +898,50 @@ void Renderer::_PaintBufferOutputHelper(_In_ IRenderEngine* const pEngine, // Arguments: // - textAttribute: the TextAttribute to generate GridLines from. // Return Value: -// - a GridLines containing all the gridline info from the TextAttribute -IRenderEngine::GridLines Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcept +// - a GridLineSet containing all the gridline info from the TextAttribute +IRenderEngine::GridLineSet Renderer::s_GetGridlines(const TextAttribute& textAttribute) noexcept { // Convert console grid line representations into rendering engine enum representations. - IRenderEngine::GridLines lines = IRenderEngine::GridLines::None; + IRenderEngine::GridLineSet lines; if (textAttribute.IsTopHorizontalDisplayed()) { - lines |= IRenderEngine::GridLines::Top; + lines.set(IRenderEngine::GridLines::Top); } if (textAttribute.IsBottomHorizontalDisplayed()) { - lines |= IRenderEngine::GridLines::Bottom; + lines.set(IRenderEngine::GridLines::Bottom); } if (textAttribute.IsLeftVerticalDisplayed()) { - lines |= IRenderEngine::GridLines::Left; + lines.set(IRenderEngine::GridLines::Left); } if (textAttribute.IsRightVerticalDisplayed()) { - lines |= IRenderEngine::GridLines::Right; + lines.set(IRenderEngine::GridLines::Right); } if (textAttribute.IsCrossedOut()) { - lines |= IRenderEngine::GridLines::Strikethrough; + lines.set(IRenderEngine::GridLines::Strikethrough); } if (textAttribute.IsUnderlined()) { - lines |= IRenderEngine::GridLines::Underline; + lines.set(IRenderEngine::GridLines::Underline); } if (textAttribute.IsDoublyUnderlined()) { - lines |= IRenderEngine::GridLines::DoubleUnderline; + lines.set(IRenderEngine::GridLines::DoubleUnderline); } if (textAttribute.IsHyperlink()) { - lines |= IRenderEngine::GridLines::HyperlinkUnderline; + lines.set(IRenderEngine::GridLines::HyperlinkUnderline); } return lines; } @@ -962,7 +962,7 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin const COORD coordTarget) { // Convert console grid line representations into rendering engine enum representations. - IRenderEngine::GridLines lines = Renderer::s_GetGridlines(textAttribute); + auto lines = Renderer::s_GetGridlines(textAttribute); // For now, we dash underline patterns and switch to regular underline on hover // Since we're only rendering pattern links on *hover*, there's no point in checking @@ -975,13 +975,13 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin { if (_pData->GetPatternId(coordTarget).size() > 0) { - lines |= IRenderEngine::GridLines::Underline; + lines.set(IRenderEngine::GridLines::Underline); } } } // Return early if there are no lines to paint. - if (lines != IRenderEngine::GridLines::None) + if (lines.any()) { // Get the current foreground color to render the lines. const COLORREF rgb = _pData->GetAttributeColors(textAttribute).first; diff --git a/src/renderer/base/renderer.hpp b/src/renderer/base/renderer.hpp index 10d853d85..1c1fb01ad 100644 --- a/src/renderer/base/renderer.hpp +++ b/src/renderer/base/renderer.hpp @@ -87,7 +87,7 @@ namespace Microsoft::Console::Render void UpdateLastHoveredInterval(const std::optional::interval>& newInterval); private: - static IRenderEngine::GridLines s_GetGridlines(const TextAttribute& textAttribute) noexcept; + static IRenderEngine::GridLineSet s_GetGridlines(const TextAttribute& textAttribute) noexcept; static bool s_IsSoftFontChar(const std::wstring_view& v, const size_t firstSoftFontChar, const size_t lastSoftFontChar); void _NotifyPaintFrame(); diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index fc8111a63..bbbdb8b20 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1706,7 +1706,7 @@ CATCH_RETURN() // - We will draw rightward (+X) from here // Return Value: // - S_OK or relevant DirectX error -[[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(GridLines const lines, +[[nodiscard]] HRESULT DxEngine::PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept @@ -1733,13 +1733,13 @@ try // offset by half the stroke width. For the start coordinate we add half // the stroke width, and for the end coordinate we subtract half the width. const DxFontRenderData::LineMetrics lineMetrics = _fontRenderData->GetLineMetrics(); - if (WI_IsAnyFlagSet(lines, (GridLines::Left | GridLines::Right))) + if (lines.any(GridLines::Left, GridLines::Right)) { const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; const auto startY = target.y + halfGridlineWidth; const auto endY = target.y + font.height - halfGridlineWidth; - if (WI_IsFlagSet(lines, GridLines::Left)) + if (lines.test(GridLines::Left)) { auto x = target.x + halfGridlineWidth; for (size_t i = 0; i < cchLine; i++, x += font.width) @@ -1748,7 +1748,7 @@ try } } - if (WI_IsFlagSet(lines, GridLines::Right)) + if (lines.test(GridLines::Right)) { auto x = target.x + font.width - halfGridlineWidth; for (size_t i = 0; i < cchLine; i++, x += font.width) @@ -1758,19 +1758,19 @@ try } } - if (WI_IsAnyFlagSet(lines, GridLines::Top | GridLines::Bottom)) + if (lines.any(GridLines::Top, GridLines::Bottom)) { const auto halfGridlineWidth = lineMetrics.gridlineWidth / 2.0f; const auto startX = target.x + halfGridlineWidth; const auto endX = target.x + fullRunWidth - halfGridlineWidth; - if (WI_IsFlagSet(lines, GridLines::Top)) + if (lines.test(GridLines::Top)) { const auto y = target.y + halfGridlineWidth; DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); } - if (WI_IsFlagSet(lines, GridLines::Bottom)) + if (lines.test(GridLines::Bottom)) { const auto y = target.y + font.height - halfGridlineWidth; DrawLine(startX, y, endX, y, lineMetrics.gridlineWidth); @@ -1780,24 +1780,24 @@ try // In the case of the underline and strikethrough offsets, the stroke width // is already accounted for, so they don't require further adjustments. - if (WI_IsAnyFlagSet(lines, GridLines::Underline | GridLines::DoubleUnderline | GridLines::HyperlinkUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline, GridLines::HyperlinkUnderline)) { const auto halfUnderlineWidth = lineMetrics.underlineWidth / 2.0f; const auto startX = target.x + halfUnderlineWidth; const auto endX = target.x + fullRunWidth - halfUnderlineWidth; const auto y = target.y + lineMetrics.underlineOffset; - if (WI_IsFlagSet(lines, GridLines::Underline)) + if (lines.test(GridLines::Underline)) { DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (WI_IsFlagSet(lines, GridLines::HyperlinkUnderline)) + if (lines.test(GridLines::HyperlinkUnderline)) { DrawHyperlinkLine(startX, y, endX, y, lineMetrics.underlineWidth); } - if (WI_IsFlagSet(lines, GridLines::DoubleUnderline)) + if (lines.test(GridLines::DoubleUnderline)) { DrawLine(startX, y, endX, y, lineMetrics.underlineWidth); const auto y2 = target.y + lineMetrics.underlineOffset2; @@ -1805,7 +1805,7 @@ try } } - if (WI_IsFlagSet(lines, GridLines::Strikethrough)) + if (lines.test(GridLines::Strikethrough)) { const auto halfStrikethroughWidth = lineMetrics.strikethroughWidth / 2.0f; const auto startX = target.x + halfStrikethroughWidth; diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 9c96e7f06..de9c69fd0 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -100,7 +100,7 @@ namespace Microsoft::Console::Render bool const fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 4caf62ad6..2e09f374d 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -52,7 +52,7 @@ namespace Microsoft::Console::Render const COORD coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines, + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override; diff --git a/src/renderer/gdi/paint.cpp b/src/renderer/gdi/paint.cpp index 347135974..598ce3489 100644 --- a/src/renderer/gdi/paint.cpp +++ b/src/renderer/gdi/paint.cpp @@ -472,7 +472,7 @@ using namespace Microsoft::Console::Render; // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK or suitable GDI HRESULT error or E_FAIL for GDI errors in functions that don't reliably return a specific error code. -[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLines lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept +[[nodiscard]] HRESULT GdiEngine::PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept { LOG_IF_FAILED(_FlushBufferLines()); @@ -499,7 +499,7 @@ using namespace Microsoft::Console::Render; return PatBlt(_hdcMemoryContext, x, y, w, h, PATCOPY); }; - if (lines & GridLines::Left) + if (lines.test(GridLines::Left)) { auto x = ptTarget.x; for (size_t i = 0; i < cchLine; i++, x += fontWidth) @@ -508,7 +508,7 @@ using namespace Microsoft::Console::Render; } } - if (lines & GridLines::Right) + if (lines.test(GridLines::Right)) { // NOTE: We have to subtract the stroke width from the cell width // to ensure the x coordinate remains inside the clipping rectangle. @@ -519,13 +519,13 @@ using namespace Microsoft::Console::Render; } } - if (lines & GridLines::Top) + if (lines.test(GridLines::Top)) { const auto y = ptTarget.y; RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } - if (lines & GridLines::Bottom) + if (lines.test(GridLines::Bottom)) { // NOTE: We have to subtract the stroke width from the cell height // to ensure the y coordinate remains inside the clipping rectangle. @@ -533,19 +533,19 @@ using namespace Microsoft::Console::Render; RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.gridlineWidth)); } - if (lines & (GridLines::Underline | GridLines::DoubleUnderline)) + if (lines.any(GridLines::Underline, GridLines::DoubleUnderline)) { const auto y = ptTarget.y + _lineMetrics.underlineOffset; RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.underlineWidth)); - if (lines & GridLines::DoubleUnderline) + if (lines.test(GridLines::DoubleUnderline)) { const auto y2 = ptTarget.y + _lineMetrics.underlineOffset2; RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y2, widthOfAllCells, _lineMetrics.underlineWidth)); } } - if (lines & GridLines::Strikethrough) + if (lines.test(GridLines::Strikethrough)) { const auto y = ptTarget.y + _lineMetrics.strikethroughOffset; RETURN_HR_IF(E_FAIL, !DrawLine(ptTarget.x, y, widthOfAllCells, _lineMetrics.strikethroughWidth)); diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index bcc98dfc4..4dd69be8a 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -30,18 +30,19 @@ namespace Microsoft::Console::Render class IRenderEngine { public: - enum GridLines + enum class GridLines { - None = 0x0, - Top = 0x1, - Bottom = 0x2, - Left = 0x4, - Right = 0x8, - Underline = 0x10, - DoubleUnderline = 0x20, - Strikethrough = 0x40, - HyperlinkUnderline = 0x80 + None, + Top, + Bottom, + Left, + Right, + Underline, + DoubleUnderline, + Strikethrough, + HyperlinkUnderline }; + using GridLineSet = til::enumset; virtual ~IRenderEngine() = 0; @@ -86,7 +87,7 @@ namespace Microsoft::Console::Render const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept = 0; - [[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLines lines, + [[nodiscard]] virtual HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept = 0; @@ -118,5 +119,3 @@ namespace Microsoft::Console::Render inline Microsoft::Console::Render::IRenderEngine::~IRenderEngine() {} } - -DEFINE_ENUM_FLAG_OPERATORS(Microsoft::Console::Render::IRenderEngine::GridLines) diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp index 2d5d17196..f3206e79d 100644 --- a/src/renderer/uia/UiaRenderer.cpp +++ b/src/renderer/uia/UiaRenderer.cpp @@ -324,7 +324,7 @@ CATCH_RETURN(); // - coordTarget - // Return Value: // - S_FALSE -[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLines const /*lines*/, +[[nodiscard]] HRESULT UiaEngine::PaintBufferGridLines(GridLineSet const /*lines*/, COLORREF const /*color*/, size_t const /*cchLine*/, COORD const /*coordTarget*/) noexcept diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp index 6591579c5..8c438f055 100644 --- a/src/renderer/uia/UiaRenderer.hpp +++ b/src/renderer/uia/UiaRenderer.hpp @@ -55,7 +55,7 @@ namespace Microsoft::Console::Render COORD const coord, bool const fTrimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp index f8b8a1266..1b9efb35e 100644 --- a/src/renderer/vt/paint.cpp +++ b/src/renderer/vt/paint.cpp @@ -142,7 +142,7 @@ using namespace Microsoft::Console::Types; // - coordTarget - The starting X/Y position of the first character to draw on. // Return Value: // - S_OK -[[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLines /*lines*/, +[[nodiscard]] HRESULT VtEngine::PaintBufferGridLines(const GridLineSet /*lines*/, const COLORREF /*color*/, const size_t /*cchLine*/, const COORD /*coordTarget*/) noexcept diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp index bb8f0413f..1965034db 100644 --- a/src/renderer/vt/vtrenderer.hpp +++ b/src/renderer/vt/vtrenderer.hpp @@ -65,7 +65,7 @@ namespace Microsoft::Console::Render const COORD coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(const GridLines lines, + [[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override; diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp index ba67819ab..1ee11d741 100644 --- a/src/renderer/wddmcon/WddmConRenderer.cpp +++ b/src/renderer/wddmcon/WddmConRenderer.cpp @@ -287,7 +287,7 @@ bool WddmConEngine::IsInitialized() CATCH_RETURN(); } -[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLines const /*lines*/, +[[nodiscard]] HRESULT WddmConEngine::PaintBufferGridLines(GridLineSet const /*lines*/, COLORREF const /*color*/, size_t const /*cchLine*/, COORD const /*coordTarget*/) noexcept diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp index 57663d5cb..0da9f5cba 100644 --- a/src/renderer/wddmcon/WddmConRenderer.hpp +++ b/src/renderer/wddmcon/WddmConRenderer.hpp @@ -45,7 +45,7 @@ namespace Microsoft::Console::Render const COORD coord, const bool trimLeft, const bool lineWrapped) noexcept override; - [[nodiscard]] HRESULT PaintBufferGridLines(GridLines const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; + [[nodiscard]] HRESULT PaintBufferGridLines(GridLineSet const lines, COLORREF const color, size_t const cchLine, COORD const coordTarget) noexcept override; [[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override; [[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override; From d25ca26142de99fcd4964a6cce90fd9fb935cfc4 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 29 Sep 2021 16:55:44 -0500 Subject: [PATCH 07/11] Always init the BG opacity with the renderer (#11368) Missed this in #11180. I forgot to init the BG opacity with the renderer on startup, because that matters when you have `"antialiasingMode": "cleartype",`. Repro json ```json { "commandline": "cmd.exe", "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", "hidden": false, "opacity": 35, "antialiasingMode": "cleartype", "padding": "0", "name": "Command Prompt" }, ``` * [x] Fixes #11315 --- src/cascadia/TerminalControl/ControlCore.cpp | 6 ++---- src/cascadia/TerminalControl/TermControl.cpp | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 534862698..efcce89c8 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -274,10 +274,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation _updateAntiAliasingMode(_renderEngine.get()); // GH#5098: Inform the engine of the opacity of the default text background. - if (_settings.UseAcrylic()) - { - _renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast(_settings.Opacity())); - } + // GH#11315: Always do this, even if they don't have acrylic on. + _renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast(_settings.Opacity())); THROW_IF_FAILED(_renderEngine->Enable()); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index f90190a5d..32bd45f6b 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -459,7 +459,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } // GH#5098: Inform the engine of the new opacity of the default text background. - _core.SetBackgroundOpacity(::base::saturated_cast(appearance.Opacity())); + _core.SetBackgroundOpacity(appearance.Opacity()); } else { From ba239026f39687ebefe482ed75db5a98534a37a6 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 29 Sep 2021 16:57:58 -0500 Subject: [PATCH 08/11] Allow the entire Tab to be hit testable again (#11369) DESPITE the fact that there's a `Background()` API that we could just call like: ```c++ TabViewItem().Background(deselectedTabBrush); ``` We actually can't, because it will make the part of the tab that doesn't contain the text totally transparent to hit tests. So we actually _do_ still need to set `TabViewItemHeaderBackground` manually. * Regressed in #11240 * Root cause up in https://github.com/microsoft/microsoft-ui-xaml/pull/3769 * [x] closes #11294 --- src/cascadia/TerminalApp/TerminalTab.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalTab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp index 8ef1847ba..aaf8768cd 100644 --- a/src/cascadia/TerminalApp/TerminalTab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -1460,7 +1460,16 @@ namespace winrt::TerminalApp::implementation // TabViewItem().Background() only sets the color of the tab background // when the TabViewItem is unselected. So we still need to set the other // properties ourselves. - TabViewItem().Background(deselectedTabBrush); + // + // GH#11294: DESPITE the fact that there's a Background() API that we + // could just call like: + // + // TabViewItem().Background(deselectedTabBrush); + // + // We actually can't, because it will make the part of the tab that + // doesn't contain the text totally transparent to hit tests. So we + // actually _do_ still need to set TabViewItemHeaderBackground manually. + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); @@ -1506,6 +1515,7 @@ namespace winrt::TerminalApp::implementation void TerminalTab::_ClearTabBackgroundColor() { winrt::hstring keys[] = { + L"TabViewItemHeaderBackground", L"TabViewItemHeaderBackgroundSelected", L"TabViewItemHeaderBackgroundPointerOver", L"TabViewItemHeaderForeground", @@ -1526,9 +1536,6 @@ namespace winrt::TerminalApp::implementation } } - // Clear out the Background. - TabViewItem().Background(nullptr); - _RefreshVisualState(); _colorCleared(); } From cf00ad7ad4fbe2f39fa2c5640609afcacdf62524 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Wed, 29 Sep 2021 16:58:26 -0500 Subject: [PATCH 09/11] Add automation names to some controls that were missing them (#11364) All these controls didn't have `Name`s assigned, and Accessibility Insights doesn't like that. Their parents did, but the actual focusable elements themselves didn't. So I've just taken the nearby headers for these things and slapped them in as the Automation names for these controls. I verified that each of these automated tests in Accessibility Insights pass again. * Will do the thing to #11155 but we need confirmation before that can be closed. --- .../TerminalSettingsEditor/Appearances.xaml | 9 ++++-- .../TerminalSettingsEditor/Launch.xaml | 6 ++-- .../TerminalSettingsEditor/Profiles.xaml | 12 ++++--- .../Resources/en-US/Resources.resw | 32 +++++++++++++++++++ 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 7765d6626..e1556e80d 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -72,13 +72,15 @@ two font lists causes a crash within the ComboBox code. As a workaround, introduce two ComboBox controls and only display one at a time. --> - - - - - diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml index 24e6a7fe1..0208a8065 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -81,7 +81,8 @@ SettingOverrideSource="{x:Bind State.Profile.CommandlineOverrideSource, Mode=OneWay}" Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}"> -