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

Display URI tooltip, render dashed/solid underline for links #7420

Merged
merged 49 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
16720a0
osc 8 support for conhost and terminal
PankajBhojwani Aug 11, 2020
51bd477
Correctly gets buffer position on click now
PankajBhojwani Aug 11, 2020
d268322
small fixes
PankajBhojwani Aug 12, 2020
3090b1c
more small fixes
PankajBhojwani Aug 12, 2020
b81014e
Merge branch 'master' of https://github.com/microsoft/terminal into d…
PankajBhojwani Aug 12, 2020
015fb8f
tryna pass pipeline checks
PankajBhojwani Aug 12, 2020
4a74f83
send event upon hyperlink click, allow custom ids, remove obsolete re…
PankajBhojwani Aug 15, 2020
049ee61
text buffer tests
PankajBhojwani Aug 17, 2020
c18f32a
spell
PankajBhojwani Aug 17, 2020
bd74eff
output engine test
PankajBhojwani Aug 17, 2020
0327750
more tests
PankajBhojwani Aug 17, 2020
405066e
resolve conflict
PankajBhojwani Aug 17, 2020
5995275
fixes
PankajBhojwani Aug 18, 2020
409b358
some requested changes
PankajBhojwani Aug 18, 2020
dfc8771
more changes
PankajBhojwani Aug 18, 2020
c28d2a6
conflict resolution
PankajBhojwani Aug 19, 2020
8a105d7
bug fix
PankajBhojwani Aug 19, 2020
7e96737
small changes
PankajBhojwani Aug 21, 2020
e19fbe5
fixed hyperlink not working after resize
PankajBhojwani Aug 21, 2020
bc70439
fixes
PankajBhojwani Aug 21, 2020
9d65b32
noexcept
PankajBhojwani Aug 21, 2020
aefb2e0
more noexcept
PankajBhojwani Aug 21, 2020
d0de3d5
except
PankajBhojwani Aug 21, 2020
eb3cda5
optimize prune hyperlinks
PankajBhojwani Aug 21, 2020
63723e8
hyperlinks is not a recognized word??
PankajBhojwani Aug 21, 2020
01928d3
check ctrl first then shift
PankajBhojwani Aug 24, 2020
121f51c
fuzzer
PankajBhojwani Aug 24, 2020
52e5743
fix
PankajBhojwani Aug 24, 2020
d1b0e83
terminator, blank space fixes
PankajBhojwani Aug 25, 2020
82cc197
ctrl+shift when no uri updates selection
PankajBhojwani Aug 26, 2020
1bece75
hover tooltip
PankajBhojwani Aug 26, 2020
351e08d
remove dead code
PankajBhojwani Aug 27, 2020
0cbd9a2
border is now 1 cell big
PankajBhojwani Aug 27, 2020
9de05e3
change colour on hover
PankajBhojwani Aug 28, 2020
8cd678f
spell
PankajBhojwani Aug 28, 2020
3bd29ca
underline on hover
PankajBhojwani Aug 28, 2020
8477af0
small optimization
PankajBhojwani Aug 31, 2020
953b49a
remove manual cast
PankajBhojwani Aug 31, 2020
3b8393d
renaming
PankajBhojwani Sep 2, 2020
c00ec2e
conflict resolution
PankajBhojwani Sep 3, 2020
e36f271
format
PankajBhojwani Sep 3, 2020
9d4f15c
variable stroke refernce, double underline fix
PankajBhojwani Sep 3, 2020
593ed9e
Merge branch 'master' of https://github.com/microsoft/terminal into d…
PankajBhojwani Sep 3, 2020
26dd3de
renames
PankajBhojwani Sep 3, 2020
1dcc60e
remove unused method
PankajBhojwani Sep 3, 2020
c273160
add italic text
PankajBhojwani Sep 4, 2020
46a6ba5
localize run
PankajBhojwani Sep 4, 2020
f42fa17
fontstyle left in xaml file
PankajBhojwani Sep 4, 2020
11b055b
remove fontstyle from resw
PankajBhojwani Sep 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,46 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_TryStopAutoScroll(ptr.PointerId());
}
}
const auto terminalPos = _GetTerminalPosition(point.Position());
if (terminalPos != _lastHoveredCell)
{
const auto uri = _terminal->GetHyperlinkAtPosition(terminalPos);
if (!uri.empty())
{
// Update the tooltip with the URI
LinkTip().Content(winrt::box_value(uri));

// Set the border thickness so it covers the entire cell
const auto charSizeInPixels = CharacterDimensions();
const auto htInDips = charSizeInPixels.Height / SwapChainPanel().CompositionScaleY();
const auto wtInDips = charSizeInPixels.Width / SwapChainPanel().CompositionScaleX();
const Thickness newThickness{ wtInDips, htInDips, 0, 0 };
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{ terminalPos.X, terminalPos.Y };
const til::size fontSize{ _actualFont.GetSize() };
const til::point posInPixels{ startPos * fontSize };
const til::point posInDIPs{ posInPixels / SwapChainPanel().CompositionScaleX() };
const til::point locationInDIPs{ posInDIPs + marginsInDips };

// Move the border to the top left corner of the cell
OverlayCanvas().SetLeft(HyperlinkTooltipBorder(), (locationInDIPs.x() - SwapChainPanel().ActualOffset().x));
OverlayCanvas().SetTop(HyperlinkTooltipBorder(), (locationInDIPs.y() - SwapChainPanel().ActualOffset().y));
}
_lastHoveredCell = terminalPos;

const auto newId = _terminal->GetHyperlinkIdAtPosition(terminalPos);
// If the hyperlink ID changed, trigger a redraw all (so this will happen both when we move
// onto a link and when we move off a link)
if (newId != _lastHoveredId)
{
_renderEngine->UpdateHyperlinkHoveredId(newId);
_renderer->TriggerRedrawAll();
_lastHoveredId = newId;
}
Comment on lines +1290 to +1295
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this logic (check equality, store last hovered ID, trigger redraw) all belongs inside UpdateHyperlinkHoveredId. Is that bad for the render engine contract @miniksa?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(right now we're tracking the last ID in two places)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh its frustrating. The problem is that the specific engine has no way of signaling the renderer base to do the redraw all operation. When I originally laid it out, it was supposed to be getting commands inbound only.

This isn't that far off from us storing the invalid regions or cursor positions in multiple places. For instance, some of the renderers remember the last cursor pos they drew so they can erase the cursor "turds". Yes, it's two places... but there's precedent and I'm not sure what else is to be done beyond rearchitecting, which is out of scope.

}
}
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch && _touchAnchor)
{
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// viewport via touch input.
std::optional<winrt::Windows::Foundation::Point> _touchAnchor;

// Track the last cell we hovered over (used in pointerMovedHandler)
COORD _lastHoveredCell;
// Track the last hyperlink ID we hovered over
uint16_t _lastHoveredId;

using Timestamp = uint64_t;

// imported from WinUser
Expand Down
14 changes: 13 additions & 1 deletion src/cascadia/TerminalControl/TermControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,19 @@

<SwapChainPanel x:Name="SwapChainPanel"
SizeChanged="_SwapChainSizeChanged"
CompositionScaleChanged="_SwapChainScaleChanged" />
CompositionScaleChanged="_SwapChainScaleChanged">

<Canvas x:Name="OverlayCanvas"
Visibility="Visible">
<Border x:Name="HyperlinkTooltipBorder"
BorderBrush="Transparent">
<ToolTipService.ToolTip>
<ToolTip x:Name="LinkTip"
Placement="Mouse"/>
</ToolTipService.ToolTip>
</Border>
</Canvas>
</SwapChainPanel>

<!-- Putting this in a grid w/ the SwapChainPanel
ensures that it's always aligned w/ the scrollbar -->
Expand Down
11 changes: 11 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,17 @@ std::wstring Terminal::GetHyperlinkAtPosition(const COORD position)
return {};
}

