terminal/src/inc/til/size.h
Michael Niksa f5ab042939
til::rectangle (#4912)
## Summary of the Pull Request
Introduces convenience type `til::rectangle` which automatically implements our best practices for rectangle-related types and provides automatic conversions in/out of the relevant types.

## PR Checklist
* [x] In support of Differential Rendering #778
* [X] I work here.
* [x] Tests added/passed
* [x] I'm a core contributor.

## Detailed Description of the Pull Request / Additional comments
- Automatically converts in from anything with a Left/Top/Right/Bottom or left/top/right/bottom (Win32 `RECT`)
- Automatically converts Console type `SMALL_RECT` and shifts it from **inclusive** to **exclusive** on instantiation
- Automatically converts out to `SMALL_RECT` (converting back to **inclusive**), `RECT`, or `D2D1_RECT_F`.
- Constructs from bare integers written into source file
- Constructs from a single `til::point` as a 1x1 size rectangle with top-left corner (origin) at that point
- Constructs from a single `til::size` as a WxH size rectangle with top-left corner (origin) at 0,0
- Constructs from a `til::point` and a `til::size` representing the top-left corner and the width by height.
- Constructs from a `til::point` and another `til::point` representing the top-left corner and the **exclusive** bottom-right corner.
- Default constructs to empty
- Uses Chromium numerics for all basic math operations (+, -, *, /)
- Provides equality tests
- Provides `operator bool` to know when it's valid (has an area > 0) and `empty()` to know the contrary
- Accessors for left/top/right/bottom
- Type converting accessors (that use safe conversions and throw) for left/top/right/bottom
- Convenience methods for finding width/height (with Chromium numerics operations) and type-converting templates (with Chromium numerics conversions).
- Accessors for origin (top-left point) and the size/dimensions (as a `til::size`).
- Intersect operation on `operator &` to find where two `til::rectangle`s overlap, returned as a `til::rectangle`.
- Union operation on `operator |` to find the total area covered by two `til::rectangles`, returned as a `til::rectangle`.
- Subtract operation on `operator -` to find the area remaining after one `til::rectangle` is removed from another, returned as a `til::some<til::rectangle, 4>`.
- TAEF/WEX Output and Comparators so they will print very nicely with `VERIFY` and `Log` macros in our testing suite.
- Additional comparators, TAEF/WEX output, and tests written on `til::some` to support the Subtract operation.
- A natvis

## Validation Steps Performed
- See automated tests of functionality.
2020-03-14 17:27:47 +00:00

266 lines
8.3 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#ifdef UNIT_TESTING
class SizeTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
class size
{
public:
constexpr size() noexcept :
size(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 size(int width, int height) noexcept :
size(static_cast<ptrdiff_t>(width), static_cast<ptrdiff_t>(height))
{
}
#endif
size(size_t width, size_t height)
{
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width).AssignIfValid(&_width));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height).AssignIfValid(&_height));
}
constexpr size(ptrdiff_t width, ptrdiff_t height) noexcept :
_width(width),
_height(height)
{
}
// This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value
template<typename TOther>
constexpr size(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().X)> && std::is_integral_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
size(static_cast<ptrdiff_t>(other.X), static_cast<ptrdiff_t>(other.Y))
{
}
// This template will convert to size from anything that has a cx and a cy field that appear convertible to an integer value
template<typename TOther>
constexpr size(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().cx)> && std::is_integral_v<decltype(std::declval<TOther>().cy)>, int> /*sentinel*/ = 0) :
size(static_cast<ptrdiff_t>(other.cx), static_cast<ptrdiff_t>(other.cy))
{
}
constexpr bool operator==(const size& other) const noexcept
{
return _width == other._width &&
_height == other._height;
}
constexpr bool operator!=(const size& other) const noexcept
{
return !(*this == other);
}
size operator+(const size& other) const
{
ptrdiff_t width;
THROW_HR_IF(E_ABORT, !base::CheckAdd(_width, other._width).AssignIfValid(&width));
ptrdiff_t height;
THROW_HR_IF(E_ABORT, !base::CheckAdd(_height, other._height).AssignIfValid(&height));
return size{ width, height };
}
size operator-(const size& other) const
{
ptrdiff_t width;
THROW_HR_IF(E_ABORT, !base::CheckSub(_width, other._width).AssignIfValid(&width));
ptrdiff_t height;
THROW_HR_IF(E_ABORT, !base::CheckSub(_height, other._height).AssignIfValid(&height));
return size{ width, height };
}
size operator*(const size& other) const
{
ptrdiff_t width;
THROW_HR_IF(E_ABORT, !base::CheckMul(_width, other._width).AssignIfValid(&width));
ptrdiff_t height;
THROW_HR_IF(E_ABORT, !base::CheckMul(_height, other._height).AssignIfValid(&height));
return size{ width, height };
}
size operator/(const size& other) const
{
ptrdiff_t width;
THROW_HR_IF(E_ABORT, !base::CheckDiv(_width, other._width).AssignIfValid(&width));
ptrdiff_t height;
THROW_HR_IF(E_ABORT, !base::CheckDiv(_height, other._height).AssignIfValid(&height));
return size{ width, height };
}
size divide_ceil(const size& other) const
{
// Divide normally to get the floor.
const size floor = *this / other;
ptrdiff_t adjWidth = 0;
ptrdiff_t adjHeight = 0;
// Check for width remainder, anything not 0.
// If we multiply the floored number with the other, it will equal
// the old width if there was no remainder.
if (other._width * floor._width != _width)
{
// If there was any remainder,
// Grow the magnitude by 1 in the
// direction of the sign.
if (floor.width() >= 0)
{
++adjWidth;
}
else
{
--adjWidth;
}
}
// Check for height remainder, anything not 0.
// If we multiply the floored number with the other, it will equal
// the old width if there was no remainder.
if (other._height * floor._height != _height)
{
// If there was any remainder,
// Grow the magnitude by 1 in the
// direction of the sign.
if (_height >= 0)
{
++adjHeight;
}
else
{
--adjHeight;
}
}
return floor + size{ adjWidth, adjHeight };
}
constexpr ptrdiff_t width() const noexcept
{
return _width;
}
template<typename T>
T width() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(width()).AssignIfValid(&ret));
return ret;
}
constexpr ptrdiff_t height() const noexcept
{
return _height;
}
template<typename T>
T height() const
{
T ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(height()).AssignIfValid(&ret));
return ret;
}
ptrdiff_t area() const
{
ptrdiff_t result;
THROW_HR_IF(E_ABORT, !base::CheckMul(_width, _height).AssignIfValid(&result));
return result;
}
#ifdef _WINCONTYPES_
operator COORD() const
{
COORD ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_width).AssignIfValid(&ret.X));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_height).AssignIfValid(&ret.Y));
return ret;
}
#endif
#ifdef _WINDEF_
operator SIZE() const
{
SIZE ret;
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_width).AssignIfValid(&ret.cx));
THROW_HR_IF(E_ABORT, !base::MakeCheckedNum(_height).AssignIfValid(&ret.cy));
return ret;
}
#endif
#ifdef DCOMMON_H_INCLUDED
constexpr operator D2D1_SIZE_F() const noexcept
{
return D2D1_SIZE_F{ gsl::narrow_cast<float>(_width), gsl::narrow_cast<float>(_height) };
}
#endif
protected:
ptrdiff_t _width;
ptrdiff_t _height;
#ifdef UNIT_TESTING
friend class ::SizeTests;
#endif
};
};
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::size>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::size& size)
{
return WEX::Common::NoThrowString().Format(L"[W:%td, H:%td]", size.width(), size.height());
}
};
template<>
class VerifyCompareTraits<::til::size, ::til::size>
{
public:
static bool AreEqual(const ::til::size& expected, const ::til::size& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::size& expected, const ::til::size& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::size& expectedLess, const ::til::size& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::size& expectedGreater, const ::til::size& expectedLess) = delete;
static bool IsNull(const ::til::size& object) noexcept
{
return object == til::size{};
}
};
};
#endif