diff --git a/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp b/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp index 327be5ad92c..4e3bdacc254 100644 --- a/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp @@ -64,7 +64,8 @@ namespace SettingsModelLocalTests }, "window": { - "applicationTheme": "light" + "applicationTheme": "light", + "useMica": true } })" }; @@ -80,6 +81,7 @@ namespace SettingsModelLocalTests VERIFY_IS_NOT_NULL(theme->Window()); VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Light, theme->Window().RequestedTheme()); + VERIFY_ARE_EQUAL(true, theme->Window().UseMica()); } void ThemeTests::ParseEmptyTheme() @@ -161,7 +163,8 @@ namespace SettingsModelLocalTests }, "window": { - "applicationTheme": "light" + "applicationTheme": "light", + "useMica": true } }, { @@ -172,14 +175,16 @@ namespace SettingsModelLocalTests }, "window": { - "applicationTheme": "light" + "applicationTheme": "light", + "useMica": true } }, { "name": "backgroundOmittedEntirely", "window": { - "applicationTheme": "light" + "applicationTheme": "light", + "useMica": true } } ] @@ -234,7 +239,8 @@ namespace SettingsModelLocalTests "tabRow": {}, "window": { - "applicationTheme": "light" + "applicationTheme": "light", + "useMica": true } } ] diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 0e0c2a4548e..a5e4b2bde1f 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -630,7 +630,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation // // Firing it manually makes sure it does. _BackgroundBrush = RootGrid().Background(); - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"BackgroundBrush" }); + _PropertyChangedHandlers(*this, Data::PropertyChangedEventArgs{ L"BackgroundBrush" }); _isBackgroundLight = _isColorLight(bg); } @@ -652,7 +652,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation { const auto opacity{ _core.Opacity() }; const auto useAcrylic{ _core.UseAcrylic() }; - + auto changed = false; // GH#11743, #11619: If we're changing whether or not acrylic is used, // then just entirely reinitialize the brush. The primary way that this // happens is on Windows 10, where we need to enable acrylic when the @@ -666,6 +666,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation _InitializeBackgroundBrush(); return; } + changed = acrylic.TintOpacity() != opacity; acrylic.TintOpacity(opacity); } else if (auto solidColor = RootGrid().Background().try_as()) @@ -675,8 +676,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation _InitializeBackgroundBrush(); return; } + changed = solidColor.Opacity() != opacity; solidColor.Opacity(opacity); } + // Send a BG brush changed event, so you can mouse wheel the + // transparency of the titlebar too. + if (changed) + { + _PropertyChangedHandlers(*this, Data::PropertyChangedEventArgs{ L"BackgroundBrush" }); + } } TermControl::~TermControl() diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 91ff1b068a4..335fc7ad864 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -122,8 +122,9 @@ Author(s): X(winrt::Microsoft::Terminal::Settings::Model::TabRowTheme, TabRow, "tabRow", nullptr) \ X(winrt::Microsoft::Terminal::Settings::Model::TabTheme, Tab, "tab", nullptr) -#define MTSM_THEME_WINDOW_SETTINGS(X) \ - X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "applicationTheme", winrt::Windows::UI::Xaml::ElementTheme::Default) +#define MTSM_THEME_WINDOW_SETTINGS(X) \ + X(winrt::Windows::UI::Xaml::ElementTheme, RequestedTheme, "applicationTheme", winrt::Windows::UI::Xaml::ElementTheme::Default) \ + X(bool, UseMica, "useMica", false) #define MTSM_THEME_TABROW_SETTINGS(X) \ X(winrt::Microsoft::Terminal::Settings::Model::ThemeColor, Background, "background", nullptr) \ diff --git a/src/cascadia/TerminalSettingsModel/Theme.cpp b/src/cascadia/TerminalSettingsModel/Theme.cpp index c7236a8463f..67c82ef7045 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.cpp +++ b/src/cascadia/TerminalSettingsModel/Theme.cpp @@ -125,10 +125,6 @@ winrt::WUX::Media::Brush ThemeColor::Evaluate(const winrt::WUX::ResourceDictiona { static const auto accentColorKey{ winrt::box_value(L"SystemAccentColor") }; - // NOTE: Currently, the DWM titlebar is always drawn, underneath our XAML - // content. If the opacity is <1.0, then you'll be able to see it, including - // the original caption buttons, which we don't want. - switch (ColorType()) { case ThemeColorType::Accent: @@ -148,51 +144,10 @@ winrt::WUX::Media::Brush ThemeColor::Evaluate(const winrt::WUX::ResourceDictiona } case ThemeColorType::Color: { - return winrt::WUX::Media::SolidColorBrush{ forTitlebar ? - Color().with_alpha(255) : - Color() }; + return winrt::WUX::Media::SolidColorBrush{ Color() }; } case ThemeColorType::TerminalBackground: { - // If we're evaluating this color for the tab row, there are some rules - // we have to follow, unfortunately. We can't allow a transparent - // background, so we have to make sure to fill that in with Opacity(1.0) - // manually. - // - // So for that case, just make a new brush with the relevant properties - // set. - if (forTitlebar) - { - if (auto acrylic = terminalBackground.try_as()) - { - winrt::WUX::Media::AcrylicBrush newBrush{}; - newBrush.TintColor(acrylic.TintColor()); - newBrush.FallbackColor(acrylic.FallbackColor()); - newBrush.TintLuminosityOpacity(acrylic.TintLuminosityOpacity()); - - // Allow acrylic opacity, but it's gotta be HostBackdrop acrylic. - // - // For now, just always use 50% opacity for this. If we do ever - // figure out how to get rid of our titlebar under the XAML tab - // row (GH#10509), we can always get rid of the HostBackdrop - // thing, and all this copying, and just return the - // terminalBackground brush directly. - // - // Because we're wholesale copying the brush, we won't be able - // to adjust it's opacity with the mouse wheel. This seems like - // an acceptable tradeoff for now. - newBrush.TintOpacity(.5); - newBrush.BackgroundSource(winrt::WUX::Media::AcrylicBackgroundSource::HostBackdrop); - return newBrush; - } - else if (auto solidColor = terminalBackground.try_as()) - { - winrt::WUX::Media::SolidColorBrush newBrush{}; - newBrush.Color(til::color{ solidColor.Color() }.with_alpha(255)); - return newBrush; - } - } - return terminalBackground; } } diff --git a/src/cascadia/TerminalSettingsModel/Theme.idl b/src/cascadia/TerminalSettingsModel/Theme.idl index 39260bb0cf4..7078030fa9b 100644 --- a/src/cascadia/TerminalSettingsModel/Theme.idl +++ b/src/cascadia/TerminalSettingsModel/Theme.idl @@ -37,6 +37,7 @@ namespace Microsoft.Terminal.Settings.Model runtimeclass WindowTheme { Windows.UI.Xaml.ElementTheme RequestedTheme { get; }; + Boolean UseMica { get; }; } runtimeclass TabRowTheme { diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 4ab2d0031bd..95d47906de4 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -1350,17 +1350,9 @@ void AppHost::_updateTheme() _window->OnApplicationThemeChanged(theme.RequestedTheme()); - // This block of code enables Mica for our window. By all accounts, this - // version of the code will only work on Windows 11, SV2. There's a slightly - // different API surface for enabling Mica on Windows 11 22000.0. - // - // This code is left here, commented out, for future enablement of Mica. - // We'll revisit this in GH#10509. Because we can't enable transparent - // titlebars for showing Mica currently, we're just gonna disable it - // entirely while we sort that out. - // - // const int attribute = theme.Window().UseMica() ? /*DWMSBT_MAINWINDOW*/ 2 : /*DWMSBT_NONE*/ 1; - // DwmSetWindowAttribute(_window->GetHandle(), /* DWMWA_SYSTEMBACKDROP_TYPE */ 38, &attribute, sizeof(attribute)); + const auto b = _logic.TitlebarBrush(); + const auto opacity = b ? ThemeColor::ColorFromBrush(b).A / 255.0 : 0.0; + _window->UseMica(theme.Window().UseMica(), opacity); } void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/, diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 446d1b46506..4d76921656c 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -1894,3 +1894,16 @@ void IslandWindow::RemoveFromSystemMenu(const winrt::hstring& itemLabel) } _systemMenuItems.erase(it->first); } + +void IslandWindow::UseMica(const bool newValue, const double /*titlebarOpacity*/) +{ + // This block of code enables Mica for our window. By all accounts, this + // version of the code will only work on Windows 11, SV2. There's a slightly + // different API surface for enabling Mica on Windows 11 22000.0. + // + // This API was only publicly supported as of Windows 11 SV2, 22621. Before + // that version, this API will just return an error and do nothing silently. + + const int attribute = newValue ? DWMSBT_MAINWINDOW : DWMSBT_NONE; + std::ignore = DwmSetWindowAttribute(GetHandle(), DWMWA_SYSTEMBACKDROP_TYPE, &attribute, sizeof(attribute)); +} diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index b0d3efff5c5..cfa3e0907d8 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -65,6 +65,8 @@ class IslandWindow : void AddToSystemMenu(const winrt::hstring& itemLabel, winrt::delegate callback); void RemoveFromSystemMenu(const winrt::hstring& itemLabel); + virtual void UseMica(const bool newValue, const double titlebarOpacity); + WINRT_CALLBACK(DragRegionClicked, winrt::delegate<>); WINRT_CALLBACK(WindowCloseButtonClicked, winrt::delegate<>); WINRT_CALLBACK(MouseScrolled, winrt::delegate); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index a7e619a2ad4..2ae1b885e93 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -442,11 +442,21 @@ til::rect NonClientIslandWindow::_GetDragAreaRect() const noexcept static_cast(_rootGrid.ActualWidth()), static_cast(_dragBar.ActualHeight()) }; + const auto clientDragBarRect = transform.TransformBounds(logicalDragBarRect); + + // Make sure to trim the right side of the rectangle, so that it doesn't + // hang off the right side of the root window. This normally wouldn't + // matter, but UIA will still think its bounds can extend past the right + // of the parent HWND. + // + // x here is the width of the tabs. + const auto x = gsl::narrow_cast(clientDragBarRect.X * scale); + return { - gsl::narrow_cast(clientDragBarRect.X * scale), + x, gsl::narrow_cast(clientDragBarRect.Y * scale), - gsl::narrow_cast((clientDragBarRect.Width + clientDragBarRect.X) * scale), + gsl::narrow_cast((clientDragBarRect.Width + clientDragBarRect.X) * scale) - x, gsl::narrow_cast((clientDragBarRect.Height + clientDragBarRect.Y) * scale), }; } @@ -879,7 +889,26 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept // at the top) in the WM_PAINT handler. This eliminates the transparency // bug and it's what a lot of Win32 apps that customize the title bar do // so it should work fine. - margins.cyTopHeight = -frame.top; + // + // Notes #3 (circa late 2022): We want to make some changes here to + // support Mica. This introduces some complications. + // - If we leave the titlebar visible AT ALL, then a transparent + // titlebar (theme.tabRow.background:#ff00ff00 for example) will allow + // the DWM titlebar to be visible, underneath our content. EVEN MORE + // SO: Mica + "show accent color on title bars" will _always_ show the + // accent-colored strip of the titlebar, even on top of the Mica. + // - It _seems_ like we can just set this to 0, and have it work. You'd + // be wrong. On Windows 10, setting this to 0 will cause the topmost + // pixel of our window to be just a little darker than the rest of the + // frame. So ONLY set this to 0 when the user has explicitly asked for + // Mica. Though it won't do anything on Windows 10, they should be + // able to opt back out of having that weird dark pixel. + // - This is LOAD-BEARING. By having the titlebar a totally empty rect, + // DWM will know that we don't have the traditional titlebar, and will + // use NCHITTEST to determine where to place the Snap Flyout. The drag + // rect will handle that. + + margins.cyTopHeight = (_useMica || _titlebarOpacity < 1.0) ? 0 : -frame.top; } // Extend the frame into the client area. microsoft/terminal#2735 - Just log @@ -1132,3 +1161,16 @@ void NonClientIslandWindow::SetTitlebarBackground(winrt::Windows::UI::Xaml::Medi { _titlebar.Background(brush); } + +void NonClientIslandWindow::UseMica(const bool newValue, const double titlebarOpacity) +{ + // Stash internally if we're using Mica. If we aren't, we don't want to + // totally blow away our titlebar with DwmExtendFrameIntoClientArea, + // especially on Windows 10 + _useMica = newValue; + _titlebarOpacity = titlebarOpacity; + + IslandWindow::UseMica(newValue, titlebarOpacity); + + _UpdateFrameMargins(); +} diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 20b9bde43ef..8b3cdfef2e9 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -49,6 +49,8 @@ class NonClientIslandWindow : public IslandWindow void SetTitlebarBackground(winrt::Windows::UI::Xaml::Media::Brush brush); + virtual void UseMica(const bool newValue, const double titlebarOpacity) override; + private: std::optional _oldIslandPos; @@ -63,6 +65,9 @@ class NonClientIslandWindow : public IslandWindow winrt::Windows::UI::Xaml::ElementTheme _theme; + bool _useMica{ false }; + double _titlebarOpacity{ 1.0 }; + bool _isMaximized; bool _trackingMouse{ false };