PRE-MERGE #17143 Improve Viewport and Viewport::WalkInBounds

This commit is contained in:
Mike Griese
2024-06-03 15:02:53 -05:00
19 changed files with 65 additions and 601 deletions

View File

@@ -993,7 +993,7 @@ const til::CoordType TextBuffer::GetFirstRowIndex() const noexcept
const Viewport TextBuffer::GetSize() const noexcept
{
return Viewport::FromDimensions({ _width, _height });
return Viewport::FromDimensions({}, { _width, _height });
}
void TextBuffer::_SetFirstRowIndex(const til::CoordType FirstRowIndex) noexcept
@@ -1597,7 +1597,7 @@ til::point TextBuffer::_GetWordEndForSelection(const til::point target, const st
}
}
bufferSize.IncrementInBoundsCircular(result);
bufferSize.IncrementInBounds(result);
}
if (_GetDelimiterClassAt(result, wordDelimiters) != initialDelimiter)

View File

@@ -2645,21 +2645,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Select the region of text between [s.start, s.end), in buffer space
void ControlCore::_selectSpan(til::point_span s)
{
// s.end is an _exclusive_ point. We need an inclusive one. But
// decrement in bounds wants an inclusive one. If you pass an exclusive
// one, then it might assert at you for being out of bounds. So we also
// take care of the case that the end point is outside the viewport
// manually.
// s.end is an _exclusive_ point. We need an inclusive one.
const auto bufferSize{ _terminal->GetTextBuffer().GetSize() };
til::point inclusiveEnd = s.end;
if (s.end.x == bufferSize.Width())
{
inclusiveEnd = til::point{ std::max(0, s.end.x - 1), s.end.y };
}
else
{
bufferSize.DecrementInBounds(inclusiveEnd);
}
bufferSize.DecrementInBounds(inclusiveEnd);
_terminal->SelectNewRegion(s.start, inclusiveEnd);
_renderer->TriggerSelection();

View File

@@ -356,7 +356,7 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen
_renderer->TriggerRedrawAll();
// Convert our new dimensions to characters
const auto viewInPixels = Viewport::FromDimensions(windowSize);
const auto viewInPixels = Viewport::FromDimensions({}, windowSize);
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
// Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support.
@@ -464,7 +464,7 @@ try
Viewport viewInPixels;
{
const auto viewInCharacters = Viewport::FromDimensions(dimensionsInCharacters);
const auto viewInCharacters = Viewport::FromDimensions({}, dimensionsInCharacters);
const auto lock = publicTerminal->_terminal->LockForReading();
viewInPixels = publicTerminal->_renderEngine->GetViewportInPixels(viewInCharacters);
}
@@ -491,7 +491,7 @@ try
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);
const auto viewInPixels = Viewport::FromDimensions({ width, height });
const auto viewInPixels = Viewport::FromDimensions({}, { width, height });
const auto lock = publicTerminal->_terminal->LockForReading();
const auto viewInCharacters = publicTerminal->_renderEngine->GetViewportInCharacters(viewInPixels);

View File

@@ -961,7 +961,7 @@ Viewport Terminal::_GetMutableViewport() const noexcept
// GH#3493: if we're in the alt buffer, then it's possible that the mutable
// viewport's size hasn't been updated yet. In that case, use the
// temporarily stashed _altBufferSize instead.
return _inAltBuffer() ? Viewport::FromDimensions(_altBufferSize) :
return _inAltBuffer() ? Viewport::FromDimensions({}, _altBufferSize) :
_mutableViewport;
}

View File

@@ -495,7 +495,7 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
searchEnd = { bufferSize.RightInclusive(), searchStart.y - 1 };
searchStart = { bufferSize.Left(), std::max(searchStart.y - viewportHeight, bufferSize.Top()) };
}
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
searchArea = Viewport::FromDimensions(searchStart, { searchEnd.x + 1, searchEnd.y + 1 });
const til::point bufferStart{ bufferSize.Origin() };
const til::point bufferEnd{ bufferSize.RightInclusive(), ViewEndIndex() };
@@ -516,7 +516,7 @@ void Terminal::SelectHyperlink(const SearchDirection dir)
searchEnd.y -= 1;
searchStart.y = std::max(searchEnd.y - viewportHeight, bufferSize.Top());
}
searchArea = Viewport::FromDimensions(searchStart, searchEnd.x + 1, searchEnd.y + 1);
searchArea = Viewport::FromDimensions(searchStart, { searchEnd.x + 1, searchEnd.y + 1 });
}
}

View File

@@ -4873,13 +4873,7 @@ void ConptyRoundtripTests::ReflowPromptRegions()
til::point afterPos = originalPos;
// walk that original pos dx times into the actual real place in the buffer.
auto bufferViewport = tb.GetSize();
const auto walkDir = Viewport::WalkDir{ dx < 0 ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft,
dx < 0 ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop };
for (auto i = 0; i < std::abs(dx); i++)
{
bufferViewport.WalkInBounds(afterPos,
walkDir);
}
bufferViewport.WalkInBounds(afterPos, -dx);
const auto expectedOutputStart = !afterResize ?
originalPos : // printed exactly a row, so we're exactly below the prompt
afterPos;

View File

@@ -150,9 +150,7 @@ VtIo::VtIo() :
if (IsValidHandle(_hOutput.get()))
{
auto initialViewport = Viewport::FromDimensions({ 0, 0 },
gci.GetWindowSize().width,
gci.GetWindowSize().height);
auto initialViewport = Viewport::FromDimensions({ 0, 0 }, gci.GetWindowSize());
switch (_IoMode)
{
case VtIoMode::XTERM_256:

View File

@@ -223,7 +223,7 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
{
// Notify accessibility
auto endingCoordinate = startingCoordinate;
bufferSize.MoveInBounds(cellsModifiedCoord, endingCoordinate);
bufferSize.WalkInBounds(endingCoordinate, cellsModifiedCoord);
screenBuffer.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y);
}
}
@@ -287,7 +287,7 @@ void WriteToScreen(SCREEN_INFORMATION& screenInfo, const Viewport& region)
if (screenInfo.HasAccessibilityEventing())
{
auto endingCoordinate = startingCoordinate;
bufferSize.MoveInBounds(cellsModifiedCoord, endingCoordinate);
bufferSize.WalkInBounds(endingCoordinate, cellsModifiedCoord);
screenInfo.NotifyAccessibilityEventing(startingCoordinate.x, startingCoordinate.y, endingCoordinate.x, endingCoordinate.y);
}

View File

@@ -321,7 +321,7 @@ bool ConhostInternalGetSet::ResizeWindow(const til::CoordType sColumns, const ti
api->GetConsoleScreenBufferInfoExImpl(screenInfo, csbiex);
const auto oldViewport = screenInfo.GetVirtualViewport();
auto newViewport = Viewport::FromDimensions(oldViewport.Origin(), sColumns, sRows);
auto newViewport = Viewport::FromDimensions(oldViewport.Origin(), { sColumns, sRows });
// Always resize the width of the console
csbiex.dwSize.X = gsl::narrow_cast<short>(sColumns);
// Only set the screen buffer's height if it's currently less than

View File

@@ -163,7 +163,7 @@ Viewport SCREEN_INFORMATION::GetTerminalBufferSize() const
auto v = _textBuffer->GetSize();
if (gci.IsTerminalScrolling() && v.Height() > _virtualBottom)
{
v = Viewport::FromDimensions({ 0, 0 }, v.Width(), _virtualBottom + 1);
v = Viewport::FromDimensions({}, { v.Width(), _virtualBottom + 1 });
}
return v;
}

View File

