Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add selection marker overlays for keyboard selection #10865

Merged
merged 12 commits into from Jun 20, 2022
26 changes: 26 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Expand Up @@ -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<implementation::UpdateSelectionMarkersEventArgs>(false));
return true;
}

Expand All @@ -417,6 +418,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}

// When there is a selection active, escape should clear it and NOT flow through
Expand Down Expand Up @@ -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();
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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:
Expand All @@ -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<implementation::UpdateSelectionMarkersEventArgs>(true));
}

// Called when the Terminal wants to set something to the clipboard, i.e.
Expand Down Expand Up @@ -997,6 +1020,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_terminal->ClearSelection();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}

// send data up for clipboard
Expand Down Expand Up @@ -1351,6 +1375,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetBlockSelection(false);
search.Select();
_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
}

// Raise a FoundMatch event, which the control will use to notify
Expand Down Expand Up @@ -1553,6 +1578,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}

_renderer->TriggerSelection();
_UpdateSelectionMarkersHandlers(*this, winrt::make<implementation::UpdateSelectionMarkersEventArgs>(true));
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
}

void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Expand Up @@ -151,6 +151,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool HasSelection() const;
bool CopyOnSelect() const;
Windows::Foundation::Collections::IVector<winrt::hstring> 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);

Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.idl
Expand Up @@ -90,6 +90,9 @@ namespace Microsoft.Terminal.Control

Boolean HasSelection { get; };
IVector<String> SelectedText(Boolean trimTrailingWhitespace);
Microsoft.Terminal.Core.Point SelectionAnchor { get; };
Microsoft.Terminal.Core.Point SelectionEnd { get; };
Boolean MovingStart { get; };

String HoveredUriText { get; };
Windows.Foundation.IReference<Microsoft.Terminal.Core.Point> HoveredCell { get; };
Expand Down Expand Up @@ -125,6 +128,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
event Windows.Foundation.TypedEventHandler<Object, FoundResultsArgs> FoundMatch;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;

};
}
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/EventArgs.cpp
Expand Up @@ -13,3 +13,4 @@
#include "TransparencyChangedEventArgs.g.cpp"
#include "FoundResultsArgs.g.cpp"
#include "ShowWindowArgs.g.cpp"
#include "UpdateSelectionMarkersEventArgs.g.cpp"
13 changes: 13 additions & 0 deletions src/cascadia/TerminalControl/EventArgs.h
Expand Up @@ -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?
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved

namespace winrt::Microsoft::Terminal::Control::implementation
{
Expand Down Expand Up @@ -157,4 +159,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation

WINRT_PROPERTY(bool, ShowOrHide);
};

struct UpdateSelectionMarkersEventArgs : public UpdateSelectionMarkersEventArgsT<UpdateSelectionMarkersEventArgs>
{
public:
UpdateSelectionMarkersEventArgs(const bool clearMarkers) :
_ClearMarkers(clearMarkers)
{
}

WINRT_PROPERTY(bool, ClearMarkers, false);
};
}
6 changes: 5 additions & 1 deletion src/cascadia/TerminalControl/EventArgs.idl
Expand Up @@ -69,7 +69,6 @@ namespace Microsoft.Terminal.Control
Double Opacity { get; };
}


runtimeclass FoundResultsArgs
{
Boolean FoundMatch { get; };
Expand All @@ -79,4 +78,9 @@ namespace Microsoft.Terminal.Control
{
Boolean ShowOrHide { get; };
}

runtimeclass UpdateSelectionMarkersEventArgs
{
Boolean ClearMarkers { get; };
}
}
92 changes: 84 additions & 8 deletions src/cascadia/TerminalControl/TermControl.cpp
Expand Up @@ -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 });

Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -1786,6 +1792,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
update.newValue = args.ViewTop();

_updateScrollBar->Run(update);
_updateSelectionMarkers(nullptr, winrt::make<UpdateSelectionMarkersEventArgs>(false));
}

// Method Description:
Expand Down Expand Up @@ -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());
Expand All @@ -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 };
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand All @@ -2728,10 +2730,84 @@ 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())
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
// 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() };
if (selectionAnchor.Y < 0 || selectionAnchor.Y >= _core.ViewHeight())
{
// if the endpoint is outside of the viewport,
// just hide the markers
icon.Opacity(0);
otherIcon.Opacity(0);
co_return;
}
else
{
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 };
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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);
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Expand Up @@ -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,
Expand All @@ -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);
};
}

Expand Down
17 changes: 17 additions & 0 deletions src/cascadia/TerminalControl/TermControl.xaml
Expand Up @@ -1213,6 +1213,23 @@
</ToolTipService.ToolTip>
</Border>
</Canvas>

<Canvas x:Name="SelectionCanvas"
Visibility="Visible">
<!--
LOAD BEARING Use Opacity instead of Visibility for these two FontIcons.
Visibility::Collapsed prevents us from acquiring the desired size of the icons,
resulting in an inability to scale them upon a FontSize change event
-->
<FontIcon x:Name="SelectionStartIcon"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE893;"
Opacity="0" />
<FontIcon x:Name="SelectionEndIcon"
FontFamily="Segoe MDL2 Assets"
Glyph="&#xE892;"
Opacity="0" />
</Canvas>
</SwapChainPanel>

<!--
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Expand Up @@ -248,6 +248,9 @@ class Microsoft::Terminal::Core::Terminal final :

using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey) const;
bool MovingStart() const noexcept;
til::point SelectionStartForRendering() const;
til::point SelectionEndForRendering() const;

const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
Expand Down