Move ConPTY to use til::bitmap (#5024)

## Summary of the Pull Request
Moves the ConPTY drawing mechanism (`VtRenderer`) to use the fine-grained `til::bitmap` individual-dirty-bit tracking mechanism instead of coarse-grained rectangle unions to improve drawing performance by dramatically reducing the total area redrawn.

## PR Checklist
* [x] Part of #778 and #1064 
* [x] I work here
* [x] Tests added and updated.
* [x] I'm a core contributor

## Detailed Description of the Pull Request / Additional comments
- Converted `GetDirtyArea()` interface from `IRenderEngine` to use a vector of `til::rectangle` instead of the `SMALL_RECT` to banhammer inclusive rectangles.
- `VtEngine` now holds and operates on the `til::bitmap` for invalidation regions. All invalidation operation functions that used to be embedded inside `VtEngine` are deleted in favor of using the ones in `til::bitmap`.
- Updated `VtEngine` tracing to use new `til::bitmap` on trace and the new `to_string()` methods detailed below.
- Comparison operators for `til::bitmap` and complementary tests.
- Fixed an issue where the dirty rectangle shortcut in `til::bitmap` was set to 0,0,0,0 by default which means that `|=` on it with each `set()` operation was stretching the rectangle from 0,0. Now it's a `std::optional` so it has no value after just being cleared and will build from whatever the first invalidated rectangle is. Complementary tests added.
- Optional run caching for `til::bitmap` in the `runs()` method since both VT and DX renderers will likely want to generate the set of runs at the beginning of a frame and refer to them over and over through that frame. Saves the iteration and creation and caches inside `til::bitmap` where the chance of invalidation of the underlying data is known best. It is still possible to iterate manually with `begin()` and `end()` from the outside without caching, if desired. Complementary tests added.
- WEX templates added for `til::bitmap` and used in tests.
- `translate()` method for `til::bitmap` which will slide the dirty points in the direction specified by a `til::point` and optionally back-fill the uncovered area as dirty. Complementary tests added.
- Moves all string generation for `til` types `size`, `point`, `rectangle`, and `some` into a `to_string` method on each object such that it can be used in both ETW tracing scenarios AND in the TAEF templates uniformly. Adds a similar method for `bitmap`.
- Add tagging to `_bitmap_const_iterator` such that it appears as a valid **Input Iterator** to STL collections and can be used in a `std::vector` constructor as a range. Adds and cleans up operators on this iterator to match the theoretical requirements for an **Input Iterator**. Complementary tests added.
- Add loose operators to `til` which will allow some basic math operations (+, -, *, /) between `til::size` and `til::point` and vice versa. Complementary tests added. Complementary tests added.
- Adds operators to `til::rectangle` to allow scaling with basic math operations (+, -, *) versus `til::size` and translation with basic math operations (+, -) against `til::point`. Complementary tests added.
- In-place variants of some operations added to assorted `til` objects. Complementary tests added.
- Update VT tests to compare invalidation against the new map structure instead of raw rectangles where possible.

## Validation Steps Performed
- Wrote additional til Unit Tests for all additional operators and functions added to the project to support this operation
- Updated the existing VT renderer tests
- Ran perf check
This commit is contained in:
Michael Niksa 2020-03-23 08:57:54 -07:00 committed by GitHub
parent cb3bab4ea8
commit ca33d895a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1639 additions and 333 deletions

View file

@ -347,17 +347,14 @@ void ConptyRoundtripTests::WriteAFewSimpleLines()
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
expectedOutput.push_back("\r\n");
// Here, we're going to emit 3 spaces. The region that got invalidated was a
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
// region in between BBB and CCC as well, because it got included in the
// rectangle Or() operation.
// This behavior should not be seen as binding - if a future optimization
// breaks this test, it wouldn't be the worst.
expectedOutput.push_back(" ");
expectedOutput.push_back("\r\n");
// Jump down to the fourth line because emitting spaces didn't do anything
// and we will skip to emitting the CCC segment.
expectedOutput.push_back("\x1b[4;1H");
expectedOutput.push_back("CCC");
// Cursor goes back on.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyData(termTb);
@ -458,14 +455,10 @@ void ConptyRoundtripTests::TestAdvancedWrapping()
expectedOutput.push_back(R"(!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnop)");
// Without line breaking, write the remaining 20 chars
expectedOutput.push_back(R"(qrstuvwxyz{|}~!"#$%&)");
// Clear the rest of row 1
expectedOutput.push_back("\x1b[K");
// This is the hard line break
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
@ -537,8 +530,6 @@ void ConptyRoundtripTests::TestExactWrappingWithoutSpaces()
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back("1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);
@ -601,8 +592,6 @@ void ConptyRoundtripTests::TestExactWrappingWithSpaces()
expectedOutput.push_back("\r\n");
// Now write row 2 of the buffer
expectedOutput.push_back(" 1234567890");
// and clear everything after the text, because the buffer is empty.
expectedOutput.push_back("\x1b[K");
VERIFY_SUCCEEDED(renderer.PaintFrame());
verifyBuffer(termTb);

View file

@ -2,7 +2,7 @@
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>INLINE_TEST_METHOD_MARKUP;UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<Import Project="$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.51.200127004\build\Taef.Redist.Wlk.targets" Condition="Exists('$(MSBuildThisFileDirectory)..\packages\Taef.Redist.Wlk.10.51.200127004\build\Taef.Redist.Wlk.targets')" />

View file

@ -306,17 +306,14 @@ void ConptyOutputTests::WriteAFewSimpleLines()
expectedOutput.push_back("AAA");
expectedOutput.push_back("\r\n");
expectedOutput.push_back("BBB");
expectedOutput.push_back("\r\n");
// Here, we're going to emit 3 spaces. The region that got invalidated was a
// rectangle from 0,0 to 3,3, so the vt renderer will try to render the
// region in between BBB and CCC as well, because it got included in the
// rectangle Or() operation.
// This behavior should not be seen as binding - if a future optimization
// breaks this test, it wouldn't be the worst.
expectedOutput.push_back(" ");
expectedOutput.push_back("\r\n");
// Jump down to the fourth line because emitting spaces didn't do anything
// and we will skip to emitting the CCC segment.
expectedOutput.push_back("\x1b[4;1H");
expectedOutput.push_back("CCC");
// Cursor goes back on.
expectedOutput.push_back("\x1b[?25h");
VERIFY_SUCCEEDED(renderer.PaintFrame());
}

View file

@ -257,21 +257,22 @@ void VtRendererTest::Xterm256TestInvalidate()
VERIFY_IS_FALSE(engine->_firstPaint);
});
Viewport view = SetUpViewport();
const Viewport view = SetUpViewport();
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_IS_TRUE(engine->_invalidMap.one());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@ -284,7 +285,9 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[H"); // Go Home
qExpectedInput.push_back("\x1b[L"); // insert a line
@ -300,7 +303,17 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but the cursor is already at the home position
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@ -314,7 +327,9 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer
qExpectedInput.push_back("\n"); // Scroll down once
@ -329,7 +344,17 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but we're already at the bottom from the last call.
qExpectedInput.push_back("\n\n\n"); // Scroll down three times
@ -349,7 +374,18 @@ void VtRendererTest::Xterm256TestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
qExpectedInput.push_back("\x1b[H"); // Go to home
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@ -357,21 +393,39 @@ void VtRendererTest::Xterm256TestInvalidate()
scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
qExpectedInput.push_back("\x1b[2J");
TestPaint(*engine, [&]() {
Log::Comment(NoThrowString().Format(
L"---- Scrolled one down and one up, nothing should change ----"
L" But it still does for now MSFT:14169294"));
invalid = view.ToExclusive();
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// only the bottom line should be dirty.
// When we scrolled down, the bitmap looked like this:
// 1111
// 0000
// 0000
// 0000
// And then we scrolled up and the top line fell off and a bottom
// line was filled in like this:
// 0000
// 0000
// 0000
// 1111
const til::rectangle expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } };
VERIFY_ARE_EQUAL(expected, invalidRect);
VERIFY_SUCCEEDED(engine->ScrollFrame());
});
@ -730,15 +784,16 @@ void VtRendererTest::XtermTestInvalidate()
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_IS_TRUE(engine->_invalidMap.one());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@ -751,7 +806,9 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[H"); // Go Home
qExpectedInput.push_back("\x1b[L"); // insert a line
@ -766,7 +823,17 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but the cursor is already at the home position
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@ -780,7 +847,9 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 1;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(1u, runs.size());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, runs.front());
qExpectedInput.push_back("\x1b[32;1H"); // Bottom of buffer
qExpectedInput.push_back("\n"); // Scroll down once
@ -795,7 +864,17 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Top = invalid.Bottom - 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
// We would expect a CUP here, but we're already at the bottom from the last call.
qExpectedInput.push_back("\n\n\n"); // Scroll down three times
@ -815,7 +894,18 @@ void VtRendererTest::XtermTestInvalidate()
invalid = view.ToExclusive();
invalid.Bottom = 3;
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
// we should have 3 runs and build a rectangle out of them
const auto runs = engine->_invalidMap.runs();
VERIFY_ARE_EQUAL(3u, runs.size());
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// verify the rect matches the invalid one.
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, invalidRect);
qExpectedInput.push_back("\x1b[H"); // Go to home
qExpectedInput.push_back("\x1b[3L"); // insert 3 lines
VERIFY_SUCCEEDED(engine->ScrollFrame());
@ -823,21 +913,39 @@ void VtRendererTest::XtermTestInvalidate()
scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
Log::Comment(NoThrowString().Format(
VerifyOutputTraits<SMALL_RECT>::ToString(engine->_invalidRect.ToExclusive())));
Log::Comment(engine->_invalidMap.to_string().c_str());
qExpectedInput.push_back("\x1b[2J");
TestPaint(*engine, [&]() {
Log::Comment(NoThrowString().Format(
L"---- Scrolled one down and one up, nothing should change ----"
L" But it still does for now MSFT:14169294"));
invalid = view.ToExclusive();
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
const auto runs = engine->_invalidMap.runs();
auto invalidRect = runs.front();
for (size_t i = 1; i < runs.size(); ++i)
{
invalidRect |= runs[i];
}
// only the bottom line should be dirty.
// When we scrolled down, the bitmap looked like this:
// 1111
// 0000
// 0000
// 0000
// And then we scrolled up and the top line fell off and a bottom
// line was filled in like this:
// 0000
// 0000
// 0000
// 1111
const til::rectangle expected{ til::point{ view.Left(), view.BottomInclusive() }, til::size{ view.Width(), 1 } };
VERIFY_ARE_EQUAL(expected, invalidRect);
VERIFY_SUCCEEDED(engine->ScrollFrame());
});
@ -1051,15 +1159,15 @@ void VtRendererTest::WinTelnetTestInvalidate()
L"Make sure that invalidating all invalidates the whole viewport."));
VERIFY_SUCCEEDED(engine->InvalidateAll());
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
});
Log::Comment(NoThrowString().Format(
L"Make sure that invalidating anything only invalidates that portion"));
SMALL_RECT invalid = { 1, 1, 1, 1 };
SMALL_RECT invalid = { 1, 1, 2, 2 };
VERIFY_SUCCEEDED(engine->Invalidate(&invalid));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(invalid, engine->_invalidRect.ToExclusive());
VERIFY_ARE_EQUAL(til::rectangle{ Viewport::FromExclusive(invalid).ToInclusive() }, *(engine->_invalidMap.begin()));
});
Log::Comment(NoThrowString().Format(
@ -1067,7 +1175,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
COORD scrollDelta = { 0, 1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL); // sentinel
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@ -1076,7 +1184,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 0, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@ -1085,7 +1193,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 1, 0 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@ -1094,7 +1202,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { -1, 0 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@ -1103,7 +1211,7 @@ void VtRendererTest::WinTelnetTestInvalidate()
scrollDelta = { 1, -1 };
VERIFY_SUCCEEDED(engine->InvalidateScroll(&scrollDelta));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(view, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
qExpectedInput.push_back(EMPTY_CALLBACK_SENTINEL);
VERIFY_SUCCEEDED(engine->ScrollFrame());
WriteCallback(EMPTY_CALLBACK_SENTINEL, 1); // This will make sure nothing was written to the callback
@ -1351,7 +1459,7 @@ void VtRendererTest::TestResize()
VERIFY_SUCCEEDED(engine->UpdateViewport(newView.ToInclusive()));
TestPaint(*engine, [&]() {
VERIFY_ARE_EQUAL(newView, engine->_invalidRect);
VERIFY_IS_TRUE(engine->_invalidMap.all());
VERIFY_IS_FALSE(engine->_firstPaint);
VERIFY_IS_FALSE(engine->_suppressResizeRepaint);
});

View file

@ -85,6 +85,12 @@
// WRL
#include <wrl.h>
// WEX/TAEF testing
// Include before TIL if we're unit testing so it can light up WEX/TAEF template extensions
#ifdef UNIT_TESTING
#include <WexTestClass.h>
#endif
// TIL - Terminal Implementation Library
#ifndef BLOCK_TIL // Certain projects may want to include TIL manually to gain superpowers
#include "til.h"

View file

@ -3,12 +3,15 @@
#pragma once
#define _TIL_INLINEPREFIX __declspec(noinline) inline
#include "til/at.h"
#include "til/color.h"
#include "til/some.h"
#include "til/size.h"
#include "til/point.h"
#include "til/rectangle.h"
#include "til/operators.h"
#include "til/bitmap.h"
#include "til/u8u16convert.h"

View file

@ -14,6 +14,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
class _bitmap_const_iterator
{
public:
using iterator_category = typename std::input_iterator_tag;
using value_type = typename const til::rectangle;
using difference_type = typename ptrdiff_t;
using pointer = typename const til::rectangle*;
using reference = typename const til::rectangle&;
_bitmap_const_iterator(const std::vector<bool>& values, til::rectangle rc, ptrdiff_t pos) :
_values(values),
_rc(rc),
@ -30,6 +36,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return (*this);
}
_bitmap_const_iterator operator++(int)
{
const auto prev = *this;
++*this;
return prev;
}
constexpr bool operator==(const _bitmap_const_iterator& other) const noexcept
{
return _pos == other._pos && _values == other._values;
@ -50,11 +63,16 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return _pos > other._pos;
}
constexpr til::rectangle operator*() const noexcept
constexpr reference operator*() const noexcept
{
return _run;
}
constexpr pointer operator->() const noexcept
{
return &_run;
}
private:
const std::vector<bool>& _values;
const til::rectangle _rc;
@ -117,7 +135,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_sz{},
_rc{},
_bits{},
_dirty{}
_dirty{},
_runs{}
{
}
@ -130,10 +149,25 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_sz(sz),
_rc(sz),
_bits(sz.area(), fill),
_dirty(fill ? sz : til::rectangle{})
_dirty(fill ? sz : til::rectangle{}),
_runs{}
{
}
constexpr bool operator==(const bitmap& other) const noexcept
{
return _sz == other._sz &&
_rc == other._rc &&
_dirty == other._dirty && // dirty is before bits because it's a rough estimate of bits and a faster comparison.
_bits == other._bits;
// _runs excluded because it's a cache of generated state.
}
constexpr bool operator!=(const bitmap& other) const noexcept
{
return !(*this == other);
}
const_iterator begin() const
{
return const_iterator(_bits, _sz, 0);
@ -144,17 +178,106 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return const_iterator(_bits, _sz, _sz.area());
}
const std::vector<til::rectangle>& runs() const
{
// If we don't have cached runs, rebuild.
if (!_runs.has_value())
{
// If there's only one square dirty, quick save it off and be done.
if (one())
{
_runs.emplace({ _dirty });
}
else
{
_runs.emplace(begin(), end());
}
}
// Return a reference to the runs.
return _runs.value();
}
// optional fill the uncovered area with bits.
void translate(const til::point delta, bool fill = false)
{
// FUTURE: PERF: GH #4015: This could use in-place walk semantics instead of a temporary.
til::bitmap other{ _sz };
for (auto run : *this)
{
// Offset by the delta
run += delta;
// Intersect with the bounds of our bitmap area
// as part of it could have slid out of bounds.
run &= _rc;
// Set it into the new bitmap.
other.set(run);
}
// If we were asked to fill... find the uncovered region.
if (fill)
{
// Original Rect of As.
//
// X <-- origin
// A A A A
// A A A A
// A A A A
// A A A A
const auto originalRect = _rc;
// If Delta = (2, 2)
// Translated Rect of Bs.
//
// X <-- origin
//
//
// B B B B
// B B B B
// B B B B
// B B B B
const auto translatedRect = _rc + delta;
// Subtract the B from the A one to see what wasn't filled by the move.
// C is the overlap of A and B:
//
// X <-- origin
// A A A A 1 1 1 1
// A A A A 1 1 1 1
// A A C C B B subtract 2 2
// A A C C B B ---------> 2 2
// B B B B A - B
// B B B B
//
// 1 and 2 are the spaces to fill that are "uncovered".
const auto fillRects = originalRect - translatedRect;
for (const auto& f : fillRects)
{
other.set(f);
}
}
// Swap us with the temporary one.
std::swap(other, *this);
}
void set(const til::point pt)
{
THROW_HR_IF(E_INVALIDARG, !_rc.contains(pt));
_runs.reset(); // reset cached runs on any non-const method
til::at(_bits, _rc.index_of(pt)) = true;
_dirty |= til::rectangle{ pt };
}
void set(const til::rectangle rc)
{
THROW_HR_IF(E_INVALIDARG, !_rc.contains(rc));
_runs.reset(); // reset cached runs on any non-const method
for (const auto pt : rc)
{
@ -166,6 +289,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
void set_all()
{
_runs.reset(); // reset cached runs on any non-const method
// .clear() then .resize(_size(), true) throws an assert (unsupported operation)
// .assign(_size(), true) throws an assert (unsupported operation)
_bits = std::vector<bool>(_sz.area(), true);
@ -174,6 +299,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
void reset_all()
{
_runs.reset(); // reset cached runs on any non-const method
// .clear() then .resize(_size(), false) throws an assert (unsupported operation)
// .assign(_size(), false) throws an assert (unsupported operation)
@ -185,6 +312,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
// Set fill if you want the new region (on growing) to be marked dirty.
bool resize(til::size size, bool fill = false)
{
_runs.reset(); // reset cached runs on any non-const method
// FYI .resize(_size(), true/false) throws an assert (unsupported operation)
// Don't resize if it's not different
@ -254,14 +383,71 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return _dirty == _rc;
}
std::wstring to_string() const
{
std::wstringstream wss;
wss << std::endl
<< L"Bitmap of size " << _sz.to_string() << " contains the following dirty regions:" << std::endl;
wss << L"Runs:" << std::endl;
for (auto& item : *this)
{
wss << L"\t- " << item.to_string() << std::endl;
}
return wss.str();
}
private:
til::rectangle _dirty;
til::size _sz;
til::rectangle _rc;
std::vector<bool> _bits;
mutable std::optional<std::vector<til::rectangle>> _runs;
#ifdef UNIT_TESTING
friend class ::BitmapTests;
#endif
};
}
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::bitmap>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::bitmap& rect)
{
return WEX::Common::NoThrowString(rect.to_string().c_str());
}
};
template<>
class VerifyCompareTraits<::til::bitmap, ::til::bitmap>
{
public:
static bool AreEqual(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::bitmap& expected, const ::til::bitmap& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::bitmap& expectedLess, const ::til::bitmap& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::bitmap& expectedGreater, const ::til::bitmap& expectedLess) = delete;
static bool IsNull(const ::til::bitmap& object) noexcept
{
return object == til::bitmap{};
}
};
};
#endif

59
src/inc/til/operators.h Normal file
View file

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// Operators go here when they involve two headers that can't/don't include each other.
#pragma region POINT VS SIZE
// This is a convenience and will take X vs WIDTH and Y vs HEIGHT.
_TIL_INLINEPREFIX point operator+(const point& lhs, const size& rhs)
{
return lhs + til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator-(const point& lhs, const size& rhs)
{
return lhs - til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator*(const point& lhs, const size& rhs)
{
return lhs * til::point{ rhs.width(), rhs.height() };
}
_TIL_INLINEPREFIX point operator/(const point& lhs, const size& rhs)
{
return lhs / til::point{ rhs.width(), rhs.height() };
}
#pragma endregion
#pragma region SIZE VS POINT
// This is a convenience and will take WIDTH vs X and HEIGHT vs Y.
_TIL_INLINEPREFIX size operator+(const size& lhs, const point& rhs)
{
return lhs + til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator-(const size& lhs, const point& rhs)
{
return lhs - til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator*(const size& lhs, const point& rhs)
{
return lhs * til::size(rhs.x(), rhs.y());
}
_TIL_INLINEPREFIX size operator/(const size& lhs, const point& rhs)
{
return lhs / til::size(rhs.x(), rhs.y());
}
#pragma endregion
}

View file

@ -193,6 +193,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"(X:%td, Y:%td)", x(), y());
}
protected:
ptrdiff_t _x;
ptrdiff_t _y;
@ -212,7 +217,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::point& point)
{
return WEX::Common::NoThrowString().Format(L"(X:%td, Y:%td)", point.x(), point.y());
return WEX::Common::NoThrowString(point.to_string().c_str());
}
};

View file

@ -212,6 +212,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return const_iterator(_topLeft, _bottomRight, { _topLeft.x(), _bottomRight.y() });
}
#pragma region RECTANGLE OPERATORS
// OR = union
constexpr rectangle operator|(const rectangle& other) const noexcept
{
@ -274,6 +275,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
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
{
@ -405,6 +412,231 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
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;
}
#pragma endregion
constexpr ptrdiff_t top() const noexcept
{
@ -587,6 +819,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#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());
}
protected:
til::point _topLeft;
til::point _bottomRight;
@ -606,7 +843,7 @@ namespace WEX::TestExecution
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());
return WEX::Common::NoThrowString(rect.to_string().c_str());
}
};