@@ -955,7 +955,7 @@ bool Selection::_HandleMarkModeSelectionNav(const INPUT_KEY_INFO* const pInputKe
if (pcoordInputEnd != nullptr)
{
// - 1 so the coordinate is on top of the last position of the text, not one past it.
gci.GetActiveOutputBuffer().GetBufferSize().MoveInBounds(-1, boundaries.end);
gci.GetActiveOutputBuffer().GetBufferSize().WalkInBounds(boundaries.end, -1);
*pcoordInputEnd = boundaries.end;
}
return true;

View File

@@ -79,7 +79,7 @@ class ScreenBufferTests
VERIFY_SUCCEEDED(currentBuffer.SetViewportOrigin(true, { 0, 0 }, true));
// Make sure the viewport always starts off at the default size.
auto defaultSize = til::size{ CommonState::s_csWindowWidth, CommonState::s_csWindowHeight };
currentBuffer.SetViewport(Viewport::FromDimensions(defaultSize), true);
currentBuffer.SetViewport(Viewport::FromDimensions({}, defaultSize), true);
VERIFY_ARE_EQUAL(til::point(0, 0), currentBuffer.GetTextBuffer().GetCursor().GetPosition());
// Make sure the virtual bottom is correctly positioned.
currentBuffer.UpdateBottom();

View File

@@ -3013,13 +3013,7 @@ void TextBufferTests::ReflowPromptRegions()
til::point afterPos = originalPos;
// walk that original pos dx times into the actual real place in the buffer.
auto bufferViewport = tb.GetSize();
const auto walkDir = Viewport::WalkDir{ dx < 0 ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft,
dx < 0 ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop };
for (auto i = 0; i < std::abs(dx); i++)
{
bufferViewport.WalkInBounds(afterPos,
walkDir);
}
bufferViewport.WalkInBounds(afterPos, -dx);
const auto expectedOutputStart = !afterResize ?
originalPos : // printed exactly a row, so we're exactly below the prompt
afterPos;

View File

@@ -109,7 +109,7 @@ class ViewportTests
dimensions.width = rect.right - rect.left + 1;
dimensions.height = rect.bottom - rect.top + 1;
const auto v = Viewport::FromDimensions(origin, dimensions.width, dimensions.height);
const auto v = Viewport::FromDimensions(origin, dimensions);
VERIFY_ARE_EQUAL(rect.left, v.Left());
VERIFY_ARE_EQUAL(rect.right, v.RightInclusive());
@@ -169,7 +169,7 @@ class ViewportTests
dimensions.width = rect.right - rect.left + 1;
dimensions.height = rect.bottom - rect.top + 1;
const auto v = Viewport::FromDimensions(dimensions);
const auto v = Viewport::FromDimensions({}, dimensions);
VERIFY_ARE_EQUAL(rect.left, v.Left());
VERIFY_ARE_EQUAL(rect.right, v.RightInclusive());
@@ -183,28 +183,6 @@ class ViewportTests
VERIFY_ARE_EQUAL(dimensions, v.Dimensions());
}
TEST_METHOD(CreateFromCoord)
{
til::point origin;
origin.x = 12;
origin.y = 24;
const auto v = Viewport::FromCoord(origin);
VERIFY_ARE_EQUAL(origin.x, v.Left());
VERIFY_ARE_EQUAL(origin.x, v.RightInclusive());
VERIFY_ARE_EQUAL(origin.x + 1, v.RightExclusive());
VERIFY_ARE_EQUAL(origin.y, v.Top());
VERIFY_ARE_EQUAL(origin.y, v.BottomInclusive());
VERIFY_ARE_EQUAL(origin.y + 1, v.BottomExclusive());
VERIFY_ARE_EQUAL(1, v.Height());
VERIFY_ARE_EQUAL(1, v.Width());
VERIFY_ARE_EQUAL(origin, v.Origin());
// clang-format off
VERIFY_ARE_EQUAL(til::size(1, 1), v.Dimensions());
// clang-format on
}
TEST_METHOD(IsInBoundsCoord)
{
til::inclusive_rect r;
@@ -503,51 +481,6 @@ class ViewportTests
VERIFY_ARE_EQUAL(screen.y, edges.bottom);
}
TEST_METHOD(IncrementInBoundsCircular)
{
auto success = false;
til::inclusive_rect edges;
edges.left = 10;
edges.right = 19;
edges.top = 20;
edges.bottom = 29;
const auto v = Viewport::FromInclusive(edges);
til::point original;
til::point screen;
// #1 coord inside region
original.x = screen.x = 15;
original.y = screen.y = 25;
success = v.IncrementInBoundsCircular(screen);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(screen.x, original.x + 1);
VERIFY_ARE_EQUAL(screen.y, original.y);
// #2 coord right edge, not bottom
original.x = screen.x = edges.right;
original.y = screen.y = 25;
success = v.IncrementInBoundsCircular(screen);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(screen.x, edges.left);
VERIFY_ARE_EQUAL(screen.y, original.y + 1);
// #3 coord right edge, bottom
original.x = screen.x = edges.right;
original.y = screen.y = edges.bottom;
success = v.IncrementInBoundsCircular(screen);
VERIFY_IS_FALSE(success);
VERIFY_ARE_EQUAL(screen.x, edges.left);
VERIFY_ARE_EQUAL(screen.y, edges.top);
}
TEST_METHOD(DecrementInBounds)
{
auto success = false;
@@ -593,52 +526,7 @@ class ViewportTests
VERIFY_ARE_EQUAL(screen.y, edges.top);
}
TEST_METHOD(DecrementInBoundsCircular)
{
auto success = false;
til::inclusive_rect edges;
edges.left = 10;
edges.right = 19;
edges.top = 20;
edges.bottom = 29;
const auto v = Viewport::FromInclusive(edges);
til::point original;
til::point screen;
// #1 coord inside region
original.x = screen.x = 15;
original.y = screen.y = 25;
success = v.DecrementInBoundsCircular(screen);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(screen.x, original.x - 1);
VERIFY_ARE_EQUAL(screen.y, original.y);
// #2 coord left edge, not top
original.x = screen.x = edges.left;
original.y = screen.y = 25;
success = v.DecrementInBoundsCircular(screen);
VERIFY_IS_TRUE(success);
VERIFY_ARE_EQUAL(screen.x, edges.right);
VERIFY_ARE_EQUAL(screen.y, original.y - 1);
// #3 coord left edge, top
original.x = screen.x = edges.left;
original.y = screen.y = edges.top;
success = v.DecrementInBoundsCircular(screen);
VERIFY_IS_FALSE(success);
VERIFY_ARE_EQUAL(screen.x, edges.right);
VERIFY_ARE_EQUAL(screen.y, edges.bottom);
}
til::CoordType RandomCoord()
static til::CoordType RandomCoord()
{
til::CoordType s;
@@ -673,29 +561,16 @@ class ViewportTests
auto sAddAmount = RandomCoord() % (sRowWidth * sRowWidth);
til::point coordFinal;
coordFinal.x = (coordPos.x + sAddAmount) % sRowWidth;
coordFinal.y = coordPos.y + ((coordPos.x + sAddAmount) / sRowWidth);
Log::Comment(String().Format(L"Add To Position: (%d, %d) Amount to add: %d", coordPos.y, coordPos.x, sAddAmount));
// Movement result is expected to be true, unless there's an error.
auto fExpectedResult = true;
// if we've calculated past the final row, then the function will reset to the original position and the output will be false.
if (coordFinal.y >= sRowWidth)
{
coordFinal = coordPos;
fExpectedResult = false;
}
const bool fActualResult = v.MoveInBounds(sAddAmount, coordPos);
const til::point coord{
(coordPos.x + sAddAmount) % sRowWidth,
coordPos.y + ((coordPos.x + sAddAmount) / sRowWidth),
};
const auto coordClamped = std::clamp(coord, v.Origin(), v.BottomRightInclusive());
const auto fExpectedResult = coord == coordClamped;
const bool fActualResult = v.WalkInBounds(coordPos, sAddAmount);
VERIFY_ARE_EQUAL(fExpectedResult, fActualResult);
VERIFY_ARE_EQUAL(coordPos.x, coordFinal.x);
VERIFY_ARE_EQUAL(coordPos.y, coordFinal.y);
Log::Comment(String().Format(L"Actual: (%d, %d) Expected: (%d, %d)", coordPos.y, coordPos.x, coordFinal.y, coordFinal.x));
VERIFY_ARE_EQUAL(coordPos, coordClamped);
}
}

