From 85dafbbfd4e1d08e33394377a065f458635d0b82 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 30 Jul 2021 17:46:42 -0700 Subject: [PATCH] Add selection marker overlays for keyboard selection --- src/cascadia/TerminalControl/ControlCore.cpp | 26 ++++++ src/cascadia/TerminalControl/ControlCore.h | 4 + src/cascadia/TerminalControl/ControlCore.idl | 4 + src/cascadia/TerminalControl/EventArgs.cpp | 1 + src/cascadia/TerminalControl/EventArgs.h | 13 +++ src/cascadia/TerminalControl/EventArgs.idl | 6 +- src/cascadia/TerminalControl/TermControl.cpp | 81 +++++++++++++++++-- src/cascadia/TerminalControl/TermControl.h | 3 + src/cascadia/TerminalControl/TermControl.xaml | 17 ++++ src/cascadia/TerminalCore/Terminal.hpp | 3 + .../TerminalCore/TerminalSelection.cpp | 25 ++++++ 11 files changed, 174 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index a224ca22c4d1..5b538ce4755f 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -409,6 +409,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto lock = _terminal->LockForWriting(); _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second, modifiers); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(false)); return true; } @@ -417,6 +418,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->ClearSelection(); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // When there is a selection active, escape should clear it and NOT flow through @@ -912,6 +914,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SetSelectionAnchor(position.to_win32_coord()); } + Core::Point ControlCore::SelectionAnchor() const + { + auto lock = _terminal->LockForReading(); + const auto start = _terminal->SelectionStartForRendering(); + return { start.X, start.Y }; + } + + Core::Point ControlCore::SelectionEnd() const + { + auto lock = _terminal->LockForReading(); + const auto end = _terminal->SelectionEndForRendering(); + return { end.X, end.Y }; + } + + bool ControlCore::MovingStart() const + { + auto lock = _terminal->LockForReading(); + return _terminal->MovingStart(); + } + // Method Description: // - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging. // Arguments: @@ -935,6 +957,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // save location (for rendering) + render _terminal->SetSelectionEnd(terminalPosition.to_win32_coord()); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // Called when the Terminal wants to set something to the clipboard, i.e. @@ -997,6 +1020,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->ClearSelection(); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // send data up for clipboard @@ -1351,6 +1375,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SetBlockSelection(false); search.Select(); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // Raise a FoundMatch event, which the control will use to notify @@ -1553,6 +1578,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine) diff --git a/src/cascadia/TerminalControl/ControlCore.h b/src/cascadia/TerminalControl/ControlCore.h index 9cf4355a6ce0..59733bcb2f9e 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -151,6 +151,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation bool HasSelection() const; bool CopyOnSelect() const; Windows::Foundation::Collections::IVector SelectedText(bool trimTrailingWhitespace) const; + Core::Point SelectionAnchor() const; + Core::Point SelectionEnd() const; + bool MovingStart() const; void SetSelectionAnchor(const til::point& position); void SetEndSelectionPoint(const til::point& position); @@ -208,6 +211,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable); TYPED_EVENT(FoundMatch, IInspectable, Control::FoundResultsArgs); TYPED_EVENT(ShowWindowChanged, IInspectable, Control::ShowWindowArgs); + TYPED_EVENT(UpdateSelectionMarkers, IInspectable, Control::UpdateSelectionMarkersEventArgs); // clang-format on private: diff --git a/src/cascadia/TerminalControl/ControlCore.idl b/src/cascadia/TerminalControl/ControlCore.idl index edcb9fb69b1f..e80ffdbe3dad 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -90,6 +90,9 @@ namespace Microsoft.Terminal.Control Boolean HasSelection { get; }; IVector SelectedText(Boolean trimTrailingWhitespace); + Microsoft.Terminal.Core.Point SelectionAnchor { get; }; + Microsoft.Terminal.Core.Point SelectionEnd { get; }; + Boolean MovingStart { get; }; String HoveredUriText { get; }; Windows.Foundation.IReference HoveredCell { get; }; @@ -125,6 +128,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler ReceivedOutput; event Windows.Foundation.TypedEventHandler FoundMatch; event Windows.Foundation.TypedEventHandler ShowWindowChanged; + event Windows.Foundation.TypedEventHandler UpdateSelectionMarkers; }; } diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index 665ea17f4488..9f9b41ac1f7d 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -13,3 +13,4 @@ #include "TransparencyChangedEventArgs.g.cpp" #include "FoundResultsArgs.g.cpp" #include "ShowWindowArgs.g.cpp" +#include "UpdateSelectionMarkersEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 0d7c6d4a0059..edefb2b00459 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -13,6 +13,8 @@ #include "TransparencyChangedEventArgs.g.h" #include "FoundResultsArgs.g.h" #include "ShowWindowArgs.g.h" +#include "UpdateSelectionMarkersEventArgs.g.h" +#include "cppwinrt_utils.h" // TODO CARLOS: maybe we can remove this include? namespace winrt::Microsoft::Terminal::Control::implementation { @@ -157,4 +159,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation WINRT_PROPERTY(bool, ShowOrHide); }; + + struct UpdateSelectionMarkersEventArgs : public UpdateSelectionMarkersEventArgsT + { + public: + UpdateSelectionMarkersEventArgs(const bool clearMarkers) : + _ClearMarkers(clearMarkers) + { + } + + WINRT_PROPERTY(bool, ClearMarkers, false); + }; } diff --git a/src/cascadia/TerminalControl/EventArgs.idl b/src/cascadia/TerminalControl/EventArgs.idl index 62ed095c8f44..941384c5b05d 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -69,7 +69,6 @@ namespace Microsoft.Terminal.Control Double Opacity { get; }; } - runtimeclass FoundResultsArgs { Boolean FoundMatch { get; }; @@ -79,4 +78,9 @@ namespace Microsoft.Terminal.Control { Boolean ShowOrHide { get; }; } + + runtimeclass UpdateSelectionMarkersEventArgs + { + Boolean ClearMarkers { get; }; + } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 37e4ad8096e3..592e684a560d 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.RaiseNotice({ this, &TermControl::_coreRaisedNotice }); _core.HoveredHyperlinkChanged({ this, &TermControl::_hoveredHyperlinkChanged }); _core.FoundMatch({ this, &TermControl::_coreFoundMatch }); + _core.UpdateSelectionMarkers({ this, &TermControl::_updateSelectionMarkers }); _interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler }); _interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged }); @@ -320,6 +321,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation // switch from a solid color brush to an acrylic one. _changeBackgroundColor(bg); + // Update selection markers + Windows::UI::Xaml::Media::SolidColorBrush selectionBackgroundBrush{ til::color{ newAppearance.SelectionBackground() } }; + SelectionStartIcon().Foreground(selectionBackgroundBrush); + SelectionEndIcon().Foreground(selectionBackgroundBrush); + // Set TSF Foreground Media::SolidColorBrush foregroundBrush{}; if (_core.Settings().UseBackgroundImageForWindow()) @@ -1786,6 +1792,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation update.newValue = args.ViewTop(); _updateScrollBar->Run(update); + _updateSelectionMarkers(nullptr, winrt::make(false)); } // Method Description: @@ -2685,8 +2692,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.ClearHoveredCell(); } - winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable sender, - IInspectable args) + winrt::fire_and_forget TermControl::_hoveredHyperlinkChanged(IInspectable /*sender*/, + IInspectable /*args*/) { auto weakThis{ get_weak() }; co_await wil::resume_foreground(Dispatcher()); @@ -2713,12 +2720,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation HyperlinkTooltipBorder().BorderThickness(newThickness); // Compute the location of the top left corner of the cell in DIPS - const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top }; - const til::point startPos{ lastHoveredCell.Value() }; - const til::size fontSize{ til::math::rounding, _core.FontSize() }; - const auto posInPixels{ startPos * fontSize }; - const til::point posInDIPs{ til::math::flooring, posInPixels.x / scale, posInPixels.y / scale }; - const auto locationInDIPs{ posInDIPs + marginsInDips }; + const til::point locationInDIPs{ _toPosInDips(lastHoveredCell.Value()) }; // Move the border to the top left corner of the cell OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), locationInDIPs.x - offset.x); @@ -2728,10 +2730,73 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } + winrt::fire_and_forget TermControl::_updateSelectionMarkers(IInspectable /*sender*/, Control::UpdateSelectionMarkersEventArgs args) + { + auto weakThis{ get_weak() }; + co_await resume_foreground(Dispatcher()); + if (weakThis.get() && args) + { + if (_core.HasSelection() && !args.ClearMarkers()) + { + // show/update selection markers + // figure out which endpoint to move, get it and the relevant icon (hide the other icon) + const auto movingStart{ _core.MovingStart() }; + const auto selectionAnchor{ movingStart ? _core.SelectionAnchor() : _core.SelectionEnd() }; + const auto& icon{ movingStart ? SelectionStartIcon() : SelectionEndIcon() }; + const auto& otherIcon{ movingStart ? SelectionEndIcon() : SelectionStartIcon() }; + icon.Opacity(1); + otherIcon.Opacity(0); + + // Compute the location of the top left corner of the cell in DIPS + const til::point locationInDIPs{ _toPosInDips(selectionAnchor) }; + + // Move the icon to the top left corner of the cell + SelectionCanvas().SetLeft(icon, + (locationInDIPs.x - SwapChainPanel().ActualOffset().x)); + SelectionCanvas().SetTop(icon, + (locationInDIPs.y - SwapChainPanel().ActualOffset().y)); + } + else + { + // hide selection markers + SelectionStartIcon().Opacity(0); + SelectionEndIcon().Opacity(0); + } + } + } + + til::point TermControl::_toPosInDips(const Core::Point terminalCellPos) + { + const til::point terminalPos{ terminalCellPos }; + const til::size marginsInDips{ til::math::rounding, GetPadding().Left, GetPadding().Top }; + const til::size fontSize{ til::math::rounding, _core.FontSize() }; + const til::point posInPixels{ terminalPos * fontSize }; + const auto scale{ SwapChainPanel().CompositionScaleX() }; + const til::point posInDIPs{ til::math::flooring, posInPixels.x / scale, posInPixels.y / scale }; + return posInDIPs + marginsInDips; + } + void TermControl::_coreFontSizeChanged(const int fontWidth, const int fontHeight, const bool isInitialChange) { + // scale the selection markers to be the size of a cell + auto scaleIconMarker = [fontWidth, fontHeight](Windows::UI::Xaml::Controls::FontIcon icon) { + const auto size{ icon.DesiredSize() }; + const auto scaleX = fontWidth / size.Width; + const auto scaleY = fontHeight / size.Height; + + Windows::UI::Xaml::Media::ScaleTransform transform{}; + transform.ScaleX(transform.ScaleX() * scaleX); + transform.ScaleY(transform.ScaleY() * scaleY); + icon.RenderTransform(transform); + + // now hide the icon + icon.Opacity(0); + }; + scaleIconMarker(SelectionStartIcon()); + scaleIconMarker(SelectionEndIcon()); + // Don't try to inspect the core here. The Core is raising this while // it's holding its write lock. If the handlers calls back to some // method on the TermControl on the same thread, and that _method_ calls diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index b8d70e7741f9..a3c881fb62e7 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -278,6 +278,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _FontInfoHandler(const IInspectable& sender, const FontInfoEventArgs& eventArgs); winrt::fire_and_forget _hoveredHyperlinkChanged(IInspectable sender, IInspectable args); + winrt::fire_and_forget _updateSelectionMarkers(IInspectable sender, Control::UpdateSelectionMarkersEventArgs args); void _coreFontSizeChanged(const int fontWidth, const int fontHeight, @@ -286,6 +287,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); void _coreFoundMatch(const IInspectable& sender, const Control::FoundResultsArgs& args); + + til::point _toPosInDips(const Core::Point terminalCellPos); }; } diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index 94afdb9c438f..71f97be53b3d 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -1213,6 +1213,23 @@ + + + + + + we're moving start endpoint ("higher") + // false --> we're moving end endpoint ("lower") + return _selection->start == _selection->pivot ? false : true; +} + // Method Description: // - updates the selection endpoints based on a direction and expansion mode. Primarily used for keyboard selection. // Arguments: