diff --git a/src/inc/til.h b/src/inc/til.h index b4a6c4f80..a5d25f3f6 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -8,6 +8,7 @@ #include "til/some.h" #include "til/size.h" #include "til/point.h" +#include "til/rectangle.h" #include "til/u8u16convert.h" namespace til // Terminal Implementation Library. Also: "Today I Learned" diff --git a/src/inc/til/point.h b/src/inc/til/point.h index 01257934d..49b78a22d 100644 --- a/src/inc/til/point.h +++ b/src/inc/til/point.h @@ -39,14 +39,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } - // This template will convert to size from anything that has an X and a Y field that appear convertable to an integer value + // This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value template constexpr point(const TOther& other, std::enable_if_t().X)> && std::is_integral_v().Y)>, int> /*sentinel*/ = 0) : point(static_cast(other.X), static_cast(other.Y)) { } - // This template will convert to size from anything that has a x and a y field that appear convertable to an integer value + // This template will convert to size from anything that has a x and a y field that appear convertible to an integer value template constexpr point(const TOther& other, std::enable_if_t().x)> && std::is_integral_v().y)>, int> /*sentinel*/ = 0) : point(static_cast(other.x), static_cast(other.y)) diff --git a/src/inc/til/rectangle.h b/src/inc/til/rectangle.h new file mode 100644 index 000000000..61379a385 --- /dev/null +++ b/src/inc/til/rectangle.h @@ -0,0 +1,484 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "point.h" +#include "size.h" +#include "some.h" + +#ifdef UNIT_TESTING +class RectangleTests; +#endif + +namespace til // Terminal Implementation Library. Also: "Today I Learned" +{ + class rectangle + { + public: + constexpr rectangle() noexcept : + rectangle(til::point{ 0, 0 }, til::point{ 0, 0 }) + { + } + + // 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(sr.Left), static_cast(sr.Top) }; + + _bottomRight = til::point{ static_cast(sr.Right), static_cast(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 + constexpr rectangle(const TOther& other, std::enable_if_t().Top)> && std::is_integral_v().Left)> && std::is_integral_v().Bottom)> && std::is_integral_v().Right)>, int> /*sentinel*/ = 0) : + rectangle(til::point{ static_cast(other.Left), static_cast(other.Top) }, til::point{ static_cast(other.Right), static_cast(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 + constexpr rectangle(const TOther& other, std::enable_if_t().top)> && std::is_integral_v().left)> && std::is_integral_v().bottom)> && std::is_integral_v().right)>, int> /*sentinel*/ = 0) : + rectangle(til::point{ static_cast(other.left), static_cast(other.top) }, til::point{ static_cast(other.right), static_cast(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 + { + return !(*this == other); + } + + constexpr operator bool() const noexcept + { + return _topLeft.x() < _bottomRight.x() && + _topLeft.y() < _bottomRight.y(); + } + + // 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 }; + } + + // 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 }; + } + + // - = subtract + some operator-(const rectangle& other) const + { + some 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; + } + + constexpr ptrdiff_t top() const noexcept + { + return _topLeft.y(); + } + + template + 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 + 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 + 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 + 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 + 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 + 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(); + } + +#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(left()), gsl::narrow_cast(top()), gsl::narrow_cast(right()), gsl::narrow_cast(bottom()) }; + } +#endif + + protected: + til::point _topLeft; + til::point _bottomRight; + +#ifdef UNIT_TESTING + friend class ::RectangleTests; +#endif + }; +} + +#ifdef __WEX_COMMON_H__ +namespace WEX::TestExecution +{ + template<> + class VerifyOutputTraits<::til::rectangle> + { + public: + static WEX::Common::NoThrowString ToString(const ::til::rectangle& rect) + { + return WEX::Common::NoThrowString().Format(L"(L:%td, T:%td, R:%td, B:%td) [W:%td, H:%td]", rect.left(), rect.top(), rect.right(), rect.bottom(), rect.width(), rect.height()); + } + }; + + template<> + class VerifyCompareTraits<::til::rectangle, ::til::rectangle> + { + public: + static bool AreEqual(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept + { + return expected == actual; + } + + static bool AreSame(const ::til::rectangle& expected, const ::til::rectangle& actual) noexcept + { + return &expected == &actual; + } + + static bool IsLessThan(const ::til::rectangle& expectedLess, const ::til::rectangle& expectedGreater) = delete; + + static bool IsGreaterThan(const ::til::rectangle& expectedGreater, const ::til::rectangle& expectedLess) = delete; + + static bool IsNull(const ::til::rectangle& object) noexcept + { + return object == til::rectangle{}; + } + }; + +}; +#endif diff --git a/src/inc/til/size.h b/src/inc/til/size.h index f996c9325..b3327896d 100644 --- a/src/inc/til/size.h +++ b/src/inc/til/size.h @@ -39,14 +39,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" { } - // This template will convert to size from anything that has an X and a Y field that appear convertable to an integer value + // This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value template constexpr size(const TOther& other, std::enable_if_t().X)> && std::is_integral_v().Y)>, int> /*sentinel*/ = 0) : size(static_cast(other.X), static_cast(other.Y)) { } - // This template will convert to size from anything that has a cx and a cy field that appear convertable to an integer value + // This template will convert to size from anything that has a cx and a cy field that appear convertible to an integer value template constexpr size(const TOther& other, std::enable_if_t().cx)> && std::is_integral_v().cy)>, int> /*sentinel*/ = 0) : size(static_cast(other.cx), static_cast(other.cy)) diff --git a/src/inc/til/some.h b/src/inc/til/some.h index 2f40da61c..aafaa0ec3 100644 --- a/src/inc/til/some.h +++ b/src/inc/til/some.h @@ -50,6 +50,16 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" _used = init.size(); } + constexpr bool operator==(const til::some& other) const noexcept + { + return std::equal(cbegin(), cend(), other.cbegin(), other.cend()); + } + + constexpr bool operator!=(const til::some& other) const noexcept + { + return !(*this == other); + } + void fill(const T& _Value) { _array.fill(_Value); @@ -182,3 +192,51 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned" } }; } + +#ifdef __WEX_COMMON_H__ +namespace WEX::TestExecution +{ + template + class VerifyOutputTraits<::til::some> + { + public: + static WEX::Common::NoThrowString ToString(const ::til::some& some) + { + auto str = WEX::Common::NoThrowString().Format(L"\r\nSome contains %d of max size %d:\r\nElements:\r\n", some.size(), some.max_size()); + + for (auto& item : some) + { + const auto itemStr = WEX::TestExecution::VerifyOutputTraits::ToString(item); + str.AppendFormat(L"\t- %ws\r\n", (const wchar_t*)itemStr); + } + + return str; + } + }; + + template + class VerifyCompareTraits<::til::some, ::til::some> + { + public: + static bool AreEqual(const ::til::some& expected, const ::til::some& actual) noexcept + { + return expected == actual; + } + + static bool AreSame(const ::til::some& expected, const ::til::some& actual) noexcept + { + return &expected == &actual; + } + + static bool IsLessThan(const ::til::some& expectedLess, const ::til::some& expectedGreater) = delete; + + static bool IsGreaterThan(const ::til::some& expectedGreater, const ::til::some& expectedLess) = delete; + + static bool IsNull(const ::til::some& object) noexcept + { + return object == til::some{}; + } + }; + +}; +#endif diff --git a/src/til/ut_til/RectangleTests.cpp b/src/til/ut_til/RectangleTests.cpp new file mode 100644 index 000000000..21557881b --- /dev/null +++ b/src/til/ut_til/RectangleTests.cpp @@ -0,0 +1,932 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "til/rectangle.h" + +using namespace WEX::Common; +using namespace WEX::Logging; +using namespace WEX::TestExecution; + +class RectangleTests +{ + TEST_CLASS(RectangleTests); + + TEST_METHOD(DefaultConstruct) + { + const til::rectangle rc; + VERIFY_ARE_EQUAL(0, rc._topLeft.x()); + VERIFY_ARE_EQUAL(0, rc._topLeft.y()); + VERIFY_ARE_EQUAL(0, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(0, rc._bottomRight.y()); + } + + TEST_METHOD(RawConstruct) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + TEST_METHOD(UnsignedConstruct) + { + Log::Comment(L"0.) Normal unsigned construct."); + { + const size_t l = 5; + const size_t t = 10; + const size_t r = 15; + const size_t b = 20; + + const til::rectangle rc{ l, t, r, b }; + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + Log::Comment(L"1.) Unsigned construct overflow on left."); + { + constexpr size_t l = std::numeric_limits().max(); + const size_t t = 10; + const size_t r = 15; + const size_t b = 20; + + auto fn = [&]() { + const til::rectangle rc{ l, t, r, b }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"2.) Unsigned construct overflow on top."); + { + const size_t l = 5; + constexpr size_t t = std::numeric_limits().max(); + const size_t r = 15; + const size_t b = 20; + + auto fn = [&]() { + const til::rectangle rc{ l, t, r, b }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"3.) Unsigned construct overflow on right."); + { + const size_t l = 5; + const size_t t = 10; + constexpr size_t r = std::numeric_limits().max(); + const size_t b = 20; + + auto fn = [&]() { + const til::rectangle rc{ l, t, r, b }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"4.) Unsigned construct overflow on bottom."); + { + const size_t l = 5; + const size_t t = 10; + const size_t r = 15; + constexpr size_t b = std::numeric_limits().max(); + + auto fn = [&]() { + const til::rectangle rc{ l, t, r, b }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(SignedConstruct) + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + + const til::rectangle rc{ l, t, r, b }; + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + TEST_METHOD(SinglePointConstruct) + { + Log::Comment(L"0.) Normal Case"); + { + const til::rectangle rc{ til::point{ 4, 8 } }; + VERIFY_ARE_EQUAL(4, rc._topLeft.x()); + VERIFY_ARE_EQUAL(8, rc._topLeft.y()); + VERIFY_ARE_EQUAL(5, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(9, rc._bottomRight.y()); + } + + Log::Comment(L"1.) Overflow x-dimension case."); + { + auto fn = [&]() { + constexpr ptrdiff_t x = std::numeric_limits().max(); + const ptrdiff_t y = 0; + const til::rectangle rc{ til::point{ x, y } }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"1.) Overflow y-dimension case."); + { + auto fn = [&]() { + const ptrdiff_t x = 0; + constexpr ptrdiff_t y = std::numeric_limits().max(); + const til::rectangle rc{ til::point{ x, y } }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(TwoPointsConstruct) + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + + const til::rectangle rc{ til::point{ l, t }, til::point{ r, b } }; + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + TEST_METHOD(SizeOnlyConstruct) + { + // Size will match bottom right point because + // til::rectangle is exclusive. + const auto sz = til::size{ 5, 10 }; + const til::rectangle rc{ sz }; + VERIFY_ARE_EQUAL(0, rc._topLeft.x()); + VERIFY_ARE_EQUAL(0, rc._topLeft.y()); + VERIFY_ARE_EQUAL(sz.width(), rc._bottomRight.x()); + VERIFY_ARE_EQUAL(sz.height(), rc._bottomRight.y()); + } + + TEST_METHOD(PointAndSizeConstruct) + { + const til::point pt{ 4, 8 }; + + Log::Comment(L"0.) Normal Case"); + { + const til::rectangle rc{ pt, til::size{ 2, 10 } }; + VERIFY_ARE_EQUAL(4, rc._topLeft.x()); + VERIFY_ARE_EQUAL(8, rc._topLeft.y()); + VERIFY_ARE_EQUAL(6, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(18, rc._bottomRight.y()); + } + + Log::Comment(L"1.) Overflow x-dimension case."); + { + auto fn = [&]() { + constexpr ptrdiff_t x = std::numeric_limits().max(); + const ptrdiff_t y = 0; + const til::rectangle rc{ pt, til::size{ x, y } }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"1.) Overflow y-dimension case."); + { + auto fn = [&]() { + const ptrdiff_t x = 0; + constexpr ptrdiff_t y = std::numeric_limits().max(); + const til::rectangle rc{ pt, til::size{ x, y } }; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(SmallRectConstruct) + { + SMALL_RECT sr; + sr.Left = 5; + sr.Top = 10; + sr.Right = 14; + sr.Bottom = 19; + + const til::rectangle rc{ sr }; + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + TEST_METHOD(ExclusiveCapitalStructConstruct) + { + struct TestStruct + { + char Left; + char Top; + char Right; + char Bottom; + }; + + const TestStruct ts{ 1, 2, 3, 4 }; + + const til::rectangle rc{ ts }; + + VERIFY_ARE_EQUAL(1, rc._topLeft.x()); + VERIFY_ARE_EQUAL(2, rc._topLeft.y()); + VERIFY_ARE_EQUAL(3, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(4, rc._bottomRight.y()); + } + + TEST_METHOD(Win32RectConstruct) + { + const RECT win32rc{ 5, 10, 15, 20 }; + const til::rectangle rc{ win32rc }; + + VERIFY_ARE_EQUAL(5, rc._topLeft.x()); + VERIFY_ARE_EQUAL(10, rc._topLeft.y()); + VERIFY_ARE_EQUAL(15, rc._bottomRight.x()); + VERIFY_ARE_EQUAL(20, rc._bottomRight.y()); + } + + TEST_METHOD(Assignment) + { + til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 5, 6, 7, 8 }; + + VERIFY_ARE_EQUAL(1, a._topLeft.x()); + VERIFY_ARE_EQUAL(2, a._topLeft.y()); + VERIFY_ARE_EQUAL(3, a._bottomRight.x()); + VERIFY_ARE_EQUAL(4, a._bottomRight.y()); + + a = b; + + VERIFY_ARE_EQUAL(5, a._topLeft.x()); + VERIFY_ARE_EQUAL(6, a._topLeft.y()); + VERIFY_ARE_EQUAL(7, a._bottomRight.x()); + VERIFY_ARE_EQUAL(8, a._bottomRight.y()); + } + + TEST_METHOD(Equality) + { + Log::Comment(L"0.) Equal."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_TRUE(a == b); + } + + Log::Comment(L"1.) Left A changed."); + { + const til::rectangle a{ 9, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"2.) Top A changed."); + { + const til::rectangle a{ 1, 9, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"3.) Right A changed."); + { + const til::rectangle a{ 1, 2, 9, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"4.) Bottom A changed."); + { + const til::rectangle a{ 1, 2, 3, 9 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"5.) Left B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 9, 2, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"6.) Top B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 9, 3, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"7.) Right B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 9, 4 }; + VERIFY_IS_FALSE(a == b); + } + + Log::Comment(L"8.) Bottom B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 9 }; + VERIFY_IS_FALSE(a == b); + } + } + + TEST_METHOD(Inequality) + { + Log::Comment(L"0.) Equal."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_FALSE(a != b); + } + + Log::Comment(L"1.) Left A changed."); + { + const til::rectangle a{ 9, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"2.) Top A changed."); + { + const til::rectangle a{ 1, 9, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"3.) Right A changed."); + { + const til::rectangle a{ 1, 2, 9, 4 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"4.) Bottom A changed."); + { + const til::rectangle a{ 1, 2, 3, 9 }; + const til::rectangle b{ 1, 2, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"5.) Left B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 9, 2, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"6.) Top B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 9, 3, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"7.) Right B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 9, 4 }; + VERIFY_IS_TRUE(a != b); + } + + Log::Comment(L"8.) Bottom B changed."); + { + const til::rectangle a{ 1, 2, 3, 4 }; + const til::rectangle b{ 1, 2, 3, 9 }; + VERIFY_IS_TRUE(a != b); + } + } + + TEST_METHOD(Boolean) + { + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:left", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:top", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:right", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:bottom", L"{0,10}") + END_TEST_METHOD_PROPERTIES() + + ptrdiff_t left, top, right, bottom; + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"left", left)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"top", top)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"right", right)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"bottom", bottom)); + + const bool expected = left < right && top < bottom; + const til::rectangle actual{ left, top, right, bottom }; + VERIFY_ARE_EQUAL(expected, (bool)actual); + } + + TEST_METHOD(OrUnion) + { + const til::rectangle one{ 4, 6, 10, 14 }; + const til::rectangle two{ 5, 2, 13, 10 }; + + const til::rectangle expected{ 4, 2, 13, 14 }; + const auto actual = one | two; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(AndIntersect) + { + const til::rectangle one{ 4, 6, 10, 14 }; + const til::rectangle two{ 5, 2, 13, 10 }; + + const til::rectangle expected{ 5, 6, 10, 10 }; + const auto actual = one & two; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractSame) + { + const til::rectangle original{ 0, 0, 10, 10 }; + const auto removal = original; + + // Since it's the same rectangle, nothing's left. We should get no results. + const til::some expected; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractNoOverlap) + { + const til::rectangle original{ 0, 0, 10, 10 }; + const til::rectangle removal{ 12, 12, 15, 15 }; + + // Since they don't overlap, we expect the original to be given back. + const til::some expected{ original }; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractOne) + { + // +--------+ + // | result | + // | | + // +-------------------------------------+ + // | | | | + // | | | | + // | |original| | + // | | | | + // | | | | + // | +--------+ | + // | | + // | | + // | removal | + // | | + // +-------------------------------------+ + + const til::rectangle original{ 0, 0, 10, 10 }; + const til::rectangle removal{ -12, 3, 15, 15 }; + + const til::some expected{ + til::rectangle{ original.left(), original.top(), original.right(), removal.top() } + }; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractTwo) + { + // +--------+ + // |result0 | + // | | + // |~~~~+-----------------+ + // |res1| | | + // | | | | + // |original| | + // | | | | + // | | | | + // +--------+ | + // | | + // | | + // | removal | + // +-----------------+ + + const til::rectangle original{ 0, 0, 10, 10 }; + const til::rectangle removal{ 3, 3, 15, 15 }; + + const til::some expected{ + til::rectangle{ original.left(), original.top(), original.right(), removal.top() }, + til::rectangle{ original.left(), removal.top(), removal.left(), original.bottom() } + }; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractThree) + { + // +--------+ + // |result0 | + // | | + // |~~~~+---------------------------+ + // |res2| | removal | + // |original| | + // |~~~~+---------------------------+ + // |result1 | + // | | + // +--------+ + + const til::rectangle original{ 0, 0, 10, 10 }; + const til::rectangle removal{ 3, 3, 15, 6 }; + + const til::some expected{ + til::rectangle{ original.left(), original.top(), original.right(), removal.top() }, + til::rectangle{ original.left(), removal.bottom(), original.right(), original.bottom() }, + til::rectangle{ original.left(), removal.top(), removal.left(), removal.bottom() } + }; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(MinusSubtractFour) + { + // (original)---+ + // | + // v + // + --------------------------+ + // | result0 | + // | o r i | + // | | + // |~~~~~~~+-----------+~~~~~~~| + // | res2 | | res3 | + // | g | removal | i | + // | | | | + // |~~~~~~~+-----------+~~~~~~~| + // | result1 | + // | n a l | + // | | + // +---------------------------+ + + const til::rectangle original{ 0, 0, 10, 10 }; + const til::rectangle removal{ 3, 3, 6, 6 }; + + const til::some expected{ + til::rectangle{ original.left(), original.top(), original.right(), removal.top() }, + til::rectangle{ original.left(), removal.bottom(), original.right(), original.bottom() }, + til::rectangle{ original.left(), removal.top(), removal.left(), removal.bottom() }, + til::rectangle{ removal.right(), removal.top(), original.right(), removal.bottom() } + }; + const auto actual = original - removal; + VERIFY_ARE_EQUAL(expected, actual); + } + + TEST_METHOD(Top) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(rc._topLeft.y(), rc.top()); + } + + TEST_METHOD(TopCast) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(static_cast(rc._topLeft.y()), rc.top()); + } + + TEST_METHOD(Bottom) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(rc._bottomRight.y(), rc.bottom()); + } + + TEST_METHOD(BottomCast) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(static_cast(rc._bottomRight.y()), rc.bottom()); + } + + TEST_METHOD(Left) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(rc._topLeft.x(), rc.left()); + } + + TEST_METHOD(LeftCast) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(static_cast(rc._topLeft.x()), rc.left()); + } + + TEST_METHOD(Right) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(rc._bottomRight.x(), rc.right()); + } + + TEST_METHOD(RightCast) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(static_cast(rc._bottomRight.x()), rc.right()); + } + + TEST_METHOD(Width) + { + Log::Comment(L"0.) Width that should be in bounds."); + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(15 - 5, rc.width()); + } + + Log::Comment(L"1.) Width that should go out of bounds on subtraction."); + { + constexpr ptrdiff_t bigVal = std::numeric_limits().min(); + const ptrdiff_t normalVal = 5; + const til::rectangle rc{ normalVal, normalVal, bigVal, normalVal }; + + auto fn = [&]() { + rc.width(); + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(WidthCast) + { + const SHORT expected = 15 - 5; + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(expected, rc.width()); + } + + TEST_METHOD(Height) + { + Log::Comment(L"0.) Height that should be in bounds."); + { + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(20 - 10, rc.height()); + } + + Log::Comment(L"1.) Height that should go out of bounds on subtraction."); + { + constexpr ptrdiff_t bigVal = std::numeric_limits().min(); + const ptrdiff_t normalVal = 5; + const til::rectangle rc{ normalVal, normalVal, normalVal, bigVal }; + + auto fn = [&]() { + rc.height(); + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(HeightCast) + { + const SHORT expected = 20 - 10; + const til::rectangle rc{ 5, 10, 15, 20 }; + VERIFY_ARE_EQUAL(expected, rc.height()); + } + + TEST_METHOD(Origin) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + const til::point expected{ rc._topLeft }; + VERIFY_ARE_EQUAL(expected, rc.origin()); + } + + TEST_METHOD(Size) + { + const til::rectangle rc{ 5, 10, 15, 20 }; + const til::size expected{ 10, 10 }; + VERIFY_ARE_EQUAL(expected, rc.size()); + } + + TEST_METHOD(Empty) + { + BEGIN_TEST_METHOD_PROPERTIES() + TEST_METHOD_PROPERTY(L"Data:left", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:top", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:right", L"{0,10}") + TEST_METHOD_PROPERTY(L"Data:bottom", L"{0,10}") + END_TEST_METHOD_PROPERTIES() + + ptrdiff_t left, top, right, bottom; + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"left", left)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"top", top)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"right", right)); + VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"bottom", bottom)); + + const bool expected = !(left < right && top < bottom); + const til::rectangle actual{ left, top, right, bottom }; + VERIFY_ARE_EQUAL(expected, actual.empty()); + } + + TEST_METHOD(CastToSmallRect) + { + Log::Comment(L"0.) Typical situation."); + { + const til::rectangle rc{ 5, 10, 15, 20 }; + SMALL_RECT val = rc; + VERIFY_ARE_EQUAL(5, val.Left); + VERIFY_ARE_EQUAL(10, val.Top); + VERIFY_ARE_EQUAL(14, val.Right); + VERIFY_ARE_EQUAL(19, val.Bottom); + } + + Log::Comment(L"1.) Overflow on left."); + { + constexpr ptrdiff_t l = std::numeric_limits().max(); + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + auto fn = [&]() { + SMALL_RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"2.) Overflow on top."); + { + const ptrdiff_t l = 5; + constexpr ptrdiff_t t = std::numeric_limits().max(); + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + auto fn = [&]() { + SMALL_RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"3.) Overflow on right."); + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + constexpr ptrdiff_t r = std::numeric_limits().max(); + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + auto fn = [&]() { + SMALL_RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + + Log::Comment(L"4.) Overflow on bottom."); + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + constexpr ptrdiff_t b = std::numeric_limits().max(); + const til::rectangle rc{ l, t, r, b }; + + auto fn = [&]() { + SMALL_RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + } + + TEST_METHOD(CastToRect) + { + Log::Comment(L"0.) Typical situation."); + { + const til::rectangle rc{ 5, 10, 15, 20 }; + RECT val = rc; + VERIFY_ARE_EQUAL(5, val.left); + VERIFY_ARE_EQUAL(10, val.top); + VERIFY_ARE_EQUAL(15, val.right); + VERIFY_ARE_EQUAL(20, val.bottom); + } + + Log::Comment(L"1.) Fit max left into RECT (may overflow)."); + { + constexpr ptrdiff_t l = std::numeric_limits().max(); + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + // On some platforms, ptrdiff_t will fit inside l/t/r/b + const bool overflowExpected = l > std::numeric_limits().max(); + + if (overflowExpected) + { + auto fn = [&]() { + RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + else + { + RECT val = rc; + VERIFY_ARE_EQUAL(l, val.left); + } + } + + Log::Comment(L"2.) Fit max top into RECT (may overflow)."); + { + const ptrdiff_t l = 5; + constexpr ptrdiff_t t = std::numeric_limits().max(); + const ptrdiff_t r = 15; + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + // On some platforms, ptrdiff_t will fit inside l/t/r/b + const bool overflowExpected = t > std::numeric_limits().max(); + + if (overflowExpected) + { + auto fn = [&]() { + RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + else + { + RECT val = rc; + VERIFY_ARE_EQUAL(t, val.top); + } + } + + Log::Comment(L"3.) Fit max right into RECT (may overflow)."); + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + constexpr ptrdiff_t r = std::numeric_limits().max(); + const ptrdiff_t b = 20; + const til::rectangle rc{ l, t, r, b }; + + // On some platforms, ptrdiff_t will fit inside l/t/r/b + const bool overflowExpected = r > std::numeric_limits().max(); + + if (overflowExpected) + { + auto fn = [&]() { + RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + else + { + RECT val = rc; + VERIFY_ARE_EQUAL(r, val.right); + } + } + + Log::Comment(L"4.) Fit max bottom into RECT (may overflow)."); + { + const ptrdiff_t l = 5; + const ptrdiff_t t = 10; + const ptrdiff_t r = 15; + constexpr ptrdiff_t b = std::numeric_limits().max(); + const til::rectangle rc{ l, t, r, b }; + + // On some platforms, ptrdiff_t will fit inside l/t/r/b + const bool overflowExpected = b > std::numeric_limits().max(); + + if (overflowExpected) + { + auto fn = [&]() { + RECT val = rc; + }; + + VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; }); + } + else + { + RECT val = rc; + VERIFY_ARE_EQUAL(b, val.bottom); + } + } + } + + TEST_METHOD(CastToD2D1RectF) + { + Log::Comment(L"0.) Typical situation."); + { + const til::rectangle rc{ 5, 10, 15, 20 }; + D2D1_RECT_F val = rc; + VERIFY_ARE_EQUAL(5, val.left); + VERIFY_ARE_EQUAL(10, val.top); + VERIFY_ARE_EQUAL(15, val.right); + VERIFY_ARE_EQUAL(20, val.bottom); + } + + // All ptrdiff_ts fit into a float, so there's no exception tests. + } +}; diff --git a/src/til/ut_til/SomeTests.cpp b/src/til/ut_til/SomeTests.cpp index e7a1c0258..c2cde75dc 100644 --- a/src/til/ut_til/SomeTests.cpp +++ b/src/til/ut_til/SomeTests.cpp @@ -28,6 +28,38 @@ class SomeTests VERIFY_THROWS(f(), std::invalid_argument); } + TEST_METHOD(Equality) + { + til::some a{ 1, 2 }; + til::some b{ 1, 2 }; + VERIFY_IS_TRUE(a == b); + + til::some c{ 3, 2 }; + VERIFY_IS_FALSE(a == c); + + til::some d{ 2, 3 }; + VERIFY_IS_FALSE(a == d); + + til::some e{ 1 }; + VERIFY_IS_FALSE(a == e); + } + + TEST_METHOD(Inequality) + { + til::some a{ 1, 2 }; + til::some b{ 1, 2 }; + VERIFY_IS_FALSE(a != b); + + til::some c{ 3, 2 }; + VERIFY_IS_TRUE(a != c); + + til::some d{ 2, 3 }; + VERIFY_IS_TRUE(a != d); + + til::some e{ 1 }; + VERIFY_IS_TRUE(a != e); + } + TEST_METHOD(Fill) { til::some s; diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index cb12b4ae7..b7d47a9b6 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -11,6 +11,7 @@ + diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters index 7b6da4d90..d03581af3 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj.filters +++ b/src/til/ut_til/til.unit.tests.vcxproj.filters @@ -10,6 +10,7 @@ + diff --git a/tools/ConsoleTypes.natvis b/tools/ConsoleTypes.natvis index 9502b3f0f..27b29d154 100644 --- a/tools/ConsoleTypes.natvis +++ b/tools/ConsoleTypes.natvis @@ -89,6 +89,10 @@ {{X: {_x,d}, Y: {_y,d}}} + + {{L: {_topLeft._x}, T: {_topLeft._y}, R: {_bottomRight._x} B: {_bottomRight._y} [W: {_bottomRight._x - _topLeft._x} x H: {_bottomRight._y - _topLeft._y} -> A: {(_bottomRight._x - _topLeft._x) * (_bottomRight._y - _topLeft._y)}]}} + + {{RGB: {(int)r,d}, {(int)g,d}, {(int)b,d}; α: {(int)a,d}}}