diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
index dae8a248f77..981be1c4b20 100644
--- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
+++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp
@@ -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);
diff --git a/src/common.build.tests.props b/src/common.build.tests.props
index 8a3527ff6e6..79410878836 100644
--- a/src/common.build.tests.props
+++ b/src/common.build.tests.props
@@ -2,7 +2,7 @@
- UNIT_TESTING;%(PreprocessorDefinitions)
+ INLINE_TEST_METHOD_MARKUP;UNIT_TESTING;%(PreprocessorDefinitions)
diff --git a/src/host/ut_host/ConptyOutputTests.cpp b/src/host/ut_host/ConptyOutputTests.cpp
index c24ec99fa15..07493ab4544 100644
--- a/src/host/ut_host/ConptyOutputTests.cpp
+++ b/src/host/ut_host/ConptyOutputTests.cpp
@@ -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());
}
diff --git a/src/host/ut_host/VtRendererTests.cpp b/src/host/ut_host/VtRendererTests.cpp
index 92410495822..4be9db4b85a 100644
--- a/src/host/ut_host/VtRendererTests.cpp
+++ b/src/host/ut_host/VtRendererTests.cpp
@@ -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::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::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::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::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);
});
diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h
index 2002fa48fb0..92b572075e3 100644
--- a/src/inc/LibraryIncludes.h
+++ b/src/inc/LibraryIncludes.h
@@ -85,6 +85,12 @@
// WRL
#include
+// WEX/TAEF testing
+// Include before TIL if we're unit testing so it can light up WEX/TAEF template extensions
+#ifdef UNIT_TESTING
+#include
+#endif
+
// TIL - Terminal Implementation Library
#ifndef BLOCK_TIL // Certain projects may want to include TIL manually to gain superpowers
#include "til.h"
diff --git a/src/inc/til.h b/src/inc/til.h
index 6cbe6a0b128..5204284a004 100644
--- a/src/inc/til.h
+++ b/src/inc/til.h
@@ -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"
diff --git a/src/inc/til/bitmap.h b/src/inc/til/bitmap.h
index 322956ed849..2070ee7a643 100644
--- a/src/inc/til/bitmap.h
+++ b/src/inc/til/bitmap.h
@@ -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& 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& _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,8 +149,23 @@ 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
@@ -144,17 +178,106 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return const_iterator(_bits, _sz, _sz.area());
}
+ const std::vector& 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(_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 _bits;
+ mutable std::optional> _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
diff --git a/src/inc/til/operators.h b/src/inc/til/operators.h
new file mode 100644
index 00000000000..109cd3e3576
--- /dev/null
+++ b/src/inc/til/operators.h
@@ -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
+}
diff --git a/src/inc/til/point.h b/src/inc/til/point.h
index 88ff6bdfd96..c7643b05286 100644
--- a/src/inc/til/point.h
+++ b/src/inc/til/point.h
@@ -193,6 +193,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
+ std::wstring to_string() const
+ {
+ return wil::str_printf(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());
}
};
diff --git a/src/inc/til/rectangle.h b/src/inc/til/rectangle.h
index 853e77ed484..19f684f589f 100644
--- a/src/inc/til/rectangle.h
+++ b/src/inc/til/rectangle.h
@@ -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 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(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());
}
};
diff --git a/src/inc/til/size.h b/src/inc/til/size.h
index 905d5be98cb..e551980fbd5 100644
--- a/src/inc/til/size.h
+++ b/src/inc/til/size.h
@@ -220,6 +220,11 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
}
#endif
+ std::wstring to_string() const
+ {
+ return wil::str_printf(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());
}
};
diff --git a/src/inc/til/some.h b/src/inc/til/some.h
index 9620bdce777..96d523c486b 100644
--- a/src/inc/til/some.h
+++ b/src/inc/til/some.h
@@ -208,6 +208,21 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
throw std::out_of_range("invalid some 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& some)
{
- auto str = WEX::Common::NoThrowString().Format(L"\r\nSome contains %d of max size %d:\r\nElements:\r\n", some.size(), some.max_size());
-
- for (auto& item : some)
- {
- const auto itemStr = WEX::TestExecution::VerifyOutputTraits::ToString(item);
- str.AppendFormat(L"\t- %ws\r\n", (const wchar_t*)itemStr);
- }
-
- return str;
+ return WEX::Common::NoThrowString(some.to_string().c_str());
}
};
diff --git a/src/interactivity/onecore/BgfxEngine.cpp b/src/interactivity/onecore/BgfxEngine.cpp
index ab6abe8b765..bfdcfa720f3 100644
--- a/src/interactivity/onecore/BgfxEngine.cpp
+++ b/src/interactivity/onecore/BgfxEngine.cpp
@@ -232,7 +232,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
-std::vector BgfxEngine::GetDirtyArea()
+std::vector BgfxEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;
diff --git a/src/interactivity/onecore/BgfxEngine.hpp b/src/interactivity/onecore/BgfxEngine.hpp
index 786da48d3f6..d2fc5488bbf 100644
--- a/src/interactivity/onecore/BgfxEngine.hpp
+++ b/src/interactivity/onecore/BgfxEngine.hpp
@@ -69,7 +69,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
- std::vector GetDirtyArea() override;
+ std::vector 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;
diff --git a/src/renderer/base/renderer.cpp b/src/renderer/base/renderer.cpp
index 0d9827a71e0..dfe4bdd3a6d 100644
--- a/src/renderer/base/renderer.cpp
+++ b/src/renderer/base/renderer.cpp
@@ -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++;
diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp
index 2ed766fdcbc..1345e6b23d1 100644
--- a/src/renderer/dx/DxRenderer.cpp
+++ b/src/renderer/dx/DxRenderer.cpp
@@ -1686,7 +1686,7 @@ float DxEngine::GetScaling() const noexcept
// -
// Return Value:
// - Rectangle describing dirty area in characters.
-[[nodiscard]] std::vector DxEngine::GetDirtyArea()
+[[nodiscard]] std::vector DxEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Top = gsl::narrow(floor(_invalidRect.top / _glyphCell.cy));
diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp
index 68ef13b35a1..b8521d9b1ee 100644
--- a/src/renderer/dx/DxRenderer.hpp
+++ b/src/renderer/dx/DxRenderer.hpp
@@ -95,7 +95,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
- [[nodiscard]] std::vector GetDirtyArea() override;
+ [[nodiscard]] std::vector 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;
diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp
index 356ce0deebd..50f2d543af0 100644
--- a/src/renderer/gdi/gdirenderer.hpp
+++ b/src/renderer/gdi/gdirenderer.hpp
@@ -68,7 +68,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
- [[nodiscard]] std::vector GetDirtyArea() override;
+ [[nodiscard]] std::vector 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;
diff --git a/src/renderer/gdi/math.cpp b/src/renderer/gdi/math.cpp
index 7b2372ba083..7b5f5ccc684 100644
--- a/src/renderer/gdi/math.cpp
+++ b/src/renderer/gdi/math.cpp
@@ -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 GdiEngine::GetDirtyArea()
+std::vector GdiEngine::GetDirtyArea()
{
RECT rc = _psInvalidData.rcPaint;
diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp
index 97951577b00..a55e0b64b3b 100644
--- a/src/renderer/inc/IRenderEngine.hpp
+++ b/src/renderer/inc/IRenderEngine.hpp
@@ -117,7 +117,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& FontInfo,
const int iDpi) noexcept = 0;
- virtual std::vector GetDirtyArea() = 0;
+ virtual std::vector 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;
diff --git a/src/renderer/uia/UiaRenderer.cpp b/src/renderer/uia/UiaRenderer.cpp
index 01235ca1cfe..68c03b7077e 100644
--- a/src/renderer/uia/UiaRenderer.cpp
+++ b/src/renderer/uia/UiaRenderer.cpp
@@ -426,7 +426,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// -
// Return Value:
// - Rectangle describing dirty area in characters.
-[[nodiscard]] std::vector UiaEngine::GetDirtyArea()
+[[nodiscard]] std::vector UiaEngine::GetDirtyArea()
{
return { Viewport::Empty().ToInclusive() };
}
diff --git a/src/renderer/uia/UiaRenderer.hpp b/src/renderer/uia/UiaRenderer.hpp
index bdebb01de27..3f7ced8e33f 100644
--- a/src/renderer/uia/UiaRenderer.hpp
+++ b/src/renderer/uia/UiaRenderer.hpp
@@ -71,7 +71,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
- [[nodiscard]] std::vector GetDirtyArea() override;
+ [[nodiscard]] std::vector 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;
diff --git a/src/renderer/vt/XtermEngine.cpp b/src/renderer/vt/XtermEngine.cpp
index 145d972fdba..8c0420e5224 100644
--- a/src/renderer/vt/XtermEngine.cpp
+++ b/src/renderer/vt/XtermEngine.cpp
@@ -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));
diff --git a/src/renderer/vt/invalidate.cpp b/src/renderer/vt/invalidate.cpp
index c3042ad6e1b..597f56b0f06 100644
--- a/src/renderer/vt/invalidate.cpp
+++ b/src/renderer/vt/invalidate.cpp
@@ -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:
-// -
-// 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;
-}
diff --git a/src/renderer/vt/math.cpp b/src/renderer/vt/math.cpp
index f7faad04778..a5aaa7129fe 100644
--- a/src/renderer/vt/math.cpp
+++ b/src/renderer/vt/math.cpp
@@ -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 VtEngine::GetDirtyArea()
+std::vector 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;
}
diff --git a/src/renderer/vt/paint.cpp b/src/renderer/vt/paint.cpp
index dc292f74f3d..babc6606caa 100644
--- a/src/renderer/vt/paint.cpp
+++ b/src/renderer/vt/paint.cpp
@@ -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;
diff --git a/src/renderer/vt/state.cpp b/src/renderer/vt/state.cpp
index c83ac780ddd..a50179cd506 100644
--- a/src/renderer/vt/state.cpp
+++ b/src/renderer/vt/state.cpp
@@ -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
-
-
- INLINE_TEST_METHOD_MARKUP;UNIT_TESTING;%(PreprocessorDefinitions)
-
-
-
+
diff --git a/src/renderer/vt/vtrenderer.hpp b/src/renderer/vt/vtrenderer.hpp
index 8ce51bbcf1c..7ebb7a71a87 100644
--- a/src/renderer/vt/vtrenderer.hpp
+++ b/src/renderer/vt/vtrenderer.hpp
@@ -89,7 +89,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
- std::vector GetDirtyArea() override;
+ std::vector 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;
diff --git a/src/renderer/wddmcon/WddmConRenderer.cpp b/src/renderer/wddmcon/WddmConRenderer.cpp
index 475fb85125a..5819511c0a3 100644
--- a/src/renderer/wddmcon/WddmConRenderer.cpp
+++ b/src/renderer/wddmcon/WddmConRenderer.cpp
@@ -355,7 +355,7 @@ bool WddmConEngine::IsInitialized()
return S_OK;
}
-std::vector WddmConEngine::GetDirtyArea()
+std::vector WddmConEngine::GetDirtyArea()
{
SMALL_RECT r;
r.Bottom = _displayHeight > 0 ? (SHORT)(_displayHeight - 1) : 0;
diff --git a/src/renderer/wddmcon/WddmConRenderer.hpp b/src/renderer/wddmcon/WddmConRenderer.hpp
index 1818ae3c649..ad137131c77 100644
--- a/src/renderer/wddmcon/WddmConRenderer.hpp
+++ b/src/renderer/wddmcon/WddmConRenderer.hpp
@@ -61,7 +61,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
- std::vector GetDirtyArea() override;
+ std::vector 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;
diff --git a/src/til/ut_til/BitmapTests.cpp b/src/til/ut_til/BitmapTests.cpp
index a034c1145d1..34005e259f0 100644
--- a/src/til/ut_til/BitmapTests.cpp
+++ b/src/til/ut_til/BitmapTests.cpp
@@ -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 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 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);
}
};
diff --git a/src/til/ut_til/OperatorTests.cpp b/src/til/ut_til/OperatorTests.cpp
new file mode 100644
index 00000000000..191c09e2484
--- /dev/null
+++ b/src/til/ut_til/OperatorTests.cpp
@@ -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);
+ }
+};
diff --git a/src/til/ut_til/RectangleTests.cpp b/src/til/ut_til/RectangleTests.cpp
index 47e17da9430..82aa5a69a75 100644
--- a/src/til/ut_til/RectangleTests.cpp
+++ b/src/til/ut_til/RectangleTests.cpp
@@ -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 };
diff --git a/src/til/ut_til/sources b/src/til/ut_til/sources
index 2e7861cdfb5..af602729cd6 100644
--- a/src/til/ut_til/sources
+++ b/src/til/ut_til/sources
@@ -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 = \
diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj
index 644322da1c1..d2e91bf250d 100644
--- a/src/til/ut_til/til.unit.tests.vcxproj
+++ b/src/til/ut_til/til.unit.tests.vcxproj
@@ -11,6 +11,7 @@
+
diff --git a/src/til/ut_til/til.unit.tests.vcxproj.filters b/src/til/ut_til/til.unit.tests.vcxproj.filters
index e07ae69b990..21bf4896239 100644
--- a/src/til/ut_til/til.unit.tests.vcxproj.filters
+++ b/src/til/ut_til/til.unit.tests.vcxproj.filters
@@ -12,6 +12,7 @@
+