// Method Description:
// - Gets the hyperlink ID of the text at the given terminal position
// Arguments:
// - The position of the text
// Return value:
// - The hyperlink ID
uint16_t Terminal::GetHyperlinkIdAtPosition(const COORD position)
{
return _buffer->GetCellDataAt(_ConvertToBufferCell(position))->TextAttr().GetHyperlinkId();
}

// Method Description:
// - Send this particular (non-character) key event to the terminal.
// - The terminal will translate the key and the modifiers pressed into the
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class Microsoft::Terminal::Core::Terminal final :
bool IsTrackingMouseInput() const noexcept;

std::wstring GetHyperlinkAtPosition(const COORD position);
uint16_t GetHyperlinkIdAtPosition(const COORD position);
#pragma endregion

#pragma region IBaseData(base to IRenderData and IUiaData)
Expand Down
5 changes: 5 additions & 0 deletions src/renderer/base/renderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,11 @@ IRenderEngine::GridLines Renderer::s_GetGridlines(const TextAttribute& textAttri
{
lines |= IRenderEngine::GridLines::DoubleUnderline;
}

if (textAttribute.IsHyperlink())
{
lines |= IRenderEngine::GridLines::HyperlinkUnderline;
}
return lines;
}

Expand Down
48 changes: 44 additions & 4 deletions src/renderer/dx/DxRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noe
RETURN_IF_FAILED(_d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White),
&_d2dBrushForeground));

const D2D1_STROKE_STYLE_PROPERTIES strokeStyleProperties{
_strokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{
D2D1_CAP_STYLE_SQUARE, // startCap
D2D1_CAP_STYLE_SQUARE, // endCap
D2D1_CAP_STYLE_SQUARE, // dashCap
Expand All @@ -627,7 +627,19 @@ static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noe
D2D1_DASH_STYLE_SOLID, // dashStyle
0.f, // dashOffset
};
RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&strokeStyleProperties, nullptr, 0, &_strokeStyle));
RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_strokeStyleProperties, nullptr, 0, &_strokeStyle));

