Clean out the rect stuff and prep for rle stuff.

This commit is contained in:
Michael Niksa 2021-01-08 12:52:42 -08:00
parent 1ed8ef0847
commit 00ca3f204c
2 changed files with 23 additions and 2415 deletions

View file

@ -4,926 +4,70 @@
#pragma once
#ifdef UNIT_TESTING
class RectangleTests;
class RunLengthEncodingTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
namespace details
{
class _rectangle_const_iterator
{
public:
constexpr _rectangle_const_iterator(point topLeft, point bottomRight) :
_topLeft(topLeft),
_bottomRight(bottomRight),
_current(topLeft)
{
}
constexpr _rectangle_const_iterator(point topLeft, point bottomRight, point start) :
_topLeft(topLeft),
_bottomRight(bottomRight),
_current(start)
{
}
_rectangle_const_iterator& operator++()
{
ptrdiff_t nextX;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(_current.x(), 1).AssignIfValid(&nextX));
if (nextX >= _bottomRight.x())
{
ptrdiff_t nextY;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(_current.y(), 1).AssignIfValid(&nextY));
// Note for the standard Left-to-Right, Top-to-Bottom walk,
// the end position is one cell below the bottom left.
// (or more accurately, on the exclusive bottom line in the inclusive left column.)
_current = { _topLeft.x(), nextY };
}
else
{
_current = { nextX, _current.y() };
}
return (*this);
}
constexpr bool operator==(const _rectangle_const_iterator& other) const
{
return _current == other._current &&
_topLeft == other._topLeft &&
_bottomRight == other._bottomRight;
}
constexpr bool operator!=(const _rectangle_const_iterator& other) const
{
return !(*this == other);
}
constexpr bool operator<(const _rectangle_const_iterator& other) const
{
return _current < other._current;
}
constexpr bool operator>(const _rectangle_const_iterator& other) const
{
return _current > other._current;
}
constexpr point operator*() const
{
return _current;
}
protected:
point _current;
const point _topLeft;
const point _bottomRight;
#ifdef UNIT_TESTING
friend class ::RectangleTests;
#endif
};
}
class rectangle
class rle
{
public:
using const_iterator = details::_rectangle_const_iterator;
constexpr rectangle() noexcept :
rectangle(til::point{ 0, 0 }, til::point{ 0, 0 })
constexpr bool operator==(const rle& other) const noexcept
{
return this == &other;
}
// On 64-bit processors, int and ptrdiff_t are different fundamental types.
// On 32-bit processors, they're the same which makes this a double-definition
// with the `ptrdiff_t` one below.
#if defined(_M_AMD64) || defined(_M_ARM64)
constexpr rectangle(int left, int top, int right, int bottom) noexcept :
rectangle(til::point{ left, top }, til::point{ right, bottom })
{
}
#endif
rectangle(size_t left, size_t top, size_t right, size_t bottom) :
rectangle(til::point{ left, top }, til::point{ right, bottom })
{
}
constexpr rectangle(ptrdiff_t left, ptrdiff_t top, ptrdiff_t right, ptrdiff_t bottom) noexcept :
rectangle(til::point{ left, top }, til::point{ right, bottom })
{
}
// Creates a 1x1 rectangle with the given top-left corner.
rectangle(til::point topLeft) :
_topLeft(topLeft)
{
_bottomRight = _topLeft + til::point{ 1, 1 };
}
// Creates a rectangle where you specify the top-left corner (included)
// and the bottom-right corner (excluded)
constexpr rectangle(til::point topLeft, til::point bottomRight) noexcept :
_topLeft(topLeft),
_bottomRight(bottomRight)
{
}
// Creates a rectangle with the given size where the top-left corner
// is set to 0,0.
constexpr rectangle(til::size size) noexcept :
_topLeft(til::point{ 0, 0 }),
_bottomRight(til::point{ size.width(), size.height() })
{
}
// Creates a rectangle at the given top-left corner point X,Y that extends
// down (+Y direction) and right (+X direction) for the given size.
rectangle(til::point topLeft, til::size size) :
_topLeft(topLeft),
_bottomRight(topLeft + til::point{ size.width(), size.height() })
{
}
#ifdef _WINCONTYPES_
// This extra specialization exists for SMALL_RECT because it's the only rectangle in the world that we know of
// with the bottom and right fields INCLUSIVE to the rectangle itself.
// It will perform math on the way in to ensure that it is represented as EXCLUSIVE.
rectangle(SMALL_RECT sr)
{
_topLeft = til::point{ static_cast<ptrdiff_t>(sr.Left), static_cast<ptrdiff_t>(sr.Top) };
_bottomRight = til::point{ static_cast<ptrdiff_t>(sr.Right), static_cast<ptrdiff_t>(sr.Bottom) } + til::point{ 1, 1 };
}
#endif
// This template will convert to rectangle from anything that has a Left, Top, Right, and Bottom field that appear convertible to an integer value
template<typename TOther>
constexpr rectangle(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().Top)> && std::is_integral_v<decltype(std::declval<TOther>().Left)> && std::is_integral_v<decltype(std::declval<TOther>().Bottom)> && std::is_integral_v<decltype(std::declval<TOther>().Right)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ static_cast<ptrdiff_t>(other.Left), static_cast<ptrdiff_t>(other.Top) }, til::point{ static_cast<ptrdiff_t>(other.Right), static_cast<ptrdiff_t>(other.Bottom) })
{
}
// This template will convert to rectangle from anything that has a left, top, right, and bottom field that appear convertible to an integer value
template<typename TOther>
constexpr rectangle(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().top)> && std::is_integral_v<decltype(std::declval<TOther>().left)> && std::is_integral_v<decltype(std::declval<TOther>().bottom)> && std::is_integral_v<decltype(std::declval<TOther>().right)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ static_cast<ptrdiff_t>(other.left), static_cast<ptrdiff_t>(other.top) }, til::point{ static_cast<ptrdiff_t>(other.right), static_cast<ptrdiff_t>(other.bottom) })
{
}
// This template will convert to rectangle from anything that has a Left, Top, Right, and Bottom field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr rectangle(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().Left)> && std::is_floating_point_v<decltype(std::declval<TOther>().Top)> && std::is_floating_point_v<decltype(std::declval<TOther>().Right)> && std::is_floating_point_v<decltype(std::declval<TOther>().Bottom)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ TilMath::template cast<ptrdiff_t>(other.Left), TilMath::template cast<ptrdiff_t>(other.Top) }, til::point{ TilMath::template cast<ptrdiff_t>(other.Right), TilMath::template cast<ptrdiff_t>(other.Bottom) })
{
}
// This template will convert to rectangle from anything that has a left, top, right, and bottom field that are floating-point;
// a math type is required.
template<typename TilMath, typename TOther>
constexpr rectangle(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().left)> && std::is_floating_point_v<decltype(std::declval<TOther>().top)> && std::is_floating_point_v<decltype(std::declval<TOther>().right)> && std::is_floating_point_v<decltype(std::declval<TOther>().bottom)>, int> /*sentinel*/ = 0) :
rectangle(til::point{ TilMath::template cast<ptrdiff_t>(other.left), TilMath::template cast<ptrdiff_t>(other.top) }, til::point{ TilMath::template cast<ptrdiff_t>(other.right), TilMath::template cast<ptrdiff_t>(other.bottom) })
{
}
constexpr bool operator==(const rectangle& other) const noexcept
{
return _topLeft == other._topLeft &&
_bottomRight == other._bottomRight;
}
constexpr bool operator!=(const rectangle& other) const noexcept
constexpr bool operator!=(const rle& other) const noexcept
{
return !(*this == other);
}
explicit constexpr operator bool() const noexcept
{
return _topLeft.x() < _bottomRight.x() &&
_topLeft.y() < _bottomRight.y();
}
constexpr const_iterator begin() const
{
return const_iterator(_topLeft, _bottomRight);
}
constexpr const_iterator end() const
{
// For the standard walk: Left-To-Right then Top-To-Bottom
// the end box is one cell below the left most column.
// |----| 5x2 square. Remember bottom & right are exclusive
// | | while top & left are inclusive.
// X----- X is the end position.
return const_iterator(_topLeft, _bottomRight, { _topLeft.x(), _bottomRight.y() });
}
#pragma region RECTANGLE OPERATORS
// OR = union
constexpr rectangle operator|(const rectangle& other) const noexcept
{
const auto thisEmpty = empty();
const auto otherEmpty = other.empty();
// If both are empty, return empty rect.
if (thisEmpty && otherEmpty)
{
return rectangle{};
}
// If this is empty but not the other one, then give the other.
if (thisEmpty)
{
return other;
}
// If the other is empty but not this, give this.
if (otherEmpty)
{
return *this;
}
// If we get here, they're both not empty. Do math.
const auto l = std::min(left(), other.left());
const auto t = std::min(top(), other.top());
const auto r = std::max(right(), other.right());
const auto b = std::max(bottom(), other.bottom());
return rectangle{ l, t, r, b };
}
constexpr rectangle& operator|=(const rectangle& other) noexcept
{
*this = *this | other;
return *this;
}
// AND = intersect
constexpr rectangle operator&(const rectangle& other) const noexcept
{
const auto l = std::max(left(), other.left());
const auto r = std::min(right(), other.right());
// If the width dimension would be empty, give back empty rectangle.
if (l >= r)
{
return rectangle{};
}
const auto t = std::max(top(), other.top());
const auto b = std::min(bottom(), other.bottom());
// If the height dimension would be empty, give back empty rectangle.
if (t >= b)
{
return rectangle{};
}
return rectangle{ l, t, r, b };
}
constexpr rectangle& operator&=(const rectangle& other) noexcept
{
*this = *this & other;
return *this;
}
// - = subtract
some<rectangle, 4> operator-(const rectangle& other) const
{
some<rectangle, 4> result;
// We could have up to four rectangles describing the area resulting when you take removeMe out of main.
// Find the intersection of the two so we know which bits of removeMe are actually applicable
// to the original rectangle for subtraction purposes.
const auto intersect = *this & other;
// If there's no intersect, there's nothing to remove.
if (intersect.empty())
{
// Just put the original rectangle into the results and return early.
result.push_back(*this);
}
// If the original rectangle matches the intersect, there is nothing to return.
else if (*this != intersect)
{
// Generate our potential four viewports that represent the region of the original that falls outside of the remove area.
// We will bias toward generating wide rectangles over tall rectangles (if possible) so that optimizations that apply
// to manipulating an entire row at once can be realized by other parts of the console code. (i.e. Run Length Encoding)
// In the following examples, the found remaining regions are represented by:
// T = Top B = Bottom L = Left R = Right
//
// 4 Sides but Identical:
// |-----------this-----------| |-----------this-----------|
// | | | |
// | | | |
// | | | |
// | | ======> | intersect | ======> early return of nothing
// | | | |
// | | | |
// | | | |
// |-----------other----------| |--------------------------|
//
// 4 Sides:
// |-----------this-----------| |-----------this-----------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
// | |other | | ======> | |intersect| | ======> |LLLLLLLL| |RRRRRRR|
// | |---------| | | |---------| | |LLLLLLLL|---------|RRRRRRR|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// |--------------------------| |--------------------------| |--------------------------|
//
// 3 Sides:
// |-----------this-----------| |-----------this-----------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
// | |other | ======> | |intersect | ======> |LLLLLLLL| |
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// | | | | |BBBBBBBBBBBBBBBBBBBBBBBBBB|
// |--------------------------| |--------------------------| |--------------------------|
//
// 2 Sides:
// |-----------this-----------| |-----------this-----------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | |--------------------| | |-----------------| |LLLLLLLL|-----------------|
// | |other | ======> | |intersect | ======> |LLLLLLLL| |
// | | | | | | |LLLLLLLL| |
// | | | | | | |LLLLLLLL| |
// | | | | | | |LLLLLLLL| |
// |--------| | |--------------------------| |--------------------------|
// | |
// |--------------------|
//
// 1 Side:
// |-----------this-----------| |-----------this-----------| |--------------------------|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// | | | | |TTTTTTTTTTTTTTTTTTTTTTTTTT|
// |-----------------------------| |--------------------------| |--------------------------|
// | other | ======> | intersect | ======> | |
// | | | | | |
// | | | | | |
// | | | | | |
// | | |--------------------------| |--------------------------|
// | |
// |-----------------------------|
//
// 0 Sides:
// |-----------this-----------| |-----------this-----------|
// | | | |
// | | | |
// | | | |
// | | ======> | | ======> early return of this
// | | | |
// | | | |
// | | | |
// |--------------------------| |--------------------------|
//
//
// |---------------|
// | other |
// |---------------|
// We generate these rectangles by the original and intersect points, but some of them might be empty when the intersect
// lines up with the edge of the original. That's OK. That just means that the subtraction didn't leave anything behind.
// We will filter those out below when adding them to the result.
const til::rectangle t{ left(), top(), right(), intersect.top() };
const til::rectangle b{ left(), intersect.bottom(), right(), bottom() };
const til::rectangle l{ left(), intersect.top(), intersect.left(), intersect.bottom() };
const til::rectangle r{ intersect.right(), intersect.top(), right(), intersect.bottom() };
if (!t.empty())
{
result.push_back(t);
}
if (!b.empty())
{
result.push_back(b);
}
if (!l.empty())
{
result.push_back(l);
}
if (!r.empty())
{
result.push_back(r);
}
}
return result;
}
#pragma endregion
#pragma region RECTANGLE VS POINT
// ADD will translate (offset) the rectangle by the point.
rectangle operator+(const point& point) const
{
ptrdiff_t l, t, r, b;
THROW_HR_IF(E_ABORT, !::base::CheckAdd(left(), point.x()).AssignIfValid(&l));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(top(), point.y()).AssignIfValid(&t));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(right(), point.x()).AssignIfValid(&r));
THROW_HR_IF(E_ABORT, !::base::CheckAdd(bottom(), point.y()).AssignIfValid(&b));
return til::rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator+=(const point& point)
{
*this = *this + point;
return *this;
}
// SUB will translate (offset) the rectangle by the point.
rectangle operator-(const point& point) const
{
ptrdiff_t l, t, r, b;
THROW_HR_IF(E_ABORT, !::base::CheckSub(left(), point.x()).AssignIfValid(&l));
THROW_HR_IF(E_ABORT, !::base::CheckSub(top(), point.y()).AssignIfValid(&t));
THROW_HR_IF(E_ABORT, !::base::CheckSub(right(), point.x()).AssignIfValid(&r));
THROW_HR_IF(E_ABORT, !::base::CheckSub(bottom(), point.y()).AssignIfValid(&b));
return til::rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator-=(const point& point)
{
*this = *this - point;
return *this;
}
#pragma endregion
#pragma region RECTANGLE VS SIZE
// ADD will grow the total area of the rectangle. The sign is the direction to grow.
rectangle operator+(const size& size) const
{
// Fetch the pieces of the rectangle.
auto l = left();
auto r = right();
auto t = top();
auto b = bottom();
// Fetch the scale factors we're using.
const auto width = size.width();
const auto height = size.height();
// Since this is the add operation versus a size, the result
// should grow the total rectangle area.
// The sign determines which edge of the rectangle moves.
// We use the magnitude as how far to move.
if (width > 0)
{
// Adding the positive makes the rectangle "grow"
// because right stretches outward (to the right).
//
// Example with adding width 3...
// |-- x = origin
// V
// x---------| x------------|
// | | | |
// | | | |
// |---------| |------------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(r, width).AssignIfValid(&r));
}
else
{
// Adding the negative makes the rectangle "grow"
// because left stretches outward (to the left).
//
// Example with adding width -3...
// |-- x = origin
// V
// x---------| |--x---------|
// | | | |
// | | | |
// |---------| |------------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(l, width).AssignIfValid(&l));
}
if (height > 0)
{
// Adding the positive makes the rectangle "grow"
// because bottom stretches outward (to the down).
//
// Example with adding height 2...
// |-- x = origin
// V
// x---------| x---------|
// | | | |
// | | | |
// |---------| | |
// | |
// |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(b, height).AssignIfValid(&b));
}
else
{
// Adding the negative makes the rectangle "grow"
// because top stretches outward (to the up).
//
// Example with adding height -2...
// |-- x = origin
// |
// | |---------|
// V | |
// x---------| x |
// | | | |
// | | | |
// |---------| |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckAdd(t, height).AssignIfValid(&t));
}
return rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator+=(const size& size)
{
*this = *this + size;
return *this;
}
// SUB will shrink the total area of the rectangle. The sign is the direction to shrink.
rectangle operator-(const size& size) const
{
// Fetch the pieces of the rectangle.
auto l = left();
auto r = right();
auto t = top();
auto b = bottom();
// Fetch the scale factors we're using.
const auto width = size.width();
const auto height = size.height();
// Since this is the subtract operation versus a size, the result
// should shrink the total rectangle area.
// The sign determines which edge of the rectangle moves.
// We use the magnitude as how far to move.
if (width > 0)
{
// Subtracting the positive makes the rectangle "shrink"
// because right pulls inward (to the left).
//
// Example with subtracting width 3...
// |-- x = origin
// V
// x---------| x------|
// | | | |
// | | | |
// |---------| |------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(r, width).AssignIfValid(&r));
}
else
{
// Subtracting the negative makes the rectangle "shrink"
// because left pulls inward (to the right).
//
// Example with subtracting width -3...
// |-- x = origin
// V
// x---------| x |------|
// | | | |
// | | | |
// |---------| |------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(l, width).AssignIfValid(&l));
}
if (height > 0)
{
// Subtracting the positive makes the rectangle "shrink"
// because bottom pulls inward (to the up).
//
// Example with subtracting height 2...
// |-- x = origin
// V
// x---------| x---------|
// | | |---------|
// | |
// |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(b, height).AssignIfValid(&b));
}
else
{
// Subtracting the positive makes the rectangle "shrink"
// because top pulls inward (to the down).
//
// Example with subtracting height -2...
// |-- x = origin
// V
// x---------| x
// | |
// | | |---------|
// |---------| |---------|
// BEFORE AFTER
THROW_HR_IF(E_ABORT, !base::CheckSub(t, height).AssignIfValid(&t));
}
return rectangle{ til::point{ l, t }, til::point{ r, b } };
}
rectangle& operator-=(const size& size)
{
*this = *this - size;
return *this;
}
// scale_up will scale the entire rectangle up by the size factor
// This includes moving the origin.
rectangle scale_up(const size& size) const
{
const auto topLeft = _topLeft * size;
const auto bottomRight = _bottomRight * size;
return til::rectangle{ topLeft, bottomRight };
}
// scale_down will scale the entire rectangle down by the size factor,
// but rounds the bottom-right corner out.
// This includes moving the origin.
rectangle scale_down(const size& size) const
{
auto topLeft = _topLeft;
auto bottomRight = _bottomRight;
topLeft = topLeft / size;
// Move bottom right point into a size
// Use size specialization of divide_ceil to round up against the size given.
// Add leading addition to point to convert it back into a point.
bottomRight = til::point{} + til::size{ right(), bottom() }.divide_ceil(size);
return til::rectangle{ topLeft, bottomRight };
}
template<typename TilMath>
rectangle scale(TilMath, const float scale) const
{
return til::rectangle{ _topLeft.scale(TilMath{}, scale), _bottomRight.scale(TilMath{}, scale) };
}
#pragma endregion
constexpr ptrdiff_t top() const noexcept
{
return _topLeft.y();
}
template<typename T>
T top() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret));
return ret;
}
constexpr ptrdiff_t bottom() const noexcept
{
return _bottomRight.y();
}
template<typename T>
T bottom() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(bottom()).AssignIfValid(&ret));
return ret;
}
constexpr ptrdiff_t left() const noexcept
{
return _topLeft.x();
}
template<typename T>
T left() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret));
return ret;
}
constexpr ptrdiff_t right() const noexcept
{
return _bottomRight.x();
}
template<typename T>
T right() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(right()).AssignIfValid(&ret));
return ret;
}
ptrdiff_t width() const
{
ptrdiff_t ret;
THROW_HR_IF(E_ABORT, !::base::CheckSub(right(), left()).AssignIfValid(&ret));
return ret;
}
template<typename T>
T width() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width()).AssignIfValid(&ret));
return ret;
}
ptrdiff_t height() const
{
ptrdiff_t ret;
THROW_HR_IF(E_ABORT, !::base::CheckSub(bottom(), top()).AssignIfValid(&ret));
return ret;
}
template<typename T>
T height() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height()).AssignIfValid(&ret));
return ret;
}
constexpr point origin() const noexcept
{
return _topLeft;
}
size size() const
{
return til::size{ width(), height() };
}
constexpr bool empty() const noexcept
{
return !operator bool();
}
constexpr bool contains(til::point pt) const
{
return pt.x() >= _topLeft.x() && pt.x() < _bottomRight.x() &&
pt.y() >= _topLeft.y() && pt.y() < _bottomRight.y();
}
bool contains(ptrdiff_t index) const
{
return index >= 0 && index < size().area();
}
constexpr bool contains(til::rectangle rc) const
{
// Union the other rectangle and ourselves.
// If the result of that didn't grow at all, then we already
// fully contained the rectangle we were given.
return (*this | rc) == *this;
}
ptrdiff_t index_of(til::point pt) const
{
THROW_HR_IF(E_INVALIDARG, !contains(pt));
// Take Y away from the top to find how many rows down
auto check = base::CheckSub(pt.y(), top());
// Multiply by the width because we've passed that many
// widths-worth of indices.
check *= width();
// Then add in the last few indices in the x position this row
// and subtract left to find the offset from left edge.
check = check + pt.x() - left();
ptrdiff_t result;
THROW_HR_IF(E_ABORT, !check.AssignIfValid(&result));
return result;
}
til::point point_at(ptrdiff_t index) const
{
THROW_HR_IF(E_INVALIDARG, !contains(index));
const auto div = std::div(index, width());
// Not checking math on these because we're presuming
// that the point can't be in bounds of a rectangle where
// this would overflow on addition after the division.
return til::point{ div.rem + left(), div.quot + top() };
}
#ifdef _WINCONTYPES_
// NOTE: This will convert back to INCLUSIVE on the way out because
// that is generally how SMALL_RECTs are handled in console code and via the APIs.
operator SMALL_RECT() const
{
SMALL_RECT ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret.Left));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret.Top));
THROW_HR_IF(E_ABORT, !base::CheckSub(right(), 1).AssignIfValid(&ret.Right));
THROW_HR_IF(E_ABORT, !base::CheckSub(bottom(), 1).AssignIfValid(&ret.Bottom));
return ret;
}
#endif
#ifdef _WINDEF_
operator RECT() const
{
RECT ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret.left));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret.top));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(right()).AssignIfValid(&ret.right));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(bottom()).AssignIfValid(&ret.bottom));
return ret;
}
#endif
#ifdef DCOMMON_H_INCLUDED
constexpr operator D2D1_RECT_F() const noexcept
{
return D2D1_RECT_F{ gsl::narrow_cast<FLOAT>(left()), gsl::narrow_cast<FLOAT>(top()), gsl::narrow_cast<FLOAT>(right()), gsl::narrow_cast<FLOAT>(bottom()) };
}
#endif
#ifdef WINRT_Windows_Foundation_H
operator winrt::Windows::Foundation::Rect() const
{
winrt::Windows::Foundation::Rect ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(left()).AssignIfValid(&ret.X));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(top()).AssignIfValid(&ret.Y));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width()).AssignIfValid(&ret.Width));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height()).AssignIfValid(&ret.Height));
return ret;
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"(L:%td, T:%td, R:%td, B:%td) [W:%td, H:%td]", left(), top(), right(), bottom(), width(), height());
return wil::str_printf<std::wstring>(L"I am RLE. Hear me roar.");
}
protected:
til::point _topLeft;
til::point _bottomRight;
#ifdef UNIT_TESTING
friend class ::RectangleTests;
#endif
#ifdef UNIT_TESTING
friend class ::RunLengthEncodingTests;
#endif
};
}
};
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::rectangle>
class VerifyOutputTraits<::til::rle>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::rectangle& rect)
static WEX::Common::NoThrowString ToString(const ::til::rle& object)
{
return WEX::Common::NoThrowString(rect.to_string().c_str());
return WEX::Common::NoThrowString(object.to_string().c_str());
}
};
template<>
class VerifyCompareTraits<::til::rectangle, ::til::rectangle>
class VerifyCompareTraits<::til::rle, ::til::rle>
{
public:
static bool AreEqual(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept
static bool AreEqual(const ::til::rle& expected, const ::til::rle& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept
static bool AreSame(const ::til::rle& expected, const ::til::rle& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::rectangle& expectedLess, const ::til::rectangle& expectedGreater) = delete;
static bool IsLessThan(const ::til::rle& expectedLess, const ::til::rle& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::rectangle& expectedGreater, const ::til::rectangle& expectedLess) = delete;
static bool IsGreaterThan(const ::til::rle& expectedGreater, const ::til::rle& expectedLess) = delete;
static bool IsNull(const ::til::rectangle& object) noexcept
static bool IsNull(const ::til::rle& object) noexcept
{
return object == til::rectangle{};
return object == til::rle{};
}
};

File diff suppressed because it is too large Load diff