View File

@@ -274,7 +274,7 @@ void Renderer::TriggerRedraw(const Viewport& region)
// - <none>
void Renderer::TriggerRedraw(const til::point* const pcoord)
{
TriggerRedraw(Viewport::FromCoord(*pcoord)); // this will notify to paint if we need it.
TriggerRedraw(Viewport::FromDimensions(*pcoord, { 1, 1 })); // this will notify to paint if we need it.
}
// Routine Description:

View File

@@ -607,8 +607,8 @@ void AdaptDispatch::_ScrollRectVertically(const Page& page, const til::rect& scr
// requested buffer range one cell at a time.
const auto srcOrigin = til::point{ scrollRect.left, top };
const auto dstOrigin = til::point{ scrollRect.left, top + actualDelta };
const auto srcView = Viewport::FromDimensions(srcOrigin, width, height);
const auto dstView = Viewport::FromDimensions(dstOrigin, width, height);
const auto srcView = Viewport::FromDimensions(srcOrigin, { width, height });
const auto dstView = Viewport::FromDimensions(dstOrigin, { width, height });
const auto walkDirection = Viewport::DetermineWalkDirection(srcView, dstView);
auto srcPos = srcView.GetWalkOrigin(walkDirection);
auto dstPos = dstView.GetWalkOrigin(walkDirection);
@@ -652,7 +652,7 @@ void AdaptDispatch::_ScrollRectHorizontally(const Page& page, const til::rect& s
const auto height = scrollRect.height();
const auto actualDelta = delta > 0 ? absoluteDelta : -absoluteDelta;
const auto source = Viewport::FromDimensions({ left, top }, width, height);
const auto source = Viewport::FromDimensions({ left, top }, { width, height });
const auto target = Viewport::Offset(source, { actualDelta, 0 });
const auto walkDirection = Viewport::DetermineWalkDirection(source, target);
auto sourcePos = source.GetWalkOrigin(walkDirection);
@@ -886,7 +886,7 @@ void AdaptDispatch::_SelectiveEraseRect(const Page& page, const til::rect& erase
{
// The text is cleared but the attributes are left as is.
rowBuffer.ClearCell(col);
page.Buffer().TriggerRedraw(Viewport::FromCoord({ col, row }));
page.Buffer().TriggerRedraw(Viewport::FromDimensions({ col, row }, { 1, 1 }));
}
}
}

View File

@@ -561,7 +561,7 @@ try
const auto originY{ std::min(_start.y, inclusiveEnd.y) };
const auto width{ std::abs(inclusiveEnd.x - _start.x + 1) };
const auto height{ std::abs(inclusiveEnd.y - _start.y + 1) };
viewportRange = Viewport::FromDimensions({ originX, originY }, width, height);
viewportRange = Viewport::FromDimensions({ originX, originY }, { width, height });
}
auto iter{ buffer.GetCellDataAt(searchStart, viewportRange) };
const auto iterStep{ searchBackwards ? -1 : 1 };
@@ -827,7 +827,7 @@ try
const auto originY{ std::min(_start.y, inclusiveEnd.y) };
const auto width{ std::abs(inclusiveEnd.x - _start.x + 1) };
const auto height{ std::abs(inclusiveEnd.y - _start.y + 1) };
viewportRange = Viewport::FromDimensions({ originX, originY }, width, height);
viewportRange = Viewport::FromDimensions({ originX, originY }, { width, height });
}
auto iter{ buffer.GetCellDataAt(_start, viewportRange) };
for (; iter && iter.Pos() != inclusiveEnd; ++iter)
@@ -1337,7 +1337,7 @@ Viewport UiaTextRangeBase::_getOptimizedBufferSize() const noexcept
const auto width = textBufferEnd.x + 1;
const auto height = textBufferEnd.y + 1;
return Viewport::FromDimensions({ 0, 0 }, width, height);
return Viewport::FromDimensions({ 0, 0 }, { width, height });
}
// We consider the "document end" to be the line beneath the cursor or

