Add support for double-width/double-height lines in conhost (#8664)
This PR adds support for the VT line rendition attributes, which allow for double-width and double-height line renditions. These renditions are enabled with the `DECDWL` (double-width line) and `DECDHL` (double-height line) escape sequences. Both reset to the default rendition with the `DECSWL` (single-width line) escape sequence. For now this functionality is only supported by the GDI renderer in conhost. There are a lot of changes, so this is just a general overview of the main areas affected. Previously it was safe to assume that the screen had a fixed width, at least for a given point in time. But now we need to deal with the possibility of different lines have different widths, so all the functions that are constrained by the right border (text wrapping, cursor movement operations, and sequences like `EL` and `ICH`) now need to lookup the width of the active line in order to behave correctly. Similarly it used to be safe to assume that buffer and screen coordinates were the same thing, but that is no longer true. Lots of places now need to translate back and forth between coordinate systems dependent on the line rendition. This includes clipboard handling, the conhost color selection and search, accessibility location tracking and screen reading, IME editor positioning, "snapping" the viewport, and of course all the rendering calculations. For the rendering itself, I've had to introduce a new `PrepareLineTransform` method that the render engines can use to setup the necessary transform matrix for a given line rendition. This is also now used to handle the horizontal viewport offset, since that could no longer be achieved just by changing the target coordinates (on a double width line, the viewport offset may be halfway through a character). I've also had to change the renderer's existing `InvalidateCursor` method to take a `SMALL_RECT` rather than a `COORD`, to allow for the cursor being a variable width. Technically this was already a problem, because the cursor could occupy two screen cells when over a double-width character, but now it can be anything between one and four screen cells (e.g. a double-width character on the double-width line). In terms of architectural changes, there is now a new `lineRendition` field in the `ROW` class that keeps track of the line rendition for each row, and several new methods in the `ROW` and `TextBuffer` classes for manipulating that state. This includes a few helper methods for handling the various issues discussed above, e.g. position clamping and translating between coordinate systems. ## Validation Steps Performed I've manually confirmed all the double-width and double-height tests in _Vttest_ are now working as expected, and the _VT100 Torture Test_ now renders correctly (at least the line rendition aspects). I've also got my own test scripts that check many of the line rendition boundary cases and have confirmed that those are now passing. I've manually tested as many areas of the conhost UI that I could think of, that might be affected by line rendition, including things like searching, selection, copying, and color highlighting. For accessibility, I've confirmed that the _Magnifier_ and _Narrator_ correctly handle double-width lines. And I've also tested the Japanese IME, which while not perfect, is at least useable. Closes #7865
This commit is contained in:
parent
72cbe59078
commit
4c53c595e7
4
.github/actions/spelling/expect/expect.txt
vendored
4
.github/actions/spelling/expect/expect.txt
vendored
|
@ -521,6 +521,8 @@ DECAUPSS
|
|||
DECAWM
|
||||
DECCKM
|
||||
DECCOLM
|
||||
DECDHL
|
||||
DECDWL
|
||||
DECEKBD
|
||||
DECID
|
||||
DECKPAM
|
||||
|
@ -557,6 +559,7 @@ DECSR
|
|||
decstandar
|
||||
DECSTBM
|
||||
DECSTR
|
||||
DECSWL
|
||||
DECTCEM
|
||||
Dedupe
|
||||
deduplicated
|
||||
|
@ -2825,6 +2828,7 @@ Xes
|
|||
XES
|
||||
xff
|
||||
XFile
|
||||
XFORM
|
||||
XManifest
|
||||
XMath
|
||||
XMFLOAT
|
||||
|
|
36
src/buffer/out/LineRendition.hpp
Normal file
36
src/buffer/out/LineRendition.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- LineRendition.hpp
|
||||
|
||||
Abstract:
|
||||
- Enumerated type for the VT line rendition attribute. This determines the
|
||||
width and height scaling with which each line is rendered.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
enum class LineRendition
|
||||
{
|
||||
SingleWidth,
|
||||
DoubleWidth,
|
||||
DoubleHeightTop,
|
||||
DoubleHeightBottom
|
||||
};
|
||||
|
||||
constexpr SMALL_RECT ScreenToBufferLine(const SMALL_RECT& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift right to quickly divide the Left and Right by 2 for double width lines.
|
||||
const auto scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left >> scale, line.Top, line.Right >> scale, line.Bottom };
|
||||
}
|
||||
|
||||
constexpr SMALL_RECT BufferToScreenLine(const SMALL_RECT& line, const LineRendition lineRendition)
|
||||
{
|
||||
// Use shift left to quickly multiply the Left and Right by 2 for double width lines.
|
||||
const SHORT scale = lineRendition == LineRendition::SingleWidth ? 0 : 1;
|
||||
return { line.Left << scale, line.Top, (line.Right << scale) + scale, line.Bottom };
|
||||
}
|
|
@ -21,6 +21,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
|
|||
_rowWidth{ rowWidth },
|
||||
_charRow{ rowWidth, this },
|
||||
_attrRow{ rowWidth, fillAttribute },
|
||||
_lineRendition{ LineRendition::SingleWidth },
|
||||
_wrapForced{ false },
|
||||
_doubleBytePadded{ false },
|
||||
_pParent{ pParent }
|
||||
|
@ -35,6 +36,7 @@ ROW::ROW(const SHORT rowId, const unsigned short rowWidth, const TextAttribute f
|
|||
// - <none>
|
||||
bool ROW::Reset(const TextAttribute Attr)
|
||||
{
|
||||
_lineRendition = LineRendition::SingleWidth;
|
||||
_wrapForced = false;
|
||||
_doubleBytePadded = false;
|
||||
_charRow.Reset();
|
||||
|
|
|
@ -21,6 +21,7 @@ Revision History:
|
|||
#pragma once
|
||||
|
||||
#include "AttrRow.hpp"
|
||||
#include "LineRendition.hpp"
|
||||
#include "OutputCell.hpp"
|
||||
#include "OutputCellIterator.hpp"
|
||||
#include "CharRow.hpp"
|
||||
|
@ -48,6 +49,9 @@ public:
|
|||
const ATTR_ROW& GetAttrRow() const noexcept { return _attrRow; }
|
||||
ATTR_ROW& GetAttrRow() noexcept { return _attrRow; }
|
||||
|
||||
LineRendition GetLineRendition() const noexcept { return _lineRendition; }
|
||||
void SetLineRendition(const LineRendition lineRendition) noexcept { _lineRendition = lineRendition; }
|
||||
|
||||
SHORT GetId() const noexcept { return _id; }
|
||||
void SetId(const SHORT id) noexcept { _id = id; }
|
||||
|
||||
|
@ -70,6 +74,7 @@ public:
|
|||
private:
|
||||
CharRow _charRow;
|
||||
ATTR_ROW _attrRow;
|
||||
LineRendition _lineRendition;
|
||||
SHORT _id;
|
||||
unsigned short _rowWidth;
|
||||
// Occurs when the user runs out of text in a given row and we're forced to wrap the cursor to the next line
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
<ClInclude Include="..\cursor.h" />
|
||||
<ClInclude Include="..\DbcsAttribute.hpp" />
|
||||
<ClInclude Include="..\ICharRow.hpp" />
|
||||
<ClInclude Include="..\LineRendition.hpp" />
|
||||
<ClInclude Include="..\OutputCell.hpp" />
|
||||
<ClInclude Include="..\OutputCellIterator.hpp" />
|
||||
<ClInclude Include="..\OutputCellRect.hpp" />
|
||||
|
|
|
@ -97,7 +97,12 @@ bool Search::FindNext()
|
|||
// - Takes the found word and selects it in the screen buffer
|
||||
void Search::Select() const
|
||||
{
|
||||
_uiaData.SelectNewRegion(_coordSelStart, _coordSelEnd);
|
||||
// Convert buffer selection offsets into the equivalent screen coordinates
|
||||
// required by SelectNewRegion, taking line renditions into account.
|
||||
const auto& textBuffer = _uiaData.GetTextBuffer();
|
||||
const auto selStart = textBuffer.BufferToScreenPosition(_coordSelStart);
|
||||
const auto selEnd = textBuffer.BufferToScreenPosition(_coordSelEnd);
|
||||
_uiaData.SelectNewRegion(selStart, selEnd);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -141,7 +146,10 @@ COORD Search::s_GetInitialAnchor(IUiaData& uiaData, const Direction direction)
|
|||
const COORD textBufferEndPosition = uiaData.GetTextBufferEndPosition();
|
||||
if (uiaData.IsSelectionActive())
|
||||
{
|
||||
auto anchor = uiaData.GetSelectionAnchor();
|
||||
// Convert the screen position of the selection anchor into an equivalent
|
||||
// buffer position to start searching, taking line rendition into account.
|
||||
auto anchor = textBuffer.ScreenToBufferPosition(uiaData.GetSelectionAnchor());
|
||||
|
||||
if (direction == Direction::Forward)
|
||||
{
|
||||
textBuffer.GetSize().IncrementInBoundsCircular(anchor);
|
||||
|
|
|
@ -290,13 +290,14 @@ bool TextBuffer::_PrepareForDoubleByteSequence(const DbcsAttribute dbcsAttribute
|
|||
// We only need to compensate for leading bytes
|
||||
if (dbcsAttribute.IsLeading())
|
||||
{
|
||||
short const sBufferWidth = GetSize().Width();
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto lineWidth = GetLineWidth(cursorPosition.Y);
|
||||
|
||||
// If we're about to lead on the last column in the row, we need to add a padding space
|
||||
if (GetCursor().GetPosition().X == sBufferWidth - 1)
|
||||
if (cursorPosition.X == lineWidth - 1)
|
||||
{
|
||||
// set that we're wrapping for double byte reasons
|
||||
auto& row = GetRowByOffset(GetCursor().GetPosition().Y);
|
||||
auto& row = GetRowByOffset(cursorPosition.Y);
|
||||
row.SetDoubleBytePadded(true);
|
||||
|
||||
// then move the cursor forward and onto the next row
|
||||
|
@ -496,7 +497,7 @@ bool TextBuffer::IncrementCursor()
|
|||
// Cursor position is stored as logical array indices (starts at 0) for the window
|
||||
// Buffer Size is specified as the "length" of the array. It would say 80 for valid values of 0-79.
|
||||
// So subtract 1 from buffer size in each direction to find the index of the final column in the buffer
|
||||
const short iFinalColumnIndex = GetSize().RightInclusive();
|
||||
const short iFinalColumnIndex = GetLineWidth(GetCursor().GetPosition().Y) - 1;
|
||||
|
||||
// Move the cursor one position to the right
|
||||
GetCursor().IncrementXPosition(1);
|
||||
|
@ -635,7 +636,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter(std::optional<const Microsoft::Consol
|
|||
// Return Value:
|
||||
// - Coordinate position in screen coordinates of the character just before the cursor.
|
||||
// - NOTE: Will return 0,0 if already in the top left corner
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const noexcept
|
||||
COORD TextBuffer::_GetPreviousFromCursor() const
|
||||
{
|
||||
COORD coordPosition = GetCursor().GetPosition();
|
||||
|
||||
|
@ -649,11 +650,11 @@ COORD TextBuffer::_GetPreviousFromCursor() const noexcept
|
|||
// Otherwise, only if we're not on the top row (e.g. we don't move anywhere in the top left corner. there is no previous)
|
||||
if (coordPosition.Y > 0)
|
||||
{
|
||||
// move the cursor to the right edge
|
||||
coordPosition.X = GetSize().RightInclusive();
|
||||
|
||||
// and up one line
|
||||
// move the cursor up one line
|
||||
coordPosition.Y--;
|
||||
|
||||
// and to the right edge
|
||||
coordPosition.X = GetLineWidth(coordPosition.Y) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -801,6 +802,78 @@ void TextBuffer::SetCurrentAttributes(const TextAttribute& currentAttributes) no
|
|||
_currentAttributes = currentAttributes;
|
||||
}
|
||||
|
||||
void TextBuffer::SetCurrentLineRendition(const LineRendition lineRendition)
|
||||
{
|
||||
const auto cursorPosition = GetCursor().GetPosition();
|
||||
const auto rowIndex = cursorPosition.Y;
|
||||
auto& row = GetRowByOffset(rowIndex);
|
||||
if (row.GetLineRendition() != lineRendition)
|
||||
{
|
||||
row.SetLineRendition(lineRendition);
|
||||
// If the line rendition has changed, the row can no longer be wrapped.
|
||||
row.SetWrapForced(false);
|
||||
// And if it's no longer single width, the right half of the row should be erased.
|
||||
if (lineRendition != LineRendition::SingleWidth)
|
||||
{
|
||||
const auto fillChar = L' ';
|
||||
auto fillAttrs = GetCurrentAttributes();
|
||||
fillAttrs.SetStandardErase();
|
||||
const size_t fillOffset = GetLineWidth(rowIndex);
|
||||
const size_t fillLength = GetSize().Width() - fillOffset;
|
||||
const auto fillData = OutputCellIterator{ fillChar, fillAttrs, fillLength };
|
||||
row.WriteCells(fillData, fillOffset, false);
|
||||
// We also need to make sure the cursor is clamped within the new width.
|
||||
GetCursor().SetPosition(ClampPositionWithinLine(cursorPosition));
|
||||
}
|
||||
_NotifyPaint(Viewport::FromDimensions({ 0, rowIndex }, { GetSize().Width(), 1 }));
|
||||
}
|
||||
}
|
||||
|
||||
void TextBuffer::ResetLineRenditionRange(const size_t startRow, const size_t endRow)
|
||||
{
|
||||
for (auto row = startRow; row < endRow; row++)
|
||||
{
|
||||
GetRowByOffset(row).SetLineRendition(LineRendition::SingleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
LineRendition TextBuffer::GetLineRendition(const size_t row) const
|
||||
{
|
||||
return GetRowByOffset(row).GetLineRendition();
|
||||
}
|
||||
|
||||
bool TextBuffer::IsDoubleWidthLine(const size_t row) const
|
||||
{
|
||||
return GetLineRendition(row) != LineRendition::SingleWidth;
|
||||
}
|
||||
|
||||
SHORT TextBuffer::GetLineWidth(const size_t row) const
|
||||
{
|
||||
// Use shift right to quickly divide the width by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(row) ? 1 : 0;
|
||||
return GetSize().Width() >> scale;
|
||||
}
|
||||
|
||||
COORD TextBuffer::ClampPositionWithinLine(const COORD position) const
|
||||
{
|
||||
const SHORT rightmostColumn = GetLineWidth(position.Y) - 1;
|
||||
return { std::min(position.X, rightmostColumn), position.Y };
|
||||
}
|
||||
|
||||
COORD TextBuffer::ScreenToBufferPosition(const COORD position) const
|
||||
{
|
||||
// Use shift right to quickly divide the X pos by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X >> scale, position.Y };
|
||||
}
|
||||
|
||||
COORD TextBuffer::BufferToScreenPosition(const COORD position) const
|
||||
{
|
||||
// Use shift left to quickly multiply the X pos by 2 for double width lines.
|
||||
const auto scale = IsDoubleWidthLine(position.Y) ? 1 : 0;
|
||||
return { position.X << scale, position.Y };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Resets the text contents of this buffer with the default character
|
||||
// and the default current color attributes
|
||||
|
@ -1425,9 +1498,11 @@ bool TextBuffer::MoveToPreviousGlyph(til::point& pos) const
|
|||
// - blockSelection: when enabled, only get the rectangular text region,
|
||||
// as opposed to the text extending to the left/right
|
||||
// buffer margins
|
||||
// - bufferCoordinates: when enabled, treat the coordinates as relative to
|
||||
// the buffer rather than the screen.
|
||||
// Return Value:
|
||||
// - the delimiter class for the given char
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection) const
|
||||
const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const
|
||||
{
|
||||
std::vector<SMALL_RECT> textRects;
|
||||
|
||||
|
@ -1461,6 +1536,13 @@ const std::vector<SMALL_RECT> TextBuffer::GetTextRects(COORD start, COORD end, b
|
|||
textRow.Right = (row == lowerCoord.Y) ? lowerCoord.X : bufferSize.RightInclusive();
|
||||
}
|
||||
|
||||
// If we were passed screen coordinates, convert the given range into
|
||||
// equivalent buffer offsets, taking line rendition into account.
|
||||
if (!bufferCoordinates)
|
||||
{
|
||||
textRow = ScreenToBufferLine(textRow, GetLineRendition(row));
|
||||
}
|
||||
|
||||
_ExpandTextRow(textRow);
|
||||
textRects.emplace_back(textRow);
|
||||
}
|
||||
|
@ -2044,7 +2126,6 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
|||
const COORD cOldLastChar = oldBuffer.GetLastNonSpaceCharacter(lastCharacterViewport);
|
||||
|
||||
const short cOldRowsTotal = cOldLastChar.Y + 1;
|
||||
const short cOldColsTotal = oldBuffer.GetSize().Width();
|
||||
|
||||
COORD cNewCursorPos = { 0 };
|
||||
bool fFoundCursorPos = false;
|
||||
|
@ -2056,9 +2137,19 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
|
|||
{
|
||||
// Fetch the row and its "right" which is the last printable character.
|
||||
const ROW& row = oldBuffer.GetRowByOffset(iOldRow);
|
||||
const short cOldColsTotal = oldBuffer.GetLineWidth(iOldRow);
|
||||
const CharRow& charRow = row.GetCharRow();
|
||||
short iRight = gsl::narrow_cast<short>(charRow.MeasureRight());
|
||||
|
||||
// If we're starting a new row, try and preserve the line rendition
|
||||
// from the row in the original buffer.
|
||||
const auto newBufferPos = newBuffer.GetCursor().GetPosition();
|
||||
if (newBufferPos.X == 0)
|
||||
{
|
||||
auto& newRow = newBuffer.GetRowByOffset(newBufferPos.Y);
|
||||
newRow.SetLineRendition(row.GetLineRendition());
|
||||
}
|
||||
|
||||
// There is a special case here. If the row has a "wrap"
|
||||
// flag on it, but the right isn't equal to the width (one
|
||||
// index past the final valid index in the row) then there
|
||||
|
|
|
@ -122,6 +122,16 @@ public:
|
|||
|
||||
void SetCurrentAttributes(const TextAttribute& currentAttributes) noexcept;
|
||||
|
||||
void SetCurrentLineRendition(const LineRendition lineRendition);
|
||||
void ResetLineRenditionRange(const size_t startRow, const size_t endRow);
|
||||
LineRendition GetLineRendition(const size_t row) const;
|
||||
bool IsDoubleWidthLine(const size_t row) const;
|
||||
|
||||
SHORT GetLineWidth(const size_t row) const;
|
||||
COORD ClampPositionWithinLine(const COORD position) const;
|
||||
COORD ScreenToBufferPosition(const COORD position) const;
|
||||
COORD BufferToScreenPosition(const COORD position) const;
|
||||
|
||||
void Reset();
|
||||
|
||||
[[nodiscard]] HRESULT ResizeTraditional(const COORD newSize) noexcept;
|
||||
|
@ -141,7 +151,7 @@ public:
|
|||
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false) const;
|
||||
bool MoveToPreviousGlyph(til::point& pos) const;
|
||||
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection = false) const;
|
||||
const std::vector<SMALL_RECT> GetTextRects(COORD start, COORD end, bool blockSelection, bool bufferCoordinates) const;
|
||||
|
||||
void AddHyperlinkToMap(std::wstring_view uri, uint16_t id);
|
||||
std::wstring GetHyperlinkUriFromId(uint16_t id) const;
|
||||
|
@ -212,7 +222,7 @@ private:
|
|||
|
||||
void _SetFirstRowIndex(const SHORT FirstRowIndex) noexcept;
|
||||
|
||||
COORD _GetPreviousFromCursor() const noexcept;
|
||||
COORD _GetPreviousFromCursor() const;
|
||||
|
||||
void _SetWrapOnCurrentRow();
|
||||
void _AdjustWrapOnCurrentRow(const bool fSet);
|
||||
|
|
|
@ -54,7 +54,7 @@ std::vector<SMALL_RECT> Terminal::_GetSelectionRects() const noexcept
|
|||
|
||||
try
|
||||
{
|
||||
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection);
|
||||
return _buffer->GetTextRects(_selection->start, _selection->end, _blockSelection, false);
|
||||
}
|
||||
CATCH_LOG();
|
||||
return result;
|
||||
|
|
|
@ -67,7 +67,8 @@ void CursorBlinker::FocusStart()
|
|||
// - <none>
|
||||
void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
|
||||
{
|
||||
Cursor& cursor = ScreenInfo.GetTextBuffer().GetCursor();
|
||||
auto& buffer = ScreenInfo.GetTextBuffer();
|
||||
auto& cursor = buffer.GetCursor();
|
||||
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto* const _pAccessibilityNotifier = ServiceLocator::LocateAccessibilityNotifier();
|
||||
|
||||
|
@ -79,7 +80,9 @@ void CursorBlinker::TimerRoutine(SCREEN_INFORMATION& ScreenInfo)
|
|||
// Update the cursor pos in USER so accessibility will work.
|
||||
if (cursor.HasMoved())
|
||||
{
|
||||
const auto position = cursor.GetPosition();
|
||||
// Convert the buffer position to the equivalent screen coordinates
|
||||
// required by the notifier, taking line rendition into account.
|
||||
const auto position = buffer.BufferToScreenPosition(cursor.GetPosition());
|
||||
const auto viewport = ScreenInfo.GetViewport();
|
||||
const auto fontSize = ScreenInfo.GetScreenFontSize();
|
||||
cursor.SetHasMoved(false);
|
||||
|
|
|
@ -353,7 +353,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
|
||||
const wchar_t* lpString = pwchRealUnicode;
|
||||
|
||||
const COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
|
||||
COORD coordScreenBufferSize = screenInfo.GetBufferSize().Dimensions();
|
||||
// In VT mode, the width at which we wrap is determined by the line rendition attribute.
|
||||
if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
||||
{
|
||||
coordScreenBufferSize.X = textBuffer.GetLineWidth(CursorPosition.Y);
|
||||
}
|
||||
|
||||
while (*pcb < BufferSize)
|
||||
{
|
||||
|
@ -371,6 +376,11 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
|||
Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);
|
||||
|
||||
CursorPosition = cursor.GetPosition();
|
||||
// In VT mode, we need to recalculate the width when moving to a new line.
|
||||
if (WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING))
|
||||
{
|
||||
coordScreenBufferSize.X = textBuffer.GetLineWidth(CursorPosition.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -422,6 +422,10 @@ void ConsoleImeInfo::_WriteUndeterminedChars(const std::wstring_view text,
|
|||
// screen buffer and viewport positioning.
|
||||
// Each conversion area write will adjust these to set up any subsequent calls to go onto the next line.
|
||||
auto pos = screenInfo.GetTextBuffer().GetCursor().GetPosition();
|
||||
// Convert the cursor buffer position to the equivalent screen
|
||||
// coordinates, taking line rendition into account.
|
||||
pos = screenInfo.GetTextBuffer().BufferToScreenPosition(pos);
|
||||
|
||||
const auto view = screenInfo.GetViewport();
|
||||
// Set cursor position relative to viewport
|
||||
|
||||
|
|
|
@ -704,13 +704,18 @@ void ApiRoutines::GetLargestConsoleWindowSizeImpl(const SCREEN_INFORMATION& cont
|
|||
buffer.GetViewport().ToInclusive();
|
||||
COORD delta{ 0 };
|
||||
{
|
||||
if (currentViewport.Left > position.X)
|
||||
// When evaluating the X offset, we must convert the buffer position to
|
||||
// equivalent screen coordinates, taking line rendition into account.
|
||||
const auto lineRendition = buffer.GetTextBuffer().GetLineRendition(position.Y);
|
||||
const auto screenPosition = BufferToScreenLine({ position.X, position.Y, position.X, position.Y }, lineRendition);
|
||||
|
||||
if (currentViewport.Left > screenPosition.Left)
|
||||
{
|
||||
delta.X = position.X - currentViewport.Left;
|
||||
delta.X = screenPosition.Left - currentViewport.Left;
|
||||
}
|
||||
else if (currentViewport.Right < position.X)
|
||||
else if (currentViewport.Right < screenPosition.Right)
|
||||
{
|
||||
delta.X = position.X - currentViewport.Right;
|
||||
delta.X = screenPosition.Right - currentViewport.Right;
|
||||
}
|
||||
|
||||
if (currentViewport.Top > position.Y)
|
||||
|
@ -1410,6 +1415,10 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
|
|||
{
|
||||
cursorPosition.X = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursorPosition = textBuffer.ClampPositionWithinLine(cursorPosition);
|
||||
}
|
||||
|
||||
return AdjustCursorPosition(screenInfo, cursorPosition, FALSE, nullptr);
|
||||
}
|
||||
|
@ -1427,7 +1436,8 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
|
|||
|
||||
const SMALL_RECT viewport = screenInfo.GetActiveBuffer().GetViewport().ToInclusive();
|
||||
const COORD oldCursorPosition = screenInfo.GetTextBuffer().GetCursor().GetPosition();
|
||||
const COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
|
||||
COORD newCursorPosition = { oldCursorPosition.X, oldCursorPosition.Y - 1 };
|
||||
newCursorPosition = screenInfo.GetTextBuffer().ClampPositionWithinLine(newCursorPosition);
|
||||
|
||||
// If the cursor is at the top of the viewport, we don't want to shift the viewport up.
|
||||
// We want it to stay exactly where it is.
|
||||
|
|
|
@ -451,6 +451,13 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
|
|||
{
|
||||
const auto& view = remaining.at(i);
|
||||
screenInfo.WriteRect(fillData, view);
|
||||
|
||||
// If we're scrolling an area that encompasses the full buffer width,
|
||||
// then the filled rows should also have their line rendition reset.
|
||||
if (view.Width() == buffer.Width() && destinationOriginGiven.X == 0)
|
||||
{
|
||||
screenInfo.GetTextBuffer().ResetLineRenditionRange(view.Top(), view.BottomExclusive());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,9 @@ bool ConhostInternalGetSet::SetConsoleScreenBufferInfoEx(const CONSOLE_SCREEN_BU
|
|||
// - true if successful (see DoSrvSetConsoleCursorPosition). false otherwise.
|
||||
bool ConhostInternalGetSet::SetConsoleCursorPosition(const COORD position)
|
||||
{
|
||||
return SUCCEEDED(ServiceLocator::LocateGlobals().api.SetConsoleCursorPositionImpl(_io.GetActiveOutputBuffer(), position));
|
||||
auto& info = _io.GetActiveOutputBuffer();
|
||||
const auto clampedPosition = info.GetTextBuffer().ClampPositionWithinLine(position);
|
||||
return SUCCEEDED(ServiceLocator::LocateGlobals().api.SetConsoleCursorPositionImpl(info, clampedPosition));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -182,6 +184,48 @@ bool ConhostInternalGetSet::PrivateSetTextAttributes(const TextAttribute& attrs)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the line rendition attribute for the current row of the active screen
|
||||
// buffer. This controls how character cells are scaled when the row is rendered.
|
||||
// Arguments:
|
||||
// - lineRendition: The new LineRendition attribute to use
|
||||
// Return Value:
|
||||
// - true if successful. false otherwise.
|
||||
bool ConhostInternalGetSet::PrivateSetCurrentLineRendition(const LineRendition lineRendition)
|
||||
{
|
||||
auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
|
||||
textBuffer.SetCurrentLineRendition(lineRendition);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Resets the line rendition attribute to SingleWidth for a specified range
|
||||
// of row numbers.
|
||||
// Arguments:
|
||||
// - startRow: The row number of first line to be modified
|
||||
// - endRow: The row number following the last line to be modified
|
||||
// Return Value:
|
||||
// - true if successful. false otherwise.
|
||||
bool ConhostInternalGetSet::PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow)
|
||||
{
|
||||
auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
|
||||
textBuffer.ResetLineRenditionRange(startRow, endRow);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the number of cells that will fit on the specified row when
|
||||
// rendered with its current line rendition.
|
||||
// Arguments:
|
||||
// - row: The row number of the line to measure
|
||||
// Return Value:
|
||||
// - the number of cells that will fit on the line
|
||||
SHORT ConhostInternalGetSet::PrivateGetLineWidth(const size_t row) const
|
||||
{
|
||||
const auto& textBuffer = _io.GetActiveOutputBuffer().GetTextBuffer();
|
||||
return textBuffer.GetLineWidth(row);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Connects the WriteConsoleInput API call directly into our Driver Message servicing call inside Conhost.exe
|
||||
// Arguments:
|
||||
|
|
|
@ -65,6 +65,10 @@ public:
|
|||
bool PrivateGetTextAttributes(TextAttribute& attrs) const override;
|
||||
bool PrivateSetTextAttributes(const TextAttribute& attrs) override;
|
||||
|
||||
bool PrivateSetCurrentLineRendition(const LineRendition lineRendition) override;
|
||||
bool PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow) override;
|
||||
SHORT PrivateGetLineWidth(const size_t row) const override;
|
||||
|
||||
bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
|
||||
size_t& eventsWritten) override;
|
||||
|
||||
|
|
|
@ -2223,6 +2223,9 @@ void SCREEN_INFORMATION::SetViewport(const Viewport& newViewport,
|
|||
auto fillData = OutputCellIterator{ fillAttributes, fillLength };
|
||||
Write(fillData, fillPosition, false);
|
||||
|
||||
// Also reset the line rendition for the erased rows.
|
||||
_textBuffer->ResetLineRenditionRange(_viewport.Top(), _viewport.BottomExclusive());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
@ -2395,7 +2398,9 @@ void SCREEN_INFORMATION::ClearTextData()
|
|||
// - word boundary positions
|
||||
std::pair<COORD, COORD> SCREEN_INFORMATION::GetWordBoundary(const COORD position) const
|
||||
{
|
||||
COORD clampedPosition = position;
|
||||
// The position argument is in screen coordinates, but we need the
|
||||
// equivalent buffer position, taking line rendition into account.
|
||||
COORD clampedPosition = _textBuffer->ScreenToBufferPosition(position);
|
||||
GetBufferSize().Clamp(clampedPosition);
|
||||
|
||||
COORD start{ clampedPosition };
|
||||
|
@ -2464,6 +2469,12 @@ std::pair<COORD, COORD> SCREEN_INFORMATION::GetWordBoundary(const COORD position
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The calculated range is in buffer coordinates, but the caller is
|
||||
// expecting screen offsets, so we have to convert these back again.
|
||||
start = _textBuffer->BufferToScreenPosition(start);
|
||||
end = _textBuffer->BufferToScreenPosition(end);
|
||||
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
|
@ -2541,6 +2552,8 @@ void SCREEN_INFORMATION::InitializeCursorRowAttributes()
|
|||
auto fillAttributes = GetAttributes();
|
||||
fillAttributes.SetStandardErase();
|
||||
row.GetAttrRow().SetAttrToEnd(0, fillAttributes);
|
||||
// The row should also be single width to start with.
|
||||
row.SetLineRendition(LineRendition::SingleWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ std::vector<SMALL_RECT> Selection::GetSelectionRects() const
|
|||
endSelectionAnchor.Y = (_coordSelectionAnchor.Y == _srSelectionRect.Top) ? _srSelectionRect.Bottom : _srSelectionRect.Top;
|
||||
|
||||
const auto blockSelection = !IsLineSelection();
|
||||
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection);
|
||||
return screenInfo.GetTextBuffer().GetTextRects(_coordSelectionAnchor, endSelectionAnchor, blockSelection, false);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
@ -417,7 +417,7 @@ void Selection::ColorSelection(const COORD coordSelectionStart, const COORD coor
|
|||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto& screenInfo = gci.GetActiveOutputBuffer();
|
||||
|
||||
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd);
|
||||
const auto rectangles = screenInfo.GetTextBuffer().GetTextRects(coordSelectionStart, coordSelectionEnd, false, true);
|
||||
for (const auto& rect : rectangles)
|
||||
{
|
||||
ColorSelection(rect, attr);
|
||||
|
|
|
@ -333,7 +333,7 @@ class SelectionTests
|
|||
|
||||
COORD startPos{ sTargetX, sTargetY };
|
||||
COORD endPos{ base::ClampAdd(sTargetX, sLength), sTargetY };
|
||||
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos);
|
||||
const auto selectionRects = screenInfo.GetTextBuffer().GetTextRects(startPos, endPos, false, false);
|
||||
|
||||
VERIFY_ARE_EQUAL(static_cast<size_t>(1), selectionRects.size());
|
||||
srSelection = selectionRects.at(0);
|
||||
|
|
|
@ -2324,7 +2324,7 @@ void TextBufferTests::GetTextRects()
|
|||
|
||||
COORD start{ 1, 0 };
|
||||
COORD end{ 7, 4 };
|
||||
const auto result = _buffer->GetTextRects(start, end, blockSelection);
|
||||
const auto result = _buffer->GetTextRects(start, end, blockSelection, false);
|
||||
VERIFY_ARE_EQUAL(expected.size(), result.size());
|
||||
for (size_t i = 0; i < expected.size(); ++i)
|
||||
{
|
||||
|
@ -2370,7 +2370,7 @@ void TextBufferTests::GetText()
|
|||
WriteLinesToBuffer(bufferText, *_buffer);
|
||||
|
||||
// simulate a selection from origin to {4,4}
|
||||
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 4 }, blockSelection);
|
||||
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 4 }, blockSelection, false);
|
||||
|
||||
std::wstring result = L"";
|
||||
const auto textData = _buffer->GetText(includeCRLF, trimTrailingWhitespace, textRects).text;
|
||||
|
@ -2471,7 +2471,7 @@ void TextBufferTests::GetText()
|
|||
// |_____|
|
||||
|
||||
// simulate a selection from origin to {4,5}
|
||||
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 5 }, blockSelection);
|
||||
const auto textRects = _buffer->GetTextRects({ 0, 0 }, { 4, 5 }, blockSelection, false);
|
||||
|
||||
std::wstring result = L"";
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT BgfxEngine::InvalidateCursor(const COORD* const /*pcoordCursor*/) noexcept
|
||||
[[nodiscard]] HRESULT BgfxEngine::InvalidateCursor(const SMALL_RECT* const /*psrRegion*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace Microsoft::Console::Render
|
|||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
|
|
|
@ -41,6 +41,18 @@ HRESULT RenderEngineBase::PrepareRenderInfo(const RenderFrameInfo& /*info*/) noe
|
|||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT RenderEngineBase::ResetLineTransform() noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
HRESULT RenderEngineBase::PrepareLineTransform(const LineRendition /*lineRendition*/,
|
||||
const size_t /*targetRow*/,
|
||||
const size_t /*viewportLeft*/) noexcept
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - By default, no one should need continuous redraw. It ruins performance
|
||||
// in terms of CPU, memory, and battery life to just paint forever.
|
||||
|
|
|
@ -220,6 +220,18 @@ void Renderer::TriggerRedraw(const Viewport& region)
|
|||
Viewport view = _viewport;
|
||||
SMALL_RECT srUpdateRegion = region.ToExclusive();
|
||||
|
||||
// If the dirty region has double width lines, we need to double the size of
|
||||
// the right margin to make sure all the affected cells are invalidated.
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
for (auto row = srUpdateRegion.Top; row < srUpdateRegion.Bottom; row++)
|
||||
{
|
||||
if (buffer.IsDoubleWidthLine(row))
|
||||
{
|
||||
srUpdateRegion.Right *= 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (view.TrimToViewport(&srUpdateRegion))
|
||||
{
|
||||
view.ConvertToOrigin(&srUpdateRegion);
|
||||
|
@ -253,25 +265,34 @@ void Renderer::TriggerRedraw(const COORD* const pcoord)
|
|||
// - <none>
|
||||
void Renderer::TriggerRedrawCursor(const COORD* const pcoord)
|
||||
{
|
||||
Viewport view = _pData->GetViewport();
|
||||
COORD updateCoord = *pcoord;
|
||||
|
||||
if (view.IsInBounds(updateCoord))
|
||||
// We first need to make sure the cursor position is within the buffer,
|
||||
// otherwise testing for a double width character can throw an exception.
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
if (buffer.GetSize().IsInBounds(*pcoord))
|
||||
{
|
||||
view.ConvertToOrigin(&updateCoord);
|
||||
for (IRenderEngine* pEngine : _rgpEngines)
|
||||
// We then calculate the region covered by the cursor. This requires
|
||||
// converting the buffer coordinates to an equivalent range of screen
|
||||
// cells for the cursor, taking line rendition into account.
|
||||
const LineRendition lineRendition = buffer.GetLineRendition(pcoord->Y);
|
||||
const SHORT cursorWidth = _pData->IsCursorDoubleWidth() ? 2 : 1;
|
||||
const SMALL_RECT cursorRect = { pcoord->X, pcoord->Y, pcoord->X + cursorWidth - 1, pcoord->Y };
|
||||
Viewport cursorView = Viewport::FromInclusive(BufferToScreenLine(cursorRect, lineRendition));
|
||||
|
||||
// The region is clamped within the viewport boundaries and we only
|
||||
// trigger a redraw if the region is not empty.
|
||||
Viewport view = _pData->GetViewport();
|
||||
cursorView = view.Clamp(cursorView);
|
||||
|
||||
if (cursorView.IsValid())
|
||||
{
|
||||
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateCoord));
|
||||
|
||||
// Double-wide cursors need to invalidate the right half as well.
|
||||
if (_pData->IsCursorDoubleWidth())
|
||||
const SMALL_RECT updateRect = view.ConvertToOrigin(cursorView).ToExclusive();
|
||||
for (IRenderEngine* pEngine : _rgpEngines)
|
||||
{
|
||||
updateCoord.X++;
|
||||
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateCoord));
|
||||
LOG_IF_FAILED(pEngine->InvalidateCursor(&updateRect));
|
||||
}
|
||||
}
|
||||
|
||||
_NotifyPaintFrame();
|
||||
_NotifyPaintFrame();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -630,6 +651,11 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
gsl::span<const til::rectangle> dirtyAreas;
|
||||
LOG_IF_FAILED(pEngine->GetDirtyArea(dirtyAreas));
|
||||
|
||||
// This is to make sure any transforms are reset when this paint is finished.
|
||||
auto resetLineTransform = wil::scope_exit([&]() {
|
||||
LOG_IF_FAILED(pEngine->ResetLineTransform());
|
||||
});
|
||||
|
||||
for (const auto& dirtyRect : dirtyAreas)
|
||||
{
|
||||
auto dirty = Viewport::FromInclusive(dirtyRect);
|
||||
|
@ -654,14 +680,19 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
{
|
||||
// Calculate the boundaries of a single line. This is from the left to right edge of the dirty
|
||||
// area in width and exactly 1 tall.
|
||||
const auto bufferLine = Viewport::FromDimensions({ redraw.Left(), row }, { redraw.Width(), 1 });
|
||||
const auto screenLine = SMALL_RECT{ redraw.Left(), row, redraw.RightInclusive(), row };
|
||||
|
||||
// Convert the screen coordinates of the line to an equivalent
|
||||
// range of buffer cells, taking line rendition into account.
|
||||
const auto lineRendition = buffer.GetLineRendition(row);
|
||||
const auto bufferLine = Viewport::FromInclusive(ScreenToBufferLine(screenLine, lineRendition));
|
||||
|
||||
// Find where on the screen we should place this line information. This requires us to re-map
|
||||
// the buffer-based origin of the line back onto the screen-based origin of the line
|
||||
// For example, the screen might say we need to paint 1,1 because it is dirty but the viewport is actually looking
|
||||
// at 13,26 relative to the buffer.
|
||||
// This means that we need 14,27 out of the backing buffer to fill in the 1,1 cell of the screen.
|
||||
const auto screenLine = Viewport::Offset(bufferLine, -view.Origin());
|
||||
// the buffer-based origin of the line back onto the screen-based origin of the line.
|
||||
// For example, the screen might say we need to paint line 1 because it is dirty but the viewport
|
||||
// is actually looking at line 26 relative to the buffer. This means that we need line 27 out
|
||||
// of the backing buffer to fill in line 1 of the screen.
|
||||
const auto screenPosition = bufferLine.Origin() - COORD{ 0, view.Top() };
|
||||
|
||||
// Retrieve the cell information iterator limited to just this line we want to redraw.
|
||||
auto it = buffer.GetCellDataAt(bufferLine.Origin(), bufferLine);
|
||||
|
@ -673,8 +704,11 @@ void Renderer::_PaintBufferOutput(_In_ IRenderEngine* const pEngine)
|
|||
const auto lineWrapped = (buffer.GetRowByOffset(bufferLine.Origin().Y).WasWrapForced()) &&
|
||||
(bufferLine.RightExclusive() == buffer.GetSize().Width());
|
||||
|
||||
// Prepare the appropriate line transform for the current row and viewport offset.
|
||||
LOG_IF_FAILED(pEngine->PrepareLineTransform(lineRendition, screenPosition.Y, view.Left()));
|
||||
|
||||
// Ask the helper to paint through this specific line.
|
||||
_PaintBufferOutputHelper(pEngine, it, screenLine.Origin(), lineWrapped);
|
||||
_PaintBufferOutputHelper(pEngine, it, screenPosition, lineWrapped);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -959,11 +993,25 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin
|
|||
// bottom of the viewport, the space that's not quite a full line in
|
||||
// height. Since we don't draw that text, we shouldn't draw the cursor
|
||||
// there either.
|
||||
Viewport view = _pData->GetViewport();
|
||||
if (view.IsInBounds(coordCursor))
|
||||
|
||||
// The cursor is never rendered as double height, so we don't care about
|
||||
// the exact line rendition - only whether it's double width or not.
|
||||
const auto doubleWidth = _pData->GetTextBuffer().IsDoubleWidthLine(coordCursor.Y);
|
||||
const auto lineRendition = doubleWidth ? LineRendition::DoubleWidth : LineRendition::SingleWidth;
|
||||
|
||||
// We need to convert the screen coordinates of the viewport to an
|
||||
// equivalent range of buffer cells, taking line rendition into account.
|
||||
const auto view = ScreenToBufferLine(_pData->GetViewport().ToInclusive(), lineRendition);
|
||||
|
||||
// Note that we allow the X coordinate to be outside the left border by 1 position,
|
||||
// because the cursor could still be visible if the focused character is double width.
|
||||
const auto xInRange = coordCursor.X >= view.Left - 1 && coordCursor.X <= view.Right;
|
||||
const auto yInRange = coordCursor.Y >= view.Top && coordCursor.Y <= view.Bottom;
|
||||
if (xInRange && yInRange)
|
||||
{
|
||||
// Adjust cursor to viewport
|
||||
view.ConvertToOrigin(&coordCursor);
|
||||
// Adjust cursor Y offset to viewport.
|
||||
// The viewport X offset is saved in the options and handled with a transform.
|
||||
coordCursor.Y -= view.Top;
|
||||
|
||||
COLORREF cursorColor = _pData->GetCursorColor();
|
||||
bool useColor = cursorColor != INVALID_COLOR;
|
||||
|
@ -971,6 +1019,8 @@ void Renderer::_PaintBufferOutputGridLineHelper(_In_ IRenderEngine* const pEngin
|
|||
// Build up the cursor parameters including position, color, and drawing options
|
||||
CursorOptions options;
|
||||
options.coordCursor = coordCursor;
|
||||
options.viewportLeft = _pData->GetViewport().Left();
|
||||
options.lineRendition = lineRendition;
|
||||
options.ulCursorHeightPercent = _pData->GetCursorHeight();
|
||||
options.cursorPixelWidth = _pData->GetCursorPixelWidth();
|
||||
options.fIsDoubleWidth = _pData->IsCursorDoubleWidth();
|
||||
|
@ -1165,14 +1215,20 @@ void Renderer::_PaintSelection(_In_ IRenderEngine* const pEngine)
|
|||
// - A vector of rectangles representing the regions to select, line by line.
|
||||
std::vector<SMALL_RECT> Renderer::_GetSelectionRects() const
|
||||
{
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
auto rects = _pData->GetSelectionRects();
|
||||
// Adjust rectangles to viewport
|
||||
Viewport view = _pData->GetViewport();
|
||||
|
||||
std::vector<SMALL_RECT> result;
|
||||
|
||||
for (auto& rect : rects)
|
||||
for (auto rect : rects)
|
||||
{
|
||||
// Convert buffer offsets to the equivalent range of screen cells
|
||||
// expected by callers, taking line rendition into account.
|
||||
const auto lineRendition = buffer.GetLineRendition(rect.Top());
|
||||
rect = Viewport::FromInclusive(BufferToScreenLine(rect.ToInclusive(), lineRendition));
|
||||
|
||||
auto sr = view.ConvertToOrigin(rect).ToInclusive();
|
||||
|
||||
// hopefully temporary, we should be receiving the right selection sizes without correction.
|
||||
|
|
|
@ -1055,24 +1055,15 @@ try
|
|||
CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates one specific character coordinate
|
||||
// - Invalidates the cells of the cursor
|
||||
// Arguments:
|
||||
// - pcoordCursor - single point in the character cell grid
|
||||
// - psrRegion - the region covered by the cursor
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
|
||||
try
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
|
||||
|
||||
if (!_allInvalid)
|
||||
{
|
||||
_InvalidateRectangle(til::rectangle{ *pcoordCursor, til::size{ 1, 1 } });
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
return Invalidate(psrRegion);
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates a rectangle describing a pixel area on the display
|
||||
|
|
|
@ -73,7 +73,7 @@ namespace Microsoft::Console::Render
|
|||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
@ -41,6 +41,11 @@ namespace Microsoft::Console::Render
|
|||
|
||||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
|
||||
const size_t targetRow,
|
||||
const size_t viewportLeft) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT PaintBackground() noexcept override;
|
||||
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
|
||||
const COORD coord,
|
||||
|
@ -97,6 +102,7 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] HRESULT _FlushBufferLines() noexcept;
|
||||
|
||||
std::vector<RECT> cursorInvertRects;
|
||||
XFORM cursorInvertTransform;
|
||||
|
||||
struct LineMetrics
|
||||
{
|
||||
|
@ -126,6 +132,9 @@ namespace Microsoft::Console::Render
|
|||
COLORREF _lastBg;
|
||||
bool _lastFontItalic;
|
||||
|
||||
XFORM _currentLineTransform;
|
||||
LineRendition _currentLineRendition;
|
||||
|
||||
// Memory pooling to save alloc/free work to the OS for things
|
||||
// frequently created and dropped.
|
||||
// It's important the pool is first so it can be given to the others on construction.
|
||||
|
@ -183,4 +192,11 @@ namespace Microsoft::Console::Render
|
|||
HDC _debugContext;
|
||||
#endif
|
||||
};
|
||||
|
||||
constexpr XFORM IDENTITY_XFORM = { 1, 0, 0, 1 };
|
||||
|
||||
inline bool operator==(const XFORM& lhs, const XFORM& rhs) noexcept
|
||||
{
|
||||
return ::memcmp(&lhs, &rhs, sizeof(XFORM)) == 0;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -81,13 +81,12 @@ HRESULT GdiEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept
|
|||
// Routine Description:
|
||||
// - Notifies us that the console has changed the position of the cursor.
|
||||
// Arguments:
|
||||
// - pcoordCursor - the new position of the cursor
|
||||
// - psrRegion - the region covered by the cursor
|
||||
// Return Value:
|
||||
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
|
||||
HRESULT GdiEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
|
||||
HRESULT GdiEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
|
||||
{
|
||||
SMALL_RECT sr = Viewport::FromCoord(*pcoordCursor).ToExclusive();
|
||||
return this->Invalidate(&sr);
|
||||
return this->Invalidate(psrRegion);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
|
|
|
@ -41,6 +41,9 @@ using namespace Microsoft::Console::Render;
|
|||
_psInvalidData.hdc = GetDC(_hwndTargetWindow);
|
||||
RETURN_HR_IF_NULL(E_FAIL, _psInvalidData.hdc);
|
||||
|
||||
// We need the advanced graphics mode in order to set a transform.
|
||||
SetGraphicsMode(_psInvalidData.hdc, GM_ADVANCED);
|
||||
|
||||
// Signal that we're starting to paint.
|
||||
_fPaintStarted = true;
|
||||
|
||||
|
@ -71,11 +74,28 @@ using namespace Microsoft::Console::Render;
|
|||
// left behind cursor copies in the scrolled region.
|
||||
if (cursorInvertRects.size() > 0)
|
||||
{
|
||||
// We first need to apply the transform that was active at the time the cursor
|
||||
// was rendered otherwise we won't be clearing the right area of the display.
|
||||
// We don't need to do this if it was an identity transform though.
|
||||
const bool identityTransform = cursorInvertTransform == IDENTITY_XFORM;
|
||||
if (!identityTransform)
|
||||
{
|
||||
LOG_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &cursorInvertTransform));
|
||||
LOG_HR_IF(E_FAIL, !SetWorldTransform(_psInvalidData.hdc, &cursorInvertTransform));
|
||||
}
|
||||
|
||||
for (RECT r : cursorInvertRects)
|
||||
{
|
||||
// Clean both the in-memory and actual window context.
|
||||
RETURN_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));
|
||||
RETURN_HR_IF(E_FAIL, !(InvertRect(_psInvalidData.hdc, &r)));
|
||||
LOG_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));
|
||||
LOG_HR_IF(E_FAIL, !(InvertRect(_psInvalidData.hdc, &r)));
|
||||
}
|
||||
|
||||
// If we've applied a transform, then we need to reset it.
|
||||
if (!identityTransform)
|
||||
{
|
||||
LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY));
|
||||
LOG_HR_IF(E_FAIL, !ModifyWorldTransform(_psInvalidData.hdc, nullptr, MWT_IDENTITY));
|
||||
}
|
||||
|
||||
cursorInvertRects.clear();
|
||||
|
@ -369,15 +389,21 @@ using namespace Microsoft::Console::Render;
|
|||
}
|
||||
}
|
||||
|
||||
// If the line rendition is double height, we need to adjust the top or bottom
|
||||
// of the clipping rect to clip half the height of the rendered characters.
|
||||
const auto halfHeight = coordFontSize.Y >> 1;
|
||||
const auto topOffset = _currentLineRendition == LineRendition::DoubleHeightBottom ? halfHeight : 0;
|
||||
const auto bottomOffset = _currentLineRendition == LineRendition::DoubleHeightTop ? halfHeight : 0;
|
||||
|
||||
pPolyTextLine->lpstr = polyString.data();
|
||||
pPolyTextLine->n = gsl::narrow<UINT>(clusters.size());
|
||||
pPolyTextLine->x = ptDraw.x;
|
||||
pPolyTextLine->y = ptDraw.y;
|
||||
pPolyTextLine->uiFlags = ETO_OPAQUE | ETO_CLIPPED;
|
||||
pPolyTextLine->rcl.left = pPolyTextLine->x;
|
||||
pPolyTextLine->rcl.top = pPolyTextLine->y;
|
||||
pPolyTextLine->rcl.right = pPolyTextLine->rcl.left + ((SHORT)cchCharWidths * coordFontSize.X);
|
||||
pPolyTextLine->rcl.bottom = pPolyTextLine->rcl.top + coordFontSize.Y;
|
||||
pPolyTextLine->rcl.top = pPolyTextLine->y + topOffset;
|
||||
pPolyTextLine->rcl.right = pPolyTextLine->rcl.left + (SHORT)cchCharWidths;
|
||||
pPolyTextLine->rcl.bottom = pPolyTextLine->y + coordFontSize.Y - bottomOffset;
|
||||
pPolyTextLine->pdx = polyWidth.data();
|
||||
|
||||
if (trimLeft)
|
||||
|
@ -628,6 +654,13 @@ using namespace Microsoft::Console::Render;
|
|||
default:
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
// Prepare the appropriate line transform for the current row.
|
||||
LOG_IF_FAILED(PrepareLineTransform(options.lineRendition, 0, options.viewportLeft));
|
||||
auto resetLineTransform = wil::scope_exit([&]() {
|
||||
LOG_IF_FAILED(ResetLineTransform());
|
||||
});
|
||||
|
||||
// Either invert all the RECTs, or paint them.
|
||||
if (options.fUseColor)
|
||||
{
|
||||
|
@ -642,6 +675,10 @@ using namespace Microsoft::Console::Render;
|
|||
}
|
||||
else
|
||||
{
|
||||
// Save the current line transform in case we need to reapply these
|
||||
// inverted rects to hide the cursor in the ScrollFrame method.
|
||||
cursorInvertTransform = _currentLineTransform;
|
||||
|
||||
for (RECT r : cursorInvertRects)
|
||||
{
|
||||
RETURN_HR_IF(E_FAIL, !(InvertRect(_hdcMemoryContext, &r)));
|
||||
|
|
|
@ -30,6 +30,8 @@ GdiEngine::GdiEngine() :
|
|||
_lastFg(INVALID_COLOR),
|
||||
_lastBg(INVALID_COLOR),
|
||||
_lastFontItalic(false),
|
||||
_currentLineTransform(IDENTITY_XFORM),
|
||||
_currentLineRendition(LineRendition::SingleWidth),
|
||||
_fPaintStarted(false),
|
||||
_invalidCharacters{},
|
||||
_hfont(nullptr),
|
||||
|
@ -46,6 +48,9 @@ GdiEngine::GdiEngine() :
|
|||
_hdcMemoryContext = CreateCompatibleDC(nullptr);
|
||||
THROW_HR_IF_NULL(E_FAIL, _hdcMemoryContext);
|
||||
|
||||
// We need the advanced graphics mode in order to set a transform.
|
||||
SetGraphicsMode(_hdcMemoryContext, GM_ADVANCED);
|
||||
|
||||
// On session zero, text GDI APIs might not be ready.
|
||||
// Calling GetTextFace causes a wait that will be
|
||||
// satisfied while GDI text APIs come online.
|
||||
|
@ -123,6 +128,9 @@ GdiEngine::~GdiEngine()
|
|||
HDC const hdcNewMemoryContext = CreateCompatibleDC(hdcRealWindow);
|
||||
RETURN_HR_IF_NULL(E_FAIL, hdcNewMemoryContext);
|
||||
|
||||
// We need the advanced graphics mode in order to set a transform.
|
||||
SetGraphicsMode(hdcNewMemoryContext, GM_ADVANCED);
|
||||
|
||||
// If we had an existing memory context stored, release it before proceeding.
|
||||
if (nullptr != _hdcMemoryContext)
|
||||
{
|
||||
|
@ -185,6 +193,77 @@ GdiEngine::~GdiEngine()
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description
|
||||
// - Resets the world transform to the identity matrix.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - S_OK if successful. S_FALSE if already reset. E_FAIL if there was an error.
|
||||
[[nodiscard]] HRESULT GdiEngine::ResetLineTransform() noexcept
|
||||
{
|
||||
// Return early if the current transform is already the identity matrix.
|
||||
RETURN_HR_IF(S_FALSE, _currentLineTransform == IDENTITY_XFORM);
|
||||
// Flush any buffer lines which would be expecting to use the current transform.
|
||||
LOG_IF_FAILED(_FlushBufferLines());
|
||||
// Reset the active transform to the identity matrix.
|
||||
RETURN_HR_IF(E_FAIL, !ModifyWorldTransform(_hdcMemoryContext, nullptr, MWT_IDENTITY));
|
||||
// Reset the current state.
|
||||
_currentLineTransform = IDENTITY_XFORM;
|
||||
_currentLineRendition = LineRendition::SingleWidth;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description
|
||||
// - Applies an appropriate transform for the given line rendition and viewport offset.
|
||||
// Arguments:
|
||||
// - lineRendition - The line rendition specifying the scaling of the line.
|
||||
// - targetRow - The row on which the line is expected to be rendered.
|
||||
// - viewportLeft - The left offset of the current viewport.
|
||||
// Return Value:
|
||||
// - S_OK if successful. S_FALSE if already set. E_FAIL if there was an error.
|
||||
[[nodiscard]] HRESULT GdiEngine::PrepareLineTransform(const LineRendition lineRendition,
|
||||
const size_t targetRow,
|
||||
const size_t viewportLeft) noexcept
|
||||
{
|
||||
XFORM lineTransform = {};
|
||||
// The X delta is to account for the horizontal viewport offset.
|
||||
lineTransform.eDx = viewportLeft ? -1.0f * viewportLeft * _GetFontSize().X : 0.0f;
|
||||
switch (lineRendition)
|
||||
{
|
||||
case LineRendition::SingleWidth:
|
||||
lineTransform.eM11 = 1; // single width
|
||||
lineTransform.eM22 = 1; // single height
|
||||
break;
|
||||
case LineRendition::DoubleWidth:
|
||||
lineTransform.eM11 = 2; // double width
|
||||
lineTransform.eM22 = 1; // single height
|
||||
break;
|
||||
case LineRendition::DoubleHeightTop:
|
||||
lineTransform.eM11 = 2; // double width
|
||||
lineTransform.eM22 = 2; // double height
|
||||
// The Y delta is to negate the offset caused by the scaled height.
|
||||
lineTransform.eDy = -1.0f * targetRow * _GetFontSize().Y;
|
||||
break;
|
||||
case LineRendition::DoubleHeightBottom:
|
||||
lineTransform.eM11 = 2; // double width
|
||||
lineTransform.eM22 = 2; // double height
|
||||
// The Y delta is to negate the offset caused by the scaled height.
|
||||
// An extra row is added because we need the bottom half of the line.
|
||||
lineTransform.eDy = -1.0f * (targetRow + 1) * _GetFontSize().Y;
|
||||
break;
|
||||
}
|
||||
// Return early if the new matrix is the same as the current transform.
|
||||
RETURN_HR_IF(S_FALSE, _currentLineRendition == lineRendition && _currentLineTransform == lineTransform);
|
||||
// Flush any buffer lines which would be expecting to use the current transform.
|
||||
LOG_IF_FAILED(_FlushBufferLines());
|
||||
// Set the active transform with the new matrix.
|
||||
RETURN_HR_IF(E_FAIL, !SetWorldTransform(_hdcMemoryContext, &lineTransform));
|
||||
// Save the current state.
|
||||
_currentLineTransform = lineTransform;
|
||||
_currentLineRendition = lineRendition;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This method will set the GDI brushes in the drawing context (and update the hung-window background color)
|
||||
// Arguments:
|
||||
|
|
|
@ -14,6 +14,7 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "../../buffer/out/LineRendition.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
|
@ -21,9 +22,15 @@ namespace Microsoft::Console::Render
|
|||
struct CursorOptions
|
||||
{
|
||||
// Character cell in the grid to draw at
|
||||
// This is relative to the viewport, not the buffer.
|
||||
// This is relative to the top of the viewport, not the buffer
|
||||
COORD coordCursor;
|
||||
|
||||
// Left offset of the viewport, which may alter the horizontal position
|
||||
SHORT viewportLeft;
|
||||
|
||||
// Line rendition of the current row, which can affect the cursor width
|
||||
LineRendition lineRendition;
|
||||
|
||||
// For an underscore type _ cursor, how tall it should be as a % of cell height
|
||||
ULONG ulCursorHeightPercent;
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ Author(s):
|
|||
#include "Cluster.hpp"
|
||||
#include "FontInfoDesired.hpp"
|
||||
#include "IRenderData.hpp"
|
||||
#include "../../buffer/out/LineRendition.hpp"
|
||||
|
||||
namespace Microsoft::Console::Render
|
||||
{
|
||||
|
@ -64,7 +65,7 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] virtual HRESULT ScrollFrame() noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
|
||||
|
@ -75,6 +76,11 @@ namespace Microsoft::Console::Render
|
|||
|
||||
[[nodiscard]] virtual HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT ResetLineTransform() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PrepareLineTransform(const LineRendition lineRendition,
|
||||
const size_t targetRow,
|
||||
const size_t viewportLeft) noexcept = 0;
|
||||
|
||||
[[nodiscard]] virtual HRESULT PaintBackground() noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters,
|
||||
const COORD coord,
|
||||
|
|
|
@ -40,6 +40,11 @@ namespace Microsoft::Console::Render
|
|||
|
||||
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition,
|
||||
const size_t targetRow,
|
||||
const size_t viewportLeft) noexcept override;
|
||||
|
||||
[[nodiscard]] virtual bool RequiresContinuousRedraw() noexcept override;
|
||||
|
||||
void WaitUntilCanRender() noexcept override;
|
||||
|
|
|
@ -21,6 +21,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
|
|||
_cursorChanged{ false },
|
||||
_isEnabled{ true },
|
||||
_prevSelection{},
|
||||
_prevCursorRegion{},
|
||||
RenderEngineBase()
|
||||
{
|
||||
}
|
||||
|
@ -66,18 +67,18 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
|
|||
// - Notifies us that the console has changed the position of the cursor.
|
||||
// For UIA, this doesn't mean anything. So do nothing.
|
||||
// Arguments:
|
||||
// - pcoordCursor - the new position of the cursor
|
||||
// - psrRegion - the region covered by the cursor
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
|
||||
[[nodiscard]] HRESULT UiaEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
|
||||
|
||||
// check if cursor moved
|
||||
if (*pcoordCursor != _prevCursorPos)
|
||||
if (*psrRegion != _prevCursorRegion)
|
||||
{
|
||||
_prevCursorPos = *pcoordCursor;
|
||||
_prevCursorRegion = *psrRegion;
|
||||
_cursorChanged = true;
|
||||
}
|
||||
return S_OK;
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
|
@ -86,6 +86,6 @@ namespace Microsoft::Console::Render
|
|||
Microsoft::Console::Types::IUiaEventDispatcher* _dispatcher;
|
||||
|
||||
std::vector<SMALL_RECT> _prevSelection;
|
||||
til::point _prevCursorPos;
|
||||
SMALL_RECT _prevCursorRegion;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -59,10 +59,10 @@ CATCH_RETURN();
|
|||
// Routine Description:
|
||||
// - Notifies us that the console has changed the position of the cursor.
|
||||
// Arguments:
|
||||
// - pcoordCursor - the new position of the cursor
|
||||
// - psrRegion - the region covered by the cursor
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const COORD* const pcoordCursor) noexcept
|
||||
[[nodiscard]] HRESULT VtEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
|
||||
{
|
||||
// If we just inherited the cursor, we're going to get an InvalidateCursor
|
||||
// for both where the old cursor was, and where the new cursor is
|
||||
|
@ -70,9 +70,9 @@ CATCH_RETURN();
|
|||
// We should ignore the first one, but after that, if the client application
|
||||
// is moving the cursor around in the viewport, move our virtual top
|
||||
// up to meet their changes.
|
||||
if (!_skipCursor && _virtualTop > pcoordCursor->Y)
|
||||
if (!_skipCursor && _virtualTop > psrRegion->Top)
|
||||
{
|
||||
_virtualTop = pcoordCursor->Y;
|
||||
_virtualTop = psrRegion->Top;
|
||||
}
|
||||
_skipCursor = false;
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace Microsoft::Console::Render
|
|||
[[nodiscard]] virtual HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept = 0;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
|
||||
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
|
||||
|
|
|
@ -166,7 +166,7 @@ bool WddmConEngine::IsInitialized()
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] HRESULT WddmConEngine::InvalidateCursor(const COORD* const /*pcoordCursor*/) noexcept
|
||||
[[nodiscard]] HRESULT WddmConEngine::InvalidateCursor(const SMALL_RECT* const /*psrRegion*/) noexcept
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Microsoft::Console::Render
|
|||
|
||||
// IRenderEngine Members
|
||||
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const COORD* const pcoordCursor) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
|
||||
|
|
|
@ -13,6 +13,7 @@ Abstract:
|
|||
*/
|
||||
#pragma once
|
||||
#include "DispatchTypes.hpp"
|
||||
#include "../buffer/out/LineRendition.hpp"
|
||||
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
|
@ -88,6 +89,7 @@ public:
|
|||
virtual bool EraseCharacters(const size_t numChars) = 0; // ECH
|
||||
|
||||
virtual bool SetGraphicsRendition(const VTParameters options) = 0; // SGR
|
||||
virtual bool SetLineRendition(const LineRendition rendition) = 0; // DECSWL, DECDWL, DECDHL
|
||||
|
||||
virtual bool PushGraphicsRendition(const VTParameters options) = 0; // XTPUSHSGR
|
||||
virtual bool PopGraphicsRendition() = 0; // XTPOPSGR
|
||||
|
|
|
@ -432,10 +432,9 @@ bool AdaptDispatch::_InsertDeleteHelper(const size_t count, const bool isInsert)
|
|||
|
||||
const auto cursor = csbiex.dwCursorPosition;
|
||||
// Rectangle to cut out of the existing buffer. This is inclusive.
|
||||
// It will be clipped to the buffer boundaries so SHORT_MAX gives us the full buffer width.
|
||||
SMALL_RECT srScroll;
|
||||
srScroll.Left = cursor.X;
|
||||
srScroll.Right = SHORT_MAX;
|
||||
srScroll.Right = _pConApi->PrivateGetLineWidth(cursor.Y) - 1;
|
||||
srScroll.Top = cursor.Y;
|
||||
srScroll.Bottom = srScroll.Top;
|
||||
|
||||
|
@ -534,7 +533,7 @@ bool AdaptDispatch::_EraseSingleLineHelper(const CONSOLE_SCREEN_BUFFER_INFOEX& c
|
|||
case DispatchTypes::EraseType::ToEnd:
|
||||
case DispatchTypes::EraseType::All:
|
||||
// Remember the .X value is 1 farther than the right most column in the buffer. Therefore no +1.
|
||||
nLength = csbiex.dwSize.X - coordStartPosition.X;
|
||||
nLength = _pConApi->PrivateGetLineWidth(lineId) - coordStartPosition.X;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -622,6 +621,23 @@ bool AdaptDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
|
|||
|
||||
if (success)
|
||||
{
|
||||
// When erasing the display, every line that is erased in full should be
|
||||
// reset to single width. When erasing to the end, this could include
|
||||
// the current line, if the cursor is in the first column. When erasing
|
||||
// from the beginning, though, the current line would never be included,
|
||||
// because the cursor could never be in the rightmost column (assuming
|
||||
// the line is double width).
|
||||
if (eraseType == DispatchTypes::EraseType::FromBeginning)
|
||||
{
|
||||
const auto endRow = csbiex.dwCursorPosition.Y;
|
||||
_pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, endRow);
|
||||
}
|
||||
if (eraseType == DispatchTypes::EraseType::ToEnd)
|
||||
{
|
||||
const auto startRow = csbiex.dwCursorPosition.Y + (csbiex.dwCursorPosition.X > 0 ? 1 : 0);
|
||||
_pConApi->PrivateResetLineRenditionRange(startRow, csbiex.srWindow.Bottom);
|
||||
}
|
||||
|
||||
// What we need to erase is grouped into 3 types:
|
||||
// 1. Lines before cursor
|
||||
// 2. Cursor Line
|
||||
|
@ -697,6 +713,18 @@ bool AdaptDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
|
|||
return success;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECSWL/DECDWL/DECDHL - Sets the line rendition attribute for the current line.
|
||||
// Arguments:
|
||||
// - rendition - Determines whether the line will be rendered as single width, double
|
||||
// width, or as one half of a double height line.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
|
||||
{
|
||||
return _pConApi->PrivateSetCurrentLineRendition(rendition);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DSR - Reports status of a console property back to the STDIN based on the type of status requested.
|
||||
// - This particular routine responds to ANSI status patterns only (CSI # n), not the DEC format (CSI ? # n)
|
||||
|
@ -1931,6 +1959,8 @@ bool AdaptDispatch::ScreenAlignmentPattern()
|
|||
auto fillPosition = COORD{ 0, csbiex.srWindow.Top };
|
||||
const auto fillLength = (csbiex.srWindow.Bottom - csbiex.srWindow.Top) * csbiex.dwSize.X;
|
||||
success = _pConApi->PrivateFillRegion(fillPosition, fillLength, L'E', false);
|
||||
// Reset the line rendition for all of these rows.
|
||||
success = success && _pConApi->PrivateResetLineRenditionRange(csbiex.srWindow.Top, csbiex.srWindow.Bottom);
|
||||
// Reset the meta/extended attributes (but leave the colors unchanged).
|
||||
TextAttribute attr;
|
||||
if (_pConApi->PrivateGetTextAttributes(attr))
|
||||
|
@ -1995,6 +2025,8 @@ bool AdaptDispatch::_EraseScrollback()
|
|||
const COORD coordBelowStartPosition = { 0, height };
|
||||
// Again we need to use the default attributes, hence standardFillAttrs is false.
|
||||
success = _pConApi->PrivateFillRegion(coordBelowStartPosition, totalAreaBelow, L' ', false);
|
||||
// Also reset the line rendition for all of the cleared rows.
|
||||
success = success && _pConApi->PrivateResetLineRenditionRange(height, csbiex.dwSize.Y);
|
||||
|
||||
if (success)
|
||||
{
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
bool InsertCharacter(const size_t count) override; // ICH
|
||||
bool DeleteCharacter(const size_t count) override; // DCH
|
||||
bool SetGraphicsRendition(const VTParameters options) override; // SGR
|
||||
bool SetLineRendition(const LineRendition rendition) override; // DECSWL, DECDWL, DECDHL
|
||||
bool PushGraphicsRendition(const VTParameters options) override; // XTPUSHSGR
|
||||
bool PopGraphicsRendition() override; // XTPOPSGR
|
||||
bool DeviceStatusReport(const DispatchTypes::AnsiStatusType statusType) override; // DSR, DSR-OS, DSR-CPR
|
||||
|
|
|
@ -16,6 +16,7 @@ Author(s):
|
|||
#pragma once
|
||||
|
||||
#include "../../types/inc/IInputEvent.hpp"
|
||||
#include "../../buffer/out/LineRendition.hpp"
|
||||
#include "../../buffer/out/TextAttribute.hpp"
|
||||
#include "../../inc/conattrs.hpp"
|
||||
|
||||
|
@ -39,6 +40,10 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
virtual bool PrivateGetTextAttributes(TextAttribute& attrs) const = 0;
|
||||
virtual bool PrivateSetTextAttributes(const TextAttribute& attrs) = 0;
|
||||
|
||||
virtual bool PrivateSetCurrentLineRendition(const LineRendition lineRendition) = 0;
|
||||
virtual bool PrivateResetLineRenditionRange(const size_t startRow, const size_t endRow) = 0;
|
||||
virtual SHORT PrivateGetLineWidth(const size_t row) const = 0;
|
||||
|
||||
virtual bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
|
||||
size_t& eventsWritten) = 0;
|
||||
virtual bool SetConsoleWindowInfo(const bool absolute,
|
||||
|
|
|
@ -82,6 +82,7 @@ public:
|
|||
bool EraseCharacters(const size_t /*numChars*/) noexcept override { return false; } // ECH
|
||||
|
||||
bool SetGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // SGR
|
||||
bool SetLineRendition(const LineRendition /*rendition*/) noexcept override { return false; } // DECSWL, DECDWL, DECDHL
|
||||
|
||||
bool PushGraphicsRendition(const VTParameters /*options*/) noexcept override { return false; } // XTPUSHSGR
|
||||
bool PopGraphicsRendition() noexcept override { return false; } // XTPOPSGR
|
||||
|
|
|
@ -249,6 +249,27 @@ public:
|
|||
return _privateSetTextAttributesResult;
|
||||
}
|
||||
|
||||
bool PrivateSetCurrentLineRendition(const LineRendition /*lineRendition*/)
|
||||
{
|
||||
Log::Comment(L"PrivateSetCurrentLineRendition MOCK called...");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PrivateResetLineRenditionRange(const size_t /*startRow*/, const size_t /*endRow*/)
|
||||
{
|
||||
Log::Comment(L"PrivateResetLineRenditionRange MOCK called...");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SHORT PrivateGetLineWidth(const size_t /*row*/) const
|
||||
{
|
||||
Log::Comment(L"PrivateGetLineWidth MOCK called...");
|
||||
|
||||
return _bufferSize.X;
|
||||
}
|
||||
|
||||
bool PrivateWriteConsoleInputW(std::deque<std::unique_ptr<IInputEvent>>& events,
|
||||
size_t& eventsWritten) override
|
||||
{
|
||||
|
|
|
@ -265,6 +265,22 @@ bool OutputStateMachineEngine::ActionEscDispatch(const VTID id)
|
|||
success = _dispatch->LockingShiftRight(3);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::LS3R);
|
||||
break;
|
||||
case EscActionCodes::DECDHL_DoubleHeightLineTop:
|
||||
_dispatch->SetLineRendition(LineRendition::DoubleHeightTop);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDHL);
|
||||
break;
|
||||
case EscActionCodes::DECDHL_DoubleHeightLineBottom:
|
||||
_dispatch->SetLineRendition(LineRendition::DoubleHeightBottom);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDHL);
|
||||
break;
|
||||
case EscActionCodes::DECSWL_SingleWidthLine:
|
||||
_dispatch->SetLineRendition(LineRendition::SingleWidth);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSWL);
|
||||
break;
|
||||
case EscActionCodes::DECDWL_DoubleWidthLine:
|
||||
_dispatch->SetLineRendition(LineRendition::DoubleWidth);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECDWL);
|
||||
break;
|
||||
case EscActionCodes::DECALN_ScreenAlignmentPattern:
|
||||
success = _dispatch->ScreenAlignmentPattern();
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN);
|
||||
|
|
|
@ -86,6 +86,10 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
LS1R_LockingShift = VTID("~"),
|
||||
LS2R_LockingShift = VTID("}"),
|
||||
LS3R_LockingShift = VTID("|"),
|
||||
DECDHL_DoubleHeightLineTop = VTID("#3"),
|
||||
DECDHL_DoubleHeightLineBottom = VTID("#4"),
|
||||
DECSWL_SingleWidthLine = VTID("#5"),
|
||||
DECDWL_DoubleWidthLine = VTID("#6"),
|
||||
DECALN_ScreenAlignmentPattern = VTID("#8")
|
||||
};
|
||||
|
||||
|
|
|
@ -273,6 +273,9 @@ void TermTelemetry::WriteFinalTraceLog() const
|
|||
TraceLoggingUInt32(_uiTimesUsed[OSCBG], "OscBackgroundColor"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[OSCSCB], "OscSetClipboard"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECSWL], "DECSWL"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECDWL], "DECDWL"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECDHL], "DECDHL"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"),
|
||||
|
|
|
@ -99,6 +99,9 @@ namespace Microsoft::Console::VirtualTerminal
|
|||
REP,
|
||||
OSCFG,
|
||||
OSCBG,
|
||||
DECSWL,
|
||||
DECDWL,
|
||||
DECDHL,
|
||||
DECALN,
|
||||
OSCSCB,
|
||||
XTPUSHSGR,
|
||||
|
|
|
@ -410,7 +410,8 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
|
|||
|
||||
// GH#6402: Get the actual buffer size here, instead of the one
|
||||
// constrained by the virtual bottom.
|
||||
const auto bufferSize = _pData->GetTextBuffer().GetSize();
|
||||
const auto& buffer = _pData->GetTextBuffer();
|
||||
const auto bufferSize = buffer.GetSize();
|
||||
|
||||
// these viewport vars are converted to the buffer coordinate space
|
||||
const auto viewport = bufferSize.ConvertToOrigin(_pData->GetViewport());
|
||||
|
@ -448,11 +449,14 @@ IFACEMETHODIMP UiaTextRangeBase::GetBoundingRectangles(_Outptr_result_maybenull_
|
|||
}
|
||||
else
|
||||
{
|
||||
const auto textRects = _pData->GetTextBuffer().GetTextRects(startAnchor, endAnchor, _blockRange);
|
||||
const auto textRects = buffer.GetTextRects(startAnchor, endAnchor, _blockRange, true);
|
||||
|
||||
for (const auto& rect : textRects)
|
||||
{
|
||||
til::rectangle r{ rect };
|
||||
// Convert the buffer coordinates to an equivalent range of
|
||||
// screen cells, taking line rendition into account.
|
||||
const auto lineRendition = buffer.GetLineRendition(rect.Top);
|
||||
til::rectangle r{ BufferToScreenLine(rect, lineRendition) };
|
||||
r -= viewportOrigin;
|
||||
_getBoundingRect(r, coords);
|
||||
}
|
||||
|
@ -551,7 +555,7 @@ std::wstring UiaTextRangeBase::_getTextValue(std::optional<unsigned int> maxLeng
|
|||
auto inclusiveEnd = _end;
|
||||
bufferSize.DecrementInBounds(inclusiveEnd, true);
|
||||
|
||||
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange);
|
||||
const auto textRects = buffer.GetTextRects(_start, inclusiveEnd, _blockRange, true);
|
||||
const auto bufferData = buffer.GetText(true,
|
||||
false,
|
||||
textRects);
|
||||
|
|
Loading…
Reference in a new issue