View file

@ -220,6 +220,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
std::wstring to_string() const
{
return wil::str_printf<std::wstring>(L"[W:%td, H:%td]", width(), height());
}
protected:
ptrdiff_t _width;
ptrdiff_t _height;
@ -239,7 +244,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::size& size)
{
return WEX::Common::NoThrowString().Format(L"[W:%td, H:%td]", size.width(), size.height());
return WEX::Common::NoThrowString(size.to_string().c_str());
}
};

View file

@ -208,6 +208,21 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
throw std::out_of_range("invalid some<T, N> subscript");
}
std::wstring to_string() const
{
std::wstringstream wss;
wss << std::endl
<< L"Some contains " << size() << " of max size " << max_size() << ":" << std::endl;
wss << L"Elements:" << std::endl;
for (auto& item : *this)
{
wss << L"\t- " << item.to_string() << std::endl;
}
return wss.str();
}
};
}
@ -220,15 +235,7 @@ namespace WEX::TestExecution
public:
static WEX::Common::NoThrowString ToString(const ::til::some<T, N>& 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<T>::ToString(item);
str.AppendFormat(L"\t- %ws\r\n", (const wchar_t*)itemStr);
}
return str;
return WEX::Common::NoThrowString(some.to_string().c_str());
}
};

View file

@ -232,7 +232,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
std::vector<SMALL_RECT> BgfxEngine::GetDirtyArea()
std::vector<til::rectangle> BgfxEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;

View file

@ -69,7 +69,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
std::vector<SMALL_RECT> GetDirtyArea() override;
std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View file

@ -862,7 +862,7 @@ void Renderer::_PaintOverlay(IRenderEngine& engine,
// Set it up in a Viewport helper structure and trim it the IME viewport to be within the full console viewport.
Viewport viewConv = Viewport::FromInclusive(srCaView);
for (auto srDirty : engine.GetDirtyArea())
for (SMALL_RECT srDirty : engine.GetDirtyArea())
{
// Dirty is an inclusive rectangle, but oddly enough the IME was an exclusive one, so correct it.
srDirty.Bottom++;

View file

@ -1686,7 +1686,7 @@ float DxEngine::GetScaling() const noexcept
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
[[nodiscard]] std::vector<SMALL_RECT> DxEngine::GetDirtyArea()
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));

View file

@ -95,7 +95,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View file

@ -68,7 +68,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View file

@ -16,7 +16,7 @@ using namespace Microsoft::Console::Render;
// Return Value:
// - The character dimensions of the current dirty area of the frame.
// This is an Inclusive rect.
std::vector<SMALL_RECT> GdiEngine::GetDirtyArea()
std::vector<til::rectangle> GdiEngine::GetDirtyArea()
{
RECT rc = _psInvalidData.rcPaint;

View file

@ -117,7 +117,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& FontInfo,
const int iDpi) noexcept = 0;
virtual std::vector<SMALL_RECT> GetDirtyArea() = 0;
virtual std::vector<til::rectangle> GetDirtyArea() = 0;
[[nodiscard]] virtual HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept = 0;
[[nodiscard]] virtual HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept = 0;
[[nodiscard]] virtual HRESULT UpdateTitle(const std::wstring& newTitle) noexcept = 0;

View file

@ -426,7 +426,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
[[nodiscard]] std::vector<SMALL_RECT> UiaEngine::GetDirtyArea()
[[nodiscard]] std::vector<til::rectangle> UiaEngine::GetDirtyArea()
{
return { Viewport::Empty().ToInclusive() };
}

View file

@ -71,7 +71,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] std::vector<SMALL_RECT> GetDirtyArea() override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View file

@ -408,21 +408,8 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
if (dx != 0 || dy != 0)
{
// Scroll the current offset
RETURN_IF_FAILED(_InvalidOffset(pcoordDelta));
// Add the top/bottom of the window to the invalid area
SMALL_RECT invalid = _lastViewport.ToOrigin().ToExclusive();
if (dy > 0)
{
invalid.Bottom = dy;
}
else if (dy < 0)
{
invalid.Top = invalid.Bottom + dy;
}
LOG_IF_FAILED(_InvalidCombine(Viewport::FromExclusive(invalid)));
// Scroll the current offset and invalidate the revealed area
_invalidMap.translate(til::point(*pcoordDelta), true);
COORD invalidScrollNew;
RETURN_IF_FAILED(ShortAdd(_scrollDelta.X, dx, &invalidScrollNew.X));

View file

@ -47,12 +47,14 @@ using namespace Microsoft::Console::Render;
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::Invalidate(const SMALL_RECT* const psrRegion) noexcept
try
{
Viewport newInvalid = Viewport::FromExclusive(*psrRegion);
_trace.TraceInvalidate(newInvalid);
return this->_InvalidCombine(newInvalid);
const til::rectangle rect{ Viewport::FromExclusive(*psrRegion).ToInclusive() };
_trace.TraceInvalidate(rect);
_invalidMap.set(rect);
return S_OK;
}
CATCH_RETURN();
// Routine Description:
// - Notifies us that the console has changed the position of the cursor.
@ -87,10 +89,13 @@ using namespace Microsoft::Console::Render;
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::InvalidateAll() noexcept
try
{
_trace.TraceInvalidateAll(_lastViewport.ToOrigin());
return this->_InvalidCombine(_lastViewport.ToOrigin());
_trace.TraceInvalidateAll(_lastViewport.ToOrigin().ToInclusive());
_invalidMap.set_all();
return S_OK;
}
CATCH_RETURN();
// Method Description:
// - Notifies us that we're about to circle the buffer, giving us a chance to
@ -132,75 +137,3 @@ using namespace Microsoft::Console::Render;
*pForcePaint = true;
return S_OK;
}
// Routine Description:
// - Helper to combine the given rectangle into the invalid region to be
// updated on the next paint
// Expects EXCLUSIVE rectangles.
// Arguments:
// - invalid - A viewport containing the character region that should be
// repainted on the next frame
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_InvalidCombine(const Viewport invalid) noexcept
{
if (!_fInvalidRectUsed)
{
_invalidRect = invalid;
_fInvalidRectUsed = true;
}
else
{
_invalidRect = Viewport::Union(_invalidRect, invalid);
}
// Ensure invalid areas remain within bounds of window.
RETURN_IF_FAILED(_InvalidRestrict());
return S_OK;
}
// Routine Description:
// - Helper to adjust the invalid region by the given offset such as when a
// scroll operation occurs.
// Arguments:
// - ppt - Distances by which we should move the invalid region in response to a scroll
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_InvalidOffset(const COORD* const pCoord) noexcept
{
if (_fInvalidRectUsed)
{
try
{
Viewport newInvalid = Viewport::Offset(_invalidRect, *pCoord);
// Add the scrolled invalid rectangle to what was left behind to get the new invalid area.
// This is the equivalent of adding in the "update rectangle" that we would get out of ScrollWindowEx/ScrollDC.
_invalidRect = Viewport::Union(_invalidRect, newInvalid);
// Ensure invalid areas remain within bounds of window.
RETURN_IF_FAILED(_InvalidRestrict());
}
CATCH_RETURN();
}
return S_OK;
}
// Routine Description:
// - Helper to ensure the invalid region remains within the bounds of the viewport.
// Arguments:
// - <none>
// Return Value:
// - S_OK, else an appropriate HRESULT for failing to allocate or safemath failure.
[[nodiscard]] HRESULT VtEngine::_InvalidRestrict() noexcept
{
SMALL_RECT oldInvalid = _invalidRect.ToExclusive();
_lastViewport.ToOrigin().TrimToViewport(&oldInvalid);
_invalidRect = Viewport::FromExclusive(oldInvalid);
return S_OK;
}

View file

@ -17,14 +17,9 @@ using namespace Microsoft::Console::Types;
// Return Value:
// - The character dimensions of the current dirty area of the frame.
// This is an Inclusive rect.
std::vector<SMALL_RECT> VtEngine::GetDirtyArea()
std::vector<til::rectangle> VtEngine::GetDirtyArea()
{
SMALL_RECT dirty = _invalidRect.ToInclusive();
if (dirty.Top < _virtualTop)
{
dirty.Top = _virtualTop;
}
return { dirty };
return _invalidMap.runs();
}
// Routine Description:
@ -68,17 +63,26 @@ void VtEngine::_OrRect(_Inout_ SMALL_RECT* const pRectExisting, const SMALL_RECT
// - true iff only the next character is invalid
bool VtEngine::_WillWriteSingleChar() const
{
COORD currentCursor = _lastText;
SMALL_RECT _srcInvalid = _invalidRect.ToExclusive();
bool noScrollDelta = (_scrollDelta.X == 0 && _scrollDelta.Y == 0);
// If there is scroll delta, return false.
if (til::point{ 0, 0 } != til::point{ _scrollDelta })
{
return false;
}
// If there is more than one invalid char, return false.
if (!_invalidMap.one())
{
return false;
}
// Get the single point at which things are invalid.
const auto invalidPoint = _invalidMap.runs().front().origin();
bool invalidIsOneChar = (_invalidRect.Width() == 1) &&
(_invalidRect.Height() == 1);
// Either the next character to the right or the immediately previous
// character should follow this code path
// (The immediate previous character would suggest a backspace)
bool invalidIsNext = (_srcInvalid.Top == _lastText.Y) && (_srcInvalid.Left == _lastText.X);
bool invalidIsLast = (_srcInvalid.Top == _lastText.Y) && (_srcInvalid.Left == (_lastText.X - 1));
bool invalidIsNext = invalidPoint == til::point{ _lastText };
bool invalidIsLast = invalidPoint == til::point{ _lastText.X - 1, _lastText.Y };
return noScrollDelta && invalidIsOneChar && (invalidIsNext || invalidIsLast);
return invalidIsNext || invalidIsLast;
}

View file

@ -26,13 +26,13 @@ using namespace Microsoft::Console::Types;
}
// If there's nothing to do, quick return
bool somethingToDo = _fInvalidRectUsed ||
bool somethingToDo = _invalidMap.any() ||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
_cursorMoved ||
_titleChanged;
_quickReturn = !somethingToDo;
_trace.TraceStartPaint(_quickReturn, _fInvalidRectUsed, _invalidRect, _lastViewport, _scrollDelta, _cursorMoved);
_trace.TraceStartPaint(_quickReturn, _invalidMap, _lastViewport.ToInclusive(), _scrollDelta, _cursorMoved);
return _quickReturn ? S_FALSE : S_OK;
}
@ -50,8 +50,8 @@ using namespace Microsoft::Console::Types;
{
_trace.TraceEndPaint();
_invalidRect = Viewport::Empty();
_fInvalidRectUsed = false;
_invalidMap.reset_all();
_scrollDelta = { 0 };
_clearedAllThisFrame = false;
_cursorMoved = false;

View file

@ -35,8 +35,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_LastBG(INVALID_COLOR),
_lastWasBold(false),
_lastViewport(initialViewport),
_invalidRect(Viewport::Empty()),
_fInvalidRectUsed(false),
_invalidMap(initialViewport.Dimensions()),
_lastRealCursor({ 0 }),
_lastText({ 0 }),
_scrollDelta({ 0 }),
@ -317,40 +316,19 @@ CATCH_RETURN();
// buffer will have triggered it's own invalidations for what it knows is
// invalid. Previously, we'd invalidate everything if the width changed,
// because we couldn't be sure if lines were reflowed.
_invalidMap.resize(newView.Dimensions());
}
else
{
if (SUCCEEDED(hr))
{
_invalidMap.resize(newView.Dimensions(), true); // resize while filling in new space with repaint requests.
// Viewport is smaller now - just update it all.
if (oldView.Height() > newView.Height() || oldView.Width() > newView.Width())
{
hr = InvalidateAll();
}
else
{
// At least one of the directions grew.
// First try and add everything to the right of the old viewport,
// then everything below where the old viewport ended.
if (oldView.Width() < newView.Width())
{
short left = oldView.RightExclusive();
short top = 0;
short right = newView.RightInclusive();
short bottom = oldView.BottomInclusive();
Viewport rightOfOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(rightOfOldViewport);
}
if (SUCCEEDED(hr) && oldView.Height() < newView.Height())
{
short left = 0;
short top = oldView.BottomExclusive();
short right = newView.RightInclusive();
short bottom = newView.BottomInclusive();
Viewport belowOldViewport = Viewport::FromInclusive({ left, top, right, bottom });
hr = _InvalidCombine(belowOldViewport);
}
}
}
}
@ -416,7 +394,7 @@ void VtEngine::SetTestCallback(_In_ std::function<bool(const char* const, size_t
// - true if the entire viewport has been invalidated
bool VtEngine::_AllIsInvalid() const
{
return _lastViewport == _invalidRect;
return _invalidMap.all();
}
// Method Description:

View file

@ -81,52 +81,16 @@ void RenderTracing::TraceString(const std::string_view& instr) const
#endif UNIT_TESTING
}
std::string _ViewportToString(const Viewport& view)
{
std::stringstream ss;
ss << "{LT:(";
ss << view.Left();
ss << ",";
ss << view.Top();
ss << ") RB:(";
ss << view.RightInclusive();
ss << ",";
ss << view.BottomInclusive();
ss << ") [";
ss << view.Width();
ss << "x";
ss << view.Height();
ss << "]}";
std::string myString = "";
const auto s = ss.str();
myString += s;
return myString;
}
std::string _CoordToString(const COORD& c)
{
std::stringstream ss;
ss << "{";
ss << c.X;
ss << ",";
ss << c.Y;
ss << "}";
const auto s = ss.str();
// Make sure you actually place this value in a local after calling, don't
// just inline it to _CoordToString().c_str()
return s;
}
void RenderTracing::TraceInvalidate(const Viewport invalidRect) const
void RenderTracing::TraceInvalidate(const til::rectangle invalidRect) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto invalidatedStr = _ViewportToString(invalidRect);
const auto invalidatedStr = invalidRect.to_string();
const auto invalidated = invalidatedStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceInvalidate",
TraceLoggingString(invalidated),
TraceLoggingWideString(invalidated),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
@ -134,16 +98,16 @@ void RenderTracing::TraceInvalidate(const Viewport invalidRect) const
#endif UNIT_TESTING
}
void RenderTracing::TraceInvalidateAll(const Viewport viewport) const
void RenderTracing::TraceInvalidateAll(const til::rectangle viewport) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto invalidatedStr = _ViewportToString(viewport);
const auto invalidatedStr = viewport.to_string();
const auto invalidatedAll = invalidatedStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceInvalidateAll",
TraceLoggingString(invalidatedAll),
TraceLoggingWideString(invalidatedAll),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
@ -164,35 +128,32 @@ void RenderTracing::TraceTriggerCircling(const bool newFrame) const
}
void RenderTracing::TraceStartPaint(const bool quickReturn,
const bool invalidRectUsed,
const Microsoft::Console::Types::Viewport invalidRect,
const Microsoft::Console::Types::Viewport lastViewport,
const COORD scrollDelt,
const til::bitmap invalidMap,
const til::rectangle lastViewport,
const til::point scrollDelt,
const bool cursorMoved) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto invalidatedStr = _ViewportToString(invalidRect);
const auto invalidatedStr = invalidMap.to_string();
const auto invalidated = invalidatedStr.c_str();
const auto lastViewStr = _ViewportToString(lastViewport);
const auto lastViewStr = lastViewport.to_string();
const auto lastView = lastViewStr.c_str();
const auto scrollDeltaStr = _CoordToString(scrollDelt);
const auto scrollDeltaStr = scrollDelt.to_string();
const auto scrollDelta = scrollDeltaStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceStartPaint",
TraceLoggingBool(quickReturn),
TraceLoggingBool(invalidRectUsed),
TraceLoggingString(invalidated),
TraceLoggingString(lastView),
TraceLoggingString(scrollDelta),
TraceLoggingWideString(invalidated),
TraceLoggingWideString(lastView),
TraceLoggingWideString(scrollDelta),
TraceLoggingBool(cursorMoved),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(quickReturn);
UNREFERENCED_PARAMETER(invalidRectUsed);
UNREFERENCED_PARAMETER(invalidRect);
UNREFERENCED_PARAMETER(invalidMap);
UNREFERENCED_PARAMETER(lastViewport);
UNREFERENCED_PARAMETER(scrollDelt);
UNREFERENCED_PARAMETER(cursorMoved);
@ -209,37 +170,37 @@ void RenderTracing::TraceEndPaint() const
#endif UNIT_TESTING
}
void RenderTracing::TraceLastText(const COORD lastTextPos) const
void RenderTracing::TraceLastText(const til::point lastTextPos) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastTextStr = lastTextPos.to_string();
const auto lastText = lastTextStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceLastText",
TraceLoggingString(lastText),
TraceLoggingWideString(lastText),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
UNREFERENCED_PARAMETER(lastTextPos);
#endif UNIT_TESTING
}
void RenderTracing::TraceMoveCursor(const COORD lastTextPos, const COORD cursor) const
void RenderTracing::TraceMoveCursor(const til::point lastTextPos, const til::point cursor) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto lastTextStr = _CoordToString(lastTextPos);
const auto lastTextStr = lastTextPos.to_string();
const auto lastText = lastTextStr.c_str();
const auto cursorStr = _CoordToString(cursor);
const auto cursorStr = cursor.to_string();
const auto cursorPos = cursorStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(lastText),
TraceLoggingString(cursorPos),
TraceLoggingWideString(lastText),
TraceLoggingWideString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else
@ -263,16 +224,16 @@ void RenderTracing::TraceWrapped() const
#endif UNIT_TESTING
}
void RenderTracing::TracePaintCursor(const COORD coordCursor) const
void RenderTracing::TracePaintCursor(const til::point coordCursor) const
{
#ifndef UNIT_TESTING
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
{
const auto cursorPosString = _CoordToString(coordCursor);
const auto cursorPosString = coordCursor.to_string();
const auto cursorPos = cursorPosString.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TracePaintCursor",
TraceLoggingString(cursorPos),
TraceLoggingWideString(cursorPos),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
}
#else

View file

@ -27,18 +27,17 @@ namespace Microsoft::Console::VirtualTerminal
RenderTracing();
~RenderTracing();
void TraceString(const std::string_view& str) const;
void TraceInvalidate(const Microsoft::Console::Types::Viewport view) const;
void TraceLastText(const COORD lastText) const;
void TraceMoveCursor(const COORD lastText, const COORD cursor) const;
void TraceInvalidate(const til::rectangle view) const;
void TraceLastText(const til::point lastText) const;
void TraceMoveCursor(const til::point lastText, const til::point cursor) const;
void TraceWrapped() const;
void TracePaintCursor(const COORD coordCursor) const;
void TraceInvalidateAll(const Microsoft::Console::Types::Viewport view) const;
void TracePaintCursor(const til::point coordCursor) const;
void TraceInvalidateAll(const til::rectangle view) const;
void TraceTriggerCircling(const bool newFrame) const;
void TraceStartPaint(const bool quickReturn,
const bool invalidRectUsed,
const Microsoft::Console::Types::Viewport invalidRect,
const Microsoft::Console::Types::Viewport lastViewport,
const COORD scrollDelta,
const til::bitmap invalidMap,
const til::rectangle lastViewport,
const til::point scrollDelta,
const bool cursorMoved) const;
void TraceEndPaint() const;
};

View file

@ -10,13 +10,8 @@
</PropertyGroup>
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\renderer\vt\vt-renderer-common.vcxitems" />
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>INLINE_TEST_METHOD_MARKUP;UNIT_TESTING;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<!-- DONT ADD NEW FILES HERE, ADD THEM TO vt-renderer-common.vcxitems -->
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
<Import Project="$(SolutionDir)src\common.build.post.props" />
<!-- <Import Project="$(SolutionDir)src\common.build.tests.props" /> -->
<Import Project="$(SolutionDir)src\common.build.tests.props" />
</Project>

View file

@ -89,7 +89,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
std::vector<SMALL_RECT> GetDirtyArea() override;
std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
@ -121,9 +121,9 @@ namespace Microsoft::Console::Render
bool _lastWasBold;
Microsoft::Console::Types::Viewport _lastViewport;
Microsoft::Console::Types::Viewport _invalidRect;
bool _fInvalidRectUsed;
til::bitmap _invalidMap;
COORD _lastRealCursor;
COORD _lastText;
COORD _scrollDelta;
@ -160,9 +160,6 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _Flush() noexcept;
void _OrRect(_Inout_ SMALL_RECT* const pRectExisting, const SMALL_RECT* const pRectToOr) const;
[[nodiscard]] HRESULT _InvalidCombine(const Microsoft::Console::Types::Viewport invalid) noexcept;
[[nodiscard]] HRESULT _InvalidOffset(const COORD* const ppt) noexcept;
[[nodiscard]] HRESULT _InvalidRestrict() noexcept;
bool _AllIsInvalid() const;
[[nodiscard]] HRESULT _StopCursorBlinking() noexcept;

View file

@ -355,7 +355,7 @@ bool WddmConEngine::IsInitialized()
return S_OK;
}
std::vector<SMALL_RECT> WddmConEngine::GetDirtyArea()
std::vector<til::rectangle> WddmConEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;

View file

@ -61,7 +61,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
std::vector<SMALL_RECT> GetDirtyArea() override;
std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;

View file

@ -25,14 +25,21 @@ class BitmapTests
Log::Comment(L"Check dirty rectangles.");
// Union up all the dirty rectangles into one big one.
auto dirtyExpected = bitsOn.front();
for (auto it = bitsOn.cbegin() + 1; it < bitsOn.cend(); ++it)
if (bitsOn.empty())
{
dirtyExpected |= *it;
VERIFY_ARE_EQUAL(til::rectangle{}, map._dirty);
}
else
{
auto dirtyExpected = bitsOn.front();
for (auto it = bitsOn.cbegin() + 1; it < bitsOn.cend(); ++it)
{
dirtyExpected |= *it;
}
// Check if it matches.
VERIFY_ARE_EQUAL(dirtyExpected, map._dirty);
// Check if it matches.
VERIFY_ARE_EQUAL(dirtyExpected, map._dirty);
}
Log::Comment(L"Check all bits in map.");
// For every point in the map...
@ -118,6 +125,486 @@ class BitmapTests
}
}
TEST_METHOD(Equality)
{
Log::Comment(L"0.) Defaults are equal");
{
const til::bitmap one;
const til::bitmap two;
VERIFY_IS_TRUE(one == two);
}
Log::Comment(L"1.) Different sizes are unequal");
{
const til::bitmap one{ til::size{ 2, 2 } };
const til::bitmap two{ til::size{ 3, 3 } };
VERIFY_IS_FALSE(one == two);
}
Log::Comment(L"2.) Same bits set are equal");
{
til::bitmap one{ til::size{ 2, 2 } };
til::bitmap two{ til::size{ 2, 2 } };
one.set(til::point{ 0, 1 });
one.set(til::point{ 1, 0 });
two.set(til::point{ 0, 1 });
two.set(til::point{ 1, 0 });
VERIFY_IS_TRUE(one == two);
}
Log::Comment(L"3.) Different bits set are not equal");
{
til::bitmap one{ til::size{ 2, 2 } };
til::bitmap two{ til::size{ 2, 2 } };
one.set(til::point{ 0, 1 });
two.set(til::point{ 1, 0 });
VERIFY_IS_FALSE(one == two);
}
}
TEST_METHOD(Inequality)
{
Log::Comment(L"0.) Defaults are equal");
{
const til::bitmap one;
const til::bitmap two;
VERIFY_IS_FALSE(one != two);
}
Log::Comment(L"1.) Different sizes are unequal");
{
const til::bitmap one{ til::size{ 2, 2 } };
const til::bitmap two{ til::size{ 3, 3 } };
VERIFY_IS_TRUE(one != two);
}
Log::Comment(L"2.) Same bits set are equal");
{
til::bitmap one{ til::size{ 2, 2 } };
til::bitmap two{ til::size{ 2, 2 } };
one.set(til::point{ 0, 1 });
one.set(til::point{ 1, 0 });
two.set(til::point{ 0, 1 });
two.set(til::point{ 1, 0 });
VERIFY_IS_FALSE(one != two);
}
Log::Comment(L"3.) Different bits set are not equal");
{
til::bitmap one{ til::size{ 2, 2 } };
til::bitmap two{ til::size{ 2, 2 } };
one.set(til::point{ 0, 1 });
two.set(til::point{ 1, 0 });
VERIFY_IS_TRUE(one != two);
}
}
TEST_METHOD(Translate)
{
const til::size mapSize{ 4, 4 };
til::bitmap map{ mapSize };
// set the middle four bits of the map.
// 0 0 0 0
// 0 1 1 0
// 0 1 1 0
// 0 0 0 0
map.set(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 2 } });
Log::Comment(L"1.) Move down and right");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v --> -->
const til::point delta{ 2, 2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 0 0 0 0 0 0 0 0
// 0 1 1 0 0 0 0 0 0 0 0 0
// 0 1 1 0 v --> 0 0 0 0 --> 0 0 0 0
// 0 0 0 0 v 0 1 1 0 0 0 0 1
// ->->
expected.set(til::point{ 3, 3 });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Move down");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v
const til::point delta{ 0, 2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 0 0 0 0
// 0 1 1 0 0 0 0 0
// 0 1 1 0 v --> 0 0 0 0
// 0 0 0 0 v 0 1 1 0
expected.set(til::rectangle{ til::point{ 1, 3 }, til::size{ 2, 1 } });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Move down and left");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v <-- <--
const til::point delta{ -2, 2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 0 0 0 0 0 0 0 0
// 0 1 1 0 0 0 0 0 0 0 0 0
// 0 1 1 0 v --> 0 0 0 0 --> 0 0 0 0
// 0 0 0 0 v 0 1 1 0 1 0 0 0
// <-<-
expected.set(til::point{ 0, 3 });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Move left");
{
auto actual = map;
// Move all contents
// <-- <--
const til::point delta{ -2, 0 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 0 0 0 0
// 0 1 1 0 1 0 0 0
// 0 1 1 0 --> 1 0 0 0
// 0 0 0 0 0 0 0 0
// <--<--
expected.set(til::rectangle{ til::point{ 0, 1 }, til::size{ 1, 2 } });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"5.) Move up and left");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// | <-- <--
const til::point delta{ -2, -2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 ^ 0 1 1 0 1 0 0 0
// 0 1 1 0 ^ 0 0 0 0 0 0 0 0
// 0 1 1 0 --> 0 0 0 0 --> 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0
// <-<-
expected.set(til::point{ 0, 0 });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"6.) Move up");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// |
const til::point delta{ 0, -2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 ^ 0 1 1 0
// 0 1 1 0 ^ 0 0 0 0
// 0 1 1 0 --> 0 0 0 0
// 0 0 0 0 0 0 0 0
expected.set(til::rectangle{ til::point{ 1, 0 }, til::size{ 2, 1 } });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"7.) Move up and right");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// | --> -->
const til::point delta{ 2, -2 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 ^ 0 1 1 0 0 0 0 1
// 0 1 1 0 ^ 0 0 0 0 0 0 0 0
// 0 1 1 0 --> 0 0 0 0 --> 0 0 0 0
// 0 0 0 0 0 0 0 0 0 0 0 0
// ->->
expected.set(til::point{ 3, 0 });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"8.) Move right");
{
auto actual = map;
// Move all contents
// --> -->
const til::point delta{ 2, 0 };
til::bitmap expected{ mapSize };
// Expected:
// 0 0 0 0 0 0 0 0
// 0 1 1 0 0 0 0 1
// 0 1 1 0 --> 0 0 0 1
// 0 0 0 0 0 0 0 0
// ->->
expected.set(til::rectangle{ til::point{ 3, 1 }, til::size{ 1, 2 } });
actual.translate(delta);
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(TranslateWithFill)
{
const til::size mapSize{ 4, 4 };
til::bitmap map{ mapSize };
// set the middle four bits of the map.
// 0 0 0 0
// 0 1 1 0
// 0 1 1 0
// 0 0 0 0
map.set(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 2 } });
Log::Comment(L"1.) Move down and right");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v --> -->
const til::point delta{ 2, 2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 F F F F F F F F
// 0 1 1 0 F F F F F F F F
// 0 1 1 0 v --> 0 0 0 0 --> F F 0 0
// 0 0 0 0 v 0 1 1 0 F F 0 1
// ->->
expected.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 4, 2 } });
expected.set(til::rectangle{ til::point{ 0, 2 }, til::size{ 2, 2 } });
expected.set(til::point{ 3, 3 });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Move down");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v
const til::point delta{ 0, 2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 F F F F
// 0 1 1 0 F F F F
// 0 1 1 0 v --> 0 0 0 0
// 0 0 0 0 v 0 1 1 0
expected.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 4, 2 } });
expected.set(til::rectangle{ til::point{ 1, 3 }, til::size{ 2, 1 } });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Move down and left");
{
auto actual = map;
// Move all contents
// |
// v
// |
// v <-- <--
const til::point delta{ -2, 2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 F F F F F F F F
// 0 1 1 0 F F F F F F F F
// 0 1 1 0 v --> 0 0 0 0 --> 0 0 F F
// 0 0 0 0 v 0 1 1 0 1 0 F F
// <-<-
expected.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 4, 2 } });
expected.set(til::rectangle{ til::point{ 2, 2 }, til::size{ 2, 2 } });
expected.set(til::point{ 0, 3 });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Move left");
{
auto actual = map;
// Move all contents
// <-- <--
const til::point delta{ -2, 0 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 0 0 F F
// 0 1 1 0 1 0 F F
// 0 1 1 0 --> 1 0 F F
// 0 0 0 0 0 0 F F
// <--<--
expected.set(til::rectangle{ til::point{ 2, 0 }, til::size{ 2, 4 } });
expected.set(til::rectangle{ til::point{ 0, 1 }, til::size{ 1, 2 } });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"5.) Move up and left");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// | <-- <--
const til::point delta{ -2, -2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 ^ 0 1 1 0 1 0 F F
// 0 1 1 0 ^ 0 0 0 0 0 0 F F
// 0 1 1 0 --> F F F F --> F F F F
// 0 0 0 0 F F F F F F F F
// <-<-
expected.set(til::rectangle{ til::point{ 2, 0 }, til::size{ 2, 2 } });
expected.set(til::rectangle{ til::point{ 0, 2 }, til::size{ 4, 2 } });
expected.set(til::point{ 0, 0 });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"6.) Move up");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// |
const til::point delta{ 0, -2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 ^ 0 1 1 0
// 0 1 1 0 ^ 0 0 0 0
// 0 1 1 0 --> F F F F
// 0 0 0 0 F F F F
expected.set(til::rectangle{ til::point{ 1, 0 }, til::size{ 2, 1 } });
expected.set(til::rectangle{ til::point{ 0, 2 }, til::size{ 4, 2 } });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"7.) Move up and right");
{
auto actual = map;
// Move all contents
// ^
// |
// ^
// | --> -->
const til::point delta{ 2, -2 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 ^ 0 1 1 0 F F 0 1
// 0 1 1 0 ^ 0 0 0 0 F F 0 0
// 0 1 1 0 --> F F F F --> F F F F
// 0 0 0 0 F F F F F F F F
// ->->
expected.set(til::point{ 3, 0 });
expected.set(til::rectangle{ til::point{ 0, 2 }, til::size{ 4, 2 } });
expected.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 2, 2 } });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"8.) Move right");
{
auto actual = map;
// Move all contents
// --> -->
const til::point delta{ 2, 0 };
til::bitmap expected{ mapSize };
// Expected: (F is filling uncovered value)
// 0 0 0 0 F F 0 0
// 0 1 1 0 F F 0 1
// 0 1 1 0 --> F F 0 1
// 0 0 0 0 F F 0 0
// ->->
expected.set(til::rectangle{ til::point{ 3, 1 }, til::size{ 1, 2 } });
expected.set(til::rectangle{ til::point{ 0, 0 }, til::size{ 2, 4 } });
actual.translate(delta, true);
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(SetReset)
{
const til::size sz{ 4, 4 };
@ -133,7 +620,8 @@ class BitmapTests
const til::point point{ 2, 2 };
bitmap.set(point);
til::rectangle expectedSet{ point };
std::vector<til::rectangle> expectedSet;
expectedSet.emplace_back(til::rectangle{ point });
// Run through every bit. Only the one we set should be true.
Log::Comment(L"Only the bit we set should be true.");
@ -142,13 +630,14 @@ class BitmapTests
Log::Comment(L"Setting all should mean they're all true.");
bitmap.set_all();
expectedSet = til::rectangle{ bitmap._rc };
expectedSet.clear();
expectedSet.emplace_back(til::rectangle{ bitmap._rc });
_checkBits(expectedSet, bitmap);
Log::Comment(L"Now reset them all.");
bitmap.reset_all();
expectedSet = {};
expectedSet.clear();
_checkBits(expectedSet, bitmap);
til::rectangle totalZone{ sz };
@ -160,13 +649,14 @@ class BitmapTests
til::rectangle setZone{ til::point{ 0, 0 }, til::size{ 2, 3 } };
bitmap.set(setZone);
expectedSet = setZone;
expectedSet.clear();
expectedSet.emplace_back(setZone);
_checkBits(expectedSet, bitmap);
Log::Comment(L"Reset all.");
bitmap.reset_all();
expectedSet = {};
expectedSet.clear();
_checkBits(expectedSet, bitmap);
}
@ -361,7 +851,7 @@ class BitmapTests
VERIFY_IS_FALSE(bitmap.all());
}
TEST_METHOD(Iterate)
TEST_METHOD(Runs)
{
// This map --> Those runs
// 1 1 0 1 A A _ B
@ -370,8 +860,6 @@ class BitmapTests
// 0 1 1 0 _ F F _
Log::Comment(L"Set up a bitmap with some runs.");
// Note: we'll test setting/resetting some overlapping rects and points.
til::bitmap map{ til::size{ 4, 4 } };
// 0 0 0 0 |1 1|0 0
@ -421,7 +909,7 @@ class BitmapTests
Log::Comment(L"Run the iterator and collect the runs.");
til::some<til::rectangle, 6> actual;
for (auto run : map)
for (auto run : map.runs())
{
actual.push_back(run);
}
@ -434,12 +922,67 @@ class BitmapTests
expected.clear();
actual.clear();
for (auto run : map)
for (auto run : map.runs())
{
actual.push_back(run);
}
Log::Comment(L"Verify they're empty.");
VERIFY_ARE_EQUAL(expected, actual);
Log::Comment(L"Set point and validate runs updated.");
const til::point setPoint{ 2, 2 };
expected.push_back(til::rectangle{ setPoint });
map.set(setPoint);
for (auto run : map.runs())
{
actual.push_back(run);
}
VERIFY_ARE_EQUAL(expected, actual);
Log::Comment(L"Set rectangle and validate runs updated.");
const til::rectangle setRect{ setPoint, til::size{ 2, 2 } };
expected.clear();
expected.push_back(til::rectangle{ til::point{ 2, 2 }, til::size{ 2, 1 } });
expected.push_back(til::rectangle{ til::point{ 2, 3 }, til::size{ 2, 1 } });
map.set(setRect);
actual.clear();
for (auto run : map.runs())
{
actual.push_back(run);
}
VERIFY_ARE_EQUAL(expected, actual);
Log::Comment(L"Set all and validate runs updated.");
expected.clear();
expected.push_back(til::rectangle{ til::point{ 0, 0 }, til::size{ 4, 1 } });
expected.push_back(til::rectangle{ til::point{ 0, 1 }, til::size{ 4, 1 } });
expected.push_back(til::rectangle{ til::point{ 0, 2 }, til::size{ 4, 1 } });
expected.push_back(til::rectangle{ til::point{ 0, 3 }, til::size{ 4, 1 } });
map.set_all();
actual.clear();
for (auto run : map.runs())
{
actual.push_back(run);
}
VERIFY_ARE_EQUAL(expected, actual);
Log::Comment(L"Resize and validate runs updated.");
const til::size newSize{ 3, 3 };
expected.clear();
expected.push_back(til::rectangle{ til::point{ 0, 0 }, til::size{ 3, 1 } });
expected.push_back(til::rectangle{ til::point{ 0, 1 }, til::size{ 3, 1 } });
expected.push_back(til::rectangle{ til::point{ 0, 2 }, til::size{ 3, 1 } });
map.resize(newSize);
actual.clear();
for (auto run : map.runs())
{
actual.push_back(run);
}
VERIFY_ARE_EQUAL(expected, actual);
}
};

View file

@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/operators.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class OperatorTests
{
TEST_CLASS(OperatorTests);
TEST_METHOD(PointAddSize)
{
const til::point pt{ 5, 10 };
const til::size sz{ 2, 4 };
const til::point expected{ 5 + 2, 10 + 4 };
const auto actual = pt + sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(PointSubSize)
{
const til::point pt{ 5, 10 };
const til::size sz{ 2, 4 };
const til::point expected{ 5 - 2, 10 - 4 };
const auto actual = pt - sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(PointMulSize)
{
const til::point pt{ 5, 10 };
const til::size sz{ 2, 4 };
const til::point expected{ 5 * 2, 10 * 4 };
const auto actual = pt * sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(PointDivSize)
{
const til::point pt{ 5, 10 };
const til::size sz{ 2, 4 };
const til::point expected{ 5 / 2, 10 / 4 };
const auto actual = pt / sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SizeAddPoint)
{
const til::size pt{ 5, 10 };
const til::point sz{ 2, 4 };
const til::size expected{ 5 + 2, 10 + 4 };
const auto actual = pt + sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SizeSubPoint)
{
const til::size pt{ 5, 10 };
const til::point sz{ 2, 4 };
const til::size expected{ 5 - 2, 10 - 4 };
const auto actual = pt - sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SizeMulPoint)
{
const til::size pt{ 5, 10 };
const til::point sz{ 2, 4 };
const til::size expected{ 5 * 2, 10 * 4 };
const auto actual = pt * sz;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SizeDivPoint)
{
const til::size pt{ 5, 10 };
const til::point sz{ 2, 4 };
const til::size expected{ 5 / 2, 10 / 4 };
const auto actual = pt / sz;
VERIFY_ARE_EQUAL(expected, actual);
}
};

View file

@ -440,6 +440,16 @@ class RectangleTests
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(OrUnionInplace)
{
til::rectangle one{ 4, 6, 10, 14 };
const til::rectangle two{ 5, 2, 13, 10 };
const til::rectangle expected{ 4, 2, 13, 14 };
one |= two;
VERIFY_ARE_EQUAL(expected, one);
}
TEST_METHOD(AndIntersect)
{
const til::rectangle one{ 4, 6, 10, 14 };
@ -450,6 +460,16 @@ class RectangleTests
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(AndIntersectInplace)
{
til::rectangle one{ 4, 6, 10, 14 };
const til::rectangle two{ 5, 2, 13, 10 };
const til::rectangle expected{ 5, 6, 10, 10 };
one &= two;
VERIFY_ARE_EQUAL(expected, one);
}
TEST_METHOD(MinusSubtractSame)
{
const til::rectangle original{ 0, 0, 10, 10 };
@ -585,6 +605,198 @@ class RectangleTests
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(AdditionPoint)
{
const til::rectangle start{ 10, 20, 30, 40 };
const til::point pt{ 3, 7 };
const til::rectangle expected{ 10 + 3, 20 + 7, 30 + 3, 40 + 7 };
const auto actual = start + pt;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(AdditionPointInplace)
{
til::rectangle start{ 10, 20, 30, 40 };
const til::point pt{ 3, 7 };
const til::rectangle expected{ 10 + 3, 20 + 7, 30 + 3, 40 + 7 };
start += pt;
VERIFY_ARE_EQUAL(expected, start);
}
TEST_METHOD(SubtractionPoint)
{
const til::rectangle start{ 10, 20, 30, 40 };
const til::point pt{ 3, 7 };
const til::rectangle expected{ 10 - 3, 20 - 7, 30 - 3, 40 - 7 };
const auto actual = start - pt;
VERIFY_ARE_EQUAL(expected, actual);
}
TEST_METHOD(SubtractionPointInplace)
{
til::rectangle start{ 10, 20, 30, 40 };
const til::point pt{ 3, 7 };
const til::rectangle expected{ 10 - 3, 20 - 7, 30 - 3, 40 - 7 };
start -= pt;
VERIFY_ARE_EQUAL(expected, start);
}
TEST_METHOD(AdditionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Add size to bottom and right");
{
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 33, 47 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Add size to top and left");
{
const til::size scale{ -3, -7 };
const til::rectangle expected{ 7, 13, 30, 40 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Add size to bottom and left");
{
const til::size scale{ -3, 7 };
const til::rectangle expected{ 7, 20, 30, 47 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Add size to top and right");
{
const til::size scale{ 3, -7 };
const til::rectangle expected{ 10, 13, 33, 40 };
const auto actual = start + scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(AdditionSizeInplace)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Add size to bottom and right");
{
auto actual = start;
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 33, 47 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Add size to top and left");
{
auto actual = start;
const til::size scale{ -3, -7 };
const til::rectangle expected{ 7, 13, 30, 40 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Add size to bottom and left");
{
auto actual = start;
const til::size scale{ -3, 7 };
const til::rectangle expected{ 7, 20, 30, 47 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Add size to top and right");
{
auto actual = start;
const til::size scale{ 3, -7 };
const til::rectangle expected{ 10, 13, 33, 40 };
actual += scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(SubtractionSize)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Subtract size from bottom and right");
{
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 27, 33 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Subtract size from top and left");
{
const til::size scale{ -3, -7 };
const til::rectangle expected{ 13, 27, 30, 40 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Subtract size from bottom and left");
{
const til::size scale{ -3, 7 };
const til::rectangle expected{ 13, 20, 30, 33 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Subtract size from top and right");
{
const til::size scale{ 3, -6 };
const til::rectangle expected{ 10, 26, 27, 40 };
const auto actual = start - scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(SubtractionSizeInplace)
{
const til::rectangle start{ 10, 20, 30, 40 };
Log::Comment(L"1.) Subtract size from bottom and right");
{
auto actual = start;
const til::size scale{ 3, 7 };
const til::rectangle expected{ 10, 20, 27, 33 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"2.) Subtract size from top and left");
{
auto actual = start;
const til::size scale{ -3, -7 };
const til::rectangle expected{ 13, 27, 30, 40 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"3.) Subtract size from bottom and left");
{
auto actual = start;
const til::size scale{ -3, 7 };
const til::rectangle expected{ 13, 20, 30, 33 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
Log::Comment(L"4.) Subtract size from top and right");
{
auto actual = start;
const til::size scale{ 3, -6 };
const til::rectangle expected{ 10, 26, 27, 40 };
actual -= scale;
VERIFY_ARE_EQUAL(expected, actual);
}
}
TEST_METHOD(Top)
{
const til::rectangle rc{ 5, 10, 15, 20 };

View file

@ -14,8 +14,14 @@ DLLDEF =
SOURCES = \
$(SOURCES) \
BitmapTests.cpp \
ColorTests.cpp \
OperatorTests.cpp \
PointTests.cpp \
RectangleTests.cpp \
SizeTests.cpp \
SomeTests.cpp \
u8u16convertTests.cpp \
DefaultResource.rc \
INCLUDES = \

View file

@ -11,6 +11,7 @@
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<ItemGroup>
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />
<ClCompile Include="SizeTests.cpp" />

View file

@ -12,6 +12,7 @@
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h" />