Compare commits

..

11 Commits

Author SHA1 Message Date
Dustin L. Howett (MSFT)
0b8b0b9d94 Pin the DisplayName to Windows Terminal to fix the UAC prompt (#4995)
There's an issue in the UAC consent dialog where it cannot read an
application's name if it's stored in a resource. When it fails, it deems
us an "Unknown Program" and that looks pretty silly.

Fixes #2289.

(cherry picked from commit aaa4943112)
2020-03-18 18:33:23 -07:00
Mike Griese
b4dae1238e Add support for Ctrl+# keys (#4938)
## Summary of the Pull Request

Fixes the <kbd>Ctrl+Num</kbd> keys in both conhost and the Terminal. These keys are supposed to be mapped to specific characters according to [this doc](https://vt100.net/docs/vt220-rm/table3-5.html). Now we actually handle them correctly.

## PR Checklist
* [x] Closes #3507
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

* Ran test
* tested in `gnome-terminal` with `showkeys -a`
* tested in conhost with `showkeys -a`
* tested in Windows Terminal with `showkeys -a`

(cherry picked from commit f7d106d3f3)
2020-03-18 18:33:23 -07:00
Mike Griese
7deaf6b5aa Fix VT sequences for Ctrl+Alt+? input (#4947)
## Summary of the Pull Request

Ctrl+/ and Ctrl-? are complicated in VT input.

* C-/ is supposed to be `^_` (the C0 character US)
* C-? is supposed to be `DEL`
* C-M-/ is supposed to be `^[^_` (ESC US)
* C-M-? is supposed to be `^[^?` (ESC DEL)

The astute reader will note that these characters also share the same key on a
standard en-us keyboard layout, which makes the problem even more complicated.
This PR does a better job of handling these weird cases.

# References
* #3079 - At first, I thought this PR would close this issue, but as I've learned below, this won't solve that one. This bug was merely found during that investigation.

## PR Checklist
* [x] Related to #3079
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed

* ran tests
* checked `showkey -a` in gnome-terminal, which gives you the wrong results for C-M-/, C-M-?
* checked `showkey -a` in xterm, which gives you the _right_ results for C-M-/, C-M-?
* checked `showkey -a` in conhost
* checked `showkey -a` in Windows Terminal

(cherry picked from commit d50409b901)
2020-03-18 18:33:23 -07:00
Mike Griese
266402a2d6 Clamp the terminal buffer to SHRT_MAX on resize (#4964)
## Summary of the Pull Request

This is 100% on me. Even after mucking around in this function for the last 3
months, I missed that there was a single addition where we weren't doing a
clamped addition. This would lead to us creating a buffer with negative height,
and all sorts of badness.

Clamping this addition was enough to fix the bug.

## PR Checklist
* [x] Closes #2815
* [x] Closes #4972
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Validation Steps Performed
* ran tests
* Created a profile with `"historySize" : 32728`, then filled the viewport with
  text, then maximized, and saw that the viewport indeed did resize to the new
  size of the window.

(cherry picked from commit f221cd245e)
2020-03-18 18:33:23 -07:00
Dustin L. Howett (MSFT)
3d7b455bb7 Fix an off-by-one that made us use EL instead of ECH (#4994)
When we painted spaces up until the character right before the right
edge of the screen, we would erroneously use Erase in Line instead of
Erase Character due to an off-by-one.

Fixes #4727

(cherry picked from commit d392a48857)
2020-03-18 18:33:23 -07:00
Leon Liang
a6dedbb25a Ignore KeyDown events during Alt-Numpad Input (#4965)
## Summary of the Pull Request
Alt-Numpad# input would be escaping each numkey before sending it through. This would result in some weird behavior, for example, in powershell, where the first alt-numpad# would start digit argument and once the user releases alt, a character is sent through and digit argument would repeat that character X times. To resolve this, we simply need to ignore KeyDowns where Alt is held and a Numpad# is pressed.

Once Alt is released, we'll receive a character through `TSFInputControl`, not `TermControl::CharacterHandler`. It seems that the `CoreTextEditContext` in `TSFInputControl` intercepts the character before it gets to `TermControl`. TSF will then send the received character through as normal.

<!-- Please review the items on the PR checklist before submitting-->
## PR Checklist
* [x] Closes #1401
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed

<!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well -->
## Validation Steps Performed
Testing various combinations of Alt-Numpad# consistently sends through only one instance of the expected symbols.

(cherry picked from commit cddac25726)
2020-03-18 18:33:23 -07:00
Mike Griese
37d417c07d Fix unbinding keys in v0.10 (#4988)
## Summary of the Pull Request

We (the royal "we") broke key unbinding in #4746. We didn't run the local tests after this, which actually would have caught this. The comment even suggests what we should have done here. We need to make sure that when we bail, it's because there's a parsing function that returned nothing. `null`, `"unbound"`, etc actually don't even have a parsing function at all, so they should just keep on keepin' on.

## References

Source of this regression: #4746

## PR Checklist
* [x] Closes #3729
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

This is a great example of why your unittests should run in CI always

## Validation Steps Performed
* **ran the tests**
* tested the following unbindings:

```json
        { "command": null, "keys": [ "ctrl+shift+t" ] },
        { "command": "unbound", "keys": [ "ctrl+shift+t" ] },
        { "command": "null", "keys": [ "ctrl+shift+t" ] },

```
and they each individually worked.

(cherry picked from commit 8211ed9fa6)
2020-03-18 18:33:23 -07:00
Carlos Zamora
91503e0c96 Make panes use xaml star sizing (#4953)
## Summary of the Pull Request
Dustin and Mike had a discussion on star sizing. Mcpiroman connected the dots. I just swooped in and implemented it.

## References
https://github.com/microsoft/terminal/issues/3996#issuecomment-566676111
https://github.com/microsoft/terminal/issues/3744#issuecomment-568926636

## PR Checklist
* [X] Closes #3744

## Detailed Description of the Pull Request / Additional comments
Star sizing allows us to keep things proportional. Since a 1::1 proportion is the same as a 100::100 proportion, I just went in and added star sizing. In the words of a some dude with a big metal fist, everything is perfectly balanced now.

![image](https://user-images.githubusercontent.com/11050425/76813679-f7103f00-67b5-11ea-9b5c-d2cc73673aba.png)

## Validation Steps Performed
Verified for vertical, horizontal, and uneven splits.

(cherry picked from commit 1d8c5bae35)
2020-03-18 18:33:23 -07:00
Leon Liang
9516372a8a Force WslDistroGenerator to timeout after 10s and return Profiles (#4905)
When WSL.exe would hang for users, WslDistroGenerator would also hang
while waiting for its `wsl.exe --list` call to return. The timeout was
`INFINITE` in the `WaitForSingleObject` call, but we should slap a
timeout on it instead (here we choose 2 seconds). In addition, if it
times out, we should also just return profiles and let the Terminal
continue to start up without the WSL distro profiles loaded.

# Validation Steps Performed
Made a sleep 30 executable as the command instead, made sure it hit the
`WAIT_TIMEOUT` and continued to start up without loading my Ubuntu
profile.

Closes #3987

(cherry picked from commit 57c7d1d7ae)
2020-03-18 18:33:23 -07:00
Mike Griese
7b9c8c7055 Fix C-M-z, C-M-x in Conpty (#4940)
## Summary of the Pull Request

This PR ensures that Conpty properly treats `^[^Z` and `^[^X` as
<kbd>Ctrl+Alt+z</kbd> and <kbd>Ctrl+Alt+x</kbd>, instead of <kbd>Ctrl+z</kbd>
and <kbd>Ctrl+x</kbd>.

## References

## PR Checklist
* [x] Closes #4201
* [x] I work here
* [x] Tests added/passed
* [n/a] Requires documentation to be updated

## Detailed Description of the Pull Request / Additional comments

`^Z` and `^X` are special control characters, SUB and CAN. For the output state
machine, these characters are supposed to be executed from _any_ state. However,
we shouldn't do this for the input engine. With the current behavior, these
characters are immediately executed regardless of what state we're in. That
means we end up synthesizing <kbd>Ctrl+z/x</kbd> for these characters. However,
for the InputStateMachine engine, when these characters are preceeded by `^[`
(ESC), we want to treat them as <kbd>Ctrl+Alt+z/x</kbd>.

This just adds a check in `StateMachine` to see if we should immediately execute
these characters from any state, similar to many of the other exceptions we
already perform in the StateMachine for the input engine.

## Validation Steps Performed
* ran tests
* checked `showkey -a` in gnome-terminal
* checked `showkey -a` in conhost
* checked `showkey -a` in vt pipeterm (conhost as a conpty terminal)
* checked `showkey -a` in Windows Terminal
2020-03-16 16:59:48 +00:00
Carlos Zamora
860affd608 Make commands in doc appear as code (#4933)
Co-authored-by: Carlos Zamora <cazamor@microsoft.com>
2020-03-16 09:44:53 -07:00
45 changed files with 687 additions and 1466 deletions

View File

@@ -110,31 +110,31 @@ For commands with arguments:
| Command | Command Description | Action (*=required) | Action Arguments | Argument Descriptions |
| ------- | ------------------- | ------ | ---------------- | ----------------- |
| closePane | Close the active pane. | | | |
| closeTab | Close the current tab. | | | |
| closeWindow | Close the current window and all tabs within it. | | | |
| copy | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| decreaseFontSize | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| duplicateTab | Make a copy and open the current tab. | | | |
| find | Open the search dialog box. | | | |
| increaseFontSize | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| moveFocus | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| newTab | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| nextTab | Open the tab to the right of the current one. | | | |
| openNewTabDropdown | Open the dropdown menu. | | | |
| openSettings | Open the settings file. | | | |
| paste | Insert the content that was copied onto the clipboard. | | | |
| prevTab | Open the tab to the left of the current one. | | | |
| resetFontSize | Reset the text size to the default value. | | | |
| resizePane | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| scrollDown | Move the screen down. | | | |
| scrollUp | Move the screen up. | | | |
| scrollUpPage | Move the screen up a whole page. | | | |
| scrollDownPage | Move the screen down a whole page. | | | |
| splitPane | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| switchToTab | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| toggleFullscreen | Switch between fullscreen and default window sizes. | | | |
| unbound | Unbind the associated keys from any command. | | | |
| `closePane` | Close the active pane. | | | |
| `closeTab` | Close the current tab. | | | |
| `closeWindow` | Close the current window and all tabs within it. | | | |
| `copy` | Copy the selected terminal content to your Windows Clipboard. | `trimWhitespace` | boolean | When `true`, newlines persist from the selected text. When `false`, copied content will paste on one line. |
| `decreaseFontSize` | Make the text smaller by one delta. | `delta` | integer | Amount of size decrease per command invocation. |
| `duplicateTab` | Make a copy and open the current tab. | | | |
| `find` | Open the search dialog box. | | | |
| `increaseFontSize` | Make the text larger by one delta. | `delta` | integer | Amount of size increase per command invocation. |
| `moveFocus` | Focus on a different pane depending on direction. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the focus will move. |
| `newTab` | Create a new tab. Without any arguments, this will open the default profile in a new tab. | 1. `commandLine`<br>2. `startingDirectory`<br>3. `tabTitle`<br>4. `index`<br>5. `profile` | 1. string<br>2. string<br>3. string<br>4. integer<br>5. string | 1. Executable run within the tab.<br>2. Directory in which the tab will open.<br>3. Title of the new tab.<br>4. Profile that will open based on its position in the dropdown (starting at 0).<br>5. Profile that will open based on its GUID or name. |
| `nextTab` | Open the tab to the right of the current one. | | | |
| `openNewTabDropdown` | Open the dropdown menu. | | | |
| `openSettings` | Open the settings file. | | | |
| `paste` | Insert the content that was copied onto the clipboard. | | | |
| `prevTab` | Open the tab to the left of the current one. | | | |
| `resetFontSize` | Reset the text size to the default value. | | | |
| `resizePane` | Change the size of the active pane. | `direction`* | `left`, `right`, `up`, `down` | Direction in which the pane will be resized. |
| `scrollDown` | Move the screen down. | | | |
| `scrollUp` | Move the screen up. | | | |
| `scrollUpPage` | Move the screen up a whole page. | | | |
| `scrollDownPage` | Move the screen down a whole page. | | | |
| `splitPane` | Halve the size of the active pane and open another. Without any arguments, this will open the default profile in the new pane. | 1. `split`*<br>2. `commandLine`<br>3. `startingDirectory`<br>4. `tabTitle`<br>5. `index`<br>6. `profile` | 1. `vertical`, `horizontal`, `auto`<br>2. string<br>3. string<br>4. string<br>5. integer<br>6. string | 1. How the pane will split. `auto` will split in the direction that provides the most surface area.<br>2. Executable run within the pane.<br>3. Directory in which the pane will open.<br>4. Title of the tab when the new pane is focused.<br>5. Profile that will open based on its position in the dropdown (starting at 0).<br>6. Profile that will open based on its GUID or name. |
| `switchToTab` | Open a specific tab depending on index. | `index`* | integer | Tab that will open based on its position in the tab bar (starting at 0). |
| `toggleFullscreen` | Switch between fullscreen and default window sizes. | | | |
| `unbound` | Unbind the associated keys from any command. | | | |
### Accepted Modifiers and Keys

View File

@@ -17,7 +17,7 @@
Version="1.0.0.0" />
<Properties>
<DisplayName>ms-resource:AppName</DisplayName>
<DisplayName>Windows Terminal</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Images\StoreLogo.png</Logo>
</Properties>

View File

@@ -516,7 +516,7 @@ std::vector<::TerminalApp::SettingsLoadWarnings> winrt::TerminalApp::implementat
warnings.insert(warnings.end(), parseWarnings.begin(), parseWarnings.end());
// if an arg parser was registered, but failed, bail
if (args == nullptr)
if (pfn && args == nullptr)
{
continue;
}

View File

@@ -803,10 +803,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
auto firstColDef = Controls::ColumnDefinition();
firstColDef.Width(GridLengthHelper::FromPixels(paneSizes.first));
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondColDef = Controls::ColumnDefinition();
secondColDef.Width(GridLengthHelper::FromPixels(paneSizes.second));
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.ColumnDefinitions().Append(firstColDef);
_root.ColumnDefinitions().Append(secondColDef);
@@ -819,10 +819,10 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
auto firstRowDef = Controls::RowDefinition();
firstRowDef.Height(GridLengthHelper::FromPixels(paneSizes.first));
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
auto secondRowDef = Controls::RowDefinition();
secondRowDef.Height(GridLengthHelper::FromPixels(paneSizes.second));
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
_root.RowDefinitions().Append(firstRowDef);
_root.RowDefinitions().Append(secondRowDef);

View File

@@ -63,13 +63,13 @@ std::vector<TerminalApp::Profile> WslDistroGenerator::GenerateProfiles()
nullptr,
&si,
&pi));
switch (WaitForSingleObject(pi.hProcess, INFINITE))
switch (WaitForSingleObject(pi.hProcess, 2000))
{
case WAIT_OBJECT_0:
break;
case WAIT_ABANDONED:
case WAIT_TIMEOUT:
THROW_HR(ERROR_CHILD_NOT_COMPLETE);
return profiles;
case WAIT_FAILED:
THROW_LAST_ERROR();
default:

View File

@@ -724,6 +724,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
bool handled = false;
// Alt-Numpad# input will send us a character once the user releases Alt, so we should be ignoring the individual keydowns.
// The character will be sent through the TSFInputControl.
// See GH#1401 for more details
if (modifiers.IsAltPressed() && (e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
{
e.Handled(true);
return;
}
// GH#2235: Terminal::Settings hasn't been modified to differentiate between AltGr and Ctrl+Alt yet.
// -> Don't check for key bindings if this is an AltGr key combination.
if (!modifiers.IsAltGrPressed())

View File

@@ -173,15 +173,17 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
{
return S_FALSE;
}
const auto dx = viewportSize.X - oldDimensions.X;
const auto dx = ::base::ClampSub(viewportSize.X, oldDimensions.X);
const auto oldTop = _mutableViewport.Top();
const short newBufferHeight = viewportSize.Y + _scrollbackLines;
const short newBufferHeight = ::base::ClampAdd(viewportSize.Y, _scrollbackLines);
COORD bufferSize{ viewportSize.X, newBufferHeight };
// Save cursor's relative height versus the viewport
const short sCursorHeightInViewportBefore = _buffer->GetCursor().GetPosition().Y - _mutableViewport.Top();
const short sCursorHeightInViewportBefore = ::base::ClampSub(_buffer->GetCursor().GetPosition().Y, _mutableViewport.Top());
// This will be used to determine where the viewport should be in the new buffer.
const short oldViewportTop = _mutableViewport.Top();
@@ -266,7 +268,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const auto maxRow = std::max(newLastChar.Y, newCursorPos.Y);
const short proposedTopFromLastLine = ::base::saturated_cast<short>(maxRow - viewportSize.Y + 1);
const short proposedTopFromLastLine = ::base::ClampAdd(::base::ClampSub(maxRow, viewportSize.Y), 1);
const short proposedTopFromScrollback = newViewportTop;
short proposedTop = std::max(proposedTopFromLastLine,
@@ -294,7 +296,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
{
try
{
auto row = newTextBuffer->GetRowByOffset(::base::saturated_cast<short>(proposedTop - 1));
auto row = newTextBuffer->GetRowByOffset(::base::ClampSub(proposedTop, 1));
if (row.GetCharRow().WasWrapForced())
{
proposedTop--;
@@ -324,7 +326,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
const auto proposedBottom = newView.BottomExclusive();
if (proposedBottom > bufferSize.Y)
{
proposedTop = ::base::saturated_cast<short>(proposedTop - (proposedBottom - bufferSize.Y));
proposedTop = ::base::ClampSub(proposedTop, ::base::ClampSub(proposedBottom, bufferSize.Y));
}
_mutableViewport = Viewport::FromDimensions({ 0, proposedTop }, viewportSize);
@@ -339,7 +341,7 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
// If the old scrolloffset was 0, then we weren't scrolled back at all
// before, and shouldn't be now either.
_scrollOffset = originalOffsetWasZero ? 0 : _mutableViewport.Top() - newVisibleTop;
_scrollOffset = originalOffsetWasZero ? 0 : ::base::ClampSub(_mutableViewport.Top(), newVisibleTop);
_NotifyScrollEvent();
return S_OK;

View File

@@ -11,6 +11,9 @@
using namespace winrt::Microsoft::Terminal::Settings;
using namespace Microsoft::Terminal::Core;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
namespace TerminalCoreUnitTests
{
@@ -21,66 +24,109 @@ namespace TerminalCoreUnitTests
{
TEST_CLASS(ScreenSizeLimitsTest);
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds)
{
DummyRenderTarget emptyRenderTarget;
TEST_METHOD(ScreenWidthAndHeightAreClampedToBounds);
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds);
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
TEST_METHOD(ScrollbackHistorySizeIsClampedToBounds)
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
TEST_METHOD(ResizeIsClampedToBounds);
};
}
using namespace TerminalCoreUnitTests;
void ScreenSizeLimitsTest::ScreenWidthAndHeightAreClampedToBounds()
{
DummyRenderTarget emptyRenderTarget;
// Negative values for initial visible row count or column count
// are clamped to 1. Too-large positive values are clamped to SHRT_MAX.
auto negativeColumnsSettings = winrt::make<MockTermSettings>(10000, 9999999, -1234);
Terminal negativeColumnsTerminal;
negativeColumnsTerminal.CreateFromSettings(negativeColumnsSettings, emptyRenderTarget);
COORD actualDimensions = negativeColumnsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, SHRT_MAX, L"Row count clamped to SHRT_MAX == " WCS(SHRT_MAX));
VERIFY_ARE_EQUAL(actualDimensions.X, 1, L"Column count clamped to 1");
// Zero values are clamped to 1 as well.
auto zeroRowsSettings = winrt::make<MockTermSettings>(10000, 0, 9999999);
Terminal zeroRowsTerminal;
zeroRowsTerminal.CreateFromSettings(zeroRowsSettings, emptyRenderTarget);
actualDimensions = zeroRowsTerminal.GetViewport().Dimensions();
VERIFY_ARE_EQUAL(actualDimensions.Y, 1, L"Row count clamped to 1");
VERIFY_ARE_EQUAL(actualDimensions.X, SHRT_MAX, L"Column count clamped to SHRT_MAX == " WCS(SHRT_MAX));
}
void ScreenSizeLimitsTest::ScrollbackHistorySizeIsClampedToBounds()
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
const unsigned int visibleRowCount = 100;
DummyRenderTarget emptyRenderTarget;
// Zero history size is acceptable.
auto noHistorySettings = winrt::make<MockTermSettings>(0, visibleRowCount, 100);
Terminal noHistoryTerminal;
noHistoryTerminal.CreateFromSettings(noHistorySettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(noHistoryTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"History size of 0 is accepted");
// Negative history sizes are clamped to zero.
auto negativeHistorySizeSettings = winrt::make<MockTermSettings>(-100, visibleRowCount, 100);
Terminal negativeHistorySizeTerminal;
negativeHistorySizeTerminal.CreateFromSettings(negativeHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(negativeHistorySizeTerminal.GetTextBuffer().TotalRowCount(), visibleRowCount, L"Negative history size is clamped to 0");
// History size + initial visible rows == SHRT_MAX is acceptable.
auto maxHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount, visibleRowCount, 100);
Terminal maxHistorySizeTerminal;
maxHistorySizeTerminal.CreateFromSettings(maxHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(maxHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == SHRT_MAX - initial row count is accepted");
// History size + initial visible rows == SHRT_MAX + 1 will be clamped slightly.
auto justTooBigHistorySizeSettings = winrt::make<MockTermSettings>(SHRT_MAX - visibleRowCount + 1, visibleRowCount, 100);
Terminal justTooBigHistorySizeTerminal;
justTooBigHistorySizeTerminal.CreateFromSettings(justTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(justTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size == 1 + SHRT_MAX - initial row count is clamped to SHRT_MAX - initial row count");
// Ridiculously large history sizes are also clamped.
auto farTooBigHistorySizeSettings = winrt::make<MockTermSettings>(99999999, visibleRowCount, 100);
Terminal farTooBigHistorySizeTerminal;
farTooBigHistorySizeTerminal.CreateFromSettings(farTooBigHistorySizeSettings, emptyRenderTarget);
VERIFY_ARE_EQUAL(farTooBigHistorySizeTerminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX), L"History size that is far too large is clamped to SHRT_MAX - initial row count");
}
void ScreenSizeLimitsTest::ResizeIsClampedToBounds()
{
// What is actually clamped is the number of rows in the internal history buffer,
// which is the *sum* of the history size plus the number of rows
// actually visible on screen at the moment.
//
// This is a test for GH#2630, GH#2815.
const unsigned int initialVisibleColCount = 50;
const unsigned int initialVisibleRowCount = 50;
const auto historySize = SHRT_MAX - (initialVisibleRowCount * 2);
DummyRenderTarget emptyRenderTarget;
Log::Comment(L"Watch out - this test takes a while on debug, because "
L"ResizeWithReflow takes a while on debug. This is expected.");
auto settings = winrt::make<MockTermSettings>(historySize, initialVisibleRowCount, initialVisibleColCount);
Log::Comment(L"First create a terminal with fewer than SHRT_MAX lines");
Terminal terminal;
terminal.CreateFromSettings(settings, emptyRenderTarget);
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
Log::Comment(L"Resize the terminal to have exactly SHRT_MAX lines");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 2 }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
Log::Comment(L"Resize the terminal to have MORE than SHRT_MAX lines - we should clamp to SHRT_MAX");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount * 3 }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(SHRT_MAX));
Log::Comment(L"Resize back down to the original size");
VERIFY_SUCCEEDED(terminal.UserResize({ initialVisibleColCount, initialVisibleRowCount }));
VERIFY_ARE_EQUAL(terminal.GetTextBuffer().TotalRowCount(), static_cast<unsigned int>(historySize + initialVisibleRowCount));
}

View File

@@ -113,6 +113,7 @@ class ConptyOutputTests
TEST_METHOD(SimpleWriteOutputTest);
TEST_METHOD(WriteTwoLinesUsesNewline);
TEST_METHOD(WriteAFewSimpleLines);
TEST_METHOD(InvalidateUntilOneBeforeEnd);
private:
bool _writeCallback(const char* const pch, size_t const cch);
@@ -124,10 +125,14 @@ private:
bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
{
// Since rendering happens on a background thread that doesn't have the exception handler on it
// we need to rely on VERIFY's return codes instead of exceptions.
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
std::string actualString = std::string(pch, cch);
VERIFY_IS_GREATER_THAN(expectedOutput.size(),
static_cast<size_t>(0),
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size()));
RETURN_BOOL_IF_FALSE(VERIFY_IS_GREATER_THAN(expectedOutput.size(),
static_cast<size_t>(0),
NoThrowString().Format(L"writing=\"%hs\", expecting %u strings", actualString.c_str(), expectedOutput.size())));
std::string first = expectedOutput.front();
expectedOutput.pop_front();
@@ -135,8 +140,8 @@ bool ConptyOutputTests::_writeCallback(const char* const pch, size_t const cch)
Log::Comment(NoThrowString().Format(L"Expected =\t\"%hs\"", first.c_str()));
Log::Comment(NoThrowString().Format(L"Actual =\t\"%hs\"", actualString.c_str()));
VERIFY_ARE_EQUAL(first.length(), cch);
VERIFY_ARE_EQUAL(first, actualString);
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first.length(), cch));
RETURN_BOOL_IF_FALSE(VERIFY_ARE_EQUAL(first, actualString));
return true;
}
@@ -314,3 +319,55 @@ void ConptyOutputTests::WriteAFewSimpleLines()
VERIFY_SUCCEEDED(renderer.PaintFrame());
}
void ConptyOutputTests::InvalidateUntilOneBeforeEnd()
{
Log::Comment(NoThrowString().Format(
L"Make sure we don't use EL and wipe out the last column of text"));
VERIFY_IS_NOT_NULL(_pVtRenderEngine.get());
auto& g = ServiceLocator::LocateGlobals();
auto& renderer = *g.pRender;
auto& gci = g.getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& sm = si.GetStateMachine();
auto& tb = si.GetTextBuffer();
_flushFirstFrame();
// Move the cursor to width-15, draw 15 characters
sm.ProcessString(L"\x1b[1;66H");
sm.ProcessString(L"ABCDEFGHIJKLMNO");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L"N", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[65C");
expectedOutput.push_back("ABCDEFGHIJKLMNO");
expectedOutput.push_back("\x1b[1;80H"); // we move the cursor to the end of the line after paint
VERIFY_SUCCEEDED(renderer.PaintFrame());
// overstrike the first with X and the middle 8 with spaces
sm.ProcessString(L"\x1b[1;66H");
// ABCDEFGHIJKLMNO
sm.ProcessString(L"X ");
{
auto iter = tb.GetCellDataAt({ 78, 0 });
VERIFY_ARE_EQUAL(L" ", (iter++)->Chars());
VERIFY_ARE_EQUAL(L"O", (iter++)->Chars());
}
expectedOutput.push_back("\x1b[1;66H");
expectedOutput.push_back("X"); // sequence optimizer should choose ECH here
expectedOutput.push_back("\x1b[13X");
expectedOutput.push_back("\x1b[13C");
expectedOutput.push_back("\x1b[?25h"); // we turn the cursor back on for good measure
VERIFY_SUCCEEDED(renderer.PaintFrame());
}

View File

@@ -6,13 +6,11 @@
#include "til/at.h"
#include "til/color.h"
#include "til/some.h"
#include "til/point.h"
#include "til/size.h"
#include "til/point.h"
#include "til/rectangle.h"
#include "til/u8u16convert.h"
//#include "til/operators.h"
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}

View File

@@ -1,347 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <vector>
#include "size.h"
#ifdef UNIT_TESTING
class BitmapTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
class const_bitterator // Bit Iterator. Bitterator.
{
public:
const_bitterator(const std::vector<bool>& values, size_t pos) :
_map(values),
_pos(pos)
{
}
const_bitterator operator+(const ptrdiff_t movement)
{
auto copy = *this;
copy += movement;
return copy;
}
const_bitterator operator-(const ptrdiff_t movement)
{
auto copy = *this;
copy -= movement;
return copy;
}
const_bitterator& operator++()
{
++_pos;
return (*this);
}
const_bitterator& operator--()
{
--_pos;
return (*this);
}
const_bitterator& operator+=(const ptrdiff_t& movement)
{
_pos += movement;
return (*this);
}
const_bitterator& operator-=(const ptrdiff_t& movement)
{
_pos -= movement;
return (*this);
}
bool operator==(const const_bitterator& other) const
{
return _pos == other._pos && _map == other._map;
}
bool operator!=(const const_bitterator& other) const
{
return !(*this == other);
}
bool operator<(const const_bitterator& other) const
{
return _pos < other._pos;
}
bool operator>(const const_bitterator& other) const
{
return _pos > other._pos;
}
bool operator*() const
{
return _map[_pos];
}
private:
size_t _pos;
const std::vector<bool>& _map;
};
class const_runerator // Run Iterator. Runerator.
{
public:
const_runerator(const std::vector<bool>& values, til::size sz, size_t pos) :
_values(values),
_size(sz),
_pos(pos)
{
_calculateArea();
}
const_runerator& operator++()
{
_pos = _nextPos;
_calculateArea();
return (*this);
}
bool operator==(const const_runerator& other) const
{
return _pos == other._pos && _values == other._values;
}
bool operator!=(const const_runerator& other) const
{
return !(*this == other);
}
bool operator<(const const_runerator& other) const
{
return _pos < other._pos;
}
bool operator>(const const_runerator& other) const
{
return _pos > other._pos;
}
til::rectangle operator*() const
{
return _run;
}
private:
const std::vector<bool>& _values;
const til::size _size;
size_t _pos;
size_t _nextPos;
til::rectangle _run;
til::point _indexToPoint(size_t index)
{
return til::point{ (ptrdiff_t)index % _size.width(), (ptrdiff_t)index / _size.width() };
}
void _calculateArea()
{
const size_t end = (size_t)_size.area();
_nextPos = _pos;
while (_nextPos < end && !_values.at(_nextPos))
{
++_nextPos;
}
if (_nextPos < end)
{
// pos is now at the first on bit.
const auto runStart = _indexToPoint(_nextPos);
const size_t rowEndIndex = (size_t)((runStart.y() + 1) * _size.width());
ptrdiff_t runLength = 0;
do
{
++_nextPos;
++runLength;
} while (_nextPos < end && _nextPos < rowEndIndex && _values.at(_nextPos));
_run = til::rectangle{ runStart, til::size{ runLength, static_cast<ptrdiff_t>(1) } };
}
else
{
_pos = _nextPos;
_run = til::rectangle{};
}
}
};
class bitmap
{
public:
using const_iterator = const const_bitterator;
bitmap() :
bitmap(0, 0)
{
}
bitmap(size_t width, size_t height) :
bitmap(til::size{ width, height })
{
}
bitmap(til::size sz) :
_size(sz),
_bits(sz.area(), true),
_empty(false)
{
}
const_iterator begin() const
{
return const_bitterator(_bits, 0);
}
const_iterator end() const
{
return const_bitterator(_bits, _size.area());
}
const_iterator begin_row(size_t row) const
{
return const_bitterator(_bits, row * _size.width());
}
const_iterator end_row(size_t row) const
{
return const_bitterator(_bits, (row + 1) * _size.width());
}
const_runerator begin_runs() const
{
return const_runerator(_bits, _size, 0);
}
const_runerator end_runs() const
{
return const_runerator(_bits, _size, _size.area());
}
void set(til::point pt)
{
_bits[pt.y() * _size.width() + pt.x()] = true;
_empty = false;
}
void reset(til::point pt)
{
_bits[pt.y() * _size.width() + pt.x()] = false;
}
void set(til::rectangle rc)
{
for (auto pt : rc)
{
set(pt);
}
}
void reset(til::rectangle rc)
{
for (auto pt : rc)
{
reset(pt);
}
}
void set_all()
{
// .clear() then .resize(_size(), true) throws an assert (unsupported operation)
// .assign(_size(), true) throws an assert (unsupported operation)
set(til::rectangle{ til::point{ 0, 0 }, _size });
}
void reset_all()
{
// .clear() then .resize(_size(), false) throws an assert (unsupported operation)
// .assign(_size(), false) throws an assert (unsupported operation)
reset(til::rectangle{ til::point{ 0, 0 }, _size });
_empty = true;
}
void resize(til::size size)
{
// Don't resize if it's not different as we mark the whole thing dirty on resize.
// TODO: marking it dirty might not be necessary or we should be smart about it
// (mark none of it dirty on resize down, mark just the edges on up?)
if (_size != size)
{
_size = size;
// .resize(_size(), true) throws an assert (unsupported operation)
_bits = std::vector<bool>(_size.area(), true);
}
}
void resize(size_t width, size_t height)
{
resize(til::size{ width, height });
}
constexpr bool empty() const
{
return _empty;
}
const til::size& size() const
{
return _size;
}
operator bool() const noexcept
{
return !_bits.empty();
}
bitmap operator+(const point& pt) const
{
auto temp = *this;
return temp += pt;
}
bitmap& operator+=(const point& pt)
{
// early return if nothing to do.
if (pt.x() == 0 && pt.y() == 0)
{
return (*this);
}
// If we're told to shift the whole thing by an entire width or height,
// the effect is to just clear the whole bitmap.
if (pt.x() >= _size.width() || pt.y() >= _size.height())
{
reset_all();
return (*this);
}
// TODO: any way to reconcile this with walk directions from scrolling apis?
// TODO: actually implement translation.
return (*this);
}
#ifdef UNIT_TESTING
friend class ::BitmapTests;
#endif
private:
bool _empty;
til::size _size;
std::vector<bool> _bits;
};
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#ifdef UNIT_TESTING
class BitteratorTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
};

View File

@@ -1,256 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "rectangle.h"
#include "size.h"
#include "bitmap.h"
#define _TIL_INLINEPREFIX __declspec(noinline) inline
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
// RECTANGLE VS SIZE
// ADD will grow the total area of the rectangle. The sign is the direction to grow.
_TIL_INLINEPREFIX rectangle operator+(const rectangle& lhs, const size& rhs)
{
// Fetch the pieces of the rectangle.
auto l = lhs.left();
auto r = lhs.right();
auto t = lhs.top();
auto b = lhs.bottom();
// Fetch the scale factors we're using.
const auto width = rhs.width();
const auto height = rhs.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 } };
}
_TIL_INLINEPREFIX rectangle& operator+=(rectangle& lhs, const size& rhs)
{
lhs = lhs + rhs;
return lhs;
}
// SUB will shrink the total area of the rectangle. The sign is the direction to shrink.
_TIL_INLINEPREFIX rectangle operator-(const rectangle& lhs, const size& rhs)
{
// Fetch the pieces of the rectangle.
auto l = lhs.left();
auto r = lhs.right();
auto t = lhs.top();
auto b = lhs.bottom();
// Fetch the scale factors we're using.
const auto width = rhs.width();
const auto height = rhs.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 } };
}
_TIL_INLINEPREFIX rectangle& operator-=(rectangle& lhs, const size& rhs)
{
lhs = lhs - rhs;
return lhs;
}
// MUL will scale the entire rectangle by the size L/R * WIDTH and T/B * HEIGHT.
_TIL_INLINEPREFIX rectangle operator*(const rectangle& lhs, const size& rhs)
{
ptrdiff_t l;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.left()) * rhs.width()).AssignIfValid(&l));
ptrdiff_t t;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.top()) * rhs.height()).AssignIfValid(&t));
ptrdiff_t r;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.right()) * rhs.width()).AssignIfValid(&r));
ptrdiff_t b;
THROW_HR_IF(E_ABORT, !(base::MakeCheckedNum(lhs.bottom()) * rhs.height()).AssignIfValid(&b));
return til::rectangle{ l, t, r, b };
}
// 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() };
}
// 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());
}
}

