From ad2678051f2e1abc979010276443de1a1f8e6a07 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 | 24 ++++++ src/cascadia/TerminalControl/ControlCore.h | 4 + src/cascadia/TerminalControl/ControlCore.idl | 4 + src/cascadia/TerminalControl/EventArgs.cpp | 1 + src/cascadia/TerminalControl/EventArgs.h | 12 +++ src/cascadia/TerminalControl/EventArgs.idl | 5 ++ src/cascadia/TerminalControl/TermControl.cpp | 80 +++++++++++++++++-- src/cascadia/TerminalControl/TermControl.h | 3 + src/cascadia/TerminalControl/TermControl.xaml | 17 ++++ src/cascadia/TerminalCore/Terminal.hpp | 3 + .../TerminalCore/TerminalSelection.cpp | 18 +++++ 11 files changed, 163 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index d1e26804d39..a3b68a66daa 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -378,6 +378,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto lock = _terminal->LockForWriting(); _terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(false)); return true; } @@ -386,6 +387,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 @@ -909,6 +911,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SetSelectionAnchor(position); } + Core::Point ControlCore::SelectionAnchor() const + { + auto lock = _terminal->LockForReading(); + return til::point{ _terminal->SelectionStartForRendering() }; + } + + Core::Point ControlCore::SelectionEnd() const + { + auto lock = _terminal->LockForReading(); + return til::point{ _terminal->SelectionEndForRendering() }; + } + + 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 +955,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // save location (for rendering) + render _terminal->SetSelectionEnd(terminalPosition); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // Called when the Terminal wants to set something to the clipboard, i.e. @@ -995,6 +1016,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { _terminal->ClearSelection(); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } // send data up for clipboard @@ -1286,6 +1308,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _terminal->SetBlockSelection(false); search.Select(); _renderer->TriggerSelection(); + _UpdateSelectionMarkersHandlers(*this, winrt::make(true)); } } @@ -1478,6 +1501,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 a04032ef78e..3a7ed43f056 100644 --- a/src/cascadia/TerminalControl/ControlCore.h +++ b/src/cascadia/TerminalControl/ControlCore.h @@ -128,6 +128,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(til::point const& position); void SetEndSelectionPoint(til::point const& position); @@ -169,6 +172,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation TYPED_EVENT(RaiseNotice, IInspectable, Control::NoticeEventArgs); TYPED_EVENT(TransparencyChanged, IInspectable, Control::TransparencyChangedEventArgs); TYPED_EVENT(ReceivedOutput, IInspectable, IInspectable); + 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 84cb83e80f2..9ebdedd6d55 100644 --- a/src/cascadia/TerminalControl/ControlCore.idl +++ b/src/cascadia/TerminalControl/ControlCore.idl @@ -80,6 +80,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; }; @@ -110,6 +113,7 @@ namespace Microsoft.Terminal.Control event Windows.Foundation.TypedEventHandler RaiseNotice; event Windows.Foundation.TypedEventHandler TransparencyChanged; event Windows.Foundation.TypedEventHandler ReceivedOutput; + event Windows.Foundation.TypedEventHandler UpdateSelectionMarkers; }; } diff --git a/src/cascadia/TerminalControl/EventArgs.cpp b/src/cascadia/TerminalControl/EventArgs.cpp index f4b4d9bc786..b93079ab0cb 100644 --- a/src/cascadia/TerminalControl/EventArgs.cpp +++ b/src/cascadia/TerminalControl/EventArgs.cpp @@ -11,3 +11,4 @@ #include "ScrollPositionChangedArgs.g.cpp" #include "RendererWarningArgs.g.cpp" #include "TransparencyChangedEventArgs.g.cpp" +#include "UpdateSelectionMarkersEventArgs.g.cpp" diff --git a/src/cascadia/TerminalControl/EventArgs.h b/src/cascadia/TerminalControl/EventArgs.h index 92b60f393d1..d8d55a21430 100644 --- a/src/cascadia/TerminalControl/EventArgs.h +++ b/src/cascadia/TerminalControl/EventArgs.h @@ -11,6 +11,7 @@ #include "ScrollPositionChangedArgs.g.h" #include "RendererWarningArgs.g.h" #include "TransparencyChangedEventArgs.g.h" +#include "UpdateSelectionMarkersEventArgs.g.h" #include "cppwinrt_utils.h" namespace winrt::Microsoft::Terminal::Control::implementation @@ -131,4 +132,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation WINRT_PROPERTY(double, Opacity); }; + + 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 dae093f51f8..d7268f549d8 100644 --- a/src/cascadia/TerminalControl/EventArgs.idl +++ b/src/cascadia/TerminalControl/EventArgs.idl @@ -67,4 +67,9 @@ namespace Microsoft.Terminal.Control { Double Opacity { get; }; } + + runtimeclass UpdateSelectionMarkersEventArgs + { + Boolean ClearMarkers { get; }; + } } diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 115f9afc580..a44dc12908b 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -83,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.TransparencyChanged({ this, &TermControl::_coreTransparencyChanged }); _core.RaiseNotice({ this, &TermControl::_coreRaisedNotice }); _core.HoveredHyperlinkChanged({ this, &TermControl::_hoveredHyperlinkChanged }); + _core.UpdateSelectionMarkers({ this, &TermControl::_updateSelectionMarkers }); _interactivity.OpenHyperlink({ this, &TermControl::_HyperlinkHandler }); _interactivity.ScrollPositionChanged({ this, &TermControl::_ScrollPositionChanged }); @@ -333,6 +334,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation const auto bg = newAppearance.DefaultBackground(); _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{}; foregroundBrush.Color(static_cast(newAppearance.DefaultForeground())); @@ -1659,6 +1665,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation update.newValue = args.ViewTop(); _updateScrollBar->Run(update); + _updatePatternLocations->Run(); + _updateSelectionMarkers(nullptr, winrt::make(false)); } // Method Description: @@ -2464,8 +2472,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 resume_foreground(Dispatcher()); @@ -2488,12 +2496,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 til::point posInPixels{ startPos * fontSize }; - const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() }; - const til::point locationInDIPs{ posInDIPs + marginsInDips }; + const til::point locationInDIPs{ _toPosInDips(lastHoveredCell.Value()) }; // Move the border to the top left corner of the cell OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), @@ -2505,10 +2508,71 @@ 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 til::point 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{ terminalCellPos * fontSize }; + const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() }; + 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 9d0f22e339f..c0727b3e850 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -267,6 +267,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, @@ -274,6 +275,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation winrt::fire_and_forget _coreTransparencyChanged(IInspectable sender, Control::TransparencyChangedEventArgs args); void _coreRaisedNotice(const IInspectable& s, const Control::NoticeEventArgs& args); void _coreWarningBell(const IInspectable& sender, const IInspectable& args); + + til::point _toPosInDips(const til::point terminalCellPos); }; } diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index 94aa92c5fa0..f0789fcaf92 100644 --- a/src/cascadia/TerminalControl/TermControl.xaml +++ b/src/cascadia/TerminalControl/TermControl.xaml @@ -73,6 +73,23 @@ + + + + + + + +