diff --git a/.github/actions/spelling/allow/apis.txt b/.github/actions/spelling/allow/apis.txt
index d44c2ddf837..0b8c0d91a9f 100644
--- a/.github/actions/spelling/allow/apis.txt
+++ b/.github/actions/spelling/allow/apis.txt
@@ -30,6 +30,8 @@ DERR
dlldata
DONTADDTORECENT
DWORDLONG
+DWMSBT
+DWMWA
endfor
enumset
environstrings
@@ -95,6 +97,7 @@ lround
Lsa
lsass
LSHIFT
+MAINWINDOW
memchr
memicmp
MENUCOMMAND
@@ -176,6 +179,8 @@ Stubless
Subheader
Subpage
syscall
+SYSTEMBACKDROP
+TABROW
TASKBARCREATED
TBPF
THEMECHANGED
diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
index 47214270725..76e0f07e293 100644
--- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
+++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj
@@ -41,6 +41,7 @@
+
Create
diff --git a/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp b/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp
new file mode 100644
index 00000000000..25e115a0cd7
--- /dev/null
+++ b/src/cascadia/LocalTests_SettingsModel/ThemeTests.cpp
@@ -0,0 +1,276 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "pch.h"
+
+#include "../TerminalSettingsModel/Theme.h"
+#include "../TerminalSettingsModel/CascadiaSettings.h"
+#include "../types/inc/colorTable.hpp"
+#include "JsonTestClass.h"
+
+#include
+
+using namespace Microsoft::Console;
+using namespace winrt::Microsoft::Terminal;
+using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
+using namespace WEX::Logging;
+using namespace WEX::TestExecution;
+using namespace WEX::Common;
+
+namespace SettingsModelLocalTests
+{
+ // TODO:microsoft/terminal#3838:
+ // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
+ // an updated TAEF that will let us install framework packages when the test
+ // package is deployed. Until then, these tests won't deploy in CI.
+
+ class ThemeTests : public JsonTestClass
+ {
+ // Use a custom AppxManifest to ensure that we can activate winrt types
+ // from our test. This property will tell taef to manually use this as
+ // the AppxManifest for this test class.
+ // This does not yet work for anything XAML-y. See TabTests.cpp for more
+ // details on that.
+ BEGIN_TEST_CLASS(ThemeTests)
+ TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
+ TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
+ END_TEST_CLASS()
+
+ TEST_METHOD(ParseSimpleTheme);
+ TEST_METHOD(ParseEmptyTheme);
+ TEST_METHOD(ParseNoWindowTheme);
+ TEST_METHOD(ParseNullWindowTheme);
+ TEST_METHOD(ParseThemeWithNullThemeColor);
+ TEST_METHOD(InvalidCurrentTheme);
+
+ static Core::Color rgb(uint8_t r, uint8_t g, uint8_t b) noexcept
+ {
+ return Core::Color{ r, g, b, 255 };
+ }
+ };
+
+ void ThemeTests::ParseSimpleTheme()
+ {
+ static constexpr std::string_view orangeTheme{ R"({
+ "name": "orange",
+ "tabRow":
+ {
+ "background": "#FFFF8800",
+ "unfocusedBackground": "#FF884400"
+ },
+ "window":
+ {
+ "applicationTheme": "light",
+ "useMica": true
+ }
+ })" };
+
+ const auto schemeObject = VerifyParseSucceeded(orangeTheme);
+ auto theme = Theme::FromJson(schemeObject);
+ VERIFY_ARE_EQUAL(L"orange", theme->Name());
+
+ VERIFY_IS_NOT_NULL(theme->TabRow());
+ VERIFY_IS_NOT_NULL(theme->TabRow().Background());
+ VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
+ VERIFY_ARE_EQUAL(rgb(0xff, 0x88, 0x00), theme->TabRow().Background().Color());
+
+ 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()
+ {
+ Log::Comment(L"This theme doesn't have any elements defined.");
+ static constexpr std::string_view emptyTheme{ R"({
+ "name": "empty"
+ })" };
+
+ const auto schemeObject = VerifyParseSucceeded(emptyTheme);
+ auto theme = Theme::FromJson(schemeObject);
+ VERIFY_ARE_EQUAL(L"empty", theme->Name());
+ VERIFY_IS_NULL(theme->TabRow());
+ VERIFY_IS_NULL(theme->Window());
+ VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
+ }
+
+ void ThemeTests::ParseNoWindowTheme()
+ {
+ Log::Comment(L"This theme doesn't have a window defined.");
+ static constexpr std::string_view emptyTheme{ R"({
+ "name": "noWindow",
+ "tabRow":
+ {
+ "background": "#FF112233",
+ "unfocusedBackground": "#FF884400"
+ },
+ })" };
+
+ const auto schemeObject = VerifyParseSucceeded(emptyTheme);
+ auto theme = Theme::FromJson(schemeObject);
+ VERIFY_ARE_EQUAL(L"noWindow", theme->Name());
+
+ VERIFY_IS_NOT_NULL(theme->TabRow());
+ VERIFY_IS_NOT_NULL(theme->TabRow().Background());
+ VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
+ VERIFY_ARE_EQUAL(rgb(0x11, 0x22, 0x33), theme->TabRow().Background().Color());
+
+ VERIFY_IS_NULL(theme->Window());
+ VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
+ }
+
+ void ThemeTests::ParseNullWindowTheme()
+ {
+ Log::Comment(L"This theme doesn't have a window defined.");
+ static constexpr std::string_view emptyTheme{ R"({
+ "name": "nullWindow",
+ "tabRow":
+ {
+ "background": "#FF112233",
+ "unfocusedBackground": "#FF884400"
+ },
+ "window": null
+ })" };
+
+ const auto schemeObject = VerifyParseSucceeded(emptyTheme);
+ auto theme = Theme::FromJson(schemeObject);
+ VERIFY_ARE_EQUAL(L"nullWindow", theme->Name());
+
+ VERIFY_IS_NOT_NULL(theme->TabRow());
+ VERIFY_IS_NOT_NULL(theme->TabRow().Background());
+ VERIFY_ARE_EQUAL(Settings::Model::ThemeColorType::Color, theme->TabRow().Background().ColorType());
+ VERIFY_ARE_EQUAL(rgb(0x11, 0x22, 0x33), theme->TabRow().Background().Color());
+
+ VERIFY_IS_NULL(theme->Window());
+ VERIFY_ARE_EQUAL(winrt::Windows::UI::Xaml::ElementTheme::Default, theme->RequestedTheme());
+ }
+
+ void ThemeTests::ParseThemeWithNullThemeColor()
+ {
+ Log::Comment(L"These themes are all missing a tabRow background. Make sure we don't somehow default-construct one for them");
+
+ static constexpr std::string_view settingsString{ R"json({
+ "themes": [
+ {
+ "name": "backgroundEmpty",
+ "tabRow":
+ {
+ },
+ "window":
+ {
+ "applicationTheme": "light",
+ "useMica": true
+ }
+ },
+ {
+ "name": "backgroundNull",
+ "tabRow":
+ {
+ "background": null
+ },
+ "window":
+ {
+ "applicationTheme": "light",
+ "useMica": true
+ }
+ },
+ {
+ "name": "backgroundOmittedEntirely",
+ "window":
+ {
+ "applicationTheme": "light",
+ "useMica": true
+ }
+ }
+ ]
+ })json" };
+
+ try
+ {
+ const auto settings{ winrt::make_self(settingsString, DefaultJson) };
+
+ const auto& themes{ settings->GlobalSettings().Themes() };
+ {
+ const auto& backgroundEmpty{ themes.Lookup(L"backgroundEmpty") };
+ VERIFY_ARE_EQUAL(L"backgroundEmpty", backgroundEmpty.Name());
+ VERIFY_IS_NOT_NULL(backgroundEmpty.TabRow());
+ VERIFY_IS_NULL(backgroundEmpty.TabRow().Background());
+ }
+ {
+ const auto& backgroundNull{ themes.Lookup(L"backgroundNull") };
+ VERIFY_ARE_EQUAL(L"backgroundNull", backgroundNull.Name());
+ VERIFY_IS_NOT_NULL(backgroundNull.TabRow());
+ VERIFY_IS_NULL(backgroundNull.TabRow().Background());
+ }
+ {
+ const auto& backgroundOmittedEntirely{ themes.Lookup(L"backgroundOmittedEntirely") };
+ VERIFY_ARE_EQUAL(L"backgroundOmittedEntirely", backgroundOmittedEntirely.Name());
+ VERIFY_IS_NULL(backgroundOmittedEntirely.TabRow());
+ }
+ }
+ catch (const SettingsException& ex)
+ {
+ auto loadError = ex.Error();
+ loadError;
+ throw ex;
+ }
+ catch (const SettingsTypedDeserializationException& e)
+ {
+ auto deserializationErrorMessage = til::u8u16(e.what());
+ Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
+ throw e;
+ }
+ }
+
+ void ThemeTests::InvalidCurrentTheme()
+ {
+ Log::Comment(L"Make sure specifying an invalid theme falls back to a sensible default.");
+
+ static constexpr std::string_view settingsString{ R"json({
+ "theme": "foo",
+ "themes": [
+ {
+ "name": "bar",
+ "tabRow": {},
+ "window":
+ {
+ "applicationTheme": "light",
+ "useMica": true
+ }
+ }
+ ]
+ })json" };
+
+ try
+ {
+ const auto settings{ winrt::make_self(settingsString, DefaultJson) };
+
+ VERIFY_ARE_EQUAL(1u, settings->Warnings().Size());
+ VERIFY_ARE_EQUAL(Settings::Model::SettingsLoadWarnings::UnknownTheme, settings->Warnings().GetAt(0));
+
+ const auto& themes{ settings->GlobalSettings().Themes() };
+ {
+ const auto& bar{ themes.Lookup(L"bar") };
+ VERIFY_ARE_EQUAL(L"bar", bar.Name());
+ VERIFY_IS_NOT_NULL(bar.TabRow());
+ VERIFY_IS_NULL(bar.TabRow().Background());
+ }
+
+ const auto currentTheme{ settings->GlobalSettings().CurrentTheme() };
+ VERIFY_IS_NOT_NULL(currentTheme);
+ VERIFY_ARE_EQUAL(L"system", currentTheme.Name());
+ }
+ catch (const SettingsException& ex)
+ {
+ auto loadError = ex.Error();
+ loadError;
+ throw ex;
+ }
+ catch (const SettingsTypedDeserializationException& e)
+ {
+ auto deserializationErrorMessage = til::u8u16(e.what());
+ Log::Comment(NoThrowString().Format(deserializationErrorMessage.c_str()));
+ throw e;
+ }
+ }
+}
diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp
index 8f58309ff53..4a05ef2f21d 100644
--- a/src/cascadia/TerminalApp/AppLogic.cpp
+++ b/src/cascadia/TerminalApp/AppLogic.cpp
@@ -51,6 +51,7 @@ static const std::array settingsLoadWarningsLabels {
USES_RESOURCE(L"InvalidSplitSize"),
USES_RESOURCE(L"FailedToParseStartupActions"),
USES_RESOURCE(L"FailedToParseSubCommands"),
+ USES_RESOURCE(L"UnknownTheme"),
};
static const std::array settingsLoadErrorsLabels {
USES_RESOURCE(L"NoProfilesText"),
@@ -367,11 +368,12 @@ namespace winrt::TerminalApp::implementation
// details here, but it does have the desired effect.
// It's not enough to set the theme on the dialog alone.
auto themingLambda{ [this](const Windows::Foundation::IInspectable& sender, const RoutedEventArgs&) {
- auto theme{ _settings.GlobalSettings().Theme() };
+ auto theme{ _settings.GlobalSettings().CurrentTheme() };
+ auto requestedTheme{ theme.RequestedTheme() };
auto element{ sender.try_as() };
while (element)
{
- element.RequestedTheme(theme);
+ element.RequestedTheme(requestedTheme);
element = element.Parent().try_as();
}
} };
@@ -737,13 +739,7 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::UI::Xaml::ElementTheme AppLogic::GetRequestedTheme()
{
- if (!_loadedInitialSettings)
- {
- // Load settings if we haven't already
- LoadSettings();
- }
-
- return _settings.GlobalSettings().Theme();
+ return Theme().RequestedTheme();
}
bool AppLogic::GetShowTabsInTitlebar()
@@ -964,7 +960,7 @@ namespace winrt::TerminalApp::implementation
void AppLogic::_RefreshThemeRoutine()
{
- _ApplyTheme(_settings.GlobalSettings().Theme());
+ _ApplyTheme(GetRequestedTheme());
}
// Function Description:
@@ -1219,6 +1215,19 @@ namespace winrt::TerminalApp::implementation
return {};
}
+ winrt::Windows::UI::Xaml::Media::Brush AppLogic::TitlebarBrush()
+ {
+ if (_root)
+ {
+ return _root->TitlebarBrush();
+ }
+ return { nullptr };
+ }
+ void AppLogic::WindowActivated(const bool activated)
+ {
+ _root->WindowActivated(activated);
+ }
+
bool AppLogic::HasCommandlineArguments() const noexcept
{
return _hasCommandLineArguments;
@@ -1645,4 +1654,15 @@ namespace winrt::TerminalApp::implementation
{
return _settings.GlobalSettings().ShowTitleInTitlebar();
}
+
+ Microsoft::Terminal::Settings::Model::Theme AppLogic::Theme()
+ {
+ if (!_loadedInitialSettings)
+ {
+ // Load settings if we haven't already
+ LoadSettings();
+ }
+ return _settings.GlobalSettings().CurrentTheme();
+ }
+
}
diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h
index 63d1c4e784d..2ba2deaeffb 100644
--- a/src/cascadia/TerminalApp/AppLogic.h
+++ b/src/cascadia/TerminalApp/AppLogic.h
@@ -117,6 +117,8 @@ namespace winrt::TerminalApp::implementation
void WindowVisibilityChanged(const bool showOrHide);
winrt::TerminalApp::TaskbarState TaskbarState();
+ winrt::Windows::UI::Xaml::Media::Brush TitlebarBrush();
+ void WindowActivated(const bool activated);
bool GetMinimizeToNotificationArea();
bool GetAlwaysShowNotificationIcon();
@@ -127,7 +129,13 @@ namespace winrt::TerminalApp::implementation
Windows::Foundation::Collections::IMapView GlobalHotkeys();
+ Microsoft::Terminal::Settings::Model::Theme Theme();
+
// -------------------------------- WinRT Events ---------------------------------
+ // PropertyChanged is surprisingly not a typed event, so we'll define that one manually.
+ winrt::event_token PropertyChanged(Windows::UI::Xaml::Data::PropertyChangedEventHandler const& handler) { return _root->PropertyChanged(handler); }
+ void PropertyChanged(winrt::event_token const& token) { _root->PropertyChanged(token); }
+
TYPED_EVENT(RequestedThemeChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::ElementTheme);
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(SystemMenuChangeRequested, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SystemMenuChangeArgs);
diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl
index a5c0eb05a7b..42096bd08d1 100644
--- a/src/cascadia/TerminalApp/AppLogic.idl
+++ b/src/cascadia/TerminalApp/AppLogic.idl
@@ -35,7 +35,7 @@ namespace TerminalApp
// See IDialogPresenter and TerminalPage's DialogPresenter for more
// information.
- [default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter
+ [default_interface] runtimeclass AppLogic : IDirectKeyListener, IDialogPresenter, Windows.UI.Xaml.Data.INotifyPropertyChanged
{
AppLogic();
@@ -94,6 +94,8 @@ namespace TerminalApp
void WindowVisibilityChanged(Boolean showOrHide);
TaskbarState TaskbarState{ get; };
+ Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
+ void WindowActivated(Boolean activated);
Boolean ShouldUsePersistedLayout();
Boolean ShouldImmediatelyHandoffToElevated();
@@ -105,6 +107,8 @@ namespace TerminalApp
Boolean GetAlwaysShowNotificationIcon();
Boolean GetShowTitleInTitlebar();
+ Microsoft.Terminal.Settings.Model.Theme Theme { get; };
+
FindTargetWindowResult FindTargetWindow(String[] args);
Windows.Foundation.Collections.IMapView GlobalHotkeys();
diff --git a/src/cascadia/TerminalApp/HighlightedText.h b/src/cascadia/TerminalApp/HighlightedText.h
index e71c7ef7523..c9c29e7cbb8 100644
--- a/src/cascadia/TerminalApp/HighlightedText.h
+++ b/src/cascadia/TerminalApp/HighlightedText.h
@@ -3,8 +3,6 @@
#pragma once
-#include "winrt/Microsoft.UI.Xaml.Controls.h"
-
#include "HighlightedTextSegment.g.h"
#include "HighlightedText.g.h"
diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index a0a653927e3..29e70316f88 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -242,6 +242,10 @@
• Found a keybinding that was missing a required parameter value. This keybinding will be ignored.
{Locked="•"} This glyph is a bullet, used in a bulleted list.
+
+ • The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.
+ {Locked="•"} This glyph is a bullet, used in a bulleted list.
+
The "globals" property is deprecated - your settings might need updating.
{Locked="\"globals\""}
diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp
index 47cfa70d7b2..d88b7a7b4fe 100644
--- a/src/cascadia/TerminalApp/TabManagement.cpp
+++ b/src/cascadia/TerminalApp/TabManagement.cpp
@@ -154,6 +154,8 @@ namespace winrt::TerminalApp::implementation
// Possibly update the icon of the tab.
page->_UpdateTabIcon(*tab);
+ page->_updateTabRowColors();
+
// Update the taskbar progress as well. We'll raise our own
// SetTaskbarProgress event here, to get tell the hosting
// application to re-query this value from us.
@@ -925,6 +927,8 @@ namespace winrt::TerminalApp::implementation
_TitleChangedHandlers(*this, tab.Title());
}
+ _updateTabRowColors();
+
auto tab_impl = _GetTerminalTabImpl(tab);
if (tab_impl)
{
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 197cbce47c6..a975a7f6149 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -183,43 +183,6 @@ namespace winrt::TerminalApp::implementation
const auto isElevated = IsElevated();
- if (_settings.GlobalSettings().UseAcrylicInTabRow())
- {
- const auto res = Application::Current().Resources();
- const auto lightKey = winrt::box_value(L"Light");
- const auto darkKey = winrt::box_value(L"Dark");
- const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground");
-
- for (const auto& dictionary : res.MergedDictionaries())
- {
- // Don't change MUX resources
- if (dictionary.Source())
- {
- continue;
- }
-
- for (const auto& kvPair : dictionary.ThemeDictionaries())
- {
- const auto themeDictionary = kvPair.Value().as();
-
- if (themeDictionary.HasKey(tabViewBackgroundKey))
- {
- const auto backgroundSolidBrush = themeDictionary.Lookup(tabViewBackgroundKey).as();
-
- const til::color backgroundColor = backgroundSolidBrush.Color();
-
- const auto acrylicBrush = Media::AcrylicBrush();
- acrylicBrush.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
- acrylicBrush.FallbackColor(backgroundColor);
- acrylicBrush.TintColor(backgroundColor);
- acrylicBrush.TintOpacity(0.5);
-
- themeDictionary.Insert(tabViewBackgroundKey, acrylicBrush);
- }
- }
- }
- }
-
_tabRow.PointerMoved({ get_weak(), &TerminalPage::_RestorePointerCursorHandler });
_tabView.CanReorderTabs(!isElevated);
_tabView.CanDragTabs(!isElevated);
@@ -260,6 +223,7 @@ namespace winrt::TerminalApp::implementation
transparent.Color(Windows::UI::Colors::Transparent());
_tabRow.Background(transparent);
}
+ _updateTabRowColors();
// Hookup our event handlers to the ShortcutActionDispatch
_RegisterActionCallbacks();
@@ -1471,6 +1435,16 @@ namespace winrt::TerminalApp::implementation
term.ConnectionStateChanged({ get_weak(), &TerminalPage::_ConnectionStateChangedHandler });
+ term.PropertyChanged([weakThis = get_weak()](auto& /*sender*/, auto& e) {
+ if (auto page{ weakThis.get() })
+ {
+ if (e.PropertyName() == L"BackgroundBrush")
+ {
+ page->_updateTabRowColors();
+ }
+ }
+ });
+
term.ShowWindowChanged({ get_weak(), &TerminalPage::_ShowWindowChangedHandler });
}
@@ -1510,37 +1484,9 @@ namespace winrt::TerminalApp::implementation
}
});
- // react on color changed events
- hostingTab.ColorSelected([weakTab, weakThis](auto&& color) {
- auto page{ weakThis.get() };
- auto tab{ weakTab.get() };
-
- if (page && tab && (tab->FocusState() != FocusState::Unfocused))
- {
- page->_SetNonClientAreaColors(color);
- }
- });
-
- hostingTab.ColorCleared([weakTab, weakThis]() {
- auto page{ weakThis.get() };
- auto tab{ weakTab.get() };
-
- if (page && tab && (tab->FocusState() != FocusState::Unfocused))
- {
- page->_ClearNonClientAreaColors();
- }
- });
-
// Add an event handler for when the terminal or tab wants to set a
// progress indicator on the taskbar
hostingTab.TaskbarProgressChanged({ get_weak(), &TerminalPage::_SetTaskbarProgressHandler });
-
- // TODO GH#3327: Once we support colorizing the NewTab button based on
- // the color of the tab, we'll want to make sure to call
- // _ClearNewTabButtonColor here, to reset it to the default (for the
- // newly created tab).
- // remove any colors left by other colored tabs
- // _ClearNewTabButtonColor();
}
// Method Description:
@@ -2750,6 +2696,13 @@ namespace winrt::TerminalApp::implementation
WUX::Media::Animation::Timeline::AllowDependentAnimations(!_settings.GlobalSettings().DisableAnimations());
_tabRow.ShowElevationShield(IsElevated() && _settings.GlobalSettings().ShowAdminShield());
+
+ Media::SolidColorBrush transparent{ Windows::UI::Colors::Transparent() };
+ _tabView.Background(transparent);
+
+ ////////////////////////////////////////////////////////////////////////
+ // Begin Theme handling
+ _updateTabRowColors();
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
@@ -3134,32 +3087,6 @@ namespace winrt::TerminalApp::implementation
_newTabButton.Foreground(foregroundBrush);
}
- // Method Description:
- // - Sets the tab split button color when a new tab color is selected
- // - This method could also set the color of the title bar and tab row
- // in the future
- // Arguments:
- // - selectedTabColor: The color of the newly selected tab
- // Return Value:
- // -
- void TerminalPage::_SetNonClientAreaColors(const Windows::UI::Color& /*selectedTabColor*/)
- {
- // TODO GH#3327: Look at what to do with the NC area when we have XAML theming
- }
-
- // Method Description:
- // - Clears the tab split button color when the tab's color is cleared
- // - This method could also clear the color of the title bar and tab row
- // in the future
- // Arguments:
- // -
- // Return Value:
- // -
- void TerminalPage::_ClearNonClientAreaColors()
- {
- // TODO GH#3327: Look at what to do with the NC area when we have XAML theming
- }
-
// Function Description:
// - This is a helper method to get the commandline out of a
// ExecuteCommandline action, break it into subcommands, and attempt to
@@ -3569,10 +3496,11 @@ namespace winrt::TerminalApp::implementation
// -
void TerminalPage::_UpdateTeachingTipTheme(winrt::Windows::UI::Xaml::FrameworkElement element)
{
- auto theme{ _settings.GlobalSettings().Theme() };
+ auto theme{ _settings.GlobalSettings().CurrentTheme() };
+ auto requestedTheme{ theme.RequestedTheme() };
while (element)
{
- element.RequestedTheme(theme);
+ element.RequestedTheme(requestedTheme);
element = element.Parent().try_as();
}
}
@@ -4076,4 +4004,132 @@ namespace winrt::TerminalApp::implementation
applicationState.DismissedMessages(std::move(messages));
}
+ void TerminalPage::_updateTabRowColors()
+ {
+ if (_settings == nullptr)
+ {
+ return;
+ }
+
+ const auto theme = _settings.GlobalSettings().CurrentTheme();
+ const auto requestedTheme{ theme.RequestedTheme() };
+
+ const auto res = Application::Current().Resources();
+
+ // XAML Hacks:
+ //
+ // the App is always in the OS theme, so the
+ // App::Current().Resources() lookup will always get the value for the
+ // OS theme, not the requested theme.
+ //
+ // This helper allows us to instead lookup the value of a resource
+ // specified by `key` for the given `requestedTheme`, from the
+ // dictionaries in App.xaml. Make sure the value is actually there!
+ // Otherwise this'll throw like any other Lookup for a resource that
+ // isn't there.
+ static const auto lookup = [](auto& res, auto& requestedTheme, auto& key) {
+ // You want the Default version of the resource? Great, the App is
+ // always in the OS theme. Just look it up and be done.
+ if (requestedTheme == ElementTheme::Default)
+ {
+ return res.Lookup(key);
+ }
+ static const auto lightKey = winrt::box_value(L"Light");
+ static const auto darkKey = winrt::box_value(L"Dark");
+ // There isn't an ElementTheme::HighContrast.
+
+ auto requestedThemeKey = requestedTheme == ElementTheme::Dark ? darkKey : lightKey;
+ for (const auto& dictionary : res.MergedDictionaries())
+ {
+ // Don't look in the MUX resources. They come first. A person
+ // with more patience than me may find a way to look through our
+ // dictionaries first, then the MUX ones, but that's not needed
+ // currently
+ if (dictionary.Source())
+ {
+ continue;
+ }
+ // Look through the theme dictionaries we defined:
+ for (const auto& [dictionaryKey, dict] : dictionary.ThemeDictionaries())
+ {
+ // Does the key for this dict match the theme we're looking for?
+ if (winrt::unbox_value(dictionaryKey) !=
+ winrt::unbox_value(requestedThemeKey))
+ {
+ // No? skip it.
+ continue;
+ }
+ // Look for the requested resource in this dict.
+ const auto themeDictionary = dict.as();
+ if (themeDictionary.HasKey(key))
+ {
+ return themeDictionary.Lookup(key);
+ }
+ }
+ }
+
+ // We didn't find it in the requested dict, fall back to the default dictionary.
+ return res.Lookup(key);
+ };
+
+ // Use our helper to lookup the theme-aware version of the resource.
+ const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground");
+ const auto backgroundSolidBrush = lookup(res, requestedTheme, tabViewBackgroundKey).as();
+
+ til::color bgColor = backgroundSolidBrush.Color();
+
+ if (_settings.GlobalSettings().UseAcrylicInTabRow())
+ {
+ const til::color backgroundColor = backgroundSolidBrush.Color();
+ const auto acrylicBrush = Media::AcrylicBrush();
+ acrylicBrush.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop);
+ acrylicBrush.FallbackColor(bgColor);
+ acrylicBrush.TintColor(bgColor);
+ acrylicBrush.TintOpacity(0.5);
+
+ TitlebarBrush(acrylicBrush);
+ }
+ else if (theme.TabRow() && theme.TabRow().Background())
+ {
+ const auto tabRowBg = theme.TabRow().Background();
+ const auto terminalBrush = [this]() -> Media::Brush {
+ if (const auto& control{ _GetActiveControl() })
+ {
+ return control.BackgroundBrush();
+ }
+ else if (auto settingsTab = _GetFocusedTab().try_as())
+ {
+ return settingsTab.Content().try_as().BackgroundBrush();
+ }
+ return nullptr;
+ }();
+
+ const auto themeBrush{ tabRowBg.Evaluate(res, terminalBrush, true) };
+ bgColor = ThemeColor::ColorFromBrush(themeBrush);
+ TitlebarBrush(themeBrush);
+ }
+ else
+ {
+ // Nothing was set in the theme - fall back to our original `TabViewBackground` color.
+ TitlebarBrush(backgroundSolidBrush);
+ }
+
+ if (!_settings.GlobalSettings().ShowTabsInTitlebar())
+ {
+ _tabRow.Background(TitlebarBrush());
+ }
+
+ // Update the new tab button to have better contrast with the new color.
+ // In theory, it would be convenient to also change these for the
+ // inactive tabs as well, but we're leaving that as a follow up.
+ _SetNewTabButtonColor(bgColor, bgColor);
+ }
+
+ void TerminalPage::WindowActivated(const bool activated)
+ {
+ // Stash if we're activated. Use that when we reload
+ // the settings, change active panes, etc.
+ _activated = activated;
+ _updateTabRowColors();
+ }
}
diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h
index 310910f5228..f4e8091dd93 100644
--- a/src/cascadia/TerminalApp/TerminalPage.h
+++ b/src/cascadia/TerminalApp/TerminalPage.h
@@ -134,6 +134,7 @@ namespace winrt::TerminalApp::implementation
bool IsElevated() const noexcept;
void OpenSettingsUI();
+ void WindowActivated(const bool activated);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@@ -157,6 +158,8 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(QuitRequested, IInspectable, IInspectable);
TYPED_EVENT(ShowWindowChanged, IInspectable, winrt::Microsoft::Terminal::Control::ShowWindowArgs)
+ WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, _PropertyChangedHandlers, nullptr);
+
private:
friend struct TerminalPageT; // for Xaml to bind events
std::optional _hostingHwnd;
@@ -196,6 +199,8 @@ namespace winrt::TerminalApp::implementation
std::optional _rearrangeFrom{};
std::optional _rearrangeTo{};
bool _removing{ false };
+
+ bool _activated{ false };
bool _visible{ true };
std::vector> _previouslyClosedPanesAndTabs{};
@@ -383,8 +388,6 @@ namespace winrt::TerminalApp::implementation
void _RefreshUIForSettingsReload();
- void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);
- void _ClearNonClientAreaColors();
void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor);
void _ClearNewTabButtonColor();
@@ -443,6 +446,8 @@ namespace winrt::TerminalApp::implementation
static bool _IsMessageDismissed(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
static void _DismissMessage(const winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage& message);
+ void _updateTabRowColors();
+
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
#pragma region ActionHandlers
diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl
index 90d317c1d98..89ea3ec3277 100644
--- a/src/cascadia/TerminalApp/TerminalPage.idl
+++ b/src/cascadia/TerminalApp/TerminalPage.idl
@@ -45,6 +45,9 @@ namespace TerminalApp
TaskbarState TaskbarState{ get; };
+ Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
+ void WindowActivated(Boolean activated);
+
event Windows.Foundation.TypedEventHandler