View File

@@ -28,17 +28,7 @@ namespace Microsoft::Console::Types
static Viewport FromInclusive(const til::inclusive_rect& sr) noexcept;
static Viewport FromExclusive(const til::rect& sr) noexcept;
static Viewport FromDimensions(const til::point origin,
const til::CoordType width,
const til::CoordType height) noexcept;
static Viewport FromDimensions(const til::point origin,
const til::size dimensions) noexcept;
static Viewport FromDimensions(const til::size dimensions) noexcept;
static Viewport FromCoord(const til::point origin) noexcept;
static Viewport FromDimensions(const til::point origin, const til::size dimensions) noexcept;
til::CoordType Left() const noexcept;
til::CoordType RightInclusive() const noexcept;
@@ -60,35 +50,13 @@ namespace Microsoft::Console::Types
void Clamp(til::point& pos) const;
Viewport Clamp(const Viewport& other) const noexcept;
bool MoveInBounds(const til::CoordType move, til::point& pos) const noexcept;
bool IncrementInBounds(til::point& pos, bool allowEndExclusive = false) const noexcept;
bool IncrementInBoundsCircular(til::point& pos) const noexcept;
bool DecrementInBounds(til::point& pos, bool allowEndExclusive = false) const noexcept;
bool DecrementInBoundsCircular(til::point& pos) const noexcept;
int CompareInBounds(const til::point first, const til::point second, bool allowEndExclusive = false) const noexcept;
enum class XWalk
{
LeftToRight,
RightToLeft
};
enum class YWalk
{
TopToBottom,
BottomToTop
};
struct WalkDir final
{
const XWalk x;
const YWalk y;
};
bool WalkInBounds(til::point& pos, const WalkDir dir, bool allowEndExclusive = false) const noexcept;
bool WalkInBoundsCircular(til::point& pos, const WalkDir dir, bool allowEndExclusive = false) const noexcept;
til::point GetWalkOrigin(const WalkDir dir) const noexcept;
static WalkDir DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept;
bool WalkInBounds(til::point& pos, const til::CoordType delta, bool allowEndExclusive = false) const noexcept;
til::point GetWalkOrigin(const til::CoordType delta) const noexcept;
static til::CoordType DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept;
bool TrimToViewport(_Inout_ til::rect* psr) const noexcept;
void ConvertToOrigin(_Inout_ til::rect* psr) const noexcept;

View File

