Skip to content

Commit

Permalink
Add selection marker overlays for keyboard selection
Browse files Browse the repository at this point in the history
  • Loading branch information
carlos-zamora committed Jun 2, 2022
1 parent 9dca6c2 commit 85dafbb
Show file tree
Hide file tree
Showing 11 changed files with 174 additions and 9 deletions.
26 changes: 26 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
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();
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));
}

void ControlCore::AttachUiaEngine(::Microsoft::Console::Render::IRenderEngine* const pEngine)
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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?

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
Original file line number Diff line number Diff line change
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; };
}
}
81 changes: 73 additions & 8 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
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 };
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,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
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
25 changes: 25 additions & 0 deletions src/cascadia/TerminalCore/TerminalSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ const COORD Terminal::GetSelectionEnd() const noexcept
return _selection->end;
}

til::point Terminal::SelectionStartForRendering() const
{
auto pos{ _selection->start };
const auto bufferSize{ GetTextBuffer().GetSize() };
bufferSize.DecrementInBounds(pos);
pos.Y = std::clamp<short>(base::ClampSub(pos.Y, _VisibleStartIndex()), bufferSize.Top(), bufferSize.BottomInclusive());
return til::point{ pos };
}

til::point Terminal::SelectionEndForRendering() const
{
auto pos{ _selection->end };
const auto bufferSize{ GetTextBuffer().GetSize() };
bufferSize.IncrementInBounds(pos);
pos.Y = std::clamp<short>(base::ClampSub(pos.Y, _VisibleStartIndex()), bufferSize.Top(), bufferSize.BottomInclusive());
return til::point{ pos };
}

// Method Description:
// - Checks if selection is active
// Return Value:
Expand Down Expand Up @@ -309,6 +327,13 @@ Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams
return std::nullopt;
}

bool Terminal::MovingStart() const noexcept
{
// true --> 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:
Expand Down

0 comments on commit 85dafbb

Please sign in to comment.