74f11b8203
## Summary of the Pull Request ![603-final](https://user-images.githubusercontent.com/18356694/132585665-afed3210-257a-4fee-9b43-4273a0f5cf69.gif) Adds support for vintage style opacity, on Windows 11+. The API we're using for this exists since the time immemorial, but there's a bug in XAML Islands that prevents it from working right until Windows 11 (which we're working on backporting). Replaces the `acrylicOpacity` setting with `opacity`, which is a uint between 0 and 100 (inclusive), default to 100. `useAcrylic` now controls whether acrylic is used or not. Setting an opacity < 100 with `"useAcrylic": false` will use vintage style opacity. Mouse wheeling adjusts opacity. Whether acrylic is used or not is dependent upon `useAcrylic`. `opacity` will stealthily default to 50 if `useAcrylic:true` is set. ## PR Checklist * [x] Closes #603 * [x] I work here * [x] Tests added/passed * [x] https://github.com/MicrosoftDocs/terminal/pull/416 ## Detailed Description of the Pull Request / Additional comments Opacity was moved to AppearanceConfig. In the future, I have a mind to allow unfocused acrylic, so that'll be important then. ## Validation Steps Performed _just look at it_
341 lines
13 KiB
C++
341 lines
13 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "../TerminalControl/EventArgs.h"
|
|
#include "../TerminalControl/ControlCore.h"
|
|
#include "MockControlSettings.h"
|
|
#include "MockConnection.h"
|
|
#include "../UnitTests_TerminalCore/TestUtils.h"
|
|
|
|
using namespace Microsoft::Console;
|
|
using namespace WEX::Logging;
|
|
using namespace WEX::TestExecution;
|
|
using namespace WEX::Common;
|
|
|
|
using namespace winrt;
|
|
using namespace winrt::Microsoft::Terminal;
|
|
|
|
namespace ControlUnitTests
|
|
{
|
|
class ControlCoreTests
|
|
{
|
|
BEGIN_TEST_CLASS(ControlCoreTests)
|
|
TEST_CLASS_PROPERTY(L"TestTimeout", L"0:0:10") // 10s timeout
|
|
END_TEST_CLASS()
|
|
|
|
TEST_METHOD(ComPtrSettings);
|
|
TEST_METHOD(InstantiateCore);
|
|
TEST_METHOD(TestInitialize);
|
|
TEST_METHOD(TestAdjustAcrylic);
|
|
|
|
TEST_METHOD(TestFreeAfterClose);
|
|
|
|
TEST_METHOD(TestFontInitializedInCtor);
|
|
|
|
TEST_METHOD(TestClearScrollback);
|
|
TEST_METHOD(TestClearScreen);
|
|
TEST_METHOD(TestClearAll);
|
|
|
|
TEST_CLASS_SETUP(ModuleSetup)
|
|
{
|
|
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
|
|
|
return true;
|
|
}
|
|
TEST_CLASS_CLEANUP(ClassCleanup)
|
|
{
|
|
winrt::uninit_apartment();
|
|
return true;
|
|
}
|
|
|
|
std::tuple<winrt::com_ptr<MockControlSettings>, winrt::com_ptr<MockConnection>> _createSettingsAndConnection()
|
|
{
|
|
Log::Comment(L"Create settings object");
|
|
auto settings = winrt::make_self<MockControlSettings>();
|
|
VERIFY_IS_NOT_NULL(settings);
|
|
|
|
Log::Comment(L"Create connection object");
|
|
auto conn = winrt::make_self<MockConnection>();
|
|
VERIFY_IS_NOT_NULL(conn);
|
|
|
|
return { settings, conn };
|
|
}
|
|
|
|
winrt::com_ptr<Control::implementation::ControlCore> createCore(Control::IControlSettings settings,
|
|
TerminalConnection::ITerminalConnection conn)
|
|
{
|
|
Log::Comment(L"Create ControlCore object");
|
|
|
|
auto core = winrt::make_self<Control::implementation::ControlCore>(settings, conn);
|
|
core->_inUnitTests = true;
|
|
return core;
|
|
}
|
|
|
|
void _standardInit(winrt::com_ptr<Control::implementation::ControlCore> core)
|
|
{
|
|
// "Consolas" ends up with an actual size of 9x21 at 96DPI. So
|
|
// let's just arbitrarily start with a 270x420px (30x20 chars) window
|
|
core->Initialize(270, 420, 1.0);
|
|
VERIFY_IS_TRUE(core->_initializedTerminal);
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
}
|
|
};
|
|
|
|
void ControlCoreTests::ComPtrSettings()
|
|
{
|
|
Log::Comment(L"Just make sure we can instantiate a settings obj in a com_ptr");
|
|
auto settings = winrt::make_self<MockControlSettings>();
|
|
|
|
Log::Comment(L"Verify literally any setting, it doesn't matter");
|
|
VERIFY_ARE_EQUAL(DEFAULT_FOREGROUND, settings->DefaultForeground());
|
|
}
|
|
|
|
void ControlCoreTests::InstantiateCore()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
}
|
|
|
|
void ControlCoreTests::TestInitialize()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
|
|
VERIFY_IS_FALSE(core->_initializedTerminal);
|
|
// "Consolas" ends up with an actual size of 9x21 at 96DPI. So
|
|
// let's just arbitrarily start with a 270x420px (30x20 chars) window
|
|
core->Initialize(270, 420, 1.0);
|
|
VERIFY_IS_TRUE(core->_initializedTerminal);
|
|
VERIFY_ARE_EQUAL(30, core->_terminal->GetViewport().Width());
|
|
}
|
|
|
|
void ControlCoreTests::TestAdjustAcrylic()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
settings->UseAcrylic(true);
|
|
settings->Opacity(0.5f);
|
|
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
|
|
// A callback to make sure that we're raising TransparencyChanged events
|
|
double expectedOpacity = 0.5;
|
|
auto opacityCallback = [&](auto&&, Control::TransparencyChangedEventArgs args) mutable {
|
|
VERIFY_ARE_EQUAL(expectedOpacity, args.Opacity());
|
|
VERIFY_ARE_EQUAL(expectedOpacity, settings->Opacity());
|
|
VERIFY_ARE_EQUAL(expectedOpacity, core->_settings.Opacity());
|
|
|
|
if (expectedOpacity < 1.0)
|
|
{
|
|
VERIFY_IS_TRUE(settings->UseAcrylic());
|
|
VERIFY_IS_TRUE(core->_settings.UseAcrylic());
|
|
}
|
|
|
|
// GH#603: Adjusting opacity shouldn't change whether or not we
|
|
// requested acrylic.
|
|
VERIFY_IS_TRUE(settings->UseAcrylic());
|
|
VERIFY_IS_TRUE(core->_settings.UseAcrylic());
|
|
};
|
|
core->TransparencyChanged(opacityCallback);
|
|
|
|
VERIFY_IS_FALSE(core->_initializedTerminal);
|
|
// "Cascadia Mono" ends up with an actual size of 9x19 at 96DPI. So
|
|
// let's just arbitrarily start with a 270x380px (30x20 chars) window
|
|
core->Initialize(270, 380, 1.0);
|
|
VERIFY_IS_TRUE(core->_initializedTerminal);
|
|
|
|
Log::Comment(L"Increasing opacity till fully opaque");
|
|
expectedOpacity += 0.1; // = 0.6;
|
|
core->AdjustOpacity(0.1);
|
|
expectedOpacity += 0.1; // = 0.7;
|
|
core->AdjustOpacity(0.1);
|
|
expectedOpacity += 0.1; // = 0.8;
|
|
core->AdjustOpacity(0.1);
|
|
expectedOpacity += 0.1; // = 0.9;
|
|
core->AdjustOpacity(0.1);
|
|
expectedOpacity += 0.1; // = 1.0;
|
|
// cast to float because floating point numbers are mean
|
|
VERIFY_ARE_EQUAL(1.0f, base::saturated_cast<float>(expectedOpacity));
|
|
core->AdjustOpacity(0.1);
|
|
|
|
Log::Comment(L"Increasing opacity more doesn't actually change it to be >1.0");
|
|
|
|
expectedOpacity = 1.0;
|
|
core->AdjustOpacity(0.1);
|
|
|
|
Log::Comment(L"Decrease opacity");
|
|
expectedOpacity -= 0.25; // = 0.75;
|
|
core->AdjustOpacity(-0.25);
|
|
expectedOpacity -= 0.25; // = 0.5;
|
|
core->AdjustOpacity(-0.25);
|
|
expectedOpacity -= 0.25; // = 0.25;
|
|
core->AdjustOpacity(-0.25);
|
|
expectedOpacity -= 0.25; // = 0.05;
|
|
// cast to float because floating point numbers are mean
|
|
VERIFY_ARE_EQUAL(0.0f, base::saturated_cast<float>(expectedOpacity));
|
|
core->AdjustOpacity(-0.25);
|
|
|
|
Log::Comment(L"Decreasing opacity more doesn't actually change it to be < 0");
|
|
expectedOpacity = 0.0;
|
|
core->AdjustOpacity(-0.25);
|
|
}
|
|
|
|
void ControlCoreTests::TestFreeAfterClose()
|
|
{
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
|
|
Log::Comment(L"Close the Core, like a TermControl would");
|
|
core->Close();
|
|
}
|
|
|
|
VERIFY_IS_TRUE(true, L"Make sure that the test didn't crash when the core when out of scope");
|
|
}
|
|
|
|
void ControlCoreTests::TestFontInitializedInCtor()
|
|
{
|
|
// This is to catch a dumb programming mistake I made while working on
|
|
// the core/control split. We want the font initialized in the ctor,
|
|
// before we even get to Core::Initialize.
|
|
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
|
|
// Make sure to use something dumb like "Impact" as a font name here so
|
|
// that you don't default to Cascadia*
|
|
settings->FontFace(L"Impact");
|
|
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
|
|
VERIFY_ARE_EQUAL(L"Impact", std::wstring_view{ core->_actualFont.GetFaceName() });
|
|
}
|
|
|
|
void ControlCoreTests::TestClearScrollback()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
Log::Comment(L"Create ControlCore object");
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
_standardInit(core);
|
|
|
|
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
|
|
L"(leaving the cursor afer 'Bar')");
|
|
for (int i = 0; i < 40; ++i)
|
|
{
|
|
conn->WriteInput(L"Foo\r\n");
|
|
}
|
|
conn->WriteInput(L"Bar");
|
|
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
Log::Comment(L"Check the buffer viewport before the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
Log::Comment(L"Clear the buffer");
|
|
core->ClearBuffer(Control::ClearBufferType::Scrollback);
|
|
|
|
Log::Comment(L"Check the buffer after the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(0, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(20, core->BufferHeight());
|
|
|
|
// In this test, we can't actually check if we cleared the buffer
|
|
// contents. ConPTY will handle the actual clearing of the buffer
|
|
// contents. We can only ensure that the viewport moved when we did a
|
|
// clear scrollback.
|
|
//
|
|
// The ConptyRoundtripTests test the actual clearing of the contents.
|
|
}
|
|
void ControlCoreTests::TestClearScreen()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
Log::Comment(L"Create ControlCore object");
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
_standardInit(core);
|
|
|
|
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
|
|
L"(leaving the cursor afer 'Bar')");
|
|
for (int i = 0; i < 40; ++i)
|
|
{
|
|
conn->WriteInput(L"Foo\r\n");
|
|
}
|
|
conn->WriteInput(L"Bar");
|
|
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
Log::Comment(L"Check the buffer viewport before the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
Log::Comment(L"Clear the buffer");
|
|
core->ClearBuffer(Control::ClearBufferType::Screen);
|
|
|
|
Log::Comment(L"Check the buffer after the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
// In this test, we can't actually check if we cleared the buffer
|
|
// contents. ConPTY will handle the actual clearing of the buffer
|
|
// contents. We can only ensure that the viewport moved when we did a
|
|
// clear scrollback.
|
|
//
|
|
// The ConptyRoundtripTests test the actual clearing of the contents.
|
|
}
|
|
void ControlCoreTests::TestClearAll()
|
|
{
|
|
auto [settings, conn] = _createSettingsAndConnection();
|
|
Log::Comment(L"Create ControlCore object");
|
|
auto core = createCore(*settings, *conn);
|
|
VERIFY_IS_NOT_NULL(core);
|
|
_standardInit(core);
|
|
|
|
Log::Comment(L"Print 40 rows of 'Foo', and a single row of 'Bar' "
|
|
L"(leaving the cursor afer 'Bar')");
|
|
for (int i = 0; i < 40; ++i)
|
|
{
|
|
conn->WriteInput(L"Foo\r\n");
|
|
}
|
|
conn->WriteInput(L"Bar");
|
|
|
|
// We printed that 40 times, but the final \r\n bumped the view down one MORE row.
|
|
Log::Comment(L"Check the buffer viewport before the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(21, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(41, core->BufferHeight());
|
|
|
|
Log::Comment(L"Clear the buffer");
|
|
core->ClearBuffer(Control::ClearBufferType::All);
|
|
|
|
Log::Comment(L"Check the buffer after the clear");
|
|
VERIFY_ARE_EQUAL(20, core->_terminal->GetViewport().Height());
|
|
VERIFY_ARE_EQUAL(0, core->ScrollOffset());
|
|
VERIFY_ARE_EQUAL(20, core->ViewHeight());
|
|
VERIFY_ARE_EQUAL(20, core->BufferHeight());
|
|
|
|
// In this test, we can't actually check if we cleared the buffer
|
|
// contents. ConPTY will handle the actual clearing of the buffer
|
|
// contents. We can only ensure that the viewport moved when we did a
|
|
// clear scrollback.
|
|
//
|
|
// The ConptyRoundtripTests test the actual clearing of the contents.
|
|
}
|
|
|
|
}
|