@@ -26,26 +26,6 @@ Viewport Viewport::FromExclusive(const til::rect& sr) noexcept
return FromInclusive(sr.to_inclusive_rect());
}
// Function Description:
// - Creates a new Viewport at the given origin, with the given dimensions.
// Arguments:
// - origin: The origin of the new Viewport. Becomes the Viewport's Left, Top
// - width: The width of the new viewport
// - height: The height of the new viewport
// Return Value:
// - a new Viewport at the given origin, with the given dimensions.
Viewport Viewport::FromDimensions(const til::point origin,
const til::CoordType width,
const til::CoordType height) noexcept
{
return Viewport(til::inclusive_rect{
origin.x,
origin.y,
origin.x + width - 1,
origin.y + height - 1,
});
}
// Function Description:
// - Creates a new Viewport at the given origin, with the given dimensions.
// Arguments:
@@ -65,29 +45,6 @@ Viewport Viewport::FromDimensions(const til::point origin,
});
}
// Function Description:
// - Creates a new Viewport at the origin, with the given dimensions.
// Arguments:
// - dimensions: A coordinate containing the width and height of the new viewport
// in the x and y coordinates respectively.
// Return Value:
// - a new Viewport at the origin, with the given dimensions.
Viewport Viewport::FromDimensions(const til::size dimensions) noexcept
{
return FromDimensions({}, dimensions);
}
// Method Description:
// - Creates a Viewport equivalent to a 1x1 rectangle at the given coordinate.
// Arguments:
// - origin: origin of the rectangle to create.
// Return Value:
// - a 1x1 Viewport at the given coordinate
Viewport Viewport::FromCoord(const til::point origin) noexcept
{
return FromInclusive(til::inclusive_rect{ origin.x, origin.y, origin.x, origin.y });
}
til::CoordType Viewport::Left() const noexcept
{
return _sr.left;
@@ -251,51 +208,6 @@ Viewport Viewport::Clamp(const Viewport& other) const noexcept
return Viewport::FromInclusive(clampMe);
}
// Method Description:
// - Moves the coordinate given by the number of positions and
// in the direction given (repeated increment or decrement)
// Arguments:
// - move - Magnitude and direction of the move
// - pos - The coordinate position to adjust
// Return Value:
// - True if we successfully moved the requested distance. False if we had to stop early.
// - If False, we will restore the original position to the given coordinate.
bool Viewport::MoveInBounds(const til::CoordType move, til::point& pos) const noexcept
{
const auto backup = pos;
auto success = true; // If nothing happens, we're still successful (e.g. add = 0)
for (til::CoordType i = 0; i < move; i++)
{
success = IncrementInBounds(pos);
// If an operation fails, break.
if (!success)
{
break;
}
}
for (til::CoordType i = 0; i > move; i--)
{
success = DecrementInBounds(pos);
// If an operation fails, break.
if (!success)
{
break;
}
}
// If any operation failed, revert to backed up state.
if (!success)
{
pos = backup;
}
return success;
}
// Method Description:
// - Increments the given coordinate within the bounds of this viewport.
// Arguments:
@@ -307,20 +219,7 @@ bool Viewport::MoveInBounds(const til::CoordType move, til::point& pos) const no
// - True if it could be incremented. False if it would move outside.
bool Viewport::IncrementInBounds(til::point& pos, bool allowEndExclusive) const noexcept
{
return WalkInBounds(pos, { XWalk::LeftToRight, YWalk::TopToBottom }, allowEndExclusive);
}
// Method Description:
// - Increments the given coordinate within the bounds of this viewport
// rotating around to the top when reaching the bottom right corner.
// Arguments:
// - pos - Coordinate position that will be incremented.
// Return Value:
// - True if it could be incremented inside the viewport.
// - False if it rolled over from the bottom right corner back to the top.
bool Viewport::IncrementInBoundsCircular(til::point& pos) const noexcept
{
return WalkInBoundsCircular(pos, { XWalk::LeftToRight, YWalk::TopToBottom });
return WalkInBounds(pos, 1, allowEndExclusive);
}
// Method Description:
@@ -334,20 +233,7 @@ bool Viewport::IncrementInBoundsCircular(til::point& pos) const noexcept
// - True if it could be incremented. False if it would move outside.
bool Viewport::DecrementInBounds(til::point& pos, bool allowEndExclusive) const noexcept
{
return WalkInBounds(pos, { XWalk::RightToLeft, YWalk::BottomToTop }, allowEndExclusive);
}
// Method Description:
// - Decrements the given coordinate within the bounds of this viewport
// rotating around to the bottom right when reaching the top left corner.
// Arguments:
// - pos - Coordinate position that will be decremented.
// Return Value:
// - True if it could be decremented inside the viewport.
// - False if it rolled over from the top left corner back to the bottom right.
bool Viewport::DecrementInBoundsCircular(til::point& pos) const noexcept
{
return WalkInBoundsCircular(pos, { XWalk::RightToLeft, YWalk::BottomToTop });
return WalkInBounds(pos, -1, allowEndExclusive);
}
// Routine Description:
@@ -402,110 +288,18 @@ int Viewport::CompareInBounds(const til::point first, const til::point second, b
// includes the last til::point in a given viewport.
// Return Value:
// - True if it could be adjusted as specified and remain in bounds. False if it would move outside.
bool Viewport::WalkInBounds(til::point& pos, const WalkDir dir, bool allowEndExclusive) const noexcept
bool Viewport::WalkInBounds(til::point& pos, const til::CoordType delta, bool allowEndExclusive) const noexcept
{
auto copy = pos;
if (WalkInBoundsCircular(copy, dir, allowEndExclusive))
{
pos = copy;
return true;
}
else
{
return false;
}
}
// Method Description:
// - Walks the given coordinate within the bounds of this viewport
// rotating around to the opposite corner when reaching the final corner
// in the specified direction.
// Arguments:
// - pos - Coordinate position that will be adjusted.
// - dir - Walking direction specifying which direction to go when reaching the end of a row/column
// - allowEndExclusive - if true, allow the EndExclusive til::point as a valid position.
// Used in accessibility to signify that the exclusive end
// includes the last til::point in a given viewport.
// Return Value:
// - True if it could be adjusted inside the viewport.
// - False if it rolled over from the final corner back to the initial corner
// for the specified walk direction.
bool Viewport::WalkInBoundsCircular(til::point& pos, const WalkDir dir, bool allowEndExclusive) const noexcept
{
// Assert that the position given fits inside this viewport.
assert(IsInBounds(pos, allowEndExclusive));
if (dir.x == XWalk::LeftToRight)
{
if (allowEndExclusive && pos.x == Left() && pos.y == BottomExclusive())
{
pos.y = Top();
return false;
}
else if (pos.x == RightInclusive())
{
pos.x = Left();
if (dir.y == YWalk::TopToBottom)
{
pos.y++;
if (allowEndExclusive && pos.y == BottomExclusive())
{
return true;
}
else if (pos.y > BottomInclusive())
{
pos.y = Top();
return false;
}
}
else
{
pos.y--;
if (pos.y < Top())
{
pos.y = BottomInclusive();
return false;
}
}
}
else
{
pos.x++;
}
}
else
{
if (pos.x == Left())
{
pos.x = RightInclusive();
if (dir.y == YWalk::TopToBottom)
{
pos.y++;
if (pos.y > BottomInclusive())
{
pos.y = Top();
return false;
}
}
else
{
pos.y--;
if (pos.y < Top())
{
pos.y = BottomInclusive();
return false;
}
}
}
else
{
pos.x--;
}
}
return true;
const auto l = static_cast<ptrdiff_t>(_sr.left);
const auto t = static_cast<ptrdiff_t>(_sr.top);
const auto w = static_cast<ptrdiff_t>(std::max(0, _sr.right - _sr.left + 1));
const auto h = static_cast<ptrdiff_t>(std::max(0, _sr.bottom - _sr.top + 1));
const auto max = w * h - !allowEndExclusive;
const auto off = w * (pos.y - t) + (pos.x - l) + delta;
const auto offClamped = std::clamp(off, ptrdiff_t{ 0 }, max);
pos.x = gsl::narrow_cast<til::CoordType>(offClamped % w + l);
pos.y = gsl::narrow_cast<til::CoordType>(offClamped / w + t);
return off == offClamped;
}
// Routine Description:
@@ -518,172 +312,24 @@ bool Viewport::WalkInBoundsCircular(til::point& pos, const WalkDir dir, bool all
// Return Value:
// - The origin for the walk to reach every position without circling
// if using this same viewport with the `WalkInBounds` methods.
til::point Viewport::GetWalkOrigin(const WalkDir dir) const noexcept
til::point Viewport::GetWalkOrigin(const til::CoordType delta) const noexcept
{
til::point origin;
origin.x = dir.x == XWalk::LeftToRight ? Left() : RightInclusive();
origin.y = dir.y == YWalk::TopToBottom ? Top() : BottomInclusive();
origin.x = delta >= 0 ? Left() : RightInclusive();
origin.y = delta >= 0 ? Top() : BottomInclusive();
return origin;
}
// Routine Description:
// - Given two viewports that will be used for copying data from one to the other (source, target),
// determine which direction you will have to walk through them to ensure that an overlapped copy
// won't erase data in the source that hasn't yet been read and copied into the target at the same
// coordinate offset position from their respective origins.
// - Note: See elaborate ASCII-art comment inside the body of this function for more details on how/why this works.
// Arguments:
// - source - The viewport representing the region that will be copied from
// - target - The viewport representing the region that will be copied to
// Return Value:
// - The direction to walk through both viewports from the walk origins to touch every cell and not
// accidentally overwrite something that hasn't been read yet. (use with GetWalkOrigin and WalkInBounds)
Viewport::WalkDir Viewport::DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept
// A text buffer is basically a list (std::vector) of characters and we just tell it where the line breaks are.
// This means that copying between overlapping ranges requires the same care as when using std::copy VS std::copy_backward:
// std::copy (aka forward copying) can only be used within an overlapping source/target range if target is before source.
// std::copy_backward, as the name implies, is to be used for the reverse situation: if target is after source.
// This method returns 1 for the former and -1 for the latter. It's the delta you can pass to GetWalkOrigin and WalkInBounds.
til::CoordType Viewport::DetermineWalkDirection(const Viewport& source, const Viewport& target) noexcept
{
// We can determine which direction we need to walk based on solely the origins of the two rectangles.
// I'll use a few examples to prove the situation.
//
// For the cardinal directions, let's start with this sample:
//
// source target
// origin 0,0 origin 4,0
// | |
// v V
// +--source-----+--target--------- +--source-----+--target---------
// | A B C D | E | 1 2 3 4 | becomes | A B C D | A | B C D E |
// | F G H I | J | 5 6 7 8 | =========> | F G H I | F | G H I J |
// | K L M N | O | 9 $ % @ | | K L M N | K | L M N O |
// -------------------------------- --------------------------------
//
// The source and target overlap in the 5th column (X=4).
// To ensure that we don't accidentally write over the source
// data before we copy it into the target, we want to start by
// reading that column (a.k.a. writing to the farthest away column
// of the target).
//
// This means we want to copy from right to left.
// Top to bottom and bottom to top don't really matter for this since it's
// a cardinal direction shift.
//
// If we do the right most column first as so...
//
// +--source-----+--target--------- +--source-----+--target---------
// | A B C D | E | 1 2 3 4 | step 1 | A B C D | E | 1 2 3 E |
// | F G H I | J | 5 6 7 8 | =========> | F G H I | J | 5 6 7 J |
// | K L M N | O | 9 $ % @ | | K L M N | O | 9 $ % O |
// -------------------------------- --------------------------------
//
// ... then we can see that the EJO column is safely copied first out of the way and
// can be overwritten on subsequent steps without losing anything.
// The rest of the columns aren't overlapping, so they'll be fine.
//
// But we extrapolate this logic to follow for rectangles that overlap more columns, up
// to and including only leaving one column not overlapped...
//
// source target
// origin origin
// 0,0 / 1,0
// | /
// v v
// +----+------target- +----+------target-
// | A | B C D | E | becomes | A | A B C | D |
// | F | G H I | J | =========> | F | F G H | I |
// | K | L M N | O | | K | K L M | N |
// ---source---------- ---source----------
//
// ... will still be OK following the same Right-To-Left rule as the first move.
//
// +----+------target- +----+------target-
// | A | B C D | E | step 1 | A | B C D | D |
// | F | G H I | J | =========> | F | G H I | I |
// | K | L M N | O | | K | L M N | N |
// ---source---------- ---source----------
//
// The DIN column from the source was moved to the target as the right most column
// of both rectangles. Now it is safe to iterate to the second column from the right
// and proceed with moving CHM on top of the source DIN as it was already moved.
//
// +----+------target- +----+------target-
// | A | B C D | E | step 2 | A | B C C | D |
// | F | G H I | J | =========> | F | G H H | I |
// | K | L M N | O | | K | L M M | N |
// ---source---------- ---source----------
//
// Continue walking right to left (an exercise left to the reader,) and we never lose
// any source data before it reaches the target with the Right To Left pattern.
//
// We notice that the target origin was Right of the source origin in this circumstance,
// (target origin X is > source origin X)
// so it is asserted that targets right of sources means that we should "walk" right to left.
//
// Reviewing the above, it doesn't appear to matter if we go Top to Bottom or Bottom to Top,
// so the conclusion is drawn that it doesn't matter as long as the source and target origin
// Y values are the same.
//
// Also, extrapolating this cardinal direction move to the other 3 cardinal directions,
// it should follow that they would follow the same rules.
// That is, a target left of a source, or a Westbound move, opposite of the above Eastbound move,
// should be "walked" left to right.
// (target origin X is < source origin X)
//
// We haven't given the sample yet that Northbound and Southbound moves are the same, but we
// could reason that the same logic applies and the conclusion would be a Northbound move
// would walk from the target toward the source again... a.k.a. Top to Bottom.
// (target origin Y is < source origin Y)
// Then the Southbound move would be the opposite, Bottom to Top.
// (target origin Y is > source origin Y)
//
// To confirm, let's try one more example but moving both at once in an ordinal direction Northeast.
//
// target
// origin 1, 0
// |
// v
// +----target-- +----target--
// source A | B C | A | D E |
// origin-->+------------ | becomes +------------ |
// 0, 1 | D | E | F | =========> | D | G | H |
// | ------------- | -------------
// | G H | I | G H | I
// --source----- --source-----
//
// Following our supposed rules from above, we have...
// Source Origin X = 0, Y = 1
// Target Origin X = 1, Y = 0
//
// Source Origin X < Target Origin X which means Right to Left
// Source Origin Y > Target Origin Y which means Top to Bottom
//
// So the first thing we should copy is the Top and Right most
// value from source to target.
//
// +----target-- +----target--
// A | B C | A | B E |
// +------------ | step 1 +------------ |
// | D | E | F | =========> | D | E | F |
// | ------------- | -------------
// | G H | I | G H | I
// --source----- --source-----
//
// And look. The E which was in the overlapping part of the source
// is the first thing copied out of the way and we're safe to copy the rest.
//
// We assume that this pattern then applies to all ordinal directions as well
// and it appears our rules hold.
//
// We've covered all cardinal and ordinal directions... all that is left is two
// rectangles of the same size and origin... and in that case, it doesn't matter
// as nothing is moving and therefore can't be covered up or lost.
//
// Therefore, we will codify our inequalities below as determining the walk direction
// for a given source and target viewport and use the helper `GetWalkOrigin`
// to return the place that we should start walking from when the copy commences.
const auto sourceOrigin = source.Origin();
const auto targetOrigin = target.Origin();
return Viewport::WalkDir{ targetOrigin.x < sourceOrigin.x ? Viewport::XWalk::LeftToRight : Viewport::XWalk::RightToLeft,
targetOrigin.y < sourceOrigin.y ? Viewport::YWalk::TopToBottom : Viewport::YWalk::BottomToTop };
return targetOrigin < sourceOrigin ? 1 : -1;
}
// Method Description: