mirror of
https://github.com/microsoft/terminal.git
synced 2026-04-07 23:01:09 +00:00
Compare commits
21 Commits
dev/duhowe
...
dev/miniks
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7649fc8a06 | ||
|
|
343ff0913d | ||
|
|
39d67e3859 | ||
|
|
1ae4252a7b | ||
|
|
2872f147f8 | ||
|
|
28d108bf32 | ||
|
|
91b84f185e | ||
|
|
dfc15780c7 | ||
|
|
ef80f665d3 | ||
|
|
d7123d571b | ||
|
|
5e9adad2a8 | ||
|
|
8c4ca4683b | ||
|
|
5bcf0fc3de | ||
|
|
d47da2d617 | ||
|
|
31efd69149 | ||
|
|
2c55ca107f | ||
|
|
b3fa88eaed | ||
|
|
9e8a716f03 | ||
|
|
78c1bc10f7 | ||
|
|
b46b5d0f6f | ||
|
|
7e8f0398c3 |
@@ -72,12 +72,25 @@ Assuming that you've installed Git Bash into `C:/Program Files/Git`:
|
||||
```json
|
||||
{
|
||||
"name" : "Git Bash",
|
||||
"commandline" : "C:/Program Files/Git/bin/bash.exe",
|
||||
"commandline" : "C:/Program Files/Git/bin/bash.exe -li",
|
||||
"icon" : "C:/Program Files/Git/mingw64/share/git/git-for-windows.ico",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
````
|
||||
|
||||
## Git Bash (WOW64)
|
||||
|
||||
Assuming that you've installed Git Bash into `C:/Program Files (x86)/Git`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "Git Bash",
|
||||
"commandline" : "%ProgramFiles(x86)%/Git/bin/bash.exe -li",
|
||||
"icon" : "%ProgramFiles(x86)%/Git/mingw32/share/git/git-for-windows.ico",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
```
|
||||
|
||||
## MSYS2
|
||||
|
||||
Assuming that you've installed MSYS2 into `C:/msys64`:
|
||||
@@ -91,4 +104,16 @@ Assuming that you've installed MSYS2 into `C:/msys64`:
|
||||
}
|
||||
````
|
||||
|
||||
## Developer Command Prompt for Visual Studio
|
||||
|
||||
Assuming that you've installed VS 2019 Professional:
|
||||
|
||||
```json
|
||||
{
|
||||
"name" : "Developer Command Prompt for VS 2019",
|
||||
"commandline" : "cmd.exe /k \"C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/Common7/Tools/VsDevCmd.bat\"",
|
||||
"startingDirectory" : "%USERPROFILE%"
|
||||
}
|
||||
```
|
||||
|
||||
<!-- Adding a tool here? Make sure to add it in alphabetical order! -->
|
||||
|
||||
@@ -107,6 +107,22 @@ add the following to your keybindings:
|
||||
This will _unbind_ <kbd>Ctrl+Shift+6</kbd>, allowing vim to use the keystroke
|
||||
instead of the terminal.
|
||||
|
||||
### Binding multiple keys
|
||||
|
||||
You can have multiple key chords bound to the same action. To do this, simply
|
||||
add multiple bindings for the same action. For example:
|
||||
|
||||
```json
|
||||
"keybindings" :
|
||||
[
|
||||
{ "command": "copy", "keys": "ctrl+shift+c" },
|
||||
{ "command": "copy", "keys": "ctrl+c" },
|
||||
{ "command": "copy", "keys": "enter" }
|
||||
]
|
||||
```
|
||||
|
||||
In this snippet, all three of <kbd>ctrl+shift+c</kbd>, <kbd>ctrl+c</kbd> and <kbd>enter</kbd> are bound to `copy`.
|
||||
|
||||
## Profiles
|
||||
|
||||
A profile contains the settings applied when a new WT tab is opened. Each
|
||||
|
||||
@@ -70,6 +70,10 @@ namespace TerminalAppLocalTests
|
||||
|
||||
TEST_METHOD(TestTerminalArgsForBinding);
|
||||
|
||||
TEST_METHOD(FindMissingProfile);
|
||||
TEST_METHOD(MakeSettingsForProfileThatDoesntExist);
|
||||
TEST_METHOD(MakeSettingsForDefaultProfileThatDoesntExist);
|
||||
|
||||
TEST_METHOD(TestLayerProfileOnColorScheme);
|
||||
|
||||
TEST_METHOD(ValidateKeybindingsWarnings);
|
||||
@@ -2094,6 +2098,146 @@ namespace TerminalAppLocalTests
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::FindMissingProfile()
|
||||
{
|
||||
// Test that CascadiaSettings::FindProfile returns null for a GUID that
|
||||
// doesn't exist
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}"
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}"
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
const Profile* const profile1 = settings->FindProfile(guid1);
|
||||
const Profile* const profile2 = settings->FindProfile(guid2);
|
||||
const Profile* const profile3 = settings->FindProfile(guid3);
|
||||
|
||||
VERIFY_IS_NOT_NULL(profile1);
|
||||
VERIFY_IS_NOT_NULL(profile2);
|
||||
VERIFY_IS_NULL(profile3);
|
||||
|
||||
VERIFY_ARE_EQUAL(L"profile0", profile1->GetName());
|
||||
VERIFY_ARE_EQUAL(L"profile1", profile2->GetName());
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings throws when the GUID doesn't exist
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = settings->BuildSettings(guid1);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(1, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
auto terminalSettings = settings->BuildSettings(guid2);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, terminalSettings);
|
||||
VERIFY_ARE_EQUAL(2, terminalSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
|
||||
VERIFY_THROWS(auto terminalSettings = settings->BuildSettings(guid3), wil::ResultException, L"This call to BuildSettings should fail");
|
||||
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::MakeSettingsForDefaultProfileThatDoesntExist()
|
||||
{
|
||||
// Test that MakeSettings _doesnt_ throw when we load settings with a
|
||||
// defaultProfile that's not in the list, we validate the settings, and
|
||||
// then call MakeSettings(nullopt). The validation should ensure that
|
||||
// the default profile is something reasonable
|
||||
const std::string settingsString{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-3333-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
const auto settingsJsonObj = VerifyParseSucceeded(settingsString);
|
||||
auto settings = CascadiaSettings::FromJson(settingsJsonObj);
|
||||
settings->_ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(2u, settings->_warnings.size());
|
||||
VERIFY_ARE_EQUAL(2u, settings->_profiles.size());
|
||||
VERIFY_ARE_EQUAL(settings->_globals.GetDefaultProfile(), settings->_profiles.at(0).GetGuid());
|
||||
try
|
||||
{
|
||||
const auto [guid, termSettings] = settings->BuildSettings(nullptr);
|
||||
VERIFY_ARE_NOT_EQUAL(nullptr, termSettings);
|
||||
VERIFY_ARE_EQUAL(1, termSettings.HistorySize());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
VERIFY_IS_TRUE(false, L"This call to BuildSettings should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestLayerProfileOnColorScheme()
|
||||
{
|
||||
Log::Comment(NoThrowString().Format(
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
using namespace WEX::Common;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
@@ -53,11 +55,20 @@ namespace TerminalAppLocalTests
|
||||
TEST_METHOD(CreateSimpleTerminalXamlType);
|
||||
TEST_METHOD(CreateTerminalMuxXamlType);
|
||||
|
||||
TEST_METHOD(CreateTerminalPage);
|
||||
|
||||
TEST_METHOD(TryDuplicateBadTab);
|
||||
TEST_METHOD(TryDuplicateBadPane);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
std::shared_ptr<CascadiaSettings> initialSettings);
|
||||
};
|
||||
|
||||
void TabTests::EnsureTestsActivate()
|
||||
@@ -164,4 +175,321 @@ namespace TerminalAppLocalTests
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::CreateTerminalPage()
|
||||
{
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to set up a TerminalPage for a unittest. This method
|
||||
// does a couple things:
|
||||
// * Create()'s a TerminalPage with the given settings. Constructing a
|
||||
// TerminalPage so that we can get at its implementation is wacky, so
|
||||
// this helper will do it correctly for you, even if this doesn't make a
|
||||
// ton of sense on the surface. This is also why you need to pass both a
|
||||
// projection and a com_ptr to this method.
|
||||
// * It will use the provided settings object to initialize the TerminalPage
|
||||
// * It will add the TerminalPage to the test Application, so that we can
|
||||
// get actual layout events. Much of the Terminal assumes there's a
|
||||
// non-zero ActualSize to the Terminal window, and adding the Page to
|
||||
// the Application will make it behave as expected.
|
||||
// * It will wait for the TerminalPage to finish initialization before
|
||||
// returning control to the caller. It does this by creating an event and
|
||||
// only setting the event when the TerminalPage raises its Initialized
|
||||
// event, to signal that startup is complete. At this point, there will
|
||||
// be one tab with the default profile in the page.
|
||||
// * It will also ensure that the first tab is focused, since that happens
|
||||
// asynchronously in the application typically.
|
||||
// Arguments:
|
||||
// - page: a TerminalPage implementation ptr that will receive the new TerminalPage instance
|
||||
// - initialSettings: a CascadiaSettings to initialize the TerminalPage with.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
|
||||
std::shared_ptr<CascadiaSettings> initialSettings)
|
||||
{
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::TerminalApp::TerminalPage projectedPage{ nullptr };
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Construct the TerminalPage"));
|
||||
auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() {
|
||||
projectedPage = winrt::TerminalApp::TerminalPage();
|
||||
page.copy_from(winrt::get_self<winrt::TerminalApp::implementation::TerminalPage>(projectedPage));
|
||||
page->_settings = initialSettings;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
VERIFY_IS_NOT_NULL(page->_settings);
|
||||
|
||||
::details::Event waitForInitEvent;
|
||||
if (!waitForInitEvent.IsValid())
|
||||
{
|
||||
VERIFY_SUCCEEDED(HRESULT_FROM_WIN32(::GetLastError()));
|
||||
}
|
||||
page->Initialized([&waitForInitEvent](auto&&, auto&&) {
|
||||
waitForInitEvent.Set();
|
||||
});
|
||||
|
||||
Log::Comment(L"Create() the TerminalPage");
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
VERIFY_IS_NOT_NULL(page);
|
||||
VERIFY_IS_NOT_NULL(page->_settings);
|
||||
page->Create();
|
||||
Log::Comment(L"Create()'d the page successfully");
|
||||
|
||||
auto app = ::winrt::Windows::UI::Xaml::Application::Current();
|
||||
|
||||
winrt::TerminalApp::TerminalPage pp = *page;
|
||||
winrt::Windows::UI::Xaml::Window::Current().Content(pp);
|
||||
winrt::Windows::UI::Xaml::Window::Current().Activate();
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Wait for the page to finish initializing...");
|
||||
VERIFY_SUCCEEDED(waitForInitEvent.Wait());
|
||||
Log::Comment(L"...Done");
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
// In the real app, this isn't a problem, but doesn't happen
|
||||
// reliably in the unit tests.
|
||||
Log::Comment(L"Ensure we set the first tab as the selected one.");
|
||||
auto tab{ page->_GetStrongTabImpl(0) };
|
||||
page->_tabView.SelectedItem(tab->GetTabViewItem());
|
||||
page->_UpdatedSelectedTab(0);
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadTab()
|
||||
{
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _DuplicateTabViewItem on tab 1
|
||||
// * No new tab should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Duplicate the first tab");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(L"Duplicate the tab, and don't crash");
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_DuplicateTabViewItem();
|
||||
VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
}
|
||||
|
||||
void TabTests::TryDuplicateBadPane()
|
||||
{
|
||||
// * Create a tab with a profile with GUID 1
|
||||
// * Reload the settings so that GUID 1 is no longer in the list of profiles
|
||||
// * Try calling _SplitPane(Duplicate) on tab 1
|
||||
// * No new pane should be created (and more importantly, the app should not crash)
|
||||
//
|
||||
// Created to test GH#2455
|
||||
|
||||
const std::string settingsJson0{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile0",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1
|
||||
},
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
const std::string settingsJson1{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name" : "profile1",
|
||||
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2
|
||||
}
|
||||
]
|
||||
})" };
|
||||
|
||||
VerifyParseSucceeded(settingsJson0);
|
||||
auto settings0 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings0);
|
||||
settings0->_ParseJsonString(settingsJson0, false);
|
||||
settings0->LayerJson(settings0->_userSettings);
|
||||
settings0->_ValidateSettings();
|
||||
|
||||
VerifyParseSucceeded(settingsJson1);
|
||||
auto settings1 = std::make_shared<CascadiaSettings>(false);
|
||||
VERIFY_IS_NOT_NULL(settings1);
|
||||
settings1->_ParseJsonString(settingsJson1, false);
|
||||
settings1->LayerJson(settings1->_userSettings);
|
||||
settings1->_ValidateSettings();
|
||||
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
// This is super wacky, but we can't just initialize the
|
||||
// com_ptr<impl::TerminalPage> in the lambda and assign it back out of
|
||||
// the lambda. We'll crash trying to get a weak_ref to the TerminalPage
|
||||
// during TerminalPage::Create() below.
|
||||
//
|
||||
// Instead, create the winrt object, then get a com_ptr to the
|
||||
// implementation _from_ the winrt object. This seems to work, even if
|
||||
// it's weird.
|
||||
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
|
||||
_initializeTerminalPage(page, settings0);
|
||||
|
||||
auto result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
result = RunOnUIThread([&page]() {
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(1, tab->_GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the first pane"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2, tab->_GetLeafPaneCount());
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
L"Change the settings of the TerminalPage so the first profile is "
|
||||
L"no longer in the list of profiles"));
|
||||
result = RunOnUIThread([&page, settings1]() {
|
||||
page->_settings = settings1;
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash"));
|
||||
result = RunOnUIThread([&page]() {
|
||||
page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr);
|
||||
|
||||
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
|
||||
auto tab = page->_GetStrongTabImpl(0);
|
||||
VERIFY_ARE_EQUAL(2,
|
||||
tab->_GetLeafPaneCount(),
|
||||
L"We should gracefully do nothing here - the profile no longer exists.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
|
||||
auto cleanup = wil::scope_exit([] {
|
||||
auto result = RunOnUIThread([]() {
|
||||
// There's something causing us to crash north of
|
||||
// TSFInputControl::NotifyEnter, or LayoutRequested. It's very
|
||||
// unclear what that issue is. Since these tests don't run in
|
||||
// CI, simply log a message so that the dev running these tests
|
||||
// knows it's expected.
|
||||
Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay.");
|
||||
});
|
||||
VERIFY_SUCCEEDED(result);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
147
src/cascadia/TerminalApp/DebugTapConnection.cpp
Normal file
147
src/cascadia/TerminalApp/DebugTapConnection.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "DebugTapConnection.h"
|
||||
|
||||
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
|
||||
using namespace ::winrt::Windows::Foundation;
|
||||
namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
// DebugInputTapConnection is an implementation detail of DebugTapConnection.
|
||||
// It wraps the _actual_ connection so it can hook WriteInput and forward it
|
||||
// into the actual debug panel.
|
||||
class DebugInputTapConnection : public winrt::implements<DebugInputTapConnection, ITerminalConnection>
|
||||
{
|
||||
public:
|
||||
DebugInputTapConnection(winrt::com_ptr<DebugTapConnection> pairedTap, ITerminalConnection wrappedConnection) :
|
||||
_pairedTap{ pairedTap },
|
||||
_wrappedConnection{ std::move(wrappedConnection) }
|
||||
{
|
||||
}
|
||||
~DebugInputTapConnection() = default;
|
||||
void Start()
|
||||
{
|
||||
_wrappedConnection.Start();
|
||||
}
|
||||
void WriteInput(hstring const& data)
|
||||
{
|
||||
_pairedTap->_PrintInput(data);
|
||||
_wrappedConnection.WriteInput(data);
|
||||
}
|
||||
void Resize(uint32_t rows, uint32_t columns) { _wrappedConnection.Resize(rows, columns); }
|
||||
void Close() { _wrappedConnection.Close(); }
|
||||
winrt::event_token TerminalOutput(TerminalOutputHandler const& args) { return _wrappedConnection.TerminalOutput(args); };
|
||||
void TerminalOutput(winrt::event_token const& token) noexcept { _wrappedConnection.TerminalOutput(token); };
|
||||
winrt::event_token StateChanged(TypedEventHandler<ITerminalConnection, IInspectable> const& handler) { return _wrappedConnection.StateChanged(handler); };
|
||||
void StateChanged(winrt::event_token const& token) noexcept { _wrappedConnection.StateChanged(token); };
|
||||
ConnectionState State() const noexcept { return _wrappedConnection.State(); }
|
||||
|
||||
private:
|
||||
winrt::com_ptr<DebugTapConnection> _pairedTap;
|
||||
ITerminalConnection _wrappedConnection;
|
||||
};
|
||||
|
||||
DebugTapConnection::DebugTapConnection(ITerminalConnection wrappedConnection)
|
||||
{
|
||||
_outputRevoker = wrappedConnection.TerminalOutput(winrt::auto_revoke, { this, &DebugTapConnection::_OutputHandler });
|
||||
_stateChangedRevoker = wrappedConnection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*e*/) {
|
||||
_StateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
_wrappedConnection = wrappedConnection;
|
||||
}
|
||||
|
||||
DebugTapConnection::~DebugTapConnection()
|
||||
{
|
||||
}
|
||||
|
||||
void DebugTapConnection::Start()
|
||||
{
|
||||
// presume the wrapped connection is started.
|
||||
}
|
||||
|
||||
void DebugTapConnection::WriteInput(hstring const& data)
|
||||
{
|
||||
// If the user types into the tap side, forward it to the input side
|
||||
if (auto strongInput{ _inputSide.get() })
|
||||
{
|
||||
auto inputAsTap{ winrt::get_self<DebugInputTapConnection>(strongInput) };
|
||||
inputAsTap->WriteInput(data);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugTapConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/)
|
||||
{
|
||||
// no resize events are propagated
|
||||
}
|
||||
|
||||
void DebugTapConnection::Close()
|
||||
{
|
||||
_outputRevoker.revoke();
|
||||
_stateChangedRevoker.revoke();
|
||||
_wrappedConnection = nullptr;
|
||||
}
|
||||
|
||||
ConnectionState DebugTapConnection::State() const noexcept
|
||||
{
|
||||
if (auto strongConnection{ _wrappedConnection.get() })
|
||||
{
|
||||
return strongConnection.State();
|
||||
}
|
||||
return ConnectionState::Failed;
|
||||
}
|
||||
|
||||
static std::wstring _sanitizeString(const std::wstring_view str)
|
||||
{
|
||||
std::wstring newString{ str.begin(), str.end() };
|
||||
for (auto& ch : newString)
|
||||
{
|
||||
if (ch < 0x20)
|
||||
{
|
||||
ch += 0x2400;
|
||||
}
|
||||
else if (ch == 0x20)
|
||||
{
|
||||
ch = 0x2423; // replace space with ␣
|
||||
}
|
||||
else if (ch == 0x7f)
|
||||
{
|
||||
ch = 0x2421; // replace del with ␡
|
||||
}
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
void DebugTapConnection::_OutputHandler(const hstring str)
|
||||
{
|
||||
_TerminalOutputHandlers(_sanitizeString(str));
|
||||
}
|
||||
|
||||
// Called by the DebugInputTapConnection to print user input
|
||||
void DebugTapConnection::_PrintInput(const hstring& str)
|
||||
{
|
||||
auto clean{ _sanitizeString(str) };
|
||||
auto formatted{ wil::str_printf<std::wstring>(L"\x1b[91m%ls\x1b[m", clean.data()) };
|
||||
_TerminalOutputHandlers(formatted);
|
||||
}
|
||||
|
||||
// Wire us up so that we can forward input through
|
||||
void DebugTapConnection::SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap)
|
||||
{
|
||||
_inputSide = inputTap;
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description
|
||||
// - Takes one connection and returns two connections:
|
||||
// 1. One that can be used in place of the original connection (wrapped)
|
||||
// 2. One that will print raw VT sequences sent into and received _from_ the original connection.
|
||||
std::tuple<ITerminalConnection, ITerminalConnection> OpenDebugTapConnection(ITerminalConnection baseConnection)
|
||||
{
|
||||
using namespace winrt::Microsoft::TerminalApp::implementation;
|
||||
auto debugSide{ winrt::make_self<DebugTapConnection>(baseConnection) };
|
||||
auto inputSide{ winrt::make_self<DebugInputTapConnection>(debugSide, baseConnection) };
|
||||
debugSide->SetInputTap(*inputSide);
|
||||
std::tuple<ITerminalConnection, ITerminalConnection> p{ *inputSide, *debugSide };
|
||||
return p;
|
||||
}
|
||||
42
src/cascadia/TerminalApp/DebugTapConnection.h
Normal file
42
src/cascadia/TerminalApp/DebugTapConnection.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
|
||||
#include "../../inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::Microsoft::TerminalApp::implementation
|
||||
{
|
||||
class DebugInputTapConnection;
|
||||
class DebugTapConnection : public winrt::implements<DebugTapConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection>
|
||||
{
|
||||
public:
|
||||
DebugTapConnection(Microsoft::Terminal::TerminalConnection::ITerminalConnection wrappedConnection);
|
||||
~DebugTapConnection();
|
||||
void Start();
|
||||
void WriteInput(hstring const& data);
|
||||
void Resize(uint32_t rows, uint32_t columns);
|
||||
void Close();
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
|
||||
|
||||
void SetInputTap(const Microsoft::Terminal::TerminalConnection::ITerminalConnection& inputTap);
|
||||
|
||||
WINRT_CALLBACK(TerminalOutput, winrt::Microsoft::Terminal::TerminalConnection::TerminalOutputHandler);
|
||||
|
||||
TYPED_EVENT(StateChanged, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
private:
|
||||
void _PrintInput(const hstring& data);
|
||||
void _OutputHandler(const hstring str);
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::TerminalOutput_revoker _outputRevoker;
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection::StateChanged_revoker _stateChangedRevoker;
|
||||
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _wrappedConnection;
|
||||
winrt::weak_ref<Microsoft::Terminal::TerminalConnection::ITerminalConnection> _inputSide;
|
||||
|
||||
friend class DebugInputTapConnection;
|
||||
};
|
||||
}
|
||||
|
||||
std::tuple<winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection> OpenDebugTapConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection baseConnection);
|
||||
@@ -41,6 +41,14 @@ static constexpr std::wstring_view LightThemeValue{ L"light" };
|
||||
static constexpr std::wstring_view DarkThemeValue{ L"dark" };
|
||||
static constexpr std::wstring_view SystemThemeValue{ L"system" };
|
||||
|
||||
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
|
||||
|
||||
#ifdef _DEBUG
|
||||
static constexpr bool debugFeaturesDefault{ true };
|
||||
#else
|
||||
static constexpr bool debugFeaturesDefault{ false };
|
||||
#endif
|
||||
|
||||
GlobalAppSettings::GlobalAppSettings() :
|
||||
_keybindings{ winrt::make_self<winrt::TerminalApp::implementation::AppKeyBindings>() },
|
||||
_keybindingsWarnings{},
|
||||
@@ -59,7 +67,8 @@ GlobalAppSettings::GlobalAppSettings() :
|
||||
_tabWidthMode{ TabViewWidthMode::Equal },
|
||||
_wordDelimiters{ DEFAULT_WORD_DELIMITERS },
|
||||
_copyOnSelect{ false },
|
||||
_launchMode{ LaunchMode::DefaultMode }
|
||||
_launchMode{ LaunchMode::DefaultMode },
|
||||
_debugFeatures{ debugFeaturesDefault }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -171,6 +180,11 @@ void GlobalAppSettings::SetConfirmCloseAllTabs(const bool confirmCloseAllTabs) n
|
||||
_confirmCloseAllTabs = confirmCloseAllTabs;
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::DebugFeaturesEnabled() const noexcept
|
||||
{
|
||||
return _debugFeatures;
|
||||
}
|
||||
|
||||
#pragma region ExperimentalSettings
|
||||
bool GlobalAppSettings::GetShowTabsInTitlebar() const noexcept
|
||||
{
|
||||
@@ -237,6 +251,7 @@ Json::Value GlobalAppSettings::ToJson() const
|
||||
jsonObject[JsonKey(KeybindingsKey)] = _keybindings->ToJson();
|
||||
jsonObject[JsonKey(ConfirmCloseAllKey)] = _confirmCloseAllTabs;
|
||||
jsonObject[JsonKey(SnapToGridOnResizeKey)] = _SnapToGridOnResize;
|
||||
jsonObject[JsonKey(DebugFeaturesKey)] = _debugFeatures;
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -324,6 +339,9 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
||||
}
|
||||
|
||||
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
|
||||
|
||||
// GetBool will only override the current value if the key exists
|
||||
JsonUtils::GetBool(json, DebugFeaturesKey, _debugFeatures);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -77,6 +77,8 @@ public:
|
||||
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode GetTabWidthMode() const noexcept;
|
||||
|
||||
bool DebugFeaturesEnabled() const noexcept;
|
||||
|
||||
Json::Value ToJson() const;
|
||||
static GlobalAppSettings FromJson(const Json::Value& json);
|
||||
void LayerJson(const Json::Value& json);
|
||||
@@ -115,6 +117,8 @@ private:
|
||||
|
||||
winrt::TerminalApp::LaunchMode _launchMode;
|
||||
|
||||
bool _debugFeatures;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
static std::wstring_view _SerializeTheme(const winrt::Windows::UI::Xaml::ElementTheme theme) noexcept;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ void Pane::ResizeContent(const Size& newSize)
|
||||
const auto width = newSize.Width;
|
||||
const auto height = newSize.Height;
|
||||
|
||||
_CreateRowColDefinitions(newSize);
|
||||
_CreateRowColDefinitions();
|
||||
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
@@ -790,23 +790,24 @@ void Pane::_SetupChildCloseHandlers()
|
||||
// which is stored in _desiredSplitPosition
|
||||
// - Does nothing if our split state is currently set to SplitState::None
|
||||
// Arguments:
|
||||
// - rootSize: The dimensions in pixels that this pane (and its children should consume.)
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
void Pane::_CreateRowColDefinitions()
|
||||
{
|
||||
const auto first = _desiredSplitPosition * 100.0f;
|
||||
const auto second = 100.0f - first;
|
||||
if (_splitState == SplitState::Vertical)
|
||||
{
|
||||
_root.ColumnDefinitions().Clear();
|
||||
|
||||
// Create two columns in this grid: one for each pane
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Width);
|
||||
|
||||
auto firstColDef = Controls::ColumnDefinition();
|
||||
firstColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
firstColDef.Width(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
|
||||
|
||||
auto secondColDef = Controls::ColumnDefinition();
|
||||
secondColDef.Width(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
secondColDef.Width(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
|
||||
|
||||
_root.ColumnDefinitions().Append(firstColDef);
|
||||
_root.ColumnDefinitions().Append(secondColDef);
|
||||
@@ -816,35 +817,18 @@ void Pane::_CreateRowColDefinitions(const Size& rootSize)
|
||||
_root.RowDefinitions().Clear();
|
||||
|
||||
// Create two rows in this grid: one for each pane
|
||||
const auto paneSizes = _CalcChildrenSizes(rootSize.Height);
|
||||
|
||||
auto firstRowDef = Controls::RowDefinition();
|
||||
firstRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.first, GridUnitType::Star));
|
||||
firstRowDef.Height(GridLengthHelper::FromValueAndType(first, GridUnitType::Star));
|
||||
|
||||
auto secondRowDef = Controls::RowDefinition();
|
||||
secondRowDef.Height(GridLengthHelper::FromValueAndType(paneSizes.second, GridUnitType::Star));
|
||||
secondRowDef.Height(GridLengthHelper::FromValueAndType(second, GridUnitType::Star));
|
||||
|
||||
_root.RowDefinitions().Append(firstRowDef);
|
||||
_root.RowDefinitions().Append(secondRowDef);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Initializes our UI for a new split in this pane. Sets up row/column
|
||||
// definitions, and initializes the separator grid. Does nothing if our split
|
||||
// state is currently set to SplitState::None
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::_CreateSplitContent()
|
||||
{
|
||||
Size actualSize{ gsl::narrow_cast<float>(_root.ActualWidth()),
|
||||
gsl::narrow_cast<float>(_root.ActualHeight()) };
|
||||
|
||||
_CreateRowColDefinitions(actualSize);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the thickness of each side of our borders to match our _borders state.
|
||||
// Arguments:
|
||||
@@ -1077,7 +1061,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitState
|
||||
_control = { nullptr };
|
||||
_secondChild = std::make_shared<Pane>(profile, control);
|
||||
|
||||
_CreateSplitContent();
|
||||
_CreateRowColDefinitions();
|
||||
|
||||
_root.Children().Append(_firstChild->GetRootElement());
|
||||
_root.Children().Append(_secondChild->GetRootElement());
|
||||
@@ -1522,4 +1506,74 @@ void Pane::_SetupResources()
|
||||
}
|
||||
}
|
||||
|
||||
int Pane::GetLeafPaneCount() const noexcept
|
||||
{
|
||||
return _IsLeaf() ? 1 : (_firstChild->GetLeafPaneCount() + _secondChild->GetLeafPaneCount());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine which direction an "Automatic" split should
|
||||
// happen in for a given pane, but without using the ActualWidth() and
|
||||
// ActualHeight() methods. This is used during the initialization of the
|
||||
// Terminal, when we could be processing many "split-pane" commands _before_
|
||||
// we've ever laid out the Terminal for the first time. When this happens, the
|
||||
// Pane's don't have an actual size yet. However, we'd still like to figure
|
||||
// out how to do an "auto" split when these Panes are all laid out.
|
||||
// - This method assumes that the Pane we're attempting to split is `target`,
|
||||
// and this method should be called on the root of a tree of Panes.
|
||||
// - We'll walk down the tree attempting to find `target`. As we traverse the
|
||||
// tree, we'll reduce the size passed to each subsequent recursive call. The
|
||||
// size passed to this method represents how much space this Pane _will_ have
|
||||
// to use.
|
||||
// * If this pane is a leaf, and it's the pane we're looking for, use the
|
||||
// available space to calculate which direction to split in.
|
||||
// * If this pane is _any other leaf_, then just return nullopt, to indicate
|
||||
// that the `target` Pane is not down this branch.
|
||||
// * If this pane is a parent, calculate how much space our children will be
|
||||
// able to use, and recurse into them.
|
||||
// Arguments:
|
||||
// - target: The Pane we're attempting to split.
|
||||
// - availableSpace: The theoretical space that's available for this pane to be able to split.
|
||||
// Return Value:
|
||||
// - nullopt if `target` is not this pane or a child of this pane, otherwise the
|
||||
// SplitState that `target` would use for an `Automatic` split given
|
||||
// `availableSpace`
|
||||
std::optional<winrt::TerminalApp::SplitState> Pane::PreCalculateAutoSplit(const std::shared_ptr<Pane> target,
|
||||
const winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
if (target.get() == this)
|
||||
{
|
||||
//If this pane is a leaf, and it's the pane we're looking for, use
|
||||
//the available space to calculate which direction to split in.
|
||||
return availableSpace.Width > availableSpace.Height ? SplitState::Vertical : SplitState::Horizontal;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this pane is _any other leaf_, then just return nullopt, to
|
||||
// indicate that the `target` Pane is not down this branch.
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this pane is a parent, calculate how much space our children will
|
||||
// be able to use, and recurse into them.
|
||||
|
||||
const bool isVerticalSplit = _splitState == SplitState::Vertical;
|
||||
const float firstWidth = isVerticalSplit ? (availableSpace.Width * _desiredSplitPosition) : availableSpace.Width;
|
||||
const float secondWidth = isVerticalSplit ? (availableSpace.Width - firstWidth) : availableSpace.Width;
|
||||
const float firstHeight = !isVerticalSplit ? (availableSpace.Height * _desiredSplitPosition) : availableSpace.Height;
|
||||
const float secondHeight = !isVerticalSplit ? (availableSpace.Height - firstHeight) : availableSpace.Height;
|
||||
|
||||
const auto firstResult = _firstChild->PreCalculateAutoSplit(target, { firstWidth, firstHeight });
|
||||
return firstResult.has_value() ? firstResult : _secondChild->PreCalculateAutoSplit(target, { secondWidth, secondHeight });
|
||||
}
|
||||
|
||||
// We should not possibly be getting here - both the above branches should
|
||||
// return a value.
|
||||
FAIL_FAST();
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Pane, GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
@@ -63,10 +63,13 @@ public:
|
||||
const GUID& profile,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
std::optional<winrt::TerminalApp::SplitState> PreCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
|
||||
|
||||
void Shutdown();
|
||||
void Close();
|
||||
|
||||
int GetLeafPaneCount() const noexcept;
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
|
||||
|
||||
@@ -107,8 +110,7 @@ private:
|
||||
const GUID& profile,
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
|
||||
void _CreateRowColDefinitions(const winrt::Windows::Foundation::Size& rootSize);
|
||||
void _CreateSplitContent();
|
||||
void _CreateRowColDefinitions();
|
||||
void _ApplySplitDefinitions();
|
||||
void _UpdateBorders();
|
||||
|
||||
@@ -133,6 +135,9 @@ private:
|
||||
float _ClampSplitPosition(const bool widthOrHeight, const float requestedValue, const float totalSize) const;
|
||||
|
||||
winrt::TerminalApp::SplitState _convertAutomaticSplitState(const winrt::TerminalApp::SplitState& splitType) const;
|
||||
|
||||
std::optional<winrt::TerminalApp::SplitState> _preCalculateAutoSplit(const std::shared_ptr<Pane> target, const winrt::Windows::Foundation::Size parentSize) const;
|
||||
|
||||
// Function Description:
|
||||
// - Returns true if the given direction can be used with the given split
|
||||
// type.
|
||||
|
||||
@@ -162,40 +162,6 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="ReloadJsonParseErrorTitle" xml:space="preserve">
|
||||
<value>Failed to reload settings</value>
|
||||
</data>
|
||||
<data name="AboutTitleText" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="VersionLabelText" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
<data name="DocumentationLabelText" xml:space="preserve">
|
||||
<value>Documentation
|
||||
</value>
|
||||
</data>
|
||||
<data name="GettingStartedLabelText" xml:space="preserve">
|
||||
<value>Getting Started
|
||||
</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesLabelText" xml:space="preserve">
|
||||
<value>Release Notes
|
||||
</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyLabelText" xml:space="preserve">
|
||||
<value>Privacy Policy
|
||||
</value>
|
||||
</data>
|
||||
<data name="DocumentationUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-documentation</value>
|
||||
</data>
|
||||
<data name="GettingStartedUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-getting-started</value>
|
||||
</data>
|
||||
<data name="ReleaseNotesUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-release-notes</value>
|
||||
</data>
|
||||
<data name="PrivacyPolicyUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-privacy-policy</value>
|
||||
</data>
|
||||
<data name="FeedbackUriValue" xml:space="preserve">
|
||||
<value>https://aka.ms/terminal-feedback</value>
|
||||
</data>
|
||||
@@ -208,15 +174,6 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="SettingsMenuItem" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseAll" xml:space="preserve">
|
||||
<value>Close all</value>
|
||||
</data>
|
||||
<data name="CloseWindowWarningTitle" xml:space="preserve">
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
<data name="InvalidBackgroundImage" xml:space="preserve">
|
||||
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
|
||||
</data>
|
||||
@@ -283,4 +240,47 @@ Temporarily using the Windows Terminal default settings.
|
||||
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Minimize</value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="AboutDialog.Title" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
<data name="AboutDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="AboutDialog_VersionLabel.Text" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
<comment>This is the heading for a version number label</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_GettingStartedLink.Content" xml:space="preserve">
|
||||
<value>Getting Started</value>
|
||||
<comment>A hyperlink name for a guide on how to get started using Terminal</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_DocumentationLink.Content" xml:space="preserve">
|
||||
<value>Documentation</value>
|
||||
<comment>A hyperlink name for user documentation</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_ReleaseNotesLink.Content" xml:space="preserve">
|
||||
<value>Release Notes</value>
|
||||
<comment>A hyperlink name for the Terminal's release notes</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_PrivacyPolicyLink.Content" xml:space="preserve">
|
||||
<value>Privacy Policy</value>
|
||||
<comment>A hyperlink name for the Terminal's privacy policy</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_DisplayNameUnpackaged" xml:space="preserve">
|
||||
<value>Windows Terminal (Unpackaged)</value>
|
||||
<comment>This display name is used when the application's name cannot be determined</comment>
|
||||
</data>
|
||||
<data name="AboutDialog_VersionUnknown" xml:space="preserve">
|
||||
<value>Unknown</value>
|
||||
<comment>This is displayed when the version of the application cannot be determined</comment>
|
||||
</data>
|
||||
<data name="CloseAllDialog.CloseButtonText" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
<data name="CloseAllDialog.PrimaryButtonText" xml:space="preserve">
|
||||
<value>Close all</value>
|
||||
</data>
|
||||
<data name="CloseAllDialog.Title" xml:space="preserve">
|
||||
<value>Do you want to close all tabs?</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -277,6 +277,12 @@ namespace winrt::TerminalApp::implementation
|
||||
// gains focus, we'll mark it as the new active pane.
|
||||
_AttachEventHandlersToPane(first);
|
||||
_AttachEventHandlersToPane(second);
|
||||
|
||||
// Immediately update our tracker of the focused pane now. If we're
|
||||
// splitting panes during startup (from a commandline), then it's
|
||||
// possible that the focus events won't propagate immediately. Updating
|
||||
// the focus here will give the same effect though.
|
||||
_UpdateActivePane(second);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -387,6 +393,28 @@ namespace winrt::TerminalApp::implementation
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Mark the given pane as the active pane in this tab. All other panes
|
||||
// will be marked as inactive. We'll also update our own UI state to
|
||||
// reflect this newly active pane.
|
||||
// Arguments:
|
||||
// - pane: a Pane to mark as active.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Tab::_UpdateActivePane(std::shared_ptr<Pane> pane)
|
||||
{
|
||||
// Clear the active state of the entire tree, and mark only the pane as active.
|
||||
_rootPane->ClearActive();
|
||||
_activePane = pane;
|
||||
_activePane->SetActive();
|
||||
|
||||
// Update our own title text to match the newly-active pane.
|
||||
SetTabText(GetActiveTitle());
|
||||
|
||||
// Raise our own ActivePaneChanged event.
|
||||
_ActivePaneChangedHandlers();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Add an event handler to this pane's GotFocus event. When that pane gains
|
||||
// focus, we'll mark it as the new active pane. We'll also query the title of
|
||||
@@ -406,19 +434,37 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (tab && sender != tab->_activePane)
|
||||
{
|
||||
// Clear the active state of the entire tree, and mark only the sender as active.
|
||||
tab->_rootPane->ClearActive();
|
||||
tab->_activePane = sender;
|
||||
tab->_activePane->SetActive();
|
||||
|
||||
// Update our own title text to match the newly-active pane.
|
||||
tab->SetTabText(tab->GetActiveTitle());
|
||||
|
||||
// Raise our own ActivePaneChanged event.
|
||||
tab->_ActivePaneChangedHandlers();
|
||||
tab->_UpdateActivePane(sender);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the total number of leaf panes in this tab. This will be the number
|
||||
// of actual controls hosted by this tab.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - The total number of leaf panes hosted by this tab.
|
||||
int Tab::_GetLeafPaneCount() const noexcept
|
||||
{
|
||||
return _rootPane->GetLeafPaneCount();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is a helper to determine which direction an "Automatic" split should
|
||||
// happen in for the active pane of this tab, but without using the ActualWidth() and
|
||||
// ActualHeight() methods.
|
||||
// - See Pane::PreCalculateAutoSplit
|
||||
// Arguments:
|
||||
// - availableSpace: The theoretical space that's available for this Tab's content
|
||||
// Return Value:
|
||||
// - The SplitState that we should use for an `Automatic` split given
|
||||
// `availableSpace`
|
||||
SplitState Tab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const
|
||||
{
|
||||
return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical);
|
||||
}
|
||||
|
||||
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
#include "Pane.h"
|
||||
#include "Tab.g.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
class TabTests;
|
||||
};
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Tab : public TabT<Tab>
|
||||
@@ -32,6 +38,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::fire_and_forget UpdateIcon(const winrt::hstring iconPath);
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const;
|
||||
|
||||
void ResizeContent(const winrt::Windows::Foundation::Size& newSize);
|
||||
void ResizePane(const winrt::TerminalApp::Direction& direction);
|
||||
@@ -64,5 +71,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);
|
||||
|
||||
int _GetLeafPaneCount() const noexcept;
|
||||
void _UpdateActivePane(std::shared_ptr<Pane> pane);
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
|
||||
#include "TelnetGenerator.h" // For TelnetConnectionType
|
||||
#include "TabRowControl.h"
|
||||
#include "DebugTapConnection.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
@@ -63,11 +64,20 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView = _tabRow.TabView();
|
||||
_rearranging = false;
|
||||
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
const auto isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
|
||||
// GH#2455 - Make sure to try/catch calls to Application::Current,
|
||||
// because that _won't_ be an instance of TerminalApp::App in the
|
||||
// LocalTests
|
||||
auto isElevated = false;
|
||||
try
|
||||
{
|
||||
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
|
||||
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
|
||||
// but that process is running at a different IL than us.
|
||||
// For now, we're disabling elevated drag.
|
||||
isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
_tabView.CanReorderTabs(!isElevated);
|
||||
_tabView.CanDragTabs(!isElevated);
|
||||
|
||||
@@ -137,161 +147,115 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
|
||||
|
||||
// Actually start the terminal.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
_OpenNewTab(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appArgs.ValidateStartupCommands();
|
||||
// Once the page is actually laid out on the screen, trigger all our
|
||||
// startup actions. Things like Panes need to know at least how big the
|
||||
// window will be, so they can subdivide that space.
|
||||
//
|
||||
// _OnFirstLayout will remove this handler so it doesn't get called more than once.
|
||||
_layoutUpdatedRevoker = _tabContent.LayoutUpdated(winrt::auto_revoke, { this, &TerminalPage::_OnFirstLayout });
|
||||
}
|
||||
|
||||
// This will kick off a chain of events to perform each startup
|
||||
// action. As each startup action is completed, the next will be
|
||||
// fired.
|
||||
_ProcessNextStartupAction();
|
||||
// Method Description:
|
||||
// - This method is called once on startup, on the first LayoutUpdated event.
|
||||
// We'll use this event to know that we have an ActualWidth and
|
||||
// ActualHeight, so we can now attempt to process our list of startup
|
||||
// actions.
|
||||
// - We'll remove this event handler when the event is first handled.
|
||||
// - If there are no startup actions, we'll open a single tab with the
|
||||
// default profile.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_OnFirstLayout(const IInspectable& /*sender*/, const IInspectable& /*eventArgs*/)
|
||||
{
|
||||
// Only let this succeed once.
|
||||
_layoutUpdatedRevoker.revoke();
|
||||
|
||||
// This event fires every time the layout changes, but it is always the
|
||||
// last one to fire in any layout change chain. That gives us great
|
||||
// flexibility in finding the right point at which to initialize our
|
||||
// renderer (and our terminal). Any earlier than the last layout update
|
||||
// and we may not know the terminal's starting size.
|
||||
if (_startupState == StartupState::NotInitialized)
|
||||
{
|
||||
_startupState = StartupState::InStartup;
|
||||
_appArgs.ValidateStartupCommands();
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
_OpenNewTab(nullptr);
|
||||
_startupState = StartupState::Initialized;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ProcessStartupActions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process the next startup action in our list of startup actions. When
|
||||
// that action is complete, fire the next (if there are any more).
|
||||
// - Process all the startup actions in our list of startup actions. We'll
|
||||
// do this all at once here.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
fire_and_forget TerminalPage::_ProcessNextStartupAction()
|
||||
winrt::fire_and_forget TerminalPage::_ProcessStartupActions()
|
||||
{
|
||||
// If there are no actions left, do nothing.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next action to be processed
|
||||
auto nextAction = _appArgs.GetStartupActions().front();
|
||||
_appArgs.GetStartupActions().pop_front();
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
// Handle it on the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
|
||||
// Handle it on a subsequent pass of the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_actionDispatch->DoAction(nextAction);
|
||||
|
||||
// Kick off the next action to be handled (if necessary)
|
||||
page->_ProcessNextStartupAction();
|
||||
for (const auto& action : _appArgs.GetStartupActions())
|
||||
{
|
||||
_actionDispatch->DoAction(action);
|
||||
}
|
||||
_startupState = StartupState::Initialized;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Show a ContentDialog with a single "Ok" button to dismiss. Looks up the
|
||||
// the title and text from our Resources using the provided keys.
|
||||
// - Only one dialog can be visible at a time. If another dialog is visible
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
// Arguments:
|
||||
// - titleKey: The key to use to lookup the title text from our resources.
|
||||
// - contentKey: The key to use to lookup the content text from our resources.
|
||||
void TerminalPage::ShowOkDialog(const winrt::hstring& titleKey,
|
||||
const winrt::hstring& contentKey)
|
||||
{
|
||||
auto title = GetLibraryResourceString(titleKey);
|
||||
auto message = GetLibraryResourceString(contentKey);
|
||||
auto buttonText = RS_(L"Ok");
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(winrt::box_value(message));
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Show a dialog with "About" information. Displays the app's Display
|
||||
// Name, version, getting started link, documentation link, release
|
||||
// Notes link, and privacy policy link.
|
||||
void TerminalPage::_ShowAboutDialog()
|
||||
{
|
||||
const auto title = RS_(L"AboutTitleText");
|
||||
const auto versionLabel = RS_(L"VersionLabelText");
|
||||
const auto gettingStartedLabel = RS_(L"GettingStartedLabelText");
|
||||
const auto documentationLabel = RS_(L"DocumentationLabelText");
|
||||
const auto releaseNotesLabel = RS_(L"ReleaseNotesLabelText");
|
||||
const auto privacyPolicyLabel = RS_(L"PrivacyPolicyLabelText");
|
||||
const auto gettingStartedUriValue = RS_(L"GettingStartedUriValue");
|
||||
const auto documentationUriValue = RS_(L"DocumentationUriValue");
|
||||
const auto releaseNotesUriValue = RS_(L"ReleaseNotesUriValue");
|
||||
const auto privacyPolicyUriValue = RS_(L"PrivacyPolicyUriValue");
|
||||
const auto package = winrt::Windows::ApplicationModel::Package::Current();
|
||||
const auto packageName = package.DisplayName();
|
||||
const auto version = package.Id().Version();
|
||||
winrt::Windows::UI::Xaml::Documents::Run about;
|
||||
winrt::Windows::UI::Xaml::Documents::Run gettingStarted;
|
||||
winrt::Windows::UI::Xaml::Documents::Run documentation;
|
||||
winrt::Windows::UI::Xaml::Documents::Run releaseNotes;
|
||||
winrt::Windows::UI::Xaml::Documents::Run privacyPolicy;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink gettingStartedLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink documentationLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink releaseNotesLink;
|
||||
winrt::Windows::UI::Xaml::Documents::Hyperlink privacyPolicyLink;
|
||||
std::wstringstream aboutTextStream;
|
||||
_showDialogHandlers(*this, FindName(L"AboutDialog").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
|
||||
gettingStarted.Text(gettingStartedLabel);
|
||||
documentation.Text(documentationLabel);
|
||||
releaseNotes.Text(releaseNotesLabel);
|
||||
privacyPolicy.Text(privacyPolicyLabel);
|
||||
winrt::hstring TerminalPage::ApplicationDisplayName()
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
return package.DisplayName();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
winrt::Windows::Foundation::Uri gettingStartedUri{ gettingStartedUriValue };
|
||||
winrt::Windows::Foundation::Uri documentationUri{ documentationUriValue };
|
||||
winrt::Windows::Foundation::Uri releaseNotesUri{ releaseNotesUriValue };
|
||||
winrt::Windows::Foundation::Uri privacyPolicyUri{ privacyPolicyUriValue };
|
||||
return RS_(L"AboutDialog_DisplayNameUnpackaged");
|
||||
}
|
||||
|
||||
gettingStartedLink.NavigateUri(gettingStartedUri);
|
||||
documentationLink.NavigateUri(documentationUri);
|
||||
releaseNotesLink.NavigateUri(releaseNotesUri);
|
||||
privacyPolicyLink.NavigateUri(privacyPolicyUri);
|
||||
winrt::hstring TerminalPage::ApplicationVersion()
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto package{ winrt::Windows::ApplicationModel::Package::Current() };
|
||||
const auto version{ package.Id().Version() };
|
||||
winrt::hstring formatted{ wil::str_printf<std::wstring>(L"%u.%u.%u.%u", version.Major, version.Minor, version.Build, version.Revision) };
|
||||
return formatted;
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
gettingStartedLink.Inlines().Append(gettingStarted);
|
||||
documentationLink.Inlines().Append(documentation);
|
||||
releaseNotesLink.Inlines().Append(releaseNotes);
|
||||
privacyPolicyLink.Inlines().Append(privacyPolicy);
|
||||
|
||||
// Format our about text. It will look like the following:
|
||||
// <Display Name>
|
||||
// Version: <Major>.<Minor>.<Build>.<Revision>
|
||||
// Getting Started
|
||||
// Documentation
|
||||
// Release Notes
|
||||
// Privacy Policy
|
||||
|
||||
aboutTextStream << packageName.c_str() << L"\n";
|
||||
|
||||
aboutTextStream << versionLabel.c_str() << L" ";
|
||||
aboutTextStream << version.Major << L"." << version.Minor << L"." << version.Build << L"." << version.Revision << L"\n";
|
||||
|
||||
winrt::hstring aboutText{ aboutTextStream.str() };
|
||||
about.Text(aboutText);
|
||||
|
||||
const auto buttonText = RS_(L"Ok");
|
||||
|
||||
WUX::Controls::TextBlock aboutTextBlock;
|
||||
aboutTextBlock.Inlines().Append(about);
|
||||
aboutTextBlock.Inlines().Append(gettingStartedLink);
|
||||
aboutTextBlock.Inlines().Append(documentationLink);
|
||||
aboutTextBlock.Inlines().Append(releaseNotesLink);
|
||||
aboutTextBlock.Inlines().Append(privacyPolicyLink);
|
||||
aboutTextBlock.IsTextSelectionEnabled(true);
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
dialog.Content(aboutTextBlock);
|
||||
dialog.CloseButtonText(buttonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Close);
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
return RS_(L"AboutDialog_VersionUnknown");
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -303,19 +267,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// when this is called, nothing happens. See _ShowDialog for details
|
||||
void TerminalPage::_ShowCloseWarningDialog()
|
||||
{
|
||||
auto title = RS_(L"CloseWindowWarningTitle");
|
||||
auto primaryButtonText = RS_(L"CloseAll");
|
||||
auto closeButtonText = RS_(L"Cancel");
|
||||
|
||||
WUX::Controls::ContentDialog dialog;
|
||||
dialog.Title(winrt::box_value(title));
|
||||
|
||||
dialog.CloseButtonText(closeButtonText);
|
||||
dialog.PrimaryButtonText(primaryButtonText);
|
||||
dialog.DefaultButton(WUX::Controls::ContentDialogButton::Primary);
|
||||
auto token = dialog.PrimaryButtonClick({ this, &TerminalPage::_CloseWarningPrimaryButtonOnClick });
|
||||
|
||||
_showDialogHandlers(*this, dialog);
|
||||
_showDialogHandlers(*this, FindName(L"CloseAllDialog").try_as<WUX::Controls::ContentDialog>());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -406,7 +358,15 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// add static items
|
||||
{
|
||||
const auto isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
// GH#2455 - Make sure to try/catch calls to Application::Current,
|
||||
// because that _won't_ be an instance of TerminalApp::App in the
|
||||
// LocalTests
|
||||
auto isUwp = false;
|
||||
try
|
||||
{
|
||||
isUwp = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsUwp();
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
if (!isUwp)
|
||||
{
|
||||
@@ -476,6 +436,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// control which profile is created and with possible other
|
||||
// configurations. See CascadiaSettings::BuildSettings for more details.
|
||||
void TerminalPage::_OpenNewTab(const winrt::TerminalApp::NewTerminalArgs& newTerminalArgs)
|
||||
try
|
||||
{
|
||||
const auto [profileGuid, settings] = _settings->BuildSettings(newTerminalArgs);
|
||||
|
||||
@@ -499,6 +460,7 @@ namespace winrt::TerminalApp::implementation
|
||||
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
|
||||
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));
|
||||
}
|
||||
CATCH_LOG();
|
||||
|
||||
winrt::fire_and_forget TerminalPage::_RemoveOnCloseRoutine(Microsoft::UI::Xaml::Controls::TabViewItem tabViewItem, winrt::com_ptr<TerminalPage> page)
|
||||
{
|
||||
@@ -514,11 +476,25 @@ namespace winrt::TerminalApp::implementation
|
||||
// - settings: the TerminalSettings object to use to create the TerminalControl with.
|
||||
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
|
||||
{
|
||||
const bool isFirstTab = _tabs.Size() == 0;
|
||||
// Initialize the new tab
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
const auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
auto connection = _CreateConnectionFromSettings(profileGuid, settings);
|
||||
|
||||
TerminalConnection::ITerminalConnection debugConnection{ nullptr };
|
||||
if (_settings->GlobalSettings().DebugFeaturesEnabled())
|
||||
{
|
||||
const CoreWindow window = CoreWindow::GetForCurrentThread();
|
||||
const auto rAltState = window.GetKeyState(VirtualKey::RightMenu);
|
||||
const auto lAltState = window.GetKeyState(VirtualKey::LeftMenu);
|
||||
const bool bothAltsPressed = WI_IsFlagSet(lAltState, CoreVirtualKeyStates::Down) &&
|
||||
WI_IsFlagSet(rAltState, CoreVirtualKeyStates::Down);
|
||||
if (bothAltsPressed)
|
||||
{
|
||||
std::tie(connection, debugConnection) = OpenDebugTapConnection(connection);
|
||||
}
|
||||
}
|
||||
|
||||
TermControl term{ settings, connection };
|
||||
|
||||
// Add the new tab to the list of our tabs.
|
||||
@@ -568,20 +544,17 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// If this is the first tab, we don't need to kick off the event to get
|
||||
// the tab's content added to the root of the page. just do it
|
||||
// immediately.
|
||||
if (isFirstTab)
|
||||
if (debugConnection) // this will only be set if global debugging is on and tap is active
|
||||
{
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(newTabImpl->GetRootElement());
|
||||
}
|
||||
else
|
||||
{
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
// we'll attach the terminal's Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
TermControl newControl{ settings, debugConnection };
|
||||
_RegisterTerminalEvents(newControl, *newTabImpl);
|
||||
// Split (auto) with the debug tap.
|
||||
newTabImpl->SplitPane(SplitState::Automatic, profileGuid, newControl);
|
||||
}
|
||||
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
// we'll attach the terminal's Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -810,13 +783,30 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
auto focusedTab = _GetStrongTabImpl(*index);
|
||||
const auto& profileGuid = focusedTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
try
|
||||
{
|
||||
const auto settings = _settings->BuildSettings(profileGuid.value());
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
auto focusedTab = _GetStrongTabImpl(*index);
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
|
||||
const auto& profileGuid = focusedTab->GetFocusedProfile();
|
||||
if (profileGuid.has_value())
|
||||
{
|
||||
const auto settings = _settings->BuildSettings(profileGuid.value());
|
||||
_CreateNewTabFromSettings(profileGuid.value(), settings);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,20 +891,36 @@ namespace winrt::TerminalApp::implementation
|
||||
// Wraparound math. By adding tabCount and then calculating modulo tabCount,
|
||||
// we clamp the values to the range [0, tabCount) while still supporting moving
|
||||
// leftward from 0 to tabCount - 1.
|
||||
_SetFocusedTabIndex(((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount));
|
||||
const auto newTabIndex = ((tabCount + *index + (bMoveRight ? 1 : -1)) % tabCount);
|
||||
_SelectTab(newTabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets focus to the desired tab. Returns false if the provided tabIndex
|
||||
// is greater than the number of tabs we have.
|
||||
// - During startup, we'll immediately set the selected tab as focused.
|
||||
// - After startup, we'll dispatch an async method to set the the selected
|
||||
// item of the TabView, which will then also trigger a
|
||||
// TabView::SelectionChanged, handled in
|
||||
// TerminalPage::_OnTabSelectionChanged
|
||||
// Return Value:
|
||||
// true iff we were able to select that tab index, false otherwise
|
||||
bool TerminalPage::_SelectTab(const uint32_t tabIndex)
|
||||
{
|
||||
if (tabIndex >= 0 && tabIndex < _tabs.Size())
|
||||
{
|
||||
_SetFocusedTabIndex(tabIndex);
|
||||
if (_startupState == StartupState::InStartup)
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(tabIndex) };
|
||||
_tabView.SelectedItem(tab->GetTabViewItem());
|
||||
_UpdatedSelectedTab(tabIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SetFocusedTabIndex(tabIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -967,6 +973,16 @@ namespace winrt::TerminalApp::implementation
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - An async method for changing the focused tab on the UI thread. This
|
||||
// method will _only_ set the selected item of the TabView, which will
|
||||
// then also trigger a TabView::SelectionChanged event, which we'll handle
|
||||
// in TerminalPage::_OnTabSelectionChanged, where we'll mark the new tab
|
||||
// as focused.
|
||||
// Arguments:
|
||||
// - tabIndex: the index in the list of tabs to focus.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TerminalPage::_SetFocusedTabIndex(const uint32_t tabIndex)
|
||||
{
|
||||
// GH#1117: This is a workaround because _tabView.SelectedIndex(tabIndex)
|
||||
@@ -1075,42 +1091,65 @@ namespace winrt::TerminalApp::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
auto focusedTab = _GetStrongTabImpl(*indexOpt);
|
||||
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
|
||||
GUID realGuid;
|
||||
bool profileFound = false;
|
||||
|
||||
if (splitMode == TerminalApp::SplitType::Duplicate)
|
||||
try
|
||||
{
|
||||
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
|
||||
if (current_guid)
|
||||
auto focusedTab = _GetStrongTabImpl(*indexOpt);
|
||||
winrt::Microsoft::Terminal::Settings::TerminalSettings controlSettings;
|
||||
GUID realGuid;
|
||||
bool profileFound = false;
|
||||
|
||||
if (splitMode == TerminalApp::SplitType::Duplicate)
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = _settings->BuildSettings(current_guid.value());
|
||||
realGuid = current_guid.value();
|
||||
std::optional<GUID> current_guid = focusedTab->GetFocusedProfile();
|
||||
if (current_guid)
|
||||
{
|
||||
profileFound = true;
|
||||
controlSettings = _settings->BuildSettings(current_guid.value());
|
||||
realGuid = current_guid.value();
|
||||
}
|
||||
// TODO: GH#5047 - In the future, we should get the Profile of
|
||||
// the focused pane, and use that to build a new instance of the
|
||||
// settings so we can duplicate this tab/pane.
|
||||
//
|
||||
// Currently, if the profile doesn't exist anymore in our
|
||||
// settings, we'll silently do nothing.
|
||||
//
|
||||
// In the future, it will be preferable to just duplicate the
|
||||
// current control's settings, but we can't do that currently,
|
||||
// because we won't be able to create a new instance of the
|
||||
// connection without keeping an instance of the original Profile
|
||||
// object around.
|
||||
}
|
||||
if (!profileFound)
|
||||
{
|
||||
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
|
||||
}
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
const auto canSplit = focusedTab->CanSplitPane(splitType);
|
||||
|
||||
if (!canSplit && _startupState == StartupState::Initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto realSplitType = splitType;
|
||||
if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized)
|
||||
{
|
||||
float contentWidth = gsl::narrow_cast<float>(_tabContent.ActualWidth());
|
||||
float contentHeight = gsl::narrow_cast<float>(_tabContent.ActualHeight());
|
||||
realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight });
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, *focusedTab);
|
||||
|
||||
focusedTab->SplitPane(realSplitType, realGuid, newControl);
|
||||
}
|
||||
if (!profileFound)
|
||||
{
|
||||
std::tie(realGuid, controlSettings) = _settings->BuildSettings(newTerminalArgs);
|
||||
}
|
||||
|
||||
const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings);
|
||||
|
||||
const auto canSplit = focusedTab->CanSplitPane(splitType);
|
||||
|
||||
if (!canSplit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TermControl newControl{ controlSettings, controlConnection };
|
||||
|
||||
// Hookup our event handlers to the new terminal
|
||||
_RegisterTerminalEvents(newControl, *focusedTab);
|
||||
|
||||
focusedTab->SplitPane(splitType, realGuid, newControl);
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -1426,6 +1465,31 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::_UpdatedSelectedTab(const int32_t index)
|
||||
{
|
||||
// Unfocus all the tabs.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->SetFocused(false);
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(index) };
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
|
||||
tab->SetFocused(true);
|
||||
_titleChangeHandlers(*this, Title());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Responds to the TabView control's Selection Changed event (to move a
|
||||
// new terminal control into focus) when not in in the middle of a tab rearrangement.
|
||||
@@ -1438,28 +1502,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
auto tabView = sender.as<MUX::Controls::TabView>();
|
||||
auto selectedIndex = tabView.SelectedIndex();
|
||||
|
||||
// Unfocus all the tabs.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->SetFocused(false);
|
||||
}
|
||||
|
||||
if (selectedIndex >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
auto tab{ _GetStrongTabImpl(selectedIndex) };
|
||||
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(tab->GetRootElement());
|
||||
|
||||
tab->SetFocused(true);
|
||||
_titleChangeHandlers(*this, Title());
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
_UpdatedSelectedTab(selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1525,16 +1568,28 @@ namespace winrt::TerminalApp::implementation
|
||||
for (auto& profile : profiles)
|
||||
{
|
||||
const GUID profileGuid = profile.GetGuid();
|
||||
const auto settings = _settings->BuildSettings(profileGuid);
|
||||
|
||||
for (auto tab : _tabs)
|
||||
try
|
||||
{
|
||||
// Attempt to reload the settings of any panes with this profile
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->UpdateSettings(settings, profileGuid);
|
||||
// BuildSettings can throw an exception if the profileGuid does
|
||||
// not belong to an actual profile in the list of profiles.
|
||||
const auto settings = _settings->BuildSettings(profileGuid);
|
||||
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
// Attempt to reload the settings of any panes with this profile
|
||||
auto tabImpl{ _GetStrongTabImpl(tab) };
|
||||
tabImpl->UpdateSettings(settings, profileGuid);
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
// GH#2455: If there are any panes with controls that had been
|
||||
// initialized with a Profile that no longer exists in our list of
|
||||
// profiles, we'll leave it unmodified. The profile doesn't exist
|
||||
// anymore, so we can't possibly update its settings.
|
||||
|
||||
// Update the icon of the tab for the currently focused profile in that tab.
|
||||
for (auto tab : _tabs)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,13 @@ namespace TerminalAppLocalTests
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
enum StartupState : int
|
||||
{
|
||||
NotInitialized = 0,
|
||||
InStartup = 1,
|
||||
Initialized = 2
|
||||
};
|
||||
|
||||
struct TerminalPage : TerminalPageT<TerminalPage>
|
||||
{
|
||||
public:
|
||||
@@ -31,12 +38,13 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
hstring Title();
|
||||
|
||||
void ShowOkDialog(const winrt::hstring& titleKey, const winrt::hstring& contentKey);
|
||||
|
||||
void TitlebarClicked();
|
||||
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
winrt::hstring ApplicationDisplayName();
|
||||
winrt::hstring ApplicationVersion();
|
||||
|
||||
void CloseWindow();
|
||||
|
||||
int32_t SetStartupCommandline(winrt::array_view<const hstring> args);
|
||||
@@ -48,8 +56,11 @@ namespace winrt::TerminalApp::implementation
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog);
|
||||
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ToggleFullscreen, _toggleFullscreenHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs);
|
||||
TYPED_EVENT(Initialized, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||
|
||||
private:
|
||||
friend struct TerminalPageT<TerminalPage>; // for Xaml to bind events
|
||||
|
||||
// If you add controls here, but forget to null them either here or in
|
||||
// the ctor, you're going to have a bad time. It'll mysteriously fail to
|
||||
// activate the app.
|
||||
@@ -75,9 +86,12 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
winrt::com_ptr<ShortcutActionDispatch> _actionDispatch{ winrt::make_self<ShortcutActionDispatch>() };
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
StartupState _startupState{ StartupState::NotInitialized };
|
||||
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
int _ParseArgs(winrt::array_view<const hstring>& args);
|
||||
fire_and_forget _ProcessNextStartupAction();
|
||||
winrt::fire_and_forget _ProcessStartupActions();
|
||||
|
||||
void _ShowAboutDialog();
|
||||
void _ShowCloseWarningDialog();
|
||||
@@ -141,6 +155,8 @@ namespace winrt::TerminalApp::implementation
|
||||
void _OnTabItemsChanged(const IInspectable& sender, const Windows::Foundation::Collections::IVectorChangedEventArgs& eventArgs);
|
||||
void _OnContentSizeChanged(const IInspectable& /*sender*/, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
void _OnTabCloseRequested(const IInspectable& sender, const Microsoft::UI::Xaml::Controls::TabViewTabCloseRequestedEventArgs& eventArgs);
|
||||
void _OnFirstLayout(const IInspectable& sender, const IInspectable& eventArgs);
|
||||
void _UpdatedSelectedTab(const int32_t index);
|
||||
|
||||
void _Find();
|
||||
|
||||
|
||||
@@ -13,10 +13,15 @@ namespace TerminalApp
|
||||
Int32 SetStartupCommandline(String[] commands);
|
||||
String EarlyExitMessage { get; };
|
||||
|
||||
// XAML bound properties
|
||||
String ApplicationDisplayName { get; };
|
||||
String ApplicationVersion { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
|
||||
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.Controls.ContentDialog> ShowDialog;
|
||||
event Windows.Foundation.TypedEventHandler<Object, ToggleFullscreenEventArgs> ToggleFullscreen;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.RoutedEventArgs> Initialized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,5 +19,39 @@ the MIT License. See LICENSE in the project root for license information. -->
|
||||
<local:TabRowControl x:Name="TabRow" Grid.Row="0" />
|
||||
|
||||
<Grid x:Name="TabContent" Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
|
||||
<ContentDialog
|
||||
x:Load="False"
|
||||
x:Name="AboutDialog"
|
||||
x:Uid="AboutDialog"
|
||||
DefaultButton="Close">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock IsTextSelectionEnabled="True">
|
||||
<Run Text="{x:Bind ApplicationDisplayName}" /> <LineBreak />
|
||||
<Run x:Uid="AboutDialog_VersionLabel" />
|
||||
<Run Text="{x:Bind ApplicationVersion}" />
|
||||
</TextBlock>
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_GettingStartedLink"
|
||||
NavigateUri="https://aka.ms/terminal-getting-started" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_DocumentationLink"
|
||||
NavigateUri="https://aka.ms/terminal-documentation" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_ReleaseNotesLink"
|
||||
NavigateUri="https://aka.ms/terminal-release-notes" />
|
||||
<HyperlinkButton
|
||||
x:Uid="AboutDialog_PrivacyPolicyLink"
|
||||
NavigateUri="https://aka.ms/terminal-privacy-policy" />
|
||||
</StackPanel>
|
||||
</ContentDialog>
|
||||
|
||||
<ContentDialog
|
||||
x:Load="False"
|
||||
x:Name="CloseAllDialog"
|
||||
x:Uid="CloseAllDialog"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
|
||||
</ContentDialog>
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
<ClInclude Include="../ActionAndArgs.h">
|
||||
<DependentUpon>../ActionArgs.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../DebugTapConnection.h" />
|
||||
<ClInclude Include="../AppKeyBindings.h">
|
||||
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -153,6 +154,7 @@
|
||||
<ClCompile Include="../WslDistroGenerator.cpp" />
|
||||
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
|
||||
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
|
||||
<ClCompile Include="../DebugTapConnection.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
|
||||
@@ -172,8 +172,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
TextBlock().FontSize(fontSizePx);
|
||||
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
|
||||
|
||||
const auto widthToTerminalEnd = Canvas().ActualWidth() - ::base::ClampedNumeric<double>(clientCursorPos.X);
|
||||
TextBlock().MaxWidth(widthToTerminalEnd);
|
||||
const auto canvasActualWidth = Canvas().ActualWidth();
|
||||
const auto widthToTerminalEnd = canvasActualWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
|
||||
// Make sure that we're setting the MaxWidth to a positive number - a
|
||||
// negative number here will crash us in mysterious ways with a useless
|
||||
// stack trace
|
||||
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
|
||||
TextBlock().MaxWidth(newMaxWidth);
|
||||
|
||||
// Set the text block bounds
|
||||
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;
|
||||
|
||||
@@ -70,13 +70,36 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_lastMouseClickTimestamp{},
|
||||
_lastMouseClickPos{},
|
||||
_selectionNeedsToBeCopied{ false },
|
||||
_searchBox{ nullptr },
|
||||
_textCursor{ Windows::UI::Core::CoreCursorType::IBeam, 0 },
|
||||
_pointerCursor{ Windows::UI::Core::CoreCursorType::Arrow, 0 }
|
||||
_searchBox{ nullptr }
|
||||
{
|
||||
_EnsureStaticInitialization();
|
||||
InitializeComponent();
|
||||
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
|
||||
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
|
||||
|
||||
// This event is explicitly revoked in the destructor: does not need weak_ref
|
||||
auto onReceiveOutputFn = [this](const hstring str) {
|
||||
_terminal->Write(str);
|
||||
};
|
||||
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
||||
|
||||
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
|
||||
_terminal->SetWriteInputCallback(inputFn);
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
|
||||
// Initialize the terminal only once the swapchainpanel is loaded - that
|
||||
// way, we'll be able to query the real pixel size it got on layout
|
||||
_layoutUpdatedRevoker = SwapChainPanel().LayoutUpdated(winrt::auto_revoke, [this](auto /*s*/, auto /*e*/) {
|
||||
@@ -92,10 +115,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// Subscribe to the connection's disconnected event and call our connection closed handlers.
|
||||
_connectionStateChangedRevoker = _connection.StateChanged(winrt::auto_revoke, [this](auto&& /*s*/, auto&& /*v*/) {
|
||||
_ConnectionStateChangedHandlers(*this, nullptr);
|
||||
});
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
|
||||
|
||||
_ApplyUISettings();
|
||||
}
|
||||
@@ -128,7 +150,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_Search(const winrt::hstring& text, const bool goForward, const bool caseSensitive)
|
||||
{
|
||||
if (text.size() == 0)
|
||||
if (text.size() == 0 || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -185,6 +207,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update our control settings
|
||||
_ApplyUISettings();
|
||||
|
||||
@@ -414,7 +441,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
|
||||
try
|
||||
{
|
||||
if (GetUiaData())
|
||||
if (_initializedTerminal && !_closing) // only set up the automation peer if we're ready to go live
|
||||
{
|
||||
// create a custom automation peer with this code pattern:
|
||||
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
|
||||
@@ -452,11 +479,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return _connection.State();
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::SwapChainChanged()
|
||||
winrt::fire_and_forget TermControl::RenderEngineSwapChainChanged()
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
return;
|
||||
{ // lock scope
|
||||
auto terminalLock = _terminal->LockForReading();
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
@@ -464,192 +494,153 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
// If 'weakThis' is locked, then we can safely work with 'this'
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_terminal)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
}
|
||||
auto terminalLock = _terminal->LockForWriting();
|
||||
|
||||
_AttachDxgiSwapChainToXaml(chain.Get());
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget TermControl::_SwapChainRoutine()
|
||||
void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain)
|
||||
{
|
||||
auto chain = _renderEngine->GetSwapChain();
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
|
||||
if (auto control{ weakThis.get() })
|
||||
{
|
||||
if (_terminal)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(chain.Get());
|
||||
}
|
||||
}
|
||||
auto nativePanel = SwapChainPanel().as<ISwapChainPanelNative>();
|
||||
nativePanel->SetSwapChain(swapChain);
|
||||
}
|
||||
|
||||
bool TermControl::_InitializeTerminal()
|
||||
{
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
{ // scope for terminalLock
|
||||
auto terminalLock = _terminal->LockForWriting();
|
||||
|
||||
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = SwapChainPanel().ActualHeight();
|
||||
|
||||
if (windowWidth == 0 || windowHeight == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_terminal = std::make_unique<::Microsoft::Terminal::Core::Terminal>();
|
||||
|
||||
// First create the render thread.
|
||||
// Then stash a local pointer to the render thread so we can initialize it and enable it
|
||||
// to paint itself *after* we hand off its ownership to the renderer.
|
||||
// We split up construction and initialization of the render thread object this way
|
||||
// because the renderer and render thread have circular references to each other.
|
||||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
|
||||
// Now create the renderer and initialize the render thread.
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
|
||||
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
|
||||
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
// and react accordingly.
|
||||
_UpdateFont(true);
|
||||
|
||||
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
|
||||
|
||||
// Fist set up the dx engine with the window size in pixels.
|
||||
// Then, using the font, get the number of characters that can fit.
|
||||
// Resize our terminal connection to match that size, and initialize the terminal with that size.
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
|
||||
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto width = vp.Width();
|
||||
const auto height = vp.Height();
|
||||
_connection.Resize(height, width);
|
||||
|
||||
// Override the default width and height to match the size of the swapChainPanel
|
||||
_settings.InitialCols(width);
|
||||
_settings.InitialRows(height);
|
||||
|
||||
_terminal->CreateFromSettings(_settings, renderTarget);
|
||||
|
||||
// Tell the DX Engine to notify us when the swap chain changes.
|
||||
dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this));
|
||||
|
||||
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
|
||||
// here, the setting will only be used when the Terminal is initialized.
|
||||
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
|
||||
// TODO:GH#3927 - hot-reload this one too
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
break;
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(dxEngine->Enable());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
|
||||
// This event is explicitly revoked in the destructor: does not need weak_ref
|
||||
auto onReceiveOutputFn = [this](const hstring str) {
|
||||
_terminal->Write(str);
|
||||
};
|
||||
_connectionOutputEventToken = _connection.TerminalOutput(onReceiveOutputFn);
|
||||
|
||||
auto inputFn = std::bind(&TermControl::_SendInputToConnection, this, std::placeholders::_1);
|
||||
_terminal->SetWriteInputCallback(inputFn);
|
||||
_terminal->SetMouseModeChangedCallback([weakThis = get_weak()]() {
|
||||
if (auto strongThis{ weakThis.get() })
|
||||
if (_initializedTerminal)
|
||||
{
|
||||
strongThis->_TerminalMouseModeChanged();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
_SwapChainRoutine();
|
||||
const auto windowWidth = SwapChainPanel().ActualWidth(); // Width() and Height() are NaN?
|
||||
const auto windowHeight = SwapChainPanel().ActualHeight();
|
||||
|
||||
// Set up the height of the ScrollViewer and the grid we're using to fake our scrolling height
|
||||
auto bottom = _terminal->GetViewport().BottomExclusive();
|
||||
auto bufferHeight = bottom;
|
||||
if (windowWidth == 0 || windowHeight == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ScrollBar().Maximum(bufferHeight - bufferHeight);
|
||||
ScrollBar().Minimum(0);
|
||||
ScrollBar().Value(0);
|
||||
ScrollBar().ViewportSize(bufferHeight);
|
||||
// First create the render thread.
|
||||
// Then stash a local pointer to the render thread so we can initialize it and enable it
|
||||
// to paint itself *after* we hand off its ownership to the renderer.
|
||||
// We split up construction and initialization of the render thread object this way
|
||||
// because the renderer and render thread have circular references to each other.
|
||||
auto renderThread = std::make_unique<::Microsoft::Console::Render::RenderThread>();
|
||||
auto* const localPointerToThread = renderThread.get();
|
||||
|
||||
localPointerToThread->EnablePainting();
|
||||
// Now create the renderer and initialize the render thread.
|
||||
_renderer = std::make_unique<::Microsoft::Console::Render::Renderer>(_terminal.get(), nullptr, 0, std::move(renderThread));
|
||||
::Microsoft::Console::Render::IRenderTarget& renderTarget = *_renderer;
|
||||
|
||||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
// Set up the DX Engine
|
||||
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
|
||||
_renderer->AddRenderEngine(dxEngine.get());
|
||||
|
||||
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
|
||||
// Initialize our font with the renderer
|
||||
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
|
||||
// and react accordingly.
|
||||
_UpdateFont(true);
|
||||
|
||||
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
|
||||
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
|
||||
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
|
||||
const COORD windowSize{ static_cast<short>(windowWidth), static_cast<short>(windowHeight) };
|
||||
|
||||
// Set up blinking cursor
|
||||
int blinkTime = GetCaretBlinkTime();
|
||||
if (blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
_cursorTimer = std::make_optional(DispatcherTimer());
|
||||
_cursorTimer.value().Interval(std::chrono::milliseconds(blinkTime));
|
||||
_cursorTimer.value().Tick({ get_weak(), &TermControl::_BlinkCursor });
|
||||
_cursorTimer.value().Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled cursor blinking
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
// Fist set up the dx engine with the window size in pixels.
|
||||
// Then, using the font, get the number of characters that can fit.
|
||||
// Resize our terminal connection to match that size, and initialize the terminal with that size.
|
||||
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
|
||||
THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
|
||||
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
// Update DxEngine's SelectionBackground
|
||||
dxEngine->SetSelectionBackground(_settings.SelectionBackground());
|
||||
|
||||
// Focus the control here. If we do it during control initialization, then
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
// becomes a no-op.
|
||||
this->Focus(FocusState::Programmatic);
|
||||
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
|
||||
const auto width = vp.Width();
|
||||
const auto height = vp.Height();
|
||||
_connection.Resize(height, width);
|
||||
|
||||
// Override the default width and height to match the size of the swapChainPanel
|
||||
_settings.InitialCols(width);
|
||||
_settings.InitialRows(height);
|
||||
|
||||
_terminal->CreateFromSettings(_settings, renderTarget);
|
||||
|
||||
// TODO:GH#3927 - Make it possible to hot-reload this setting. Right
|
||||
// here, the setting will only be used when the Terminal is initialized.
|
||||
dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect());
|
||||
|
||||
// TODO:GH#3927 - hot-reload this one too
|
||||
// Update DxEngine's AntialiasingMode
|
||||
switch (_settings.AntialiasingMode())
|
||||
{
|
||||
case TextAntialiasingMode::Cleartype:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
|
||||
break;
|
||||
case TextAntialiasingMode::Aliased:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED);
|
||||
break;
|
||||
case TextAntialiasingMode::Grayscale:
|
||||
default:
|
||||
dxEngine->SetAntialiasingMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
|
||||
break;
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(dxEngine->Enable());
|
||||
_renderEngine = std::move(dxEngine);
|
||||
|
||||
_AttachDxgiSwapChainToXaml(_renderEngine->GetSwapChain().Get());
|
||||
|
||||
// Tell the DX Engine to notify us when the swap chain changes.
|
||||
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
|
||||
_renderEngine->SetCallback(std::bind(&TermControl::RenderEngineSwapChainChanged, this));
|
||||
|
||||
auto bottom = _terminal->GetViewport().BottomExclusive();
|
||||
auto bufferHeight = bottom;
|
||||
|
||||
ScrollBar().Maximum(bufferHeight - bufferHeight);
|
||||
ScrollBar().Minimum(0);
|
||||
ScrollBar().Value(0);
|
||||
ScrollBar().ViewportSize(bufferHeight);
|
||||
|
||||
localPointerToThread->EnablePainting();
|
||||
|
||||
// Set up blinking cursor
|
||||
int blinkTime = GetCaretBlinkTime();
|
||||
if (blinkTime != INFINITE)
|
||||
{
|
||||
// Create a timer
|
||||
DispatcherTimer cursorTimer;
|
||||
cursorTimer.Interval(std::chrono::milliseconds(blinkTime));
|
||||
cursorTimer.Tick({ get_weak(), &TermControl::_CursorTimerTick });
|
||||
cursorTimer.Start();
|
||||
_cursorTimer.emplace(std::move(cursorTimer));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user has disabled cursor blinking
|
||||
_cursorTimer = std::nullopt;
|
||||
}
|
||||
|
||||
// import value from WinUser (convert from milli-seconds to micro-seconds)
|
||||
_multiClickTimer = GetDoubleClickTime() * 1000;
|
||||
|
||||
// Focus the control here. If we do it during control initialization, then
|
||||
// focus won't actually get passed to us. I believe this is because
|
||||
// we're not technically a part of the UI tree yet, so focusing us
|
||||
// becomes a no-op.
|
||||
this->Focus(FocusState::Programmatic);
|
||||
|
||||
_connection.Start();
|
||||
_initializedTerminal = true;
|
||||
} // scope for TerminalLock
|
||||
// call this event dispatcher outside of lock
|
||||
|
||||
_connection.Start();
|
||||
_initializedTerminal = true;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
return true;
|
||||
}
|
||||
@@ -722,11 +713,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
e.OriginalKey() == VirtualKey::RightWindows)
|
||||
|
||||
{
|
||||
if (!_closing && e.OriginalKey() == VirtualKey::Shift)
|
||||
{
|
||||
// If the user presses or releases shift, check whether we're in mouse mode and the cursor needs updating
|
||||
_TerminalMouseModeChanged();
|
||||
}
|
||||
e.Handled(true);
|
||||
return;
|
||||
}
|
||||
@@ -778,23 +764,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
e.Handled(handled);
|
||||
}
|
||||
|
||||
void TermControl::_KeyUpHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
// If the current focused element is a child element of searchbox,
|
||||
// we do not send this event up to terminal
|
||||
if (_searchBox && _searchBox->ContainsFocus())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_closing && e.OriginalKey() == VirtualKey::Shift)
|
||||
{
|
||||
// If the user presses or releases shift, check whether we're in mouse mode and the cursor needs updating
|
||||
_TerminalMouseModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Send this particular key event to the terminal.
|
||||
// See Terminal::SendKeyEvent for more information.
|
||||
@@ -908,6 +877,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - point: the PointerPoint object representing a mouse event from our XAML input handler
|
||||
bool TermControl::_CanSendVTMouseInput()
|
||||
{
|
||||
if (!_terminal)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If the user is holding down Shift, suppress mouse events
|
||||
// TODO GH#4875: disable/customize this functionality
|
||||
const auto modifiers = _GetPressedModifierKeys();
|
||||
@@ -918,18 +891,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
return _terminal->IsTrackingMouseInput();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handles changes in mouse mode state
|
||||
winrt::fire_and_forget TermControl::_TerminalMouseModeChanged()
|
||||
{
|
||||
co_await Dispatcher();
|
||||
if (_oldCursor) // if we have an active cursor transition
|
||||
{
|
||||
auto coreWindow = Window::Current().CoreWindow();
|
||||
coreWindow.PointerCursor(_CanSendVTMouseInput() ? _pointerCursor : _textCursor);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - handle a mouse click event. Begin selection process.
|
||||
// Arguments:
|
||||
@@ -938,6 +899,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_CapturePointer(sender, args);
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
@@ -1045,6 +1011,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
@@ -1148,6 +1119,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ptr = args.Pointer();
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
@@ -1189,45 +1165,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the PointerEntered event. We use this for cursor manipulation.
|
||||
// Arguments:
|
||||
// - sender: the XAML element responding to the pointer input
|
||||
// - args: event data
|
||||
void TermControl::_PointerEnteredHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& /*args*/)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto coreWindow = Window::Current().CoreWindow();
|
||||
_oldCursor = coreWindow.PointerCursor();
|
||||
|
||||
if (_terminal->IsTrackingMouseInput())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
coreWindow.PointerCursor(_textCursor);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the PointerExited event. We use this for cursor manipulation.
|
||||
// Arguments:
|
||||
// - sender: the XAML element responding to the pointer input
|
||||
// - args: event data
|
||||
void TermControl::_PointerExitedHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& /*args*/)
|
||||
{
|
||||
if (auto oldCursor{ std::exchange(_oldCursor, std::nullopt) })
|
||||
{
|
||||
auto coreWindow = Window::Current().CoreWindow();
|
||||
coreWindow.PointerCursor(*oldCursor);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the PointerWheelChanged event. This is raised in
|
||||
// response to mouse wheel changes. Depending upon what modifier keys are
|
||||
@@ -1237,6 +1174,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Input::PointerRoutedEventArgs const& args)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto point = args.GetCurrentPoint(*this);
|
||||
|
||||
if (_CanSendVTMouseInput())
|
||||
@@ -1367,7 +1309,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Controls::Primitives::RangeBaseValueChangedEventArgs const& args)
|
||||
{
|
||||
if (_isTerminalInitiatedScroll)
|
||||
if (_isTerminalInitiatedScroll || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1552,6 +1494,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_focused = false;
|
||||
|
||||
if (_uiaEngine.get())
|
||||
@@ -1623,8 +1566,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// concerned with initialization process. Value forwarded to event handler.
|
||||
void TermControl::_UpdateFont(const bool initialUpdate)
|
||||
{
|
||||
auto lock = _terminal->LockForWriting();
|
||||
|
||||
const int newDpi = static_cast<int>(static_cast<double>(USER_DEFAULT_SCREEN_DPI) * SwapChainPanel().CompositionScaleX());
|
||||
|
||||
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
|
||||
@@ -1651,11 +1592,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
// Refresh our font with the renderer
|
||||
_UpdateFont();
|
||||
|
||||
auto lock = _terminal->LockForWriting();
|
||||
// Resize the terminal's BUFFER to match the new font size. This does
|
||||
// NOT change the size of the window, because that can lead to more
|
||||
// problems (like what happens when you change the font size while the
|
||||
// window is maximized?)
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_DoResize(SwapChainPanel().ActualWidth(), SwapChainPanel().ActualHeight());
|
||||
}
|
||||
CATCH_LOG();
|
||||
@@ -1669,7 +1611,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/,
|
||||
SizeChangedEventArgs const& e)
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
if (!_initializedTerminal || _closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1700,8 +1642,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// Arguments:
|
||||
// - sender: not used
|
||||
// - e: not used
|
||||
void TermControl::_BlinkCursor(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */,
|
||||
Windows::Foundation::IInspectable const& /* e */)
|
||||
{
|
||||
if ((_closing) || (!_terminal->IsCursorBlinkingAllowed() && _terminal->IsCursorVisible()))
|
||||
{
|
||||
@@ -1853,10 +1795,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
hstring TermControl::Title()
|
||||
{
|
||||
if (!_initializedTerminal)
|
||||
return L"";
|
||||
|
||||
hstring hstr(_terminal->GetConsoleTitle());
|
||||
hstring hstr{ _terminal->GetConsoleTitle() };
|
||||
return hstr;
|
||||
}
|
||||
|
||||
@@ -1873,8 +1812,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - collapseText: collapse all of the text to one line
|
||||
bool TermControl::CopySelectionToClipboard(bool collapseText)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// no selection --> nothing to copy
|
||||
if (_terminal == nullptr || !_terminal->IsSelectionActive())
|
||||
if (!_terminal->IsSelectionActive())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1941,6 +1885,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_connectionStateChangedRevoker.revoke();
|
||||
|
||||
TSFInputControl().Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
_autoScrollTimer.Stop();
|
||||
|
||||
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
||||
{
|
||||
@@ -1958,11 +1903,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// renderEngine is destroyed
|
||||
}
|
||||
|
||||
if (auto localTerminal{ std::exchange(_terminal, nullptr) })
|
||||
{
|
||||
_initializedTerminal = false;
|
||||
// terminal is destroyed.
|
||||
}
|
||||
// we don't destroy _terminal here; it now has the same lifetime as the
|
||||
// control.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2276,6 +2218,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_CompositionCompleted(winrt::hstring text)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_connection.WriteInput(text);
|
||||
}
|
||||
|
||||
@@ -2288,11 +2235,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - <none>
|
||||
void TermControl::_CurrentCursorPositionHandler(const IInspectable& /*sender*/, const CursorPositionEventArgs& eventArgs)
|
||||
{
|
||||
// If we haven't initialized yet, just quick return.
|
||||
if (!_terminal)
|
||||
auto lock = _terminal->LockForReading();
|
||||
if (!_initializedTerminal)
|
||||
{
|
||||
// fake it
|
||||
eventArgs.CurrentPosition({ 0, 0 });
|
||||
return;
|
||||
}
|
||||
|
||||
const COORD cursorPos = _terminal->GetCursorPosition();
|
||||
Windows::Foundation::Point p = { gsl::narrow_cast<float>(cursorPos.X), gsl::narrow_cast<float>(cursorPos.Y) };
|
||||
eventArgs.CurrentPosition(p);
|
||||
@@ -2362,8 +2312,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// - e: The DragEventArgs from the Drop event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget TermControl::_DoDragDrop(DragEventArgs const e)
|
||||
winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const e)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.DataView().Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
auto items = co_await e.DataView().GetStorageItemsAsync();
|
||||
@@ -2398,22 +2354,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Synchronous handler for the "Drop" event. We'll dispatch the async
|
||||
// _DoDragDrop method to handle this, because getting information about
|
||||
// the file that was potentially dropped onto us must be done off the UI
|
||||
// thread.
|
||||
// Arguments:
|
||||
// - e: The DragEventArgs from the Drop event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TermControl::_DragDropHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const& e)
|
||||
{
|
||||
// Dispatch an async method to handle the drop event.
|
||||
_DoDragDrop(e);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Handle the DragOver event. We'll signal that the drag operation we
|
||||
// support is the "copy" operation, and we'll also customize the
|
||||
@@ -2427,6 +2367,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
DragEventArgs const& e)
|
||||
{
|
||||
if (_closing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!e.DataView().Contains(StandardDataFormats::StorageItems()))
|
||||
{
|
||||
// We can't do anything for non-storageitems right now.
|
||||
|
||||
@@ -77,7 +77,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void AdjustFontSize(int fontSizeDelta);
|
||||
void ResetFontSize();
|
||||
|
||||
winrt::fire_and_forget SwapChainChanged();
|
||||
winrt::fire_and_forget RenderEngineSwapChainChanged();
|
||||
void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain);
|
||||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
@@ -167,10 +168,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
|
||||
|
||||
std::optional<winrt::Windows::UI::Core::CoreCursor> _oldCursor; // when we toggle the cursor, we have to save it here to restore it
|
||||
winrt::Windows::UI::Core::CoreCursor _textCursor;
|
||||
winrt::Windows::UI::Core::CoreCursor _pointerCursor;
|
||||
|
||||
void _ApplyUISettings();
|
||||
void _InitializeBackgroundBrush();
|
||||
winrt::fire_and_forget _BackgroundColorChanged(const uint32_t color);
|
||||
@@ -179,26 +176,21 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _SetFontSize(int fontSize);
|
||||
void _TappedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& e);
|
||||
void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _KeyUpHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
void _CharacterHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::CharacterReceivedRoutedEventArgs const& e);
|
||||
void _PointerPressedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _PointerMovedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _PointerEnteredHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _PointerExitedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _MouseWheelHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _ScrollbarChangeHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::Primitives::RangeBaseValueChangedEventArgs const& e);
|
||||
void _GotFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||
void _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
|
||||
winrt::fire_and_forget _DragDropHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const e);
|
||||
void _DragOverHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::DragEventArgs const& e);
|
||||
winrt::fire_and_forget _DoDragDrop(Windows::UI::Xaml::DragEventArgs const e);
|
||||
|
||||
void _BlinkCursor(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _CursorTimerTick(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
|
||||
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
|
||||
void _SendInputToConnection(const std::wstring& wstr);
|
||||
void _SendPastedTextToConnection(const std::wstring& wstr);
|
||||
winrt::fire_and_forget _SwapChainRoutine();
|
||||
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
|
||||
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
|
||||
void _DoResize(const double newWidth, const double newHeight);
|
||||
@@ -223,7 +215,6 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
bool _TrySendKeyEvent(const WORD vkey, const WORD scanCode, ::Microsoft::Terminal::Core::ControlKeyStates modifiers);
|
||||
bool _TrySendMouseEvent(Windows::UI::Input::PointerPoint const& point);
|
||||
bool _CanSendVTMouseInput();
|
||||
winrt::fire_and_forget _TerminalMouseModeChanged();
|
||||
|
||||
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
|
||||
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
Tapped="_TappedHandler"
|
||||
PointerWheelChanged="_MouseWheelHandler"
|
||||
PreviewKeyDown="_KeyDownHandler"
|
||||
KeyUp="_KeyUpHandler"
|
||||
CharacterReceived="_CharacterHandler"
|
||||
GotFocus="_GotFocusHandler"
|
||||
LostFocus="_LostFocusHandler">
|
||||
@@ -44,9 +43,7 @@
|
||||
CompositionScaleChanged="_SwapChainScaleChanged"
|
||||
PointerPressed="_PointerPressedHandler"
|
||||
PointerMoved="_PointerMovedHandler"
|
||||
PointerReleased="_PointerReleasedHandler"
|
||||
PointerEntered="_PointerEnteredHandler"
|
||||
PointerExited="_PointerExitedHandler" />
|
||||
PointerReleased="_PointerReleasedHandler" />
|
||||
|
||||
<!-- Putting this in a grid w/ the SwapChainPanel
|
||||
ensures that it's always aligned w/ the scrollbar -->
|
||||
|
||||
@@ -701,6 +701,7 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
|
||||
|
||||
if (notifyScroll)
|
||||
{
|
||||
// TODO: don't do this, thanks migrie
|
||||
_buffer->GetRenderTarget().TriggerRedrawAll();
|
||||
_NotifyScrollEvent();
|
||||
}
|
||||
@@ -791,12 +792,3 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
||||
const auto& cursor = _buffer->GetCursor();
|
||||
return cursor.IsBlinkingAllowed();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets up the callback for mouse input mode changes
|
||||
// Parameters:
|
||||
// - mouseModeChangedCallback: the callback
|
||||
void Terminal::SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept
|
||||
{
|
||||
_terminalInput->SetMouseModeChangedCallback(std::move(mouseModeChangedCallback));
|
||||
}
|
||||
|
||||
@@ -172,7 +172,6 @@ public:
|
||||
|
||||
void SetCursorOn(const bool isOn) noexcept;
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
void SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
|
||||
@@ -53,6 +53,9 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
static const SHORT TerminalViewWidth = 80;
|
||||
static const SHORT TerminalViewHeight = 32;
|
||||
|
||||
// This test class is for tests that are supposed to emit something in the PTY layer
|
||||
// and then check that they've been staged for presentation correctly inside
|
||||
// the Terminal application. Which sequences were used to get here don't matter.
|
||||
TEST_CLASS(ConptyRoundtripTests);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
@@ -171,6 +174,8 @@ class TerminalCoreUnitTests::ConptyRoundtripTests final
|
||||
|
||||
TEST_METHOD(TestResizeHeight);
|
||||
|
||||
TEST_METHOD(ScrollWithMargins);
|
||||
|
||||
private:
|
||||
bool _writeCallback(const char* const pch, size_t const cch);
|
||||
void _flushFirstFrame();
|
||||
@@ -1055,3 +1060,245 @@ void ConptyRoundtripTests::PassthroughHardReset()
|
||||
TestUtils::VerifyExpectedString(termTb, std::wstring(TerminalViewWidth, L' '), { 0, y });
|
||||
}
|
||||
}
|
||||
|
||||
void ConptyRoundtripTests::ScrollWithMargins()
|
||||
{
|
||||
auto& g = ServiceLocator::LocateGlobals();
|
||||
auto& renderer = *g.pRender;
|
||||
auto& gci = g.getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& hostSm = si.GetStateMachine();
|
||||
|
||||
auto& hostTb = si.GetTextBuffer();
|
||||
auto& termTb = *term->_buffer;
|
||||
const auto initialTermView = term->GetViewport();
|
||||
|
||||
Log::Comment(L"Flush first frame.");
|
||||
_flushFirstFrame();
|
||||
|
||||
// Fill up the buffer with some text.
|
||||
// We're going to write something like this:
|
||||
// AAAA
|
||||
// BBBB
|
||||
// CCCC
|
||||
// ........
|
||||
// QQQQ
|
||||
// ****************
|
||||
// The letters represent the data in the TMUX pane.
|
||||
// The final *** line represents the mode line which we will
|
||||
// attempt to hold in place and not scroll.
|
||||
|
||||
Log::Comment(L"Fill host with text pattern by feeding it into VT parser.");
|
||||
const auto rowsToWrite = initialTermView.Height() - 1;
|
||||
|
||||
// For all lines but the last one, write out a few of a letter.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const wchar_t wch = static_cast<wchar_t>(L'A' + i);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter(wch);
|
||||
hostSm.ProcessCharacter('\n');
|
||||
}
|
||||
|
||||
// For the last one, write out the asterisks for the mode line.
|
||||
for (auto i = 0; i < initialTermView.Width(); ++i)
|
||||
{
|
||||
hostSm.ProcessCharacter('*');
|
||||
}
|
||||
|
||||
// no newline in the bottom right corner or it will move unexpectedly.
|
||||
|
||||
// Now set up the verification that the buffers are full of the pattern we expect.
|
||||
// This function will verify the text backing buffers.
|
||||
auto verifyBuffer = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor is waiting in the bottom right corner
|
||||
VERIFY_ARE_EQUAL(initialTermView.Height() - 1, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(initialTermView.Width() - 1, cursor.GetPosition().X);
|
||||
|
||||
// For all rows except the last one, verify that we have a run of four letters.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const std::wstring expectedString(4, static_cast<wchar_t>(L'A' + i));
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
|
||||
}
|
||||
|
||||
// For the last row, verify we have an entire row of asterisks for the mode line.
|
||||
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedModeLine, expectedPos);
|
||||
};
|
||||
|
||||
// This will verify the text emitted from the PTY.
|
||||
for (auto i = 0; i < rowsToWrite; ++i)
|
||||
{
|
||||
const std::string expectedString(4, static_cast<char>('A' + i));
|
||||
expectedOutput.push_back(expectedString);
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
const std::string expectedString(initialTermView.Width(), '*');
|
||||
expectedOutput.push_back(expectedString);
|
||||
|
||||
// Cursor gets reset into bottom right corner as we're writing all the way into that corner.
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() << ";" << initialTermView.Width() << "H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
}
|
||||
|
||||
Log::Comment(L"Verify host buffer contains pattern.");
|
||||
// Verify the host side.
|
||||
verifyBuffer(hostTb);
|
||||
|
||||
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
|
||||
// Paint the frame
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
Log::Comment(L"Verify terminal buffer contains pattern.");
|
||||
// Verify the terminal side.
|
||||
verifyBuffer(termTb);
|
||||
|
||||
Log::Comment(L"!!! OK. Set up the scroll region and let's get scrolling!");
|
||||
// This is a simulation of what TMUX does to scroll everything except the mode line.
|
||||
// First build up our VT strings...
|
||||
std::wstring reducedScrollRegion;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall buffer...
|
||||
// ESC[1;19r
|
||||
// Set scroll region to lines 1-19.
|
||||
wss << L"\x1b[1;" << initialTermView.Height() - 1 << L"r";
|
||||
reducedScrollRegion = wss.str();
|
||||
}
|
||||
std::wstring completeScrollRegion;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall buffer...
|
||||
// ESC[1;20r
|
||||
// Set scroll region to lines 1-20. (or the whole buffer)
|
||||
wss << L"\x1b[1;" << initialTermView.Height() << L"r";
|
||||
completeScrollRegion = wss.str();
|
||||
}
|
||||
std::wstring reducedCursorBottomRight;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall and 100 wide buffer
|
||||
// ESC[19;100H
|
||||
// Put cursor on line 19 (1 before last) and the right most column 100.
|
||||
// (Remember that in VT, we start counting from 1 not 0.)
|
||||
wss << L"\x1b[" << initialTermView.Height() - 1 << L";" << initialTermView.Width() << "H";
|
||||
reducedCursorBottomRight = wss.str();
|
||||
}
|
||||
std::wstring completeCursorAtPromptLine;
|
||||
{
|
||||
std::wstringstream wss;
|
||||
// For 20 tall and 100 wide buffer
|
||||
// ESC[19;1H
|
||||
// Put cursor on line 19 (1 before last) and the left most column 1.
|
||||
// (Remember that in VT, we start counting from 1 not 0.)
|
||||
wss << L"\x1b[" << initialTermView.Height() - 1 << L";1H";
|
||||
completeCursorAtPromptLine = wss.str();
|
||||
}
|
||||
|
||||
Log::Comment(L"Perform all the operations on the buffer.");
|
||||
|
||||
// OK this is what TMUX does.
|
||||
// 1. Mark off the scroll area as everything but the mode line.
|
||||
hostSm.ProcessString(reducedScrollRegion);
|
||||
// 2. Put the cursor in the bottom-right corner of the scroll region.
|
||||
hostSm.ProcessString(reducedCursorBottomRight);
|
||||
// 3. Send a single newline which should do the heavy lifting
|
||||
// of pushing everything in the scroll region up by 1 line and
|
||||
// leave everything outside the region alone.
|
||||
|
||||
// This entire block is subject to change in the future with optimizations.
|
||||
{
|
||||
// Cursor gets redrawn in the bottom right of the scroll region with the repaint that is forced
|
||||
// early while the screen is rotated.
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() - 1 << ";" << initialTermView.Width() << "H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
|
||||
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
|
||||
}
|
||||
|
||||
hostSm.ProcessString(L"\n");
|
||||
// 4. Remove the scroll area by setting it to the entire size of the viewport.
|
||||
hostSm.ProcessString(completeScrollRegion);
|
||||
// 5. Put the cursor back at the beginning of the new line that was just revealed.
|
||||
hostSm.ProcessString(completeCursorAtPromptLine);
|
||||
|
||||
// Set up the verifications like above.
|
||||
auto verifyBufferAfter = [&](const TextBuffer& tb) {
|
||||
auto& cursor = tb.GetCursor();
|
||||
// Verify the cursor is waiting on the freshly revealed line (1 above mode line)
|
||||
// and in the left most column.
|
||||
VERIFY_ARE_EQUAL(initialTermView.Height() - 2, cursor.GetPosition().Y);
|
||||
VERIFY_ARE_EQUAL(0, cursor.GetPosition().X);
|
||||
|
||||
// For all rows except the last two, verify that we have a run of four letters.
|
||||
for (auto i = 0; i < rowsToWrite - 1; ++i)
|
||||
{
|
||||
// Start with B this time because the A line got scrolled off the top.
|
||||
const std::wstring expectedString(4, static_cast<wchar_t>(L'B' + i));
|
||||
const COORD expectedPos{ 0, gsl::narrow<SHORT>(i) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedString, expectedPos);
|
||||
}
|
||||
|
||||
// For the second to last row, verify that it is blank.
|
||||
{
|
||||
const std::wstring expectedBlankLine(initialTermView.Width(), L' ');
|
||||
const COORD blankLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite - 1) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedBlankLine, blankLinePos);
|
||||
}
|
||||
|
||||
// For the last row, verify we have an entire row of asterisks for the mode line.
|
||||
{
|
||||
const std::wstring expectedModeLine(initialTermView.Width(), L'*');
|
||||
const COORD modeLinePos{ 0, gsl::narrow<SHORT>(rowsToWrite) };
|
||||
TestUtils::VerifyExpectedString(tb, expectedModeLine, modeLinePos);
|
||||
}
|
||||
};
|
||||
|
||||
// This will verify the text emitted from the PTY.
|
||||
|
||||
expectedOutput.push_back("\x1b[H"); // cursor returns to top left corner.
|
||||
for (auto i = 0; i < rowsToWrite - 1; ++i)
|
||||
{
|
||||
const std::string expectedString(4, static_cast<char>('B' + i));
|
||||
expectedOutput.push_back(expectedString);
|
||||
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
expectedOutput.push_back(""); // nothing for the empty line
|
||||
expectedOutput.push_back("\x1b[K"); // erase the rest of the line.
|
||||
expectedOutput.push_back("\r\n");
|
||||
}
|
||||
{
|
||||
const std::string expectedString(initialTermView.Width(), '*');
|
||||
expectedOutput.push_back(expectedString);
|
||||
}
|
||||
{
|
||||
// Cursor gets reset into second line from bottom, left most column
|
||||
std::stringstream ss;
|
||||
ss << "\x1b[" << initialTermView.Height() - 1 << ";1H";
|
||||
expectedOutput.push_back(ss.str());
|
||||
}
|
||||
expectedOutput.push_back("\x1b[?25h"); // turn the cursor back on too.
|
||||
|
||||
Log::Comment(L"Verify host buffer contains pattern moved up one and mode line still in place.");
|
||||
// Verify the host side.
|
||||
verifyBufferAfter(hostTb);
|
||||
|
||||
Log::Comment(L"Emit PTY frame and validate it transmits the right data.");
|
||||
// Paint the frame
|
||||
VERIFY_SUCCEEDED(renderer.PaintFrame());
|
||||
|
||||
Log::Comment(L"Verify terminal buffer contains pattern moved up one and mode line still in place.");
|
||||
// Verify the terminal side.
|
||||
verifyBufferAfter(termTb);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
WM_MOUSEACTIVATE = 0x0021,
|
||||
|
||||
WM_GETOBJECT = 0x003D,
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The WM_WINDOWPOSCHANGED message is sent to a window whose size, position, or place in the Z order has changed as a result of a call to the SetWindowPos function or another window-management function.
|
||||
/// </summary>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
private DispatcherTimer blinkTimer;
|
||||
private NativeMethods.ScrollCallback scrollCallback;
|
||||
private NativeMethods.WriteCallback writeCallback;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TerminalContainer"/> class.
|
||||
/// </summary>
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
this.MessageHook += this.TerminalContainer_MessageHook;
|
||||
this.GotFocus += this.TerminalContainer_GotFocus;
|
||||
this.Focusable = true;
|
||||
|
||||
|
||||
var blinkTime = NativeMethods.GetCaretBlinkTime();
|
||||
|
||||
if (blinkTime != uint.MaxValue)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft.Terminal.Wpf
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A basic terminal control. This control can receive and render standard VT100 sequences.
|
||||
/// </summary>
|
||||
|
||||
@@ -171,7 +171,33 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
|
||||
// displays the correct text.
|
||||
if (newViewOrigin == viewport.Origin())
|
||||
{
|
||||
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), delta });
|
||||
// Inside this block, we're shifting down at the bottom.
|
||||
// This means that we had something like this:
|
||||
// AAAA
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// EEEE
|
||||
//
|
||||
// Our margins were set for lines A-D, but not on line E.
|
||||
// So we circled the whole buffer up by one:
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// EEEE
|
||||
// <blank, was AAAA>
|
||||
//
|
||||
// Then we scrolled the contents of everything OUTSIDE the margin frame down.
|
||||
// BBBB
|
||||
// CCCC
|
||||
// DDDD
|
||||
// <blank, filled during scroll down of EEEE>
|
||||
// EEEE
|
||||
//
|
||||
// And now we need to report that only the bottom line didn't "move" as we put the EEEE
|
||||
// back where it started, but everything else moved.
|
||||
// In this case, delta was 1. So the amount that moved is the entire viewport height minus the delta.
|
||||
Viewport invalid = Viewport::FromDimensions(viewport.Origin(), { viewport.Width(), viewport.Height() - delta });
|
||||
screenInfo.GetRenderTarget().TriggerRedraw(invalid);
|
||||
}
|
||||
|
||||
|
||||
@@ -481,7 +481,7 @@ namespace Conhost.UIA.Tests
|
||||
TextPatternRange testRange = visibleRanges.First().Clone();
|
||||
|
||||
// assumes that range is a line range at the top of the screen buffer
|
||||
Action<TextPatternRange> testTopBoundary = delegate(TextPatternRange range)
|
||||
Action<TextPatternRange> testTopBoundary = delegate (TextPatternRange range)
|
||||
{
|
||||
// the first visible range is at the top of the screen
|
||||
// buffer, we shouldn't be able to move the starting endpoint up
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Conhost.UIA.Tests.Common
|
||||
|
||||
~ShortcutHelper()
|
||||
{
|
||||
this.Dispose(false);
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
|
||||
tab.Click();
|
||||
Globals.WaitForTimeout();
|
||||
|
||||
|
||||
this.PopulateItemsOnNavigate(this.propDialog.PropWindow);
|
||||
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
this.tabs.Add(new FontTab(this.propDialog));
|
||||
this.tabs.Add(new LayoutTab(this.propDialog));
|
||||
this.tabs.Add(new ColorsTab(this.propDialog));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Tabs()
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Conhost.UIA.Tests.Elements
|
||||
app.UIRoot.SendKeys(Keys.Escape);
|
||||
this.state = ViewportStates.Normal;
|
||||
}
|
||||
|
||||
|
||||
public void EnterMode(ViewportStates state)
|
||||
{
|
||||
if (state == ViewportStates.Normal)
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Conhost.UIA.Tests
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
|
||||
afterScroll = app.GetScreenBufferInfo();
|
||||
|
||||
switch (dir)
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Conhost.UIA.Tests
|
||||
reg.BackupRegistry(); // we're going to modify the virtual terminal state for this, so back it up first.
|
||||
VersionSelector.SetConsoleVersion(reg, ConsoleVersion.V2);
|
||||
reg.SetDefaultValue(VIRTUAL_TERMINAL_KEY_NAME, VIRTUAL_TERMINAL_ON_VALUE);
|
||||
|
||||
|
||||
bool haveVtAppPath = !string.IsNullOrEmpty(vtAppLocation);
|
||||
|
||||
Verify.IsTrue(haveVtAppPath, "Ensure that we passed in the location to VtApp.exe");
|
||||
@@ -114,12 +114,12 @@ namespace Conhost.UIA.Tests
|
||||
Log.Comment("Move cursor to the middle-ish");
|
||||
Point cursorExpected = new Point();
|
||||
// H is at 5, 1. VT coords are 1-based and buffer is 0-based so adjust.
|
||||
cursorExpected.Y = 5 - 1;
|
||||
cursorExpected.Y = 5 - 1;
|
||||
cursorExpected.X = 1 - 1;
|
||||
app.UIRoot.SendKeys("H");
|
||||
|
||||
// Move to middle-ish from here. 10 Bs and 10 Cs should about do it.
|
||||
for (int i=0; i < 10; i++)
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
app.UIRoot.SendKeys("BC");
|
||||
cursorExpected.Y++;
|
||||
@@ -715,7 +715,7 @@ namespace Conhost.UIA.Tests
|
||||
}
|
||||
|
||||
delegate char GetExpectedChar(int rowId, int colId, int height, int width);
|
||||
|
||||
|
||||
private static void ScreenFillHelper(CmdApp app, ViewportArea area, IntPtr hConsole)
|
||||
{
|
||||
Log.Comment("Fill screen with junk");
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Conhost.UIA.Tests
|
||||
[TestMethod]
|
||||
public void TestSelection()
|
||||
{
|
||||
RunTest(TestSelectionImpl);
|
||||
RunTest(TestSelectionImpl);
|
||||
}
|
||||
|
||||
private void TestSelectionImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
@@ -295,7 +295,7 @@ namespace Conhost.UIA.Tests
|
||||
[TestMethod]
|
||||
public void TestLaunchAndExitChild()
|
||||
{
|
||||
RunTest(TestLaunchAndExitChildImpl);
|
||||
RunTest(TestLaunchAndExitChildImpl);
|
||||
}
|
||||
|
||||
private void TestLaunchAndExitChildImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
@@ -357,11 +357,11 @@ namespace Conhost.UIA.Tests
|
||||
{
|
||||
RunTest(TestScrollByWheelImpl);
|
||||
}
|
||||
|
||||
|
||||
private void TestScrollByWheelImpl(CmdApp app, ViewportArea area, IntPtr hConsole, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiex, Queue<EventData> expected, WinCon.CONSOLE_SCREEN_BUFFER_INFO_EX sbiexOriginal)
|
||||
{
|
||||
int rowsPerScroll = app.GetRowsPerScroll();
|
||||
int scrollDelta;
|
||||
int scrollDelta;
|
||||
|
||||
// A. Scroll down.
|
||||
{
|
||||
|
||||
@@ -27,6 +27,9 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
class ConptyOutputTests
|
||||
{
|
||||
// This test class is to write some things into the PTY and then check that
|
||||
// the rendering that is coming out of the VT-sequence generator is exactly
|
||||
// as we expect it to be.
|
||||
BEGIN_TEST_CLASS(ConptyOutputTests)
|
||||
TEST_CLASS_PROPERTY(L"IsolationLevel", L"Class")
|
||||
END_TEST_CLASS()
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
|
||||
#include "til/at.h"
|
||||
#include "til/color.h"
|
||||
#include "til/math.h"
|
||||
#include "til/some.h"
|
||||
#include "til/size.h"
|
||||
#include "til/point.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/operators.h"
|
||||
#include "til/rectangle.h"
|
||||
#include "til/bitmap.h"
|
||||
#include "til/u8u16convert.h"
|
||||
|
||||
|
||||
@@ -285,7 +285,8 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
|
||||
for (const auto pt : rc)
|
||||
{
|
||||
til::at(_bits, _rc.index_of(pt)) = true;
|
||||
auto idx = _rc.index_of(pt);
|
||||
til::at(_bits, idx) = true;
|
||||
}
|
||||
|
||||
_dirty |= rc;
|
||||
|
||||
85
src/inc/til/math.h
Normal file
85
src/inc/til/math.h
Normal file
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til
|
||||
{
|
||||
// The til::math namespace contains TIL math guidance casts;
|
||||
// they are intended to be used as the first argument to
|
||||
// floating-point universal converters elsewhere in the til namespace.
|
||||
namespace math
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
struct ceiling_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::ceil(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct flooring_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::floor(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct rounding_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
return ::base::saturated_cast<O>(::std::round(val));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct truncating_t
|
||||
{
|
||||
template<typename O, typename T>
|
||||
static O cast(T val)
|
||||
{
|
||||
if constexpr (std::is_floating_point_v<T>)
|
||||
{
|
||||
THROW_HR_IF(E_ABORT, ::std::isnan(val));
|
||||
}
|
||||
return ::base::saturated_cast<O>(val);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr details::ceiling_t ceiling; // positives become more positive, negatives become less negative
|
||||
static constexpr details::flooring_t flooring; // positives become less positive, negatives become more negative
|
||||
static constexpr details::rounding_t rounding; // it's rounding, from math class
|
||||
static constexpr details::truncating_t truncating; // drop the decimal point, regardless of how close it is to the next value
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rectangle.h"
|
||||
#include "size.h"
|
||||
#include "bitmap.h"
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
// Operators go here when they involve two headers that can't/don't include each other.
|
||||
|
||||
@@ -53,6 +53,22 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a X and a Y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
point(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a x and a y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr point(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().x)> && std::is_floating_point_v<decltype(std::declval<TOther>().y)>, int> /*sentinel*/ = 0) :
|
||||
point(TilMath::template cast<ptrdiff_t>(other.x), TilMath::template cast<ptrdiff_t>(other.y))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const point& other) const noexcept
|
||||
{
|
||||
return _x == other._x &&
|
||||
@@ -107,6 +123,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator+=(const point& other)
|
||||
{
|
||||
*this = *this + other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator-(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -118,6 +140,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator-=(const point& other)
|
||||
{
|
||||
*this = *this - other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator*(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -129,6 +157,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator*=(const point& other)
|
||||
{
|
||||
*this = *this * other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
point operator/(const point& other) const
|
||||
{
|
||||
ptrdiff_t x;
|
||||
@@ -140,6 +174,12 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return point{ x, y };
|
||||
}
|
||||
|
||||
point& operator/=(const point& other)
|
||||
{
|
||||
*this = *this / other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr ptrdiff_t x() const noexcept
|
||||
{
|
||||
return _x;
|
||||
|
||||
@@ -3,10 +3,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "point.h"
|
||||
#include "size.h"
|
||||
#include "some.h"
|
||||
|
||||
#ifdef UNIT_TESTING
|
||||
class RectangleTests;
|
||||
#endif
|
||||
@@ -636,6 +632,32 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
return *this;
|
||||
}
|
||||
|
||||
// MUL will scale the entire rectangle up by the size factor
|
||||
rectangle operator*(const size& size)
|
||||
{
|
||||
auto topLeft = _topLeft;
|
||||
auto bottomRight = _bottomRight;
|
||||
topLeft = topLeft * size;
|
||||
bottomRight = bottomRight * size;
|
||||
return til::rectangle{ topLeft, bottomRight };
|
||||
}
|
||||
|
||||
// DIV will scale the entire rectangle down by the size factor,
|
||||
// but rounds the bottom-right corner out.
|
||||
rectangle operator/(const size& size)
|
||||
{
|
||||
auto topLeft = _topLeft;
|
||||
auto bottomRight = _bottomRight;
|
||||
topLeft = topLeft / size;
|
||||
|
||||
// Move bottom right point into a size
|
||||
// Use size specialization of divide_ceil to round up against the size given.
|
||||
// Add leading addition to point to convert it back into a point.
|
||||
bottomRight = til::point{} + til::size{ right(), bottom() }.divide_ceil(size);
|
||||
|
||||
return til::rectangle{ topLeft, bottomRight };
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
constexpr ptrdiff_t top() const noexcept
|
||||
|
||||
@@ -53,6 +53,30 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a X and a Y field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().X)> && std::is_floating_point_v<decltype(std::declval<TOther>().Y)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.X), TilMath::template cast<ptrdiff_t>(other.Y))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a cx and a cy field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().cx)> && std::is_floating_point_v<decltype(std::declval<TOther>().cy)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.cx), TilMath::template cast<ptrdiff_t>(other.cy))
|
||||
{
|
||||
}
|
||||
|
||||
// This template will convert to size from anything that has a Width and a Height field that are floating-point;
|
||||
// a math type is required.
|
||||
template<typename TilMath, typename TOther>
|
||||
constexpr size(TilMath, const TOther& other, std::enable_if_t<std::is_floating_point_v<decltype(std::declval<TOther>().Width)> && std::is_floating_point_v<decltype(std::declval<TOther>().Height)>, int> /*sentinel*/ = 0) :
|
||||
size(TilMath::template cast<ptrdiff_t>(other.Width), TilMath::template cast<ptrdiff_t>(other.Height))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr bool operator==(const size& other) const noexcept
|
||||
{
|
||||
return _width == other._width &&
|
||||
|
||||
@@ -273,7 +273,12 @@ using namespace Microsoft::Console::Render;
|
||||
rect.right = std::accumulate(advancesSpan.cbegin(), advancesSpan.cend(), rect.right);
|
||||
|
||||
// Clip all drawing in this glyph run to where we expect.
|
||||
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
// We need the AntialiasMode here to be Aliased to ensure
|
||||
// that background boxes line up with each other and don't leave behind
|
||||
// stray colors.
|
||||
// See GH#3626 for more details.
|
||||
d2dContext->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
// Ensure we pop it on the way out
|
||||
auto popclip = wil::scope_exit([&d2dContext]() noexcept {
|
||||
d2dContext->PopAxisAlignedClip();
|
||||
|
||||
@@ -65,9 +65,8 @@ using namespace Microsoft::Console::Types;
|
||||
// TODO GH 2683: The default constructor should not throw.
|
||||
DxEngine::DxEngine() :
|
||||
RenderEngineBase(),
|
||||
_isInvalidUsed{ false },
|
||||
_invalidRect{ 0 },
|
||||
_invalidScroll{ 0 },
|
||||
_invalidMap{},
|
||||
_invalidScroll{},
|
||||
_presentParams{ 0 },
|
||||
_presentReady{ false },
|
||||
_presentScroll{ 0 },
|
||||
@@ -75,16 +74,16 @@ DxEngine::DxEngine() :
|
||||
_presentOffset{ 0 },
|
||||
_isEnabled{ false },
|
||||
_isPainting{ false },
|
||||
_displaySizePixels{ 0 },
|
||||
_displaySizePixels{},
|
||||
_foregroundColor{ 0 },
|
||||
_backgroundColor{ 0 },
|
||||
_selectionBackground{},
|
||||
_glyphCell{ 0 },
|
||||
_glyphCell{},
|
||||
_haveDeviceResources{ false },
|
||||
_retroTerminalEffects{ false },
|
||||
_antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE },
|
||||
_hwndTarget{ static_cast<HWND>(INVALID_HANDLE_VALUE) },
|
||||
_sizeTarget{ 0 },
|
||||
_sizeTarget{},
|
||||
_dpi{ USER_DEFAULT_SCREEN_DPI },
|
||||
_scale{ 1.0f },
|
||||
_chainMode{ SwapChainMode::ForComposition },
|
||||
@@ -238,8 +237,8 @@ HRESULT DxEngine::_SetupTerminalEffects()
|
||||
|
||||
// Setup the viewport.
|
||||
D3D11_VIEWPORT vp;
|
||||
vp.Width = static_cast<FLOAT>(_displaySizePixels.cx);
|
||||
vp.Height = static_cast<FLOAT>(_displaySizePixels.cy);
|
||||
vp.Width = _displaySizePixels.width<float>();
|
||||
vp.Height = _displaySizePixels.height<float>();
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = 0;
|
||||
@@ -370,7 +369,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
// You can find out how to install it here:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
|
||||
// clang-format on
|
||||
// D3D11_CREATE_DEVICE_DEBUG |
|
||||
D3D11_CREATE_DEVICE_DEBUG |
|
||||
D3D11_CREATE_DEVICE_SINGLETHREADED;
|
||||
|
||||
const std::array<D3D_FEATURE_LEVEL, 5> FeatureLevels{ D3D_FEATURE_LEVEL_11_1,
|
||||
@@ -424,8 +423,7 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
// use the HWND's dimensions for the swap chain dimensions.
|
||||
RECT rect = { 0 };
|
||||
RETURN_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &rect));
|
||||
@@ -454,11 +452,10 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
|
||||
break;
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
// Use the given target size for compositions.
|
||||
SwapChainDesc.Width = _displaySizePixels.cx;
|
||||
SwapChainDesc.Height = _displaySizePixels.cy;
|
||||
SwapChainDesc.Width = _displaySizePixels.width<UINT>();
|
||||
SwapChainDesc.Height = _displaySizePixels.height<UINT>();
|
||||
|
||||
// We're doing advanced composition pretty much for the purpose of pretty alpha, so turn it on.
|
||||
SwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
|
||||
@@ -533,6 +530,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
|
||||
&props,
|
||||
&_d2dRenderTarget));
|
||||
|
||||
// We need the AntialiasMode for non-text object to be Aliased to ensure
|
||||
// that background boxes line up with each other and don't leave behind
|
||||
// stray colors.
|
||||
// See GH#3626 for more details.
|
||||
_d2dRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_d2dRenderTarget->SetTextAntialiasMode(_antialiasingMode);
|
||||
|
||||
RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed),
|
||||
@@ -628,8 +630,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
|
||||
return _dwriteFactory->CreateTextLayout(string,
|
||||
gsl::narrow<UINT32>(stringLength),
|
||||
_dwriteTextFormat.Get(),
|
||||
gsl::narrow<float>(_displaySizePixels.cx),
|
||||
_glyphCell.cy != 0 ? _glyphCell.cy : gsl::narrow<float>(_displaySizePixels.cy),
|
||||
_displaySizePixels.width<float>(),
|
||||
_glyphCell.height() != 0 ? _glyphCell.height<float>() : _displaySizePixels.height<float>(),
|
||||
ppTextLayout);
|
||||
}
|
||||
|
||||
@@ -650,9 +652,7 @@ void DxEngine::_ReleaseDeviceResources() noexcept
|
||||
[[nodiscard]] HRESULT DxEngine::SetWindowSize(const SIZE Pixels) noexcept
|
||||
{
|
||||
_sizeTarget = Pixels;
|
||||
|
||||
RETURN_IF_FAILED(InvalidateAll());
|
||||
|
||||
_invalidMap.resize(_sizeTarget / _glyphCell, true);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -686,7 +686,8 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, psrRegion);
|
||||
|
||||
_InvalidOr(*psrRegion);
|
||||
_invalidMap.set(Viewport::FromExclusive(*psrRegion).ToInclusive());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -700,8 +701,9 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pcoordCursor);
|
||||
|
||||
const SMALL_RECT sr = Microsoft::Console::Types::Viewport::FromCoord(*pcoordCursor).ToInclusive();
|
||||
return Invalidate(&sr);
|
||||
_invalidMap.set(*pcoordCursor);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -711,13 +713,17 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateSystem(const RECT* const prcDirtyClient) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, prcDirtyClient);
|
||||
|
||||
_InvalidOr(*prcDirtyClient);
|
||||
// Dirty client is in pixels. Use divide specialization against glyph factor to make conversion
|
||||
// to cells.
|
||||
_invalidMap.set(til::rectangle{ *prcDirtyClient } / _glyphCell);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates a series of character rectangles
|
||||
@@ -743,50 +749,24 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
|
||||
try
|
||||
{
|
||||
if (pcoordDelta->X != 0 || pcoordDelta->Y != 0)
|
||||
const til::point deltaCells{ *pcoordDelta };
|
||||
|
||||
if (deltaCells != til::point{ 0, 0 })
|
||||
{
|
||||
try
|
||||
{
|
||||
POINT delta = { 0 };
|
||||
delta.x = pcoordDelta->X * _glyphCell.cx;
|
||||
delta.y = pcoordDelta->Y * _glyphCell.cy;
|
||||
const til::point deltaPixels = deltaCells * _glyphCell;
|
||||
|
||||
_InvalidOffset(delta);
|
||||
// Shift the contents of the map and fill in revealed area.
|
||||
_invalidMap.translate(deltaCells, true);
|
||||
|
||||
_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;
|
||||
|
||||
// X delta first
|
||||
OffsetRect(&reveal, delta.x, 0);
|
||||
IntersectRect(&reveal, &reveal, &display);
|
||||
SubtractRect(&reveal, &display, &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);
|
||||
|
||||
if (!IsRectEmpty(&reveal))
|
||||
{
|
||||
_InvalidOr(reveal);
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
// TODO: should we just maintain it all in cells?
|
||||
_invalidScroll += deltaPixels;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Invalidates the entire window area
|
||||
@@ -795,12 +775,12 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::InvalidateAll() noexcept
|
||||
try
|
||||
{
|
||||
const RECT screen = _GetDisplayRect();
|
||||
_InvalidOr(screen);
|
||||
|
||||
_invalidMap.set_all();
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - This currently has no effect in this renderer.
|
||||
@@ -822,23 +802,17 @@ Microsoft::WRL::ComPtr<IDXGISwapChain1> DxEngine::GetSwapChain()
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - X by Y area in pixels of the surface
|
||||
[[nodiscard]] SIZE DxEngine::_GetClientSize() const noexcept
|
||||
[[nodiscard]] til::size DxEngine::_GetClientSize() const noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
RECT clientRect = { 0 };
|
||||
LOG_IF_WIN32_BOOL_FALSE(GetClientRect(_hwndTarget, &clientRect));
|
||||
|
||||
SIZE clientSize = { 0 };
|
||||
clientSize.cx = clientRect.right - clientRect.left;
|
||||
clientSize.cy = clientRect.bottom - clientRect.top;
|
||||
|
||||
return clientSize;
|
||||
return til::rectangle{ clientRect }.size();
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
SIZE size = _sizeTarget;
|
||||
size.cx = static_cast<LONG>(size.cx * _scale);
|
||||
size.cy = static_cast<LONG>(size.cy * _scale);
|
||||
@@ -865,90 +839,6 @@ void _ScaleByFont(RECT& cellsToPixels, SIZE fontSize) noexcept
|
||||
cellsToPixels.bottom *= fontSize.cy;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves a rectangle representation of the pixel size of the
|
||||
// surface we are drawing on
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value;
|
||||
// - Origin-placed rectangle representing the pixel size of the surface
|
||||
[[nodiscard]] RECT DxEngine::_GetDisplayRect() const noexcept
|
||||
{
|
||||
return { 0, 0, _displaySizePixels.cx, _displaySizePixels.cy };
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to shift the existing dirty rectangle by a pixel offset
|
||||
// and crop it to still be within the bounds of the display surface
|
||||
// Arguments:
|
||||
// - delta - Adjustment distance in pixels
|
||||
// - -Y is up, Y is down, -X is left, X is right.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOffset(POINT delta)
|
||||
{
|
||||
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));
|
||||
|
||||
// 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);
|
||||
|
||||
_invalidRect = invalidNew;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine description:
|
||||
// - Adds the given character rectangle to the total dirty region
|
||||
// - Will scale internally to pixels based on the current font.
|
||||
// Arguments:
|
||||
// - sr - character rectangle
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(SMALL_RECT sr) noexcept
|
||||
{
|
||||
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;
|
||||
|
||||
_InvalidOr(region);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Adds the given pixel rectangle to the total dirty region
|
||||
// Arguments:
|
||||
// - rc - Dirty pixel rectangle
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
if (_isInvalidUsed)
|
||||
{
|
||||
UnionRect(&_invalidRect, &_invalidRect, &rc);
|
||||
|
||||
const RECT rcScreen = _GetDisplayRect();
|
||||
IntersectRect(&_invalidRect, &_invalidRect, &rcScreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
_invalidRect = rc;
|
||||
_isInvalidUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - This is unused by this renderer.
|
||||
// Arguments:
|
||||
@@ -971,24 +861,19 @@ 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.
|
||||
|
||||
if (TraceLoggingProviderEnabled(g_hDxRenderProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto invalidatedStr = _invalidMap.to_string();
|
||||
const auto invalidated = invalidatedStr.c_str();
|
||||
|
||||
#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"));
|
||||
TraceLoggingWrite(g_hDxRenderProvider,
|
||||
"Invalid",
|
||||
TraceLoggingWideString(invalidated),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
|
||||
if (_isEnabled)
|
||||
{
|
||||
@@ -999,8 +884,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_CreateDeviceResources(true));
|
||||
}
|
||||
else if (_displaySizePixels.cy != clientSize.cy ||
|
||||
_displaySizePixels.cx != clientSize.cx)
|
||||
else if (_displaySizePixels != clientSize)
|
||||
{
|
||||
// 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.
|
||||
@@ -1013,7 +897,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.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0));
|
||||
RETURN_IF_FAILED(_dxgiSwapChain->ResizeBuffers(2, clientSize.width<UINT>(), clientSize.height<UINT>(), 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.
|
||||
@@ -1039,6 +923,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// Return Value:
|
||||
// - Any DirectX error, a memory error, etc.
|
||||
[[nodiscard]] HRESULT DxEngine::EndPaint() noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF(E_INVALIDARG, !_isPainting); // invalid to end paint when we're not painting
|
||||
|
||||
@@ -1052,21 +937,33 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (_invalidScroll.cy != 0 || _invalidScroll.cx != 0)
|
||||
if (_invalidScroll != til::point{ 0, 0 })
|
||||
{
|
||||
_presentDirty = _invalidRect;
|
||||
// Copy `til::rectangles` into RECT map.
|
||||
_presentDirty.assign(_invalidMap.begin(), _invalidMap.end());
|
||||
|
||||
const RECT display = _GetDisplayRect();
|
||||
SubtractRect(&_presentScroll, &display, &_presentDirty);
|
||||
_presentOffset.x = _invalidScroll.cx;
|
||||
_presentOffset.y = _invalidScroll.cy;
|
||||
// The scroll rect is the entire screen minus the revealed areas.
|
||||
// Get the entire screen into a rectangle.
|
||||
til::rectangle scrollArea{ _displaySizePixels };
|
||||
|
||||
_presentParams.DirtyRectsCount = 1;
|
||||
_presentParams.pDirtyRects = &_presentDirty;
|
||||
// Reduce the size of the rectangle by the scroll.
|
||||
scrollArea -= til::size{} - _invalidScroll;
|
||||
|
||||
// Assign the area to the present storage
|
||||
_presentScroll = scrollArea;
|
||||
|
||||
// Pass the offset.
|
||||
_presentOffset = _invalidScroll;
|
||||
|
||||
// Now fill up the parameters structure from the member variables.
|
||||
_presentParams.DirtyRectsCount = gsl::narrow<UINT>(_presentDirty.size());
|
||||
_presentParams.pDirtyRects = _presentDirty.data();
|
||||
|
||||
_presentParams.pScrollOffset = &_presentOffset;
|
||||
_presentParams.pScrollRect = &_presentScroll;
|
||||
|
||||
// The scroll rect will be empty if we scrolled >= 1 full screen size.
|
||||
// Present1 doesn't like that. So clear it out. Everything will be dirty anyway.
|
||||
if (IsRectEmpty(&_presentScroll))
|
||||
{
|
||||
_presentParams.pScrollRect = nullptr;
|
||||
@@ -1083,13 +980,13 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
}
|
||||
}
|
||||
|
||||
_invalidRect = { 0 };
|
||||
_isInvalidUsed = false;
|
||||
_invalidMap.reset_all();
|
||||
|
||||
_invalidScroll = { 0 };
|
||||
_invalidScroll = {};
|
||||
|
||||
return hr;
|
||||
}
|
||||
CATCH_RETURN()
|
||||
|
||||
// Routine Description:
|
||||
// - Copies the front surface of the swap chain (the one being displayed)
|
||||
@@ -1141,8 +1038,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
hr = _dxgiSwapChain->Present(1, 0);
|
||||
/*hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);*/
|
||||
/*hr = _dxgiSwapChain->Present(1, 0);*/
|
||||
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
@@ -1161,7 +1058,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
RETURN_IF_FAILED(_CopyFrontToBack());
|
||||
_presentReady = false;
|
||||
|
||||
_presentDirty = { 0 };
|
||||
_presentDirty.clear();
|
||||
_presentOffset = { 0 };
|
||||
_presentScroll = { 0 };
|
||||
_presentParams = { 0 };
|
||||
@@ -1191,21 +1088,19 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::PaintBackground() noexcept
|
||||
{
|
||||
switch (_chainMode)
|
||||
{
|
||||
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 };
|
||||
D2D1_COLOR_F nothing = { 0 };
|
||||
|
||||
// Runs are counts of cells.
|
||||
// Use a transform by the size of one cell to convert cells-to-pixels
|
||||
// as we clear.
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Scale(_glyphCell));
|
||||
for (const auto rect : _invalidMap.runs())
|
||||
{
|
||||
_d2dRenderTarget->PushAxisAlignedClip(rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_d2dRenderTarget->Clear(nothing);
|
||||
break;
|
||||
_d2dRenderTarget->PopAxisAlignedClip();
|
||||
}
|
||||
_d2dRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
@@ -1226,9 +1121,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
try
|
||||
{
|
||||
// Calculate positioning of our origin.
|
||||
D2D1_POINT_2F origin;
|
||||
origin.x = static_cast<float>(coord.X * _glyphCell.cx);
|
||||
origin.y = static_cast<float>(coord.Y * _glyphCell.cy);
|
||||
D2D1_POINT_2F origin = til::point{ coord } * _glyphCell;
|
||||
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
@@ -1236,7 +1129,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
clusters,
|
||||
_glyphCell.cx);
|
||||
_glyphCell.width());
|
||||
|
||||
// Get the baseline for this font as that's where we draw from
|
||||
DWRITE_LINE_SPACING spacing;
|
||||
@@ -1248,7 +1141,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_d2dBrushBackground.Get(),
|
||||
_dwriteFactory.Get(),
|
||||
spacing,
|
||||
D2D1::SizeF(gsl::narrow<FLOAT>(_glyphCell.cx), gsl::narrow<FLOAT>(_glyphCell.cy)),
|
||||
_glyphCell,
|
||||
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
|
||||
|
||||
// Layout then render the text
|
||||
@@ -1279,10 +1172,8 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
|
||||
_d2dBrushForeground->SetColor(_ColorFFromColorRef(color));
|
||||
|
||||
const auto font = _GetFontSize();
|
||||
D2D_POINT_2F target;
|
||||
target.x = static_cast<float>(coordTarget.X) * font.X;
|
||||
target.y = static_cast<float>(coordTarget.Y) * font.Y;
|
||||
const auto font = _glyphCell;
|
||||
D2D_POINT_2F target = til::point{ coordTarget } * font;
|
||||
|
||||
D2D_POINT_2F start = { 0 };
|
||||
D2D_POINT_2F end = { 0 };
|
||||
@@ -1295,7 +1186,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
if (lines & GridLines::Top)
|
||||
{
|
||||
end = start;
|
||||
end.x += font.X;
|
||||
end.x += font.width();
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
@@ -1303,7 +1194,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
if (lines & GridLines::Left)
|
||||
{
|
||||
end = start;
|
||||
end.y += font.Y;
|
||||
end.y += font.height();
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
@@ -1316,28 +1207,28 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
// The top right corner inclusive is at 7,0 which is X (0) + Font Height (8) - 1 = 7.
|
||||
|
||||
// 0.5 pixel offset for crisp lines; -0.5 on the Y to fit _in_ the cell, not outside it.
|
||||
start = { target.x + 0.5f, target.y + font.Y - 0.5f };
|
||||
start = { target.x + 0.5f, target.y + font.height() - 0.5f };
|
||||
|
||||
if (lines & GridLines::Bottom)
|
||||
{
|
||||
end = start;
|
||||
end.x += font.X - 1.f;
|
||||
end.x += font.width() - 1.f;
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
|
||||
start = { target.x + font.X - 0.5f, target.y + 0.5f };
|
||||
start = { target.x + font.width() - 0.5f, target.y + 0.5f };
|
||||
|
||||
if (lines & GridLines::Right)
|
||||
{
|
||||
end = start;
|
||||
end.y += font.Y - 1.f;
|
||||
end.y += font.height() - 1.f;
|
||||
|
||||
_d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get(), 1.0f, _strokeStyle.Get());
|
||||
}
|
||||
|
||||
// Move to the next character in this run.
|
||||
target.x += font.X;
|
||||
target.x += font.width();
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
@@ -1356,17 +1247,7 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
|
||||
_d2dBrushForeground->SetColor(_selectionBackground);
|
||||
const auto resetColorOnExit = wil::scope_exit([&]() noexcept { _d2dBrushForeground->SetColor(existingColor); });
|
||||
|
||||
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);
|
||||
const D2D1_RECT_F draw = til::rectangle{ rect } *_glyphCell;
|
||||
|
||||
_d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get());
|
||||
|
||||
@@ -1394,52 +1275,42 @@ enum class CursorPaintType
|
||||
{
|
||||
return S_FALSE;
|
||||
}
|
||||
// Create rectangular block representing where the cursor can fill.
|
||||
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);
|
||||
// Create rectangular block representing where the cursor can fill.s
|
||||
D2D1_RECT_F rect = til::rectangle{ til::point{options.coordCursor} } *_glyphCell;
|
||||
|
||||
// If we're double-width, make it one extra glyph wider
|
||||
if (options.fIsDoubleWidth)
|
||||
{
|
||||
rect.right += _glyphCell.cx;
|
||||
rect.right += _glyphCell.width();
|
||||
}
|
||||
|
||||
CursorPaintType paintType = CursorPaintType::Fill;
|
||||
|
||||
switch (options.cursorType)
|
||||
{
|
||||
case CursorType::Legacy:
|
||||
{
|
||||
case CursorType::Legacy: {
|
||||
// Enforce min/max cursor height
|
||||
ULONG ulHeight = std::clamp(options.ulCursorHeightPercent, s_ulMinCursorHeightPercent, s_ulMaxCursorHeightPercent);
|
||||
|
||||
ulHeight = gsl::narrow<ULONG>((_glyphCell.cy * ulHeight) / 100);
|
||||
ulHeight = (_glyphCell.height<ULONG>() * ulHeight) / 100;
|
||||
rect.top = rect.bottom - ulHeight;
|
||||
break;
|
||||
}
|
||||
case CursorType::VerticalBar:
|
||||
{
|
||||
case CursorType::VerticalBar: {
|
||||
// It can't be wider than one cell or we'll have problems in invalidation, so restrict here.
|
||||
// It's either the left + the proposed width from the ease of access setting, or
|
||||
// it's the right edge of the block cursor as a maximum.
|
||||
rect.right = std::min(rect.right, rect.left + options.cursorPixelWidth);
|
||||
break;
|
||||
}
|
||||
case CursorType::Underscore:
|
||||
{
|
||||
case CursorType::Underscore: {
|
||||
rect.top = rect.bottom - 1;
|
||||
break;
|
||||
}
|
||||
case CursorType::EmptyBox:
|
||||
{
|
||||
case CursorType::EmptyBox: {
|
||||
paintType = CursorPaintType::Outline;
|
||||
break;
|
||||
}
|
||||
case CursorType::FullBox:
|
||||
{
|
||||
case CursorType::FullBox: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1456,13 +1327,11 @@ enum class CursorPaintType
|
||||
|
||||
switch (paintType)
|
||||
{
|
||||
case CursorPaintType::Fill:
|
||||
{
|
||||
case CursorPaintType::Fill: {
|
||||
_d2dRenderTarget->FillRectangle(rect, brush.Get());
|
||||
break;
|
||||
}
|
||||
case CursorPaintType::Outline:
|
||||
{
|
||||
case CursorPaintType::Outline: {
|
||||
// DrawRectangle in straddles physical pixels in an attempt to draw a line
|
||||
// between them. To avoid this, bump the rectangle around by half the stroke width.
|
||||
rect.top += 0.5f;
|
||||
@@ -1576,6 +1445,7 @@ CATCH_RETURN()
|
||||
// Return Value:
|
||||
// - S_OK or relevant DirectX error
|
||||
[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_IF_FAILED(_GetProposedFont(pfiFontInfoDesired,
|
||||
fiFontInfo,
|
||||
@@ -1584,22 +1454,16 @@ CATCH_RETURN()
|
||||
_dwriteTextAnalyzer,
|
||||
_dwriteFontFace));
|
||||
|
||||
try
|
||||
{
|
||||
const auto size = fiFontInfo.GetSize();
|
||||
|
||||
_glyphCell.cx = size.X;
|
||||
_glyphCell.cy = size.Y;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
_glyphCell = fiFontInfo.GetSize();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
[[nodiscard]] Viewport DxEngine::GetViewportInCharacters(const Viewport& viewInPixels) noexcept
|
||||
{
|
||||
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.cx);
|
||||
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.cy);
|
||||
const short widthInChars = gsl::narrow_cast<short>(viewInPixels.Width() / _glyphCell.width());
|
||||
const short heightInChars = gsl::narrow_cast<short>(viewInPixels.Height() / _glyphCell.height());
|
||||
|
||||
return Viewport::FromDimensions(viewInPixels.Origin(), { widthInChars, heightInChars });
|
||||
}
|
||||
@@ -1688,29 +1552,7 @@ float DxEngine::GetScaling() const noexcept
|
||||
// - Rectangle describing dirty area in characters.
|
||||
[[nodiscard]] std::vector<til::rectangle> DxEngine::GetDirtyArea()
|
||||
{
|
||||
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:
|
||||
// - Gets COORD packed with shorts of each glyph (character) cell's
|
||||
// height and width.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - Nearest integer short x and y values for each cell.
|
||||
[[nodiscard]] COORD DxEngine::_GetFontSize() const noexcept
|
||||
{
|
||||
return { gsl::narrow<SHORT>(_glyphCell.cx), gsl::narrow<SHORT>(_glyphCell.cy) };
|
||||
return _invalidMap.runs();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -1720,10 +1562,12 @@ float DxEngine::GetScaling() const noexcept
|
||||
// Return Value:
|
||||
// - S_OK
|
||||
[[nodiscard]] HRESULT DxEngine::GetFontSize(_Out_ COORD* const pFontSize) noexcept
|
||||
try
|
||||
{
|
||||
*pFontSize = _GetFontSize();
|
||||
*pFontSize = _glyphCell;
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Currently unused by this renderer.
|
||||
@@ -1733,30 +1577,28 @@ float DxEngine::GetScaling() const noexcept
|
||||
// Return Value:
|
||||
// - S_OK or relevant DirectWrite error.
|
||||
[[nodiscard]] HRESULT DxEngine::IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pResult);
|
||||
|
||||
try
|
||||
{
|
||||
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
|
||||
const Cluster cluster(glyph, 0); // columns don't matter, we're doing analysis not layout.
|
||||
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
_dwriteTextAnalyzer.Get(),
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
{ &cluster, 1 },
|
||||
_glyphCell.cx);
|
||||
// Create the text layout
|
||||
CustomTextLayout layout(_dwriteFactory.Get(),
|
||||
_dwriteTextAnalyzer.Get(),
|
||||
_dwriteTextFormat.Get(),
|
||||
_dwriteFontFace.Get(),
|
||||
{ &cluster, 1 },
|
||||
_glyphCell.width());
|
||||
|
||||
UINT32 columns = 0;
|
||||
RETURN_IF_FAILED(layout.GetColumns(&columns));
|
||||
UINT32 columns = 0;
|
||||
RETURN_IF_FAILED(layout.GetColumns(&columns));
|
||||
|
||||
*pResult = columns != 1;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
*pResult = columns != 1;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Method Description:
|
||||
// - Updates the window's title string.
|
||||
@@ -2130,12 +1972,10 @@ float DxEngine::GetScaling() const noexcept
|
||||
|
||||
switch (_chainMode)
|
||||
{
|
||||
case SwapChainMode::ForHwnd:
|
||||
{
|
||||
case SwapChainMode::ForHwnd: {
|
||||
return D2D1::ColorF(rgb);
|
||||
}
|
||||
case SwapChainMode::ForComposition:
|
||||
{
|
||||
case SwapChainMode::ForComposition: {
|
||||
// Get the A value we've snuck into the highest byte
|
||||
const BYTE a = ((color >> 24) & 0xFF);
|
||||
const float aFloat = a / 255.0f;
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Microsoft::Console::Render
|
||||
SwapChainMode _chainMode;
|
||||
|
||||
HWND _hwndTarget;
|
||||
SIZE _sizeTarget;
|
||||
til::size _sizeTarget;
|
||||
int _dpi;
|
||||
float _scale;
|
||||
|
||||
@@ -130,8 +130,8 @@ namespace Microsoft::Console::Render
|
||||
bool _isEnabled;
|
||||
bool _isPainting;
|
||||
|
||||
SIZE _displaySizePixels;
|
||||
SIZE _glyphCell;
|
||||
til::size _displaySizePixels;
|
||||
til::size _glyphCell;
|
||||
|
||||
D2D1_COLOR_F _defaultForegroundColor;
|
||||
D2D1_COLOR_F _defaultBackgroundColor;
|
||||
@@ -140,19 +140,11 @@ namespace Microsoft::Console::Render
|
||||
D2D1_COLOR_F _backgroundColor;
|
||||
D2D1_COLOR_F _selectionBackground;
|
||||
|
||||
[[nodiscard]] RECT _GetDisplayRect() const noexcept;
|
||||
|
||||
bool _isInvalidUsed;
|
||||
RECT _invalidRect;
|
||||
SIZE _invalidScroll;
|
||||
|
||||
void _InvalidOr(SMALL_RECT sr) noexcept;
|
||||
void _InvalidOr(RECT rc) noexcept;
|
||||
|
||||
void _InvalidOffset(POINT pt);
|
||||
til::bitmap _invalidMap;
|
||||
til::point _invalidScroll;
|
||||
|
||||
bool _presentReady;
|
||||
RECT _presentDirty;
|
||||
std::vector<RECT> _presentDirty;
|
||||
RECT _presentScroll;
|
||||
POINT _presentOffset;
|
||||
DXGI_PRESENT_PARAMETERS _presentParams;
|
||||
@@ -244,9 +236,7 @@ namespace Microsoft::Console::Render
|
||||
::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1>& textAnalyzer,
|
||||
::Microsoft::WRL::ComPtr<IDWriteFontFace1>& fontFace) const noexcept;
|
||||
|
||||
[[nodiscard]] COORD _GetFontSize() const noexcept;
|
||||
|
||||
[[nodiscard]] SIZE _GetClientSize() const noexcept;
|
||||
[[nodiscard]] til::size _GetClientSize() const noexcept;
|
||||
|
||||
[[nodiscard]] D2D1_COLOR_F _ColorFFromColorRef(const COLORREF color) noexcept;
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#pragma once
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#define BLOCK_TIL // We want to include it later, after DX.
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <winmeta.h>
|
||||
|
||||
#include "..\host\conddkrefs.h"
|
||||
#include <condrv.h>
|
||||
@@ -34,4 +36,7 @@
|
||||
#include <dwrite_2.h>
|
||||
#include <dwrite_3.h>
|
||||
|
||||
// Re-include TIL at the bottom to gain DX superpowers.
|
||||
#include "til.h"
|
||||
|
||||
#pragma hdrstop
|
||||
|
||||
@@ -343,19 +343,20 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write.
|
||||
[[nodiscard]] HRESULT XtermEngine::ScrollFrame() noexcept
|
||||
try
|
||||
{
|
||||
if (_scrollDelta.X != 0)
|
||||
if (_scrollDelta.x() != 0)
|
||||
{
|
||||
// No easy way to shift left-right. Everything needs repainting.
|
||||
return InvalidateAll();
|
||||
}
|
||||
if (_scrollDelta.Y == 0)
|
||||
if (_scrollDelta.y() == 0)
|
||||
{
|
||||
// There's nothing to do here. Do nothing.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const short dy = _scrollDelta.Y;
|
||||
const short dy = _scrollDelta.y<SHORT>();
|
||||
const short absDy = static_cast<short>(abs(dy));
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
@@ -391,6 +392,7 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
|
||||
return hr;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Notifies us that the console is attempting to scroll the existing screen
|
||||
@@ -402,25 +404,23 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// Return Value:
|
||||
// - S_OK if we succeeded, else an appropriate HRESULT for safemath failure
|
||||
[[nodiscard]] HRESULT XtermEngine::InvalidateScroll(const COORD* const pcoordDelta) noexcept
|
||||
try
|
||||
{
|
||||
const short dx = pcoordDelta->X;
|
||||
const short dy = pcoordDelta->Y;
|
||||
const til::point delta{ *pcoordDelta };
|
||||
|
||||
if (dx != 0 || dy != 0)
|
||||
if (delta != til::point{ 0, 0 })
|
||||
{
|
||||
_trace.TraceInvalidateScroll(delta);
|
||||
|
||||
// Scroll the current offset and invalidate the revealed area
|
||||
_invalidMap.translate(til::point(*pcoordDelta), true);
|
||||
_invalidMap.translate(delta, true);
|
||||
|
||||
COORD invalidScrollNew;
|
||||
RETURN_IF_FAILED(ShortAdd(_scrollDelta.X, dx, &invalidScrollNew.X));
|
||||
RETURN_IF_FAILED(ShortAdd(_scrollDelta.Y, dy, &invalidScrollNew.Y));
|
||||
|
||||
// Store if safemath succeeded
|
||||
_scrollDelta = invalidScrollNew;
|
||||
_scrollDelta += delta;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
CATCH_RETURN();
|
||||
|
||||
// Routine Description:
|
||||
// - Draws one line of the buffer to the screen. Writes the characters to the
|
||||
|
||||
@@ -121,6 +121,8 @@ CATCH_RETURN();
|
||||
_circled = true;
|
||||
}
|
||||
|
||||
_trace.TraceTriggerCircling(*pForcePaint);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ void VtEngine::_OrRect(_Inout_ SMALL_RECT* const pRectExisting, const SMALL_RECT
|
||||
// - true iff only the next character is invalid
|
||||
bool VtEngine::_WillWriteSingleChar() const
|
||||
{
|
||||
// If there is scroll delta, return false.
|
||||
if (til::point{ 0, 0 } != til::point{ _scrollDelta })
|
||||
// If there is no scroll delta, return false.
|
||||
if (til::point{ 0, 0 } != _scrollDelta)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
// If there's nothing to do, quick return
|
||||
bool somethingToDo = _invalidMap.any() ||
|
||||
(_scrollDelta.X != 0 || _scrollDelta.Y != 0) ||
|
||||
_scrollDelta != til::point{ 0, 0 } ||
|
||||
_cursorMoved ||
|
||||
_titleChanged;
|
||||
|
||||
@@ -52,7 +52,7 @@ using namespace Microsoft::Console::Types;
|
||||
|
||||
_invalidMap.reset_all();
|
||||
|
||||
_scrollDelta = { 0 };
|
||||
_scrollDelta = { 0, 0 };
|
||||
_clearedAllThisFrame = false;
|
||||
_cursorMoved = false;
|
||||
_firstPaint = false;
|
||||
|
||||
@@ -38,7 +38,7 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
_invalidMap(initialViewport.Dimensions()),
|
||||
_lastRealCursor({ 0 }),
|
||||
_lastText({ 0 }),
|
||||
_scrollDelta({ 0 }),
|
||||
_scrollDelta({ 0, 0 }),
|
||||
_quickReturn(false),
|
||||
_clearedAllThisFrame(false),
|
||||
_cursorMoved(false),
|
||||
|
||||
@@ -127,6 +127,23 @@ void RenderTracing::TraceTriggerCircling(const bool newFrame) const
|
||||
#endif UNIT_TESTING
|
||||
}
|
||||
|
||||
void RenderTracing::TraceInvalidateScroll(const til::point scroll) const
|
||||
{
|
||||
#ifndef UNIT_TESTING
|
||||
if (TraceLoggingProviderEnabled(g_hConsoleVtRendererTraceProvider, WINEVENT_LEVEL_VERBOSE, 0))
|
||||
{
|
||||
const auto scrollDeltaStr = scroll.to_string();
|
||||
const auto scrollDelta = scrollDeltaStr.c_str();
|
||||
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
|
||||
"VtEngine_TraceInvalidateScroll",
|
||||
TraceLoggingWideString(scrollDelta),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
|
||||
}
|
||||
#else
|
||||
UNREFERENCED_PARAMETER(scroll);
|
||||
#endif
|
||||
}
|
||||
|
||||
void RenderTracing::TraceStartPaint(const bool quickReturn,
|
||||
const til::bitmap invalidMap,
|
||||
const til::rectangle lastViewport,
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void TracePaintCursor(const til::point coordCursor) const;
|
||||
void TraceInvalidateAll(const til::rectangle view) const;
|
||||
void TraceTriggerCircling(const bool newFrame) const;
|
||||
void TraceInvalidateScroll(const til::point scroll) const;
|
||||
void TraceStartPaint(const bool quickReturn,
|
||||
const til::bitmap invalidMap,
|
||||
const til::rectangle lastViewport,
|
||||
|
||||
@@ -14,3 +14,7 @@ TEST_CODE = 1
|
||||
# -------------------------------------
|
||||
|
||||
C_DEFINES = $(C_DEFINES) -DINLINE_TEST_METHOD_MARKUP -DUNIT_TESTING
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
COORD _lastRealCursor;
|
||||
COORD _lastText;
|
||||
COORD _scrollDelta;
|
||||
til::point _scrollDelta;
|
||||
|
||||
bool _quickReturn;
|
||||
bool _clearedAllThisFrame;
|
||||
|
||||
@@ -47,15 +47,6 @@ void TerminalInput::EnableDefaultTracking(const bool enable) noexcept
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::Default : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
|
||||
if (_mouseModeChangedCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
_mouseModeChangedCallback();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -72,15 +63,6 @@ void TerminalInput::EnableButtonEventTracking(const bool enable) noexcept
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::ButtonEvent : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
|
||||
if (_mouseModeChangedCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
_mouseModeChangedCallback();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -97,15 +79,6 @@ void TerminalInput::EnableAnyEventTracking(const bool enable) noexcept
|
||||
_mouseInputState.trackingMode = enable ? TrackingMode::AnyEvent : TrackingMode::None;
|
||||
_mouseInputState.lastPos = { -1, -1 }; // Clear out the last saved mouse position & button.
|
||||
_mouseInputState.lastButton = 0;
|
||||
|
||||
if (_mouseModeChangedCallback)
|
||||
{
|
||||
try
|
||||
{
|
||||
_mouseModeChangedCallback();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -140,12 +113,3 @@ void TerminalInput::UseMainScreenBuffer() noexcept
|
||||
{
|
||||
_mouseInputState.inAlternateBuffer = false;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Sets up the callback for mouse input mode changes
|
||||
// Parameters:
|
||||
// - mouseModeChangedCallback: the callback
|
||||
void TerminalInput::SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept
|
||||
{
|
||||
_mouseModeChangedCallback = std::move(mouseModeChangedCallback);
|
||||
}
|
||||
|
||||
@@ -60,13 +60,10 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void EnableAlternateScroll(const bool enable) noexcept;
|
||||
void UseAlternateScreenBuffer() noexcept;
|
||||
void UseMainScreenBuffer() noexcept;
|
||||
|
||||
void SetMouseModeChangedCallback(std::function<void()> mouseModeChangedCallback) noexcept;
|
||||
#pragma endregion
|
||||
|
||||
private:
|
||||
std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> _pfnWriteEvents;
|
||||
std::function<void()> _mouseModeChangedCallback;
|
||||
|
||||
// storage location for the leading surrogate of a utf-16 surrogate pair
|
||||
std::optional<wchar_t> _leadingSurrogate;
|
||||
|
||||
@@ -50,6 +50,7 @@ SOURCES = \
|
||||
|
||||
INCLUDES = \
|
||||
$(INCLUDES); \
|
||||
$(ONECORESDKTOOLS_INTERNAL_INC_PATH_L)\wextest\cue; \
|
||||
|
||||
TARGETLIBS = \
|
||||
$(TARGETLIBS) \
|
||||
|
||||
127
src/til/ut_til/MathTests.cpp
Normal file
127
src/til/ut_til/MathTests.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
|
||||
#include "til/math.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
class MathTests
|
||||
{
|
||||
TEST_CLASS(MathTests);
|
||||
|
||||
template<typename TG, typename TX = ptrdiff_t>
|
||||
struct TestCase
|
||||
{
|
||||
TG given;
|
||||
TX expected;
|
||||
};
|
||||
|
||||
template<class TilMath, typename TG, typename TX, int N>
|
||||
static void _RunCases(TilMath, const std::array<TestCase<TG, TX>, N>& cases)
|
||||
{
|
||||
for (const auto& tc : cases)
|
||||
{
|
||||
VERIFY_ARE_EQUAL(tc.expected, TilMath::template cast<decltype(tc.expected)>(tc.given));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Truncating)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 1 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -8 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::truncating, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::truncating_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Ceiling)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 2 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -8 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::ceiling, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::ceiling_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Flooring)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 1 },
|
||||
{ -7.1, -8 },
|
||||
{ -8.5, -9 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::flooring, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::flooring_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(Rounding)
|
||||
{
|
||||
std::array<TestCase<long double, ptrdiff_t>, 8> cases{
|
||||
TestCase<long double, ptrdiff_t>{ 1., 1 },
|
||||
{ 1.9, 2 },
|
||||
{ -7.1, -7 },
|
||||
{ -8.5, -9 },
|
||||
{ PTRDIFF_MAX + 0.5, PTRDIFF_MAX },
|
||||
{ PTRDIFF_MIN - 0.5, PTRDIFF_MIN },
|
||||
{ INFINITY, PTRDIFF_MAX },
|
||||
{ -INFINITY, PTRDIFF_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::rounding, cases);
|
||||
|
||||
const auto fn = []() {
|
||||
const auto v = til::math::details::rounding_t::cast<ptrdiff_t>(NAN);
|
||||
};
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
TEST_METHOD(NormalIntegers)
|
||||
{
|
||||
std::array<TestCase<ptrdiff_t, int>, 4> cases{
|
||||
TestCase<ptrdiff_t, int>{ 1, 1 },
|
||||
{ -1, -1 },
|
||||
{ PTRDIFF_MAX, INT_MAX },
|
||||
{ PTRDIFF_MIN, INT_MIN },
|
||||
};
|
||||
|
||||
_RunCases(til::math::rounding, cases);
|
||||
}
|
||||
};
|
||||
@@ -207,6 +207,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(AdditionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Addition of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() + pt2.x(), pt.y() + pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Addition results in value that is too large (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Addition results in value that is too large (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual += pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Subtraction)
|
||||
{
|
||||
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
|
||||
@@ -246,6 +290,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(SubtractionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Subtraction of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() - pt2.x(), pt.y() - pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual -= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Subtraction results in value that is too small (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ -2, -2 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual -= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Subtraction results in value that is too small (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ -2, -2 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual -= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Multiplication)
|
||||
{
|
||||
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
|
||||
@@ -285,6 +373,50 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(MultiplicationInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Multiplication of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() * pt2.x(), pt.y() * pt2.y() };
|
||||
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Multiplication results in value that is too large (x).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 10, 10 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Multiplication results in value that is too large (y).");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ static_cast<ptrdiff_t>(0), bigSize };
|
||||
const til::point pt2{ 10, 10 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt;
|
||||
actual *= pt2;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(Division)
|
||||
{
|
||||
Log::Comment(L"0.) Division of two things that should be in bounds.");
|
||||
@@ -311,6 +443,35 @@ class PointTests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(DivisionInplace)
|
||||
{
|
||||
Log::Comment(L"0.) Division of two things that should be in bounds.");
|
||||
{
|
||||
const til::point pt{ 555, 510 };
|
||||
const til::point pt2{ 23, 47 };
|
||||
|
||||
const til::point expected{ pt.x() / pt2.x(), pt.y() / pt2.y() };
|
||||
auto actual = pt;
|
||||
actual /= pt2;
|
||||
|
||||
VERIFY_ARE_EQUAL(expected, actual);
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Division by zero");
|
||||
{
|
||||
constexpr ptrdiff_t bigSize = std::numeric_limits<ptrdiff_t>().max();
|
||||
const til::point pt{ bigSize, static_cast<ptrdiff_t>(0) };
|
||||
const til::point pt2{ 1, 1 };
|
||||
|
||||
auto fn = [&]() {
|
||||
auto actual = pt2;
|
||||
actual /= pt;
|
||||
};
|
||||
|
||||
VERIFY_THROWS_SPECIFIC(fn(), wil::ResultException, [](wil::ResultException& e) { return e.GetErrorCode() == E_ABORT; });
|
||||
}
|
||||
}
|
||||
|
||||
TEST_METHOD(X)
|
||||
{
|
||||
const til::point pt{ 5, 10 };
|
||||
@@ -443,4 +604,101 @@ class PointTests
|
||||
|
||||
// All ptrdiff_ts fit into a float, so there's no exception tests.
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct PointTypeWith_xy
|
||||
{
|
||||
T x, y;
|
||||
};
|
||||
template<typename T>
|
||||
struct PointTypeWith_XY
|
||||
{
|
||||
T X, Y;
|
||||
};
|
||||
TEST_METHOD(CastFromFloatWithMathTypes)
|
||||
{
|
||||
PointTypeWith_xy<float> xyFloatIntegral{ 1.f, 2.f };
|
||||
PointTypeWith_xy<float> xyFloat{ 1.6f, 2.4f };
|
||||
PointTypeWith_XY<double> XYDoubleIntegral{ 3., 4. };
|
||||
PointTypeWith_XY<double> XYDouble{ 3.6, 4.4 };
|
||||
Log::Comment(L"0.) Ceiling");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::ceiling, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 2, 3 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::ceiling, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, 5 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Flooring");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::flooring, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::flooring, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Rounding");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::rounding, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 2, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::rounding, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 4, 4 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"3.) Truncating");
|
||||
{
|
||||
{
|
||||
til::point converted{ til::math::truncating, xyFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, xyFloat };
|
||||
VERIFY_ARE_EQUAL((til::point{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, XYDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::point converted{ til::math::truncating, XYDouble };
|
||||
VERIFY_ARE_EQUAL((til::point{ 3, 4 }), converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -515,4 +515,140 @@ class SizeTests
|
||||
|
||||
// All ptrdiff_ts fit into a float, so there's no exception tests.
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct SizeTypeWith_XY
|
||||
{
|
||||
T X, Y;
|
||||
};
|
||||
template<typename T>
|
||||
struct SizeTypeWith_cxcy
|
||||
{
|
||||
T cx, cy;
|
||||
};
|
||||
template<typename T>
|
||||
struct SizeTypeWith_WidthHeight
|
||||
{
|
||||
T Width, Height;
|
||||
};
|
||||
TEST_METHOD(CastFromFloatWithMathTypes)
|
||||
{
|
||||
SizeTypeWith_XY<float> XYFloatIntegral{ 1.f, 2.f };
|
||||
SizeTypeWith_XY<float> XYFloat{ 1.6f, 2.4f };
|
||||
SizeTypeWith_cxcy<double> cxcyDoubleIntegral{ 3., 4. };
|
||||
SizeTypeWith_cxcy<double> cxcyDouble{ 3.6, 4.4 };
|
||||
SizeTypeWith_WidthHeight<double> WHDoubleIntegral{ 5., 6. };
|
||||
SizeTypeWith_WidthHeight<double> WHDouble{ 5.6, 6.4 };
|
||||
Log::Comment(L"0.) Ceiling");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::ceiling, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 2, 3 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 4, 5 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::ceiling, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 6, 7 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"1.) Flooring");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::flooring, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::flooring, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"2.) Rounding");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::rounding, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 2, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 4, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::rounding, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 6, 6 }), converted);
|
||||
}
|
||||
}
|
||||
|
||||
Log::Comment(L"3.) Truncating");
|
||||
{
|
||||
{
|
||||
til::size converted{ til::math::truncating, XYFloatIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, XYFloat };
|
||||
VERIFY_ARE_EQUAL((til::size{ 1, 2 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, cxcyDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, cxcyDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 3, 4 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, WHDoubleIntegral };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
{
|
||||
til::size converted{ til::math::truncating, WHDouble };
|
||||
VERIFY_ARE_EQUAL((til::size{ 5, 6 }), converted);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ SOURCES = \
|
||||
ColorTests.cpp \
|
||||
OperatorTests.cpp \
|
||||
PointTests.cpp \
|
||||
MathTests.cpp \
|
||||
RectangleTests.cpp \
|
||||
SizeTests.cpp \
|
||||
SomeTests.cpp \
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ClCompile Include="BitmapTests.cpp" />
|
||||
<ClCompile Include="OperatorTests.cpp" />
|
||||
<ClCompile Include="PointTests.cpp" />
|
||||
<ClCompile Include="MathTests.cpp" />
|
||||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="SizeTests.cpp" />
|
||||
<ClCompile Include="ColorTests.cpp" />
|
||||
|
||||
@@ -23,6 +23,7 @@ bool gVtInput = false;
|
||||
bool gVtOutput = true;
|
||||
bool gWindowInput = false;
|
||||
bool gUseAltBuffer = false;
|
||||
bool gUseAscii = false;
|
||||
|
||||
bool gExitRequested = false;
|
||||
|
||||
@@ -52,7 +53,7 @@ void useMainBuffer()
|
||||
csi("?1049l");
|
||||
}
|
||||
|
||||
void toPrintableBuffer(char c, char* printBuffer, int* printCch)
|
||||
void toPrintableBufferA(char c, char* printBuffer, int* printCch)
|
||||
{
|
||||
if (c == '\x1b')
|
||||
{
|
||||
@@ -111,13 +112,72 @@ void toPrintableBuffer(char c, char* printBuffer, int* printCch)
|
||||
*printCch = 2;
|
||||
}
|
||||
}
|
||||
void toPrintableBufferW(wchar_t c, wchar_t* printBuffer, int* printCch)
|
||||
{
|
||||
if (c == L'\x1b')
|
||||
{
|
||||
printBuffer[0] = L'^';
|
||||
printBuffer[1] = L'[';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x03')
|
||||
{
|
||||
printBuffer[0] = L'^';
|
||||
printBuffer[1] = L'C';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\x0')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'0';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\r')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'r';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\n')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'n';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\t')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L't';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else if (c == '\b')
|
||||
{
|
||||
printBuffer[0] = L'\\';
|
||||
printBuffer[1] = L'b';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
printBuffer[0] = (wchar_t)c;
|
||||
printBuffer[1] = L' ';
|
||||
printBuffer[2] = L'\0';
|
||||
*printCch = 2;
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyEvent(KEY_EVENT_RECORD keyEvent)
|
||||
void handleKeyEventA(KEY_EVENT_RECORD keyEvent)
|
||||
{
|
||||
char printBuffer[3];
|
||||
int printCch = 0;
|
||||
const char c = keyEvent.uChar.AsciiChar;
|
||||
toPrintableBuffer(c, printBuffer, &printCch);
|
||||
toPrintableBufferA(c, printBuffer, &printCch);
|
||||
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
@@ -137,13 +197,45 @@ void handleKeyEvent(KEY_EVENT_RECORD keyEvent)
|
||||
// restore colors
|
||||
csi("0m");
|
||||
|
||||
// Die on Ctrl+C
|
||||
// Die on Ctrl+D
|
||||
if (keyEvent.uChar.AsciiChar == CTRL_D)
|
||||
{
|
||||
gExitRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handleKeyEventW(KEY_EVENT_RECORD keyEvent)
|
||||
{
|
||||
wchar_t printBuffer[3];
|
||||
int printCch = 0;
|
||||
const wchar_t c = keyEvent.uChar.UnicodeChar;
|
||||
toPrintableBufferW(c, printBuffer, &printCch);
|
||||
|
||||
if (!keyEvent.bKeyDown)
|
||||
{
|
||||
// Print in grey
|
||||
csi("38;5;242m");
|
||||
}
|
||||
|
||||
wprintf(L"Down: %d Repeat: %d KeyCode: 0x%x ScanCode: 0x%x Char: %s (0x%x) KeyState: 0x%x\r\n",
|
||||
keyEvent.bKeyDown,
|
||||
keyEvent.wRepeatCount,
|
||||
keyEvent.wVirtualKeyCode,
|
||||
keyEvent.wVirtualScanCode,
|
||||
printBuffer,
|
||||
keyEvent.uChar.UnicodeChar,
|
||||
keyEvent.dwControlKeyState);
|
||||
|
||||
// restore colors
|
||||
csi("0m");
|
||||
|
||||
// Die on Ctrl+D
|
||||
if (c == CTRL_D)
|
||||
{
|
||||
gExitRequested = true;
|
||||
}
|
||||
}
|
||||
|
||||
void handleWindowEvent(WINDOW_BUFFER_SIZE_RECORD windowEvent)
|
||||
{
|
||||
SHORT bufferWidth = windowEvent.dwSize.X;
|
||||
@@ -190,6 +282,7 @@ void usage()
|
||||
wprintf(L"\t-i: enable reading VT input mode.\n");
|
||||
wprintf(L"\t-o: disable VT output.\n");
|
||||
wprintf(L"\t-w: enable reading window events.\n");
|
||||
wprintf(L"\t-a: Use ReadConsoleInputA instead.\n");
|
||||
wprintf(L"\t--alt: run in the alt buffer. Cannot be combined with `-o`\n");
|
||||
wprintf(L"\t-?: print this help message\n");
|
||||
}
|
||||
@@ -201,6 +294,7 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
gWindowInput = false;
|
||||
gUseAltBuffer = false;
|
||||
gExitRequested = false;
|
||||
gUseAscii = false;
|
||||
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
@@ -226,6 +320,11 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
gVtOutput = false;
|
||||
wprintf(L"Disabling VT Output\n");
|
||||
}
|
||||
else if (arg.compare(L"-a") == 0)
|
||||
{
|
||||
gUseAscii = true;
|
||||
wprintf(L"Using ReadConsoleInputA\n");
|
||||
}
|
||||
else if (arg.compare(L"-?") == 0)
|
||||
{
|
||||
usage();
|
||||
@@ -288,13 +387,28 @@ int __cdecl wmain(int argc, wchar_t* argv[])
|
||||
{
|
||||
INPUT_RECORD rc;
|
||||
DWORD dwRead = 0;
|
||||
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
|
||||
|
||||
if (gUseAscii)
|
||||
{
|
||||
ReadConsoleInputA(g_hIn, &rc, 1, &dwRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadConsoleInputW(g_hIn, &rc, 1, &dwRead);
|
||||
}
|
||||
|
||||
switch (rc.EventType)
|
||||
{
|
||||
case KEY_EVENT:
|
||||
{
|
||||
handleKeyEvent(rc.Event.KeyEvent);
|
||||
if (gUseAscii)
|
||||
{
|
||||
handleKeyEventA(rc.Event.KeyEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
handleKeyEventW(rc.Event.KeyEvent);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WINDOW_BUFFER_SIZE_EVENT:
|
||||
|
||||
@@ -13,14 +13,14 @@ namespace VTApp
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static string CSI = ((char)0x1b)+"[";
|
||||
static string CSI = ((char)0x1b) + "[";
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WindowHeight = 25;
|
||||
Console.BufferHeight = 9000;
|
||||
Console.WindowWidth = 80;
|
||||
Console.BufferWidth = 80;
|
||||
|
||||
|
||||
Console.WriteLine("VT Tester");
|
||||
|
||||
while (true)
|
||||
@@ -271,11 +271,11 @@ namespace VTApp
|
||||
Console.Write("49m");
|
||||
break;
|
||||
case '<':
|
||||
Console.Write('\xD'); // carriage return \r
|
||||
break;
|
||||
Console.Write('\xD'); // carriage return \r
|
||||
break;
|
||||
case '>':
|
||||
Console.Write('\xA'); // line feed/new line \n
|
||||
break;
|
||||
Console.Write('\xA'); // line feed/new line \n
|
||||
break;
|
||||
case '`':
|
||||
Console.Write("z");
|
||||
break;
|
||||
@@ -385,7 +385,7 @@ namespace VTApp
|
||||
for (int j = 0; j < 80; j++)
|
||||
{
|
||||
if (j == 0)
|
||||
Console.Write(i%10);
|
||||
Console.Write(i % 10);
|
||||
else
|
||||
Console.Write("Z");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user