_dashStrokeStyleProperties = D2D1_STROKE_STYLE_PROPERTIES{
D2D1_CAP_STYLE_SQUARE, // startCap
D2D1_CAP_STYLE_SQUARE, // endCap
D2D1_CAP_STYLE_SQUARE, // dashCap
D2D1_LINE_JOIN_MITER, // lineJoin
0.f, // miterLimit
D2D1_DASH_STYLE_DASH, // dashStyle
0.f, // dashOffset
};
RETURN_IF_FAILED(_d2dFactory->CreateStrokeStyle(&_dashStrokeStyleProperties, nullptr, 0, &_dashStrokeStyle));
_hyperlinkStrokeStyle = _dashStrokeStyle;

// If in composition mode, apply scaling factor matrix
if (_chainMode == SwapChainMode::ForComposition)
Expand Down Expand Up @@ -1492,6 +1504,10 @@ try
_d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _strokeStyle.Get());
};

const auto DrawHyperlinkLine = [=](const auto x0, const auto y0, const auto x1, const auto y1, const auto strokeWidth) noexcept {
_d2dDeviceContext->DrawLine({ x0, y0 }, { x1, y1 }, _d2dBrushForeground.Get(), strokeWidth, _hyperlinkStrokeStyle.Get());
};

// NOTE: Line coordinates are centered within the line, so they need to be
// 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.
Expand Down Expand Up @@ -1543,17 +1559,26 @@ 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 (lines & (GridLines::Underline | GridLines::DoubleUnderline))
if (lines & (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;

DrawLine(startX, y, endX, y, _lineMetrics.underlineWidth);
if (lines & GridLines::Underline)
{
DrawLine(startX, y, endX, y, _lineMetrics.underlineWidth);
}

if (lines & GridLines::HyperlinkUnderline)
{
DrawHyperlinkLine(startX, y, endX, y, _lineMetrics.underlineWidth);
}

if (lines & GridLines::DoubleUnderline)
{
DrawLine(startX, y, endX, y, _lineMetrics.underlineWidth);
const auto y2 = target.y + _lineMetrics.underlineOffset2;
DrawLine(startX, y2, endX, y2, _lineMetrics.underlineWidth);
}
Expand Down Expand Up @@ -1715,6 +1740,11 @@ CATCH_RETURN()
_drawingContext->forceGrayscaleAA = _ShouldForceGrayscaleAA();
}

if (textAttributes.IsHyperlink())
{
_hyperlinkStrokeStyle = (textAttributes.GetHyperlinkId() == _hyperlinkHoveredId) ? _strokeStyle : _dashStrokeStyle;
}

return S_OK;
}

Expand Down Expand Up @@ -2403,6 +2433,16 @@ try
}
CATCH_LOG()

// Method Description:
// - Updates our internal tracker for which hyperlink ID we are hovering over
// This is needed for UpdateDrawingBrushes to know where we need to set a different style
// Arguments:
// - The new link ID we are hovering over
void DxEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
{
_hyperlinkHoveredId = hoveredId;
}

// Method Description:
// - Informs this render engine about certain state for this frame at the
// beginning of this frame. We'll use it to get information about the cursor
Expand Down
9 changes: 9 additions & 0 deletions src/renderer/dx/DxRenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ namespace Microsoft::Console::Render
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;

void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;

protected:
[[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring& newTitle) noexcept override;
[[nodiscard]] HRESULT _PaintTerminalEffects() noexcept;
Expand Down Expand Up @@ -164,6 +166,8 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;

uint16_t _hyperlinkHoveredId;

bool _firstFrame;
bool _invalidateFullRows;
til::bitmap _invalidMap;
Expand All @@ -188,6 +192,11 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<CustomTextLayout> _customLayout;
::Microsoft::WRL::ComPtr<CustomTextRenderer> _customRenderer;
::Microsoft::WRL::ComPtr<ID2D1StrokeStyle> _strokeStyle;
::Microsoft::WRL::ComPtr<ID2D1StrokeStyle> _dashStrokeStyle;
::Microsoft::WRL::ComPtr<ID2D1StrokeStyle> _hyperlinkStrokeStyle;

D2D1_STROKE_STYLE_PROPERTIES _strokeStyleProperties;
D2D1_STROKE_STYLE_PROPERTIES _dashStrokeStyleProperties;

// Device-Dependent Resources
bool _recreateDeviceRequested;
Expand Down
3 changes: 2 additions & 1 deletion src/renderer/inc/IRenderEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ namespace Microsoft::Console::Render
Right = 0x8,
Underline = 0x10,
DoubleUnderline = 0x20,
Strikethrough = 0x40
Strikethrough = 0x40,
HyperlinkUnderline = 0x80
};

virtual ~IRenderEngine() = 0;
Expand Down
1 change: 1 addition & 0 deletions src/renderer/vt/vtrenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _SetInvisible(const bool isInvisible) noexcept;
[[nodiscard]] HRESULT _SetCrossedOut(const bool isCrossedOut) noexcept;
[[nodiscard]] HRESULT _SetReverseVideo(const bool isReversed) noexcept;

[[nodiscard]] HRESULT _SetHyperlink(const std::wstring_view& uri, const std::wstring_view& customId, const uint16_t& numberId) noexcept;
[[nodiscard]] HRESULT _EndHyperlink() noexcept;

Expand Down