View File

@@ -39,14 +39,14 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
// This template will convert to point from anything that has an X and a Y field that appear convertible to an integer value
// This template will convert to size from anything that has an X and a Y field that appear convertible to an integer value
template<typename TOther>
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().X)> && std::is_integral_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
point(static_cast<ptrdiff_t>(other.X), static_cast<ptrdiff_t>(other.Y))
{
}
// This template will convert to point from anything that has a x and a y field that appear convertible to an integer value
// This template will convert to size from anything that has a x and a y field that appear convertible to an integer value
template<typename TOther>
constexpr point(const TOther& other, std::enable_if_t<std::is_integral_v<decltype(std::declval<TOther>().x)> && std::is_integral_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
point(static_cast<ptrdiff_t>(other.x), static_cast<ptrdiff_t>(other.y))
@@ -64,7 +64,7 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr operator bool() const noexcept
operator bool() const noexcept
{
return _x != 0 || _y != 0;
}

View File

@@ -7,8 +7,6 @@
#include "size.h"
#include "some.h"
#include "recterator.h"
#ifdef UNIT_TESTING
class RectangleTests;
#endif
@@ -18,8 +16,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
class rectangle
{
public:
using const_iterator = recterator;
constexpr rectangle() noexcept :
rectangle(til::point{ 0, 0 }, til::point{ 0, 0 })
{
@@ -102,13 +98,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
}
constexpr rectangle& operator=(const rectangle other) noexcept
{
_topLeft = other._topLeft;
_bottomRight = other._bottomRight;
return (*this);
}
constexpr bool operator==(const rectangle& other) const noexcept
{
return _topLeft == other._topLeft &&
@@ -126,16 +115,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
_topLeft.y() < _bottomRight.y();
}
const_iterator begin() const
{
return recterator(_topLeft, size());
}
const_iterator end() const
{
return recterator(_topLeft, size(), { _topLeft.x(), _topLeft.y() + height() });
}
// OR = union
constexpr rectangle operator|(const rectangle& other) const noexcept
{

View File

@@ -1,119 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "point.h"
#include "size.h"
#ifdef UNIT_TESTING
class RecteratorTests;
#endif
namespace til // Terminal Implementation Library. Also: "Today I Learned"
{
class recterator
{
public:
recterator(point topLeft, size size) :
_topLeft(topLeft),
_size(size),
_current(topLeft)
{
}
recterator(point topLeft, size size, point start) :
_topLeft(topLeft),
_size(size),
_current(start)
{
}
recterator& operator++()
{
if (_current.x() + 1 >= _topLeft.x() + _size.width())
{
_current = { _topLeft.x(), _current.y() + 1 };
}
else
{
_current = { _current.x() + 1, _current.y() };
}
return (*this);
}
bool operator==(const recterator& other) const
{
return _current == other._current &&
_topLeft == other._topLeft &&
_size == other._size;
}
bool operator!=(const recterator& other) const
{
return !(*this == other);
}
bool operator<(const recterator& other) const
{
return _current < other._current;
}
bool operator>(const recterator& other) const
{
return _current > other._current;
}
point operator*() const
{
return _current;
}
protected:
point _current;
const point _topLeft;
const size _size;
#ifdef UNIT_TESTING
friend class ::RecteratorTests;
#endif
};
};
#ifdef __WEX_COMMON_H__
namespace WEX::TestExecution
{
template<>
class VerifyOutputTraits<::til::recterator>
{
public:
static WEX::Common::NoThrowString ToString(const ::til::recterator& /*rect*/)
{
return WEX::Common::NoThrowString().Format(L"Yep that's a recterator.");
}
};
template<>
class VerifyCompareTraits<::til::recterator, ::til::recterator>
{
public:
static bool AreEqual(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
{
return expected == actual;
}
static bool AreSame(const ::til::recterator& expected, const ::til::recterator& actual) noexcept
{
return &expected == &actual;
}
static bool IsLessThan(const ::til::recterator& expectedLess, const ::til::recterator& expectedGreater) = delete;
static bool IsGreaterThan(const ::til::recterator& expectedGreater, const ::til::recterator& expectedLess) = delete;
static bool IsNull(const ::til::recterator& object) noexcept = delete;
};
};
#endif

View File

@@ -64,11 +64,6 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
return !(*this == other);
}
constexpr operator bool() const noexcept
{
return _width != 0 || _height != 0;
}
size operator+(const size& other) const
{
ptrdiff_t width;

View File

@@ -232,7 +232,7 @@ BgfxEngine::BgfxEngine(PVOID SharedViewBase, LONG DisplayHeight, LONG DisplayWid
return S_OK;
}
std::vector<til::rectangle> BgfxEngine::GetDirtyArea()
std::vector<SMALL_RECT> 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<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> 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,10 +862,8 @@ 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 rect : engine.GetDirtyArea())
for (auto srDirty : engine.GetDirtyArea())
{
SMALL_RECT srDirty = rect;
// Dirty is an inclusive rectangle, but oddly enough the IME was an exclusive one, so correct it.
srDirty.Bottom++;
srDirty.Right++;

View File

@@ -65,27 +65,26 @@ using namespace Microsoft::Console::Types;
// TODO GH 2683: The default constructor should not throw.
DxEngine::DxEngine() :
RenderEngineBase(),
_invalidMap{},
/*_isInvalidUsed{ false },
_invalidRect{ 0 },*/
_isInvalidUsed{ false },
_invalidRect{ 0 },
_invalidScroll{ 0 },
_presentParams{ 0 },
_presentReady{ false },
_presentScroll{ 0 },
/*_presentDirty{ 0 },*/
_presentDirty{ 0 },
_presentOffset{ 0 },
_isEnabled{ false },
_isPainting{ false },
_displaySizePixels{},
_displaySizePixels{ 0 },
_foregroundColor{ 0 },
_backgroundColor{ 0 },
_selectionBackground{},
_glyphCell{ 1, 1 },
_glyphCell{ 0 },
_haveDeviceResources{ false },
_retroTerminalEffects{ false },
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
_sizeTarget{},
_sizeTarget{ 0 },
_dpi{ USER_DEFAULT_SCREEN_DPI },
_scale{ 1.0f },
_chainMode{ SwapChainMode::ForComposition },
@@ -239,8 +238,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
// Setup the viewport.
D3D11_VIEWPORT vp;
vp.Width = _displaySizePixels.width<FLOAT>();
vp.Height = _displaySizePixels.height<FLOAT>();
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f;
vp.TopLeftX = 0;
@@ -410,8 +409,6 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
_displaySizePixels = _GetClientSize();
_invalidMap.resize(_displaySizePixels / _glyphCell);
if (createSwapChain)
{
DXGI_SWAP_CHAIN_DESC1 SwapChainDesc = { 0 };
@@ -430,15 +427,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
case SwapChainMode::ForHwnd:
{
// use the HWND's dimensions for the swap chain dimensions.
til::rectangle clientRect;
{
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
clientRect = rect;
}
RECT rect = { 0 };
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
SwapChainDesc.Width = clientRect.width<UINT>();
SwapChainDesc.Height = clientRect.height<UINT>();
SwapChainDesc.Width = rect.right - rect.left;
SwapChainDesc.Height = rect.bottom - rect.top;
// We can't do alpha for HWNDs. Set to ignore. It will fail otherwise.
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
@@ -464,8 +457,8 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
case SwapChainMode::ForComposition:
{
// Use the given target size for compositions.
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
SwapChainDesc.Width = _displaySizePixels.cx;
SwapChainDesc.Height = _displaySizePixels.cy;
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
@@ -635,8 +628,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
return _dwriteFactory->CreateTextLayout(string,
gsl::narrow<UINT32>(stringLength),
_dwriteTextFormat.Get(),
_displaySizePixels.width<float>(),
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
gsl::narrow<float>(_displaySizePixels.cx),
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
ppTextLayout);
}
@@ -693,11 +686,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
SMALL_RECT inclusive = *psrRegion;
inclusive.Right--;
inclusive.Bottom--;
_InvalidOr(inclusive);
_InvalidOr(*psrRegion);
return S_OK;
}
@@ -711,7 +700,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToExclusive();
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
return Invalidate(&sr);
}
@@ -759,42 +748,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
{
try
{
/*til::point delta(*pcoordDelta);*/
// TODO: do this.
POINT delta = { 0 };
delta.x = pcoordDelta->X * _glyphCell.cx;
delta.y = pcoordDelta->Y * _glyphCell.cy;
//POINT delta = { 0 };
//delta.x = pcoordDelta->X * _glyphCell.cx;
//delta.y = pcoordDelta->Y * _glyphCell.cy;
_InvalidOffset(delta);
//_InvalidOffset(delta);
_invalidScroll.cx += delta.x;
_invalidScroll.cy += delta.y;
//_invalidScroll.cx += delta.x;
//_invalidScroll.cy += delta.y;
// Add the revealed portion of the screen from the scroll to the invalid area.
const RECT display = _GetDisplayRect();
RECT reveal = display;
//// Add the revealed portion of the screen from the scroll to the invalid area.
//const RECT display = _GetDisplayRect();
//RECT reveal = display;
// X delta first
OffsetRect(&reveal, delta.x, 0);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
//// X delta first
//OffsetRect(&reveal, delta.x, 0);
//IntersectRect(&reveal, &reveal, &display);
//SubtractRect(&reveal, &display, &reveal);
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
//if (!IsRectEmpty(&reveal))
//{
// _InvalidOr(reveal);
//}
// Y delta second (subtract rect won't work if you move both)
reveal = display;
OffsetRect(&reveal, 0, delta.y);
IntersectRect(&reveal, &reveal, &display);
SubtractRect(&reveal, &display, &reveal);
//// Y delta second (subtract rect won't work if you move both)
//reveal = display;
//OffsetRect(&reveal, 0, delta.y);
//IntersectRect(&reveal, &reveal, &display);
//SubtractRect(&reveal, &display, &reveal);
//if (!IsRectEmpty(&reveal))
//{
// _InvalidOr(reveal);
//}
if (!IsRectEmpty(&reveal))
{
_InvalidOr(reveal);
}
}
CATCH_RETURN();
}
@@ -810,9 +796,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
{
_invalidMap.set_all();
/*const RECT screen = _GetDisplayRect();
_InvalidOr(screen);*/
const RECT screen = _GetDisplayRect();
_InvalidOr(screen);
return S_OK;
}
@@ -837,7 +822,7 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
// Return Value:
// - X by Y area in pixels of the surface
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
{
switch (_chainMode)
{
@@ -846,42 +831,39 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
RECT clientRect = { 0 };
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
til::rectangle client{ clientRect };
SIZE clientSize = { 0 };
clientSize.cx = clientRect.right - clientRect.left;
clientSize.cy = clientRect.bottom - clientRect.top;
return client.size();
return clientSize;
}
case SwapChainMode::ForComposition:
{
try
{
// TODO: fix scale
/*return _sizeTarget * _scale;*/
return _sizeTarget;
}
CATCH_LOG();
return _sizeTarget;
SIZE size = _sizeTarget;
size.cx = static_cast<LONG>(size.cx * _scale);
size.cy = static_cast<LONG>(size.cy * _scale);
return size;
}
default:
FAIL_FAST_HR(E_NOTIMPL);
}
}
//// Routine Description:
//// - Helper to multiply all parameters of a rectangle by the font size
//// to convert from characters to pixels.
//// Arguments:
//// - cellsToPixels - rectangle to update
//// - fontSize - scaling factors
//// Return Value:
//// - <none> - Updates reference
//void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
//{
// cellsToPixels.left *= fontSize.cx;
// cellsToPixels.right *= fontSize.cx;
// cellsToPixels.top *= fontSize.cy;
// cellsToPixels.bottom *= fontSize.cy;
//}
// Routine Description:
// - Helper to multiply all parameters of a rectangle by the font size
// to convert from characters to pixels.
// Arguments:
// - cellsToPixels - rectangle to update
// - fontSize - scaling factors
// Return Value:
// - <none> - Updates reference
void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
{
cellsToPixels.left *= fontSize.cx;
cellsToPixels.right *= fontSize.cx;
cellsToPixels.top *= fontSize.cy;
cellsToPixels.bottom *= fontSize.cy;
}
// Routine Description:
// - Retrieves a rectangle representation of the pixel size of the
@@ -890,9 +872,9 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
// Return Value;
// - Origin-placed rectangle representing the pixel size of the surface
[[nodiscard]] til::rectangle DxEngine::_GetDisplayRect() const noexcept
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
{
return til::rectangle{ til::point{ 0, 0 }, _displaySizePixels };
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
}
// Routine Description:
@@ -905,25 +887,22 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
// - <none>
void DxEngine::_InvalidOffset(POINT delta)
{
til::point pt{ delta };
_invalidMap += pt;
if (_isInvalidUsed)
{
// Copy the existing invalid rect
RECT invalidNew = _invalidRect;
//if (_isInvalidUsed)
//{
// // Copy the existing invalid rect
// RECT invalidNew = _invalidRect;
// Offset it to the new position
THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
// // Offset it to the new position
// THROW_IF_WIN32_BOOL_FALSE(OffsetRect(&invalidNew, delta.x, delta.y));
// Get the rect representing the display
const RECT rectScreen = _GetDisplayRect();
// // Get the rect representing the display
// const RECT rectScreen = _GetDisplayRect();
// Ensure that the new invalid rectangle is still on the display
IntersectRect(&invalidNew, &invalidNew, &rectScreen);
// // Ensure that the new invalid rectangle is still on the display
// IntersectRect(&invalidNew, &invalidNew, &rectScreen);
// _invalidRect = invalidNew;
//}
_invalidRect = invalidNew;
}
}
// Routine description:
@@ -935,22 +914,17 @@ void DxEngine::_InvalidOffset(POINT delta)
// - <none>
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
{
if (_invalidMap)
{
_invalidMap.set(sr);
}
RECT region;
region.left = sr.Left;
region.top = sr.Top;
region.right = sr.Right;
region.bottom = sr.Bottom;
_ScaleByFont(region, _glyphCell);
//RECT region;
//region.left = sr.Left;
//region.top = sr.Top;
//region.right = sr.Right;
//region.bottom = sr.Bottom;
//_ScaleByFont(region, _glyphCell);
region.right += _glyphCell.cx;
region.bottom += _glyphCell.cy;
//region.right += _glyphCell.cx;
//region.bottom += _glyphCell.cy;
//_InvalidOr(region);
_InvalidOr(region);
}
// Routine Description:
@@ -959,20 +933,20 @@ void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
// - rc - Dirty pixel rectangle
// Return Value:
// - <none>
void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
void DxEngine::_InvalidOr(RECT rc) noexcept
{
//if (_isInvalidUsed)
//{
// UnionRect(&_invalidRect, &_invalidRect, &rc);
if (_isInvalidUsed)
{
UnionRect(&_invalidRect, &_invalidRect, &rc);
// const RECT rcScreen = _GetDisplayRect();
// IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
//}
//else
//{
// _invalidRect = rc;
// _isInvalidUsed = true;
//}
const RECT rcScreen = _GetDisplayRect();
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
}
else
{
_invalidRect = rc;
_isInvalidUsed = true;
}
}
// Routine Description:
@@ -997,8 +971,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::StartPaint() noexcept
{
FAIL_FAST_IF_FAILED(InvalidateAll());
RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting.
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32(_invalidRect.bottom - _invalidRect.top, "InvalidHeight"),
TraceLoggingInt32((_invalidRect.bottom - _invalidRect.top) / _glyphCell.cy, "InvalidHeightChars"),
TraceLoggingInt32(_invalidRect.right - _invalidRect.left, "InvalidWidth"),
TraceLoggingInt32((_invalidRect.right - _invalidRect.left) / _glyphCell.cx, "InvalidWidthChars"),
TraceLoggingInt32(_invalidRect.left, "InvalidX"),
TraceLoggingInt32(_invalidRect.left / _glyphCell.cx, "InvalidXChars"),
TraceLoggingInt32(_invalidRect.top, "InvalidY"),
TraceLoggingInt32(_invalidRect.top / _glyphCell.cy, "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidth"),
TraceLoggingInt32(_invalidScroll.cx / _glyphCell.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeight"),
TraceLoggingInt32(_invalidScroll.cy / _glyphCell.cy, "ScrollHeightChars"));
if (_isEnabled)
{
try
@@ -1008,7 +999,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
RETURN_IF_FAILED(_CreateDeviceResources(true));
}
else if (_displaySizePixels != clientSize)
else if (_displaySizePixels.cy != clientSize.cy ||
_displaySizePixels.cx != clientSize.cx)
{
// OK, we're going to play a dangerous game here for the sake of optimizing resize
// First, set up a complete clear of all device resources if something goes terribly wrong.
@@ -1021,7 +1013,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_d2dRenderTarget.Reset();
// Change the buffer size and recreate the render target (and surface)
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
RETURN_IF_FAILED(_PrepareRenderTarget());
// OK we made it past the parts that can cause errors. We can release our failure handler.
@@ -1029,32 +1021,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// And persist the new size.
_displaySizePixels = clientSize;
_invalidMap.resize(_displaySizePixels / _glyphCell);
}
_d2dRenderTarget->BeginDraw();
_isPainting = true;
// Walk the map for dirty rectangles and store them up.
// We're going to have to go over them multiple times, so don't spend all the iteration
// work multiple times.
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
{
auto rect = *it;
#pragma warning(suppress : 26477 26485 26494 26482 26446 26447) // We don't control TraceLoggingWrite
TraceLoggingWrite(g_hDxRenderProvider,
"Invalid",
TraceLoggingInt32((LONG)rect.height(), "InvalidHeightChars"),
TraceLoggingInt32((LONG)rect.width(), "InvalidWidthChars"),
TraceLoggingInt32((LONG)rect.left(), "InvalidXChars"),
TraceLoggingInt32((LONG)rect.top(), "InvalidYChars"),
TraceLoggingInt32(_invalidScroll.cx, "ScrollWidthChars"),
TraceLoggingInt32(_invalidScroll.cy, "ScrollHeightChars"));
_dirtyRects.push_back(rect);
}
}
CATCH_RETURN();
}
@@ -1084,19 +1054,15 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
{
// The scroll rect is the entire screen minus the revealed areas.
// Get the entire screen into a rectangle.
til::rectangle scrollArea = _GetDisplayRect();
_presentDirty = _invalidRect;
// Reduce the size of the rectangle by the scroll
scrollArea -= _invalidScroll;
_presentScroll = scrollArea;
const RECT display = _GetDisplayRect();
SubtractRect(&_presentScroll, &display, &_presentDirty);
_presentOffset.x = _invalidScroll.cx;
_presentOffset.y = _invalidScroll.cy;
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_dirtyRectRects.size());
_presentParams.pDirtyRects = _dirtyRectRects.data();
_presentParams.DirtyRectsCount = 1;
_presentParams.pDirtyRects = &_presentDirty;
_presentParams.pScrollOffset = &_presentOffset;
_presentParams.pScrollRect = &_presentScroll;
@@ -1117,11 +1083,8 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
}
}
_dirtyRects.clear();
_invalidMap.reset_all();
/*_invalidRect = { 0 };
_isInvalidUsed = false;*/
_invalidRect = { 0 };
_isInvalidUsed = false;
_invalidScroll = { 0 };
@@ -1198,8 +1161,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
RETURN_IF_FAILED(_CopyFrontToBack());
_presentReady = false;
_dirtyRectRects.clear();
/*_presentDirty = { 0 };*/
_presentDirty = { 0 };
_presentOffset = { 0 };
_presentScroll = { 0 };
_presentParams = { 0 };
@@ -1222,22 +1184,27 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
}
// Routine Description:
// - This paints in the back most layer of the frame with clear/nothing so it can
// be transparent if it wants to be.
// - This paints in the back most layer of the frame with the background color.
// Arguments:
// - <none>
// Return Value:
// - S_OK
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
{
const D2D1_COLOR_F nothing = { 0 }; // 0 alpha and color is black.
for (const D2D1_RECT_F rect : _dirtyRects)
switch (_chainMode)
{
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
case SwapChainMode::ForHwnd:
_d2dRenderTarget->FillRectangle(D2D1::RectF(static_cast<float>(_invalidRect.left),
static_cast<float>(_invalidRect.top),
static_cast<float>(_invalidRect.right),
static_cast<float>(_invalidRect.bottom)),
_d2dBrushBackground.Get());
break;
case SwapChainMode::ForComposition:
D2D1_COLOR_F nothing = { 0 };
_d2dRenderTarget->Clear(nothing);
_d2dRenderTarget->PopAxisAlignedClip();
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
break;
}
return S_OK;
@@ -1258,10 +1225,10 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
{
try
{
const til::point cellPoint{ coord };
// Calculate positioning of our origin.
const D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
D2D1_POINT_2F origin;
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
// Create the text layout
CustomTextLayout layout(_dwriteFactory.Get(),
@@ -1269,7 +1236,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
clusters,
_glyphCell.width());
_glyphCell.cx);
// Get the baseline for this font as that's where we draw from
DWRITE_LINE_SPACING spacing;
@@ -1281,7 +1248,7 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
_d2dBrushBackground.Get(),
_dwriteFactory.Get(),
spacing,
_glyphCell,
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
// Layout then render the text
@@ -1382,18 +1349,25 @@ void DxEngine::_InvalidOr(RECT /*rc*/) noexcept
// - rect - Rectangle to invert or highlight to make the selection area
// Return Value:
// - S_OK or relevant DirectX error.
[[nodiscard]] HRESULT DxEngine::PaintSelection(SMALL_RECT rect) noexcept
[[nodiscard]] HRESULT DxEngine::PaintSelection(const SMALL_RECT rect) noexcept
{
const auto existingColor = _d2dBrushForeground->GetColor();
_d2dBrushForeground->SetColor(_selectionBackground);
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
rect.Bottom--;
rect.Right--;
RECT pixels;
pixels.left = rect.Left * _glyphCell.cx;
pixels.top = rect.Top * _glyphCell.cy;
pixels.right = rect.Right * _glyphCell.cx;
pixels.bottom = rect.Bottom * _glyphCell.cy;
D2D1_RECT_F draw = { 0 };
draw.left = static_cast<float>(pixels.left);
draw.top = static_cast<float>(pixels.top);
draw.right = static_cast<float>(pixels.right);
draw.bottom = static_cast<float>(pixels.bottom);
/* rect is SMALL_RECT */
const D2D1_RECT_F draw = til::rectangle{ rect } * _glyphCell;
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
return S_OK;
@@ -1421,12 +1395,16 @@ enum class CursorPaintType
return S_FALSE;
}
// Create rectangular block representing where the cursor can fill.
D2D1_RECT_F rect = til::rectangle{ til::point{ options.coordCursor } } * _glyphCell;
D2D1_RECT_F rect = { 0 };
rect.left = static_cast<float>(options.coordCursor.X * _glyphCell.cx);
rect.top = static_cast<float>(options.coordCursor.Y * _glyphCell.cy);
rect.right = static_cast<float>(rect.left + _glyphCell.cx);
rect.bottom = static_cast<float>(rect.top + _glyphCell.cy);
// If we're double-width, make it one extra glyph wider
if (options.fIsDoubleWidth)
{
rect.right += _glyphCell.width();
rect.right += _glyphCell.cx;
}
CursorPaintType paintType = CursorPaintType::Fill;
@@ -1438,7 +1416,7 @@ enum class CursorPaintType
// Enforce min/max cursor height
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
ulHeight = gsl::narrow<ULONG>((_glyphCell.height() * ulHeight) / 100);
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
rect.top = rect.bottom - ulHeight;
break;
}
@@ -1608,9 +1586,10 @@ CATCH_RETURN()
try
{
_glyphCell = fiFontInfo.GetSize();
const auto size = fiFontInfo.GetSize();
_invalidMap.resize(_displaySizePixels / _glyphCell);
_glyphCell.cx = size.X;
_glyphCell.cy = size.Y;
}
CATCH_RETURN();
@@ -1619,9 +1598,10 @@ CATCH_RETURN()
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
{
const auto cellSize = til::size{ viewInPixels.Dimensions() } / _glyphCell;
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
return Viewport::FromDimensions(viewInPixels.Origin(), cellSize);
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
}
// Routine Description:
@@ -1706,10 +1686,19 @@ float DxEngine::GetScaling() const noexcept
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
// TODO: maybe this should be returning a ref... not a copy...
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
[[nodiscard]] std::vector<SMALL_RECT> DxEngine::GetDirtyArea()
{
return _dirtyRects;
SMALL_RECT r;
r.Top = gsl::narrow<SHORT>(floor(_invalidRect.top / _glyphCell.cy));
r.Left = gsl::narrow<SHORT>(floor(_invalidRect.left / _glyphCell.cx));
r.Bottom = gsl::narrow<SHORT>(floor(_invalidRect.bottom / _glyphCell.cy));
r.Right = gsl::narrow<SHORT>(floor(_invalidRect.right / _glyphCell.cx));
// Exclusive to inclusive
r.Bottom--;
r.Right--;
return { r };
}
// Routine Description:
@@ -1721,7 +1710,7 @@ float DxEngine::GetScaling() const noexcept
// - Nearest integer short x and y values for each cell.
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
{
return _glyphCell;
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
}
// Routine Description:
@@ -1757,7 +1746,7 @@ float DxEngine::GetScaling() const noexcept
_dwriteTextFormat.Get(),
_dwriteFontFace.Get(),
{ &cluster, 1 },
_glyphCell.width());
_glyphCell.cx);
UINT32 columns = 0;
RETURN_IF_FAILED(layout.GetColumns(&columns));

View File

@@ -27,9 +27,6 @@
#include <TraceLoggingProvider.h>
#include "til/bitmap.h"
#include "til/operators.h"
TRACELOGGING_DECLARE_PROVIDER(g_hDxRenderProvider);
namespace Microsoft::Console::Render
@@ -98,7 +95,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, int const iDpi) noexcept override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> 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;
@@ -124,7 +121,7 @@ namespace Microsoft::Console::Render
SwapChainMode _chainMode;
HWND _hwndTarget;
til::size _sizeTarget;
SIZE _sizeTarget;
int _dpi;
float _scale;
@@ -133,8 +130,8 @@ namespace Microsoft::Console::Render
bool _isEnabled;
bool _isPainting;
til::size _displaySizePixels;
til::size _glyphCell;
SIZE _displaySizePixels;
SIZE _glyphCell;
D2D1_COLOR_F _defaultForegroundColor;
D2D1_COLOR_F _defaultBackgroundColor;
@@ -143,14 +140,10 @@ namespace Microsoft::Console::Render
D2D1_COLOR_F _backgroundColor;
D2D1_COLOR_F _selectionBackground;
[[nodiscard]] til::rectangle _GetDisplayRect() const noexcept;
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
til::bitmap _invalidMap;
std::vector<til::rectangle> _dirtyRects;
std::vector<RECT> _dirtyRectRects;
//bool _isInvalidUsed;
//RECT _invalidRect;
bool _isInvalidUsed;
RECT _invalidRect;
SIZE _invalidScroll;
void _InvalidOr(SMALL_RECT sr) noexcept;
@@ -159,7 +152,7 @@ namespace Microsoft::Console::Render
void _InvalidOffset(POINT pt);
bool _presentReady;
/*RECT _presentDirty;*/
RECT _presentDirty;
RECT _presentScroll;
POINT _presentOffset;
DXGI_PRESENT_PARAMETERS _presentParams;
@@ -253,7 +246,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] COORD _GetFontSize() const noexcept;
[[nodiscard]] til::size _GetClientSize() const noexcept;
[[nodiscard]] SIZE _GetClientSize() const noexcept;
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;

View File

@@ -4,7 +4,6 @@
#pragma once
// This includes support libraries from the CRT, STL, WIL, and GSL
#define BLOCK_TIL
#include "LibraryIncludes.h"
#include <windows.h>
@@ -35,7 +34,4 @@
#include <dwrite_2.h>
#include <dwrite_3.h>
// Include TIL after DX so we can use the DX conversions.
#include "til.h"
#pragma hdrstop

View File

@@ -68,7 +68,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
[[nodiscard]] std::vector<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> 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<til::rectangle> GdiEngine::GetDirtyArea()
std::vector<SMALL_RECT> GdiEngine::GetDirtyArea()
{
RECT rc = _psInvalidData.rcPaint;

View File

@@ -14,7 +14,6 @@ Author(s):
#pragma once
#include "til/rectangle.h"
#include "../../inc/conattrs.hpp"
#include "Cluster.hpp"
#include "FontInfoDesired.hpp"
@@ -118,7 +117,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& FontInfo,
const int iDpi) noexcept = 0;
[[nodiscard]] virtual std::vector<til::rectangle> GetDirtyArea() = 0;
virtual std::vector<SMALL_RECT> 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

@@ -403,7 +403,7 @@ UiaEngine::UiaEngine(IUiaEventDispatcher* dispatcher) :
// - <none>
// Return Value:
// - Rectangle describing dirty area in characters.
[[nodiscard]] std::vector<til::rectangle> UiaEngine::GetDirtyArea()
[[nodiscard]] std::vector<SMALL_RECT> 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<til::rectangle> GetDirtyArea() override;
[[nodiscard]] std::vector<SMALL_RECT> 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

@@ -144,11 +144,6 @@ using namespace Microsoft::Console::Render;
// - S_OK, else an appropriate HRESULT for failing to allocate or write.
[[nodiscard]] HRESULT VtEngine::_InvalidCombine(const Viewport invalid) noexcept
{
if (_invalidMap)
{
_invalidMap.set(invalid.ToInclusive());
}
if (!_fInvalidRectUsed)
{
_invalidRect = invalid;

View File

@@ -17,9 +17,14 @@ 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<til::rectangle> VtEngine::GetDirtyArea()
std::vector<SMALL_RECT> VtEngine::GetDirtyArea()
{
return _invalidRects;
SMALL_RECT dirty = _invalidRect.ToInclusive();
if (dirty.Top < _virtualTop)
{
dirty.Top = _virtualTop;
}
return { dirty };
}
// Routine Description:

View File

@@ -27,7 +27,6 @@ using namespace Microsoft::Console::Types;
// If there's nothing to do, quick return
bool somethingToDo = _fInvalidRectUsed ||
!_invalidMap.empty() ||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
_cursorMoved ||
_titleChanged;
@@ -35,14 +34,6 @@ using namespace Microsoft::Console::Types;
_quickReturn = !somethingToDo;
_trace.TraceStartPaint(_quickReturn, _fInvalidRectUsed, _invalidRect, _lastViewport, _scrollDelta, _cursorMoved);
if (somethingToDo)
{
for (auto it = _invalidMap.begin_runs(); it < _invalidMap.end_runs(); ++it)
{
_invalidRects.push_back(*it);
}
}
return _quickReturn ? S_FALSE : S_OK;
}
@@ -59,8 +50,6 @@ using namespace Microsoft::Console::Types;
{
_trace.TraceEndPaint();
_invalidMap.reset_all();
_invalidRects.clear();
_invalidRect = Viewport::Empty();
_fInvalidRectUsed = false;
_scrollDelta = { 0 };
@@ -540,7 +529,7 @@ using namespace Microsoft::Console::Types;
// before we need to print new text.
_deferredCursorPos = { _lastText.X + sNumSpaces, _lastText.Y };
if (_deferredCursorPos.X < _lastViewport.RightInclusive())
if (_deferredCursorPos.X <= _lastViewport.RightInclusive())
{
RETURN_IF_FAILED(_EraseCharacter(sNumSpaces));
}

View File

@@ -36,8 +36,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
_lastWasBold(false),
_lastViewport(initialViewport),
_invalidRect(Viewport::Empty()),
_invalidMap(initialViewport.Dimensions()),
_invalidRects(),
_fInvalidRectUsed(false),
_lastRealCursor({ 0 }),
_lastText({ 0 }),
@@ -295,8 +293,6 @@ CATCH_RETURN();
if ((oldView.Height() != newView.Height()) || (oldView.Width() != newView.Width()))
{
_invalidMap.resize(_lastViewport.Dimensions());
// Don't emit a resize event if we've requested it be suppressed
if (!_suppressResizeRepaint)
{

View File

@@ -24,8 +24,6 @@ Author(s):
#include <string>
#include <functional>
#include "til/bitmap.h"
// fwdecl unittest classes
#ifdef UNIT_TESTING
namespace TerminalCoreUnitTests
@@ -91,7 +89,7 @@ namespace Microsoft::Console::Render
_Out_ FontInfo& Font,
const int iDpi) noexcept override;
std::vector<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> 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;
@@ -122,8 +120,6 @@ namespace Microsoft::Console::Render
COLORREF _LastBG;
bool _lastWasBold;
til::bitmap _invalidMap;
std::vector<til::rectangle> _invalidRects;
Microsoft::Console::Types::Viewport _lastViewport;
Microsoft::Console::Types::Viewport _invalidRect;

View File

@@ -355,7 +355,7 @@ bool WddmConEngine::IsInitialized()
return S_OK;
}
std::vector<til::rectangle> WddmConEngine::GetDirtyArea()
std::vector<SMALL_RECT> 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<til::rectangle> GetDirtyArea() override;
std::vector<SMALL_RECT> 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

@@ -45,6 +45,7 @@ public:
TEST_METHOD(TerminalInputModifierKeyTests);
TEST_METHOD(TerminalInputNullKeyTests);
TEST_METHOD(DifferentModifiersTest);
TEST_METHOD(CtrlNumTest);
wchar_t GetModifierChar(const bool fShift, const bool fAlt, const bool fCtrl)
{
@@ -511,6 +512,13 @@ void InputTest::TerminalInputModifierKeyTests()
s_pwsInputBuffer[1] = wchShifted;
fExpectedKeyHandled = true;
}
else if (ControlPressed(uiKeystate) && (vkey >= '1' && vkey <= '9'))
{
// The C-# keys get translated into very specific control
// characters that don't play nicely with this test. These keys
// are tested in the CtrlNumTest Test instead.
continue;
}
else
{
fExpectedKeyHandled = false;
@@ -684,4 +692,79 @@ void InputTest::DifferentModifiersTest()
TestKey(pInput, uiKeystate, vkey, L'/');
uiKeystate = RIGHT_ALT_PRESSED;
TestKey(pInput, uiKeystate, vkey, L'/');
// See https://github.com/microsoft/terminal/pull/4947#issuecomment-600382856
// C-? -> DEL -> 0x7f
Log::Comment(NoThrowString().Format(L"Checking C-?"));
// Use SHIFT_PRESSED to force us into differentiating between '/' and '?'
vkey = LOBYTE(VkKeyScan(L'?'));
s_pwszInputExpected = L"\x7f";
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED, vkey, L'?');
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED, vkey, L'?');
// C-M-/ -> 0x1b0x1f
Log::Comment(NoThrowString().Format(L"Checking C-M-/"));
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
vkey = LOBYTE(VkKeyScan(L'/'));
s_pwszInputExpected = L"\x1b\x1f";
TestKey(pInput, LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
TestKey(pInput, RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'/');
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
TestKey(pInput, RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'/');
// C-M-? -> 0x1b0x7f
Log::Comment(NoThrowString().Format(L"Checking C-M-?"));
uiKeystate = LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED;
vkey = LOBYTE(VkKeyScan(L'?'));
s_pwszInputExpected = L"\x1b\x7f";
TestKey(pInput, SHIFT_PRESSED | LEFT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | LEFT_ALT_PRESSED, vkey, L'?');
// LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED is skipped because that's AltGr
TestKey(pInput, SHIFT_PRESSED | RIGHT_CTRL_PRESSED | RIGHT_ALT_PRESSED, vkey, L'?');
}
void InputTest::CtrlNumTest()
{
Log::Comment(L"Starting test...");
const TerminalInput* const pInput = new TerminalInput(s_TerminalInputTestCallback);
Log::Comment(L"Sending the various Ctrl+Num keys.");
unsigned int uiKeystate = LEFT_CTRL_PRESSED;
BYTE vkey = static_cast<WORD>('1');
s_pwszInputExpected = L"1";
TestKey(pInput, uiKeystate, vkey);
Log::Comment(NoThrowString().Format(
L"Skipping Ctrl+2, since that's supposed to send NUL, and doesn't play "
L"nicely with this test. Ctrl+2 is covered by other tests in this class."));
vkey = static_cast<WORD>('3');
s_pwszInputExpected = L"\x1b";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('4');
s_pwszInputExpected = L"\x1c";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('5');
s_pwszInputExpected = L"\x1d";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('6');
s_pwszInputExpected = L"\x1e";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('7');
s_pwszInputExpected = L"\x1f";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('8');
s_pwszInputExpected = L"\x7f";
TestKey(pInput, uiKeystate, vkey);
vkey = static_cast<WORD>('9');
s_pwszInputExpected = L"9";
TestKey(pInput, uiKeystate, vkey);
}

View File

@@ -185,19 +185,40 @@ static constexpr std::array<TermKeyMap, 22> s_modifierKeyMapping{
// These sequences are not later updated to encode the modifier state in the
// sequence itself, they are just weird exceptional cases to the general
// rules above.
static constexpr std::array<TermKeyMap, 6> s_simpleModifiedKeyMapping{
static constexpr std::array<TermKeyMap, 14> s_simpleModifiedKeyMapping{
TermKeyMap{ VK_BACK, CTRL_PRESSED, L"\x8" },
TermKeyMap{ VK_BACK, ALT_PRESSED, L"\x1b\x7f" },
TermKeyMap{ VK_BACK, CTRL_PRESSED | ALT_PRESSED, L"\x1b\x8" },
TermKeyMap{ VK_TAB, CTRL_PRESSED, L"\t" },
TermKeyMap{ VK_TAB, SHIFT_PRESSED, L"\x1b[Z" },
TermKeyMap{ VK_DIVIDE, CTRL_PRESSED, L"\x1F" },
// GH#3507 - We should also be encoding Ctrl+# according to the following table:
// https://vt100.net/docs/vt220-rm/table3-5.html
// * 1 and 9 do not send any special characters, but they _should_ send
// through the character unmodified.
// * 0 doesn't seem to send even an unmodified '0' through.
// * Ctrl+2 is already special-cased below in `HandleKey`, so it's not
// included here.
TermKeyMap{ static_cast<WORD>('1'), CTRL_PRESSED, L"1" },
// TermKeyMap{ static_cast<WORD>('2'), CTRL_PRESSED, L"\x00" },
TermKeyMap{ static_cast<WORD>('3'), CTRL_PRESSED, L"\x1B" },
TermKeyMap{ static_cast<WORD>('4'), CTRL_PRESSED, L"\x1C" },
TermKeyMap{ static_cast<WORD>('5'), CTRL_PRESSED, L"\x1D" },
TermKeyMap{ static_cast<WORD>('6'), CTRL_PRESSED, L"\x1E" },
TermKeyMap{ static_cast<WORD>('7'), CTRL_PRESSED, L"\x1F" },
TermKeyMap{ static_cast<WORD>('8'), CTRL_PRESSED, L"\x7F" },
TermKeyMap{ static_cast<WORD>('9'), CTRL_PRESSED, L"9" },
// These two are not implemented here, because they are system keys.
// TermKeyMap{ VK_TAB, ALT_PRESSED, L""}, This is the Windows system shortcut for switching windows.
// TermKeyMap{ VK_ESCAPE, ALT_PRESSED, L""}, This is another Windows system shortcut for switching windows.
};
const wchar_t* const CTRL_SLASH_SEQUENCE = L"\x1f";
const wchar_t* const CTRL_QUESTIONMARK_SEQUENCE = L"\x7F";
const wchar_t* const CTRL_ALT_SLASH_SEQUENCE = L"\x1b\x1f";
const wchar_t* const CTRL_ALT_QUESTIONMARK_SEQUENCE = L"\x1b\x7F";
void TerminalInput::ChangeKeypadMode(const bool applicationMode) noexcept
{
@@ -323,13 +344,73 @@ static bool _searchWithModifier(const KeyEvent& keyEvent, InputSender sender)
}
else
{
// One last check: C-/ is supposed to be C-_
// But '/' is not the same VKEY on all keyboards. So we have to
// figure out the vkey at runtime.
const BYTE slashVkey = LOBYTE(VkKeyScan(L'/'));
if (keyEvent.GetVirtualKeyCode() == slashVkey && keyEvent.IsCtrlPressed())
// One last check:
// * C-/ is supposed to be ^_ (the C0 character US)
// * C-? is supposed to be DEL
// * C-M-/ is supposed to be ^[^_
// * C-M-? is supposed to be ^[^?
//
// But this whole scenario is tricky. '/' is not the same VKEY on
// all keyboards. On USASCII keyboards, '/' and '?' share the _same_
// key. So we have to figure out the vkey at runtime, and we have to
// determine if the key that was pressed was '?' with some
// modifiers, or '/' with some modifiers.
//
// These translations are not in s_simpleModifiedKeyMapping, because
// the aformentioned fact that they aren't the same VKEY on all
// keyboards.
//
// See GH#3079 for details.
// Also see https://github.com/microsoft/terminal/pull/4947#issuecomment-600382856
// VkKeyScan will give us both the Vkey of the key needed for this
// character, and the modifiers the user might need to press to get
// this character.
const auto slashKeyScan = VkKeyScan(L'/'); // On USASCII: 0x00bf
const auto questionMarkKeyScan = VkKeyScan(L'?'); //On USASCII: 0x01bf
const auto slashVkey = LOBYTE(slashKeyScan);
const auto questionMarkVkey = LOBYTE(questionMarkKeyScan);
const auto ctrl = keyEvent.IsCtrlPressed();
const auto alt = keyEvent.IsAltPressed();
const bool shift = keyEvent.IsShiftPressed();
// From the KeyEvent we're translating, synthesize the equivalent VkKeyScan result
const auto vkey = keyEvent.GetVirtualKeyCode();
const short keyScanFromEvent = vkey |
(shift ? 0x100 : 0) |
(ctrl ? 0x200 : 0) |
(alt ? 0x400 : 0);
// Make sure the VKEY is an _exact_ match, and that the modifier
// bits also match. This handles the hypothetical case we get a
// keyscan back that's ctrl+alt+some_random_VK, and some_random_VK
// has bits that are a superset of the bits set for question mark.
const bool wasQuestionMark = vkey == questionMarkVkey && WI_AreAllFlagsSet(keyScanFromEvent, questionMarkKeyScan);
const bool wasSlash = vkey == slashVkey && WI_AreAllFlagsSet(keyScanFromEvent, slashKeyScan);
// If the key pressed was exactly the ? key, then try to send the
// appropriate sequence for a modified '?'. Otherwise, check if this
// was a modified '/' keypress. These mappings don't need to be
// changed at all.
if ((ctrl && alt) && wasQuestionMark)
{
sender(CTRL_ALT_QUESTIONMARK_SEQUENCE);
success = true;
}
else if (ctrl && wasQuestionMark)
{
sender(CTRL_QUESTIONMARK_SEQUENCE);
success = true;
}
else if ((ctrl && alt) && wasSlash)
{
sender(CTRL_ALT_SLASH_SEQUENCE);
success = true;
}
else if (ctrl && wasSlash)
{
// This mapping doesn't need to be changed at all.
sender(CTRL_SLASH_SEQUENCE);
success = true;
}

View File

@@ -1167,8 +1167,13 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
_trace.TraceCharInput(wch);
// Process "from anywhere" events first.
if (wch == AsciiChars::CAN ||
wch == AsciiChars::SUB)
const bool isFromAnywhereChar = (wch == AsciiChars::CAN || wch == AsciiChars::SUB);
// GH#4201 - If this sequence was ^[^X or ^[^Z, then we should
// _ActionExecuteFromEscape, as to send a Ctrl+Alt+key key. We should only
// do this for the InputStateMachineEngine - the OutputEngine should execute
// these from any state.
if (isFromAnywhereChar && !(_state == VTStates::Escape && _engine->DispatchControlCharsFromEscape()))
{
_ActionExecute(wch);
_EnterGround();

View File

@@ -259,6 +259,7 @@ class Microsoft::Console::VirtualTerminal::InputEngineTest
TEST_METHOD(SGRMouseTest_Modifiers);
TEST_METHOD(SGRMouseTest_Movement);
TEST_METHOD(SGRMouseTest_Scroll);
TEST_METHOD(CtrlAltZCtrlAltXTest);
friend class TestInteractDispatch;
};
@@ -1141,3 +1142,68 @@ void InputEngineTest::SGRMouseTest_Scroll()
// clang-format on
VerifySGRMouseData(testData);
}
void InputEngineTest::CtrlAltZCtrlAltXTest()
{
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
VERIFY_IS_NOT_NULL(_stateMachine);
testState._stateMachine = _stateMachine.get();
// This is a test for GH#4201. See that issue for more details.
Log::Comment(L"Test Ctrl+Alt+Z and Ctrl+Alt+X, which execute from anywhere "
L"in the output engine, but should be Escape-Executed in the "
L"input engine.");
DisableVerifyExceptions disable;
{
auto inputSeq = L"\x1b\x1a"; // ^[^Z
wchar_t expectedWch = L'Z';
short keyscan = VkKeyScanW(expectedWch);
short vkey = keyscan & 0xff;
WORD scanCode = (WORD)MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC);
INPUT_RECORD inputRec;
inputRec.EventType = KEY_EVENT;
inputRec.Event.KeyEvent.bKeyDown = TRUE;
inputRec.Event.KeyEvent.dwControlKeyState = LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED;
inputRec.Event.KeyEvent.wRepeatCount = 1;
inputRec.Event.KeyEvent.wVirtualKeyCode = vkey;
inputRec.Event.KeyEvent.wVirtualScanCode = scanCode;
inputRec.Event.KeyEvent.uChar.UnicodeChar = expectedWch - 0x40;
testState.vExpectedInput.push_back(inputRec);
_stateMachine->ProcessString(inputSeq);
}
{
auto inputSeq = L"\x1b\x18"; // ^[^X
wchar_t expectedWch = L'X';
short keyscan = VkKeyScanW(expectedWch);
short vkey = keyscan & 0xff;
WORD scanCode = (WORD)MapVirtualKeyW(vkey, MAPVK_VK_TO_VSC);
INPUT_RECORD inputRec;
inputRec.EventType = KEY_EVENT;
inputRec.Event.KeyEvent.bKeyDown = TRUE;
inputRec.Event.KeyEvent.dwControlKeyState = LEFT_ALT_PRESSED | LEFT_CTRL_PRESSED;
inputRec.Event.KeyEvent.wRepeatCount = 1;
inputRec.Event.KeyEvent.wVirtualKeyCode = vkey;
inputRec.Event.KeyEvent.wVirtualScanCode = scanCode;
inputRec.Event.KeyEvent.uChar.UnicodeChar = expectedWch - 0x40;
testState.vExpectedInput.push_back(inputRec);
_stateMachine->ProcessString(inputSeq);
}
VerifyExpectedInputDrained();
}

View File

@@ -1,134 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "til/bitmap.h"
using namespace WEX::Common;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
class BitmapTests
{
TEST_CLASS(BitmapTests);
TEST_METHOD(Construct)
{
COORD foo;
foo.X = 12;
foo.Y = 14;
til::point p(foo);
VERIFY_ARE_EQUAL(foo.X, p.x());
VERIFY_ARE_EQUAL(foo.Y, p.y());
POINT pt;
pt.x = 88;
pt.y = 98;
til::point q(pt);
VERIFY_ARE_EQUAL(pt.x, q.x());
VERIFY_ARE_EQUAL(pt.y, q.y());
SIZE sz;
sz.cx = 11;
sz.cy = 13;
til::size r(sz);
VERIFY_ARE_EQUAL(sz.cx, r.width());
VERIFY_ARE_EQUAL(sz.cy, r.height());
COORD bar;
bar.X = 57;
bar.Y = 15;
til::size s(bar);
VERIFY_ARE_EQUAL(bar.X, s.width());
VERIFY_ARE_EQUAL(bar.Y, s.height());
SIZE mapSize{ 10, 10 };
til::bitmap x(mapSize);
x.set({ 4, 4 });
til::rectangle area(til::point{ 5, 5 }, til::size{ 2, 2 });
x.set(area);
Log::Comment(L"Row 4!");
for (auto it = x.begin_row(4); it < x.end_row(4); ++it)
{
if (*it)
{
Log::Comment(L"True");
}
else
{
Log::Comment(L"False");
}
}
Log::Comment(L"All!");
auto start = x.begin();
auto end = x.end();
for (const auto& y : x)
{
if (y)
{
Log::Comment(L"True");
}
else
{
Log::Comment(L"False");
}
}
SMALL_RECT smrc;
smrc.Top = 31;
smrc.Bottom = 41;
smrc.Left = 59;
smrc.Right = 265;
til::rectangle smrectangle(smrc);
VERIFY_ARE_EQUAL(smrc.Top, smrectangle.top());
VERIFY_ARE_EQUAL(smrc.Bottom, smrectangle.bottom());
VERIFY_ARE_EQUAL(smrc.Left, smrectangle.left());
VERIFY_ARE_EQUAL(smrc.Right, smrectangle.right());
RECT bgrc;
bgrc.top = 3;
bgrc.bottom = 5;
bgrc.left = 8;
bgrc.right = 9;
til::rectangle bgrectangle(bgrc);
VERIFY_ARE_EQUAL(bgrc.top, bgrectangle.top());
VERIFY_ARE_EQUAL(bgrc.bottom, bgrectangle.bottom());
VERIFY_ARE_EQUAL(bgrc.left, bgrectangle.left());
VERIFY_ARE_EQUAL(bgrc.right, bgrectangle.right());
}
TEST_METHOD(Runerator)
{
til::bitmap foo{ til::size{ 4, 8 } };
foo.reset_all();
foo.set(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 2 } });
foo.set(til::rectangle{ til::point{ 3, 5 } });
foo.set(til::rectangle{ til::point{ 0, 6 } });
std::deque<til::rectangle> expectedRects;
expectedRects.push_back(til::rectangle{ til::point{ 1, 1 }, til::size{ 2, 1 } });
expectedRects.push_back(til::rectangle{ til::point{ 1, 2 }, til::size{ 2, 1 } });
expectedRects.push_back(til::rectangle{ til::point{ 3, 5 } });
expectedRects.push_back(til::rectangle{ til::point{ 0, 6 } });
for (auto it = foo.begin_runs(); it < foo.end_runs(); ++it)
{
const auto actual = *it;
const auto expected = expectedRects.front();
VERIFY_ARE_EQUAL(expected, actual);
expectedRects.pop_front();
}
}
};

View File

@@ -1,171 +0,0 @@
// 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(RectangleAdditionSize)
{
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(RectangleInplaceAdditionSize)
{
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(RectangleSubtractionSize)
{
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(RectangleInplaceSubtractionSize)
{
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);
}
}
};

View File

@@ -168,21 +168,6 @@ class SizeTests
}
}
TEST_METHOD(Boolean)
{
const til::size empty;
VERIFY_IS_FALSE(empty);
const til::size heightOnly{ 0, 10 };
VERIFY_IS_TRUE(heightOnly);
const til::size widthOnly{ 10, 0 };
VERIFY_IS_TRUE(widthOnly);
const til::size both{ 10, 10 };
VERIFY_IS_TRUE(both);
}
TEST_METHOD(Addition)
{
Log::Comment(L"0.) Addition of two things that should be in bounds.");

View File

@@ -10,8 +10,6 @@
</PropertyGroup>
<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

@@ -4,12 +4,10 @@
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="BitmapTests.cpp" />
<ClCompile Include="SomeTests.cpp" />
<ClCompile Include="..\precomp.cpp" />
<ClCompile Include="u8u16convertTests.cpp" />
<ClCompile Include="SizeTests.cpp" />
<ClCompile Include="OperatorTests.cpp" />
<ClCompile Include="ColorTests.cpp" />
<ClCompile Include="PointTests.cpp" />
<ClCompile Include="RectangleTests.cpp" />

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
#include "precomp.h"
#include "WexTestClass.h"
using namespace WEX::Common;
using namespace WEX::Logging;