Initial Theme support (#12992)

##### ⚠️ targeting 1.15

## Summary of the Pull Request

Adds support for Themes, a new type of customization for the Terminal. Themes allow the user to customize elements of the Terminal window itself. In this first iteration, this PR adds support for two main properties:
* enabling Mica as the window backdrop
* changing the tab row color (read: changing the titelbar color)

These represent the most important asks of theming in the Terminal. The properties added in this PR are:

* Theme color variants:
    - `"#rrggbb"` or `"#aarrggbb"`
    - `"accent"`
    - `"terminalBackground"`
* Properties (_listed here in dot notation, but implemented as sub-objects_)
    - `tabRow.background`: accepts a ThemeColor (above)
    - `window.applicationTheme`: accepts one of `{"system", "light", "dark"}`
    - `window.useMica`: accepts a boolean, defaults to false.

## References
* As first described in #3327
* spec'd in #12530

## PR Checklist
* [x] Sorta enables #10509, but doesn't close it. That'll need more comprehensive changes to the titlebar code.
  * **update**: I totally disabled mica, but left the serialization code. It just seems silly without #10509. 
* [x] Closes #1963
* [x] Closes #3774 
* [x] Closes #12939
* [x] Does the bulk of the #3327 work, but I'm going to leave that open since that's become my megathread for everything related to theming.
* [x] I work here
* [x] Tests added/passed
* [ ] Requires documentation to be updated - **SURE DOES**

## Detailed Description of the Pull Request / Additional comments

### --> GO READ #12530 <--

Seriously. 

These themes aren't customizable in the SUI currently. You can change the active theme, and the UI will show all of the defined themes, but they're not editable there. 

They don't layer. You'll need to define your own themes.

Thay can't come from fragments. This is a really cool future idea, but not implemented in this v0.

The sub objects have some gnarly macros to generate a lot of the serialization code for you. 

### TODOs

* [x] I still have yet to establish what the accent color algorithm is. This might be proprietary and require a ThemeHelpers workaround.
* [x] Make sure `terminalBackground` & the SUI result in something sensible
* [x] Make sure runtime BG changes work with `terminalBackground`. One time, they didn't. `printf "\x1b]11;rgb:ff/00/ff\x07"`
* [x] Acrylic Terminal BG's look weird, like, the opacity is always 50% or something. And the tab row looks all wrong then.

## Validation Steps Performed

This is the blob I've been testing with:
<details>

```jsonc
    // "useAcrylicInTabRow": true,
    "theme": "my dark",
    // "theme": "Edge",
    "theme": "orangey",
    "theme": "WHITE",
    // "theme": "terminal",
    "themes": [
        {
            "name": "my dark",
            "window": {
                "applicationTheme": "dark",
                "useMica": true,
            },
            "tabRow": {
                "background": "#00000000",
            },
        },
        {
            "name": "Edge",
            "tabRow": { "background": "accent" },
            "window": { "applicationTheme": "system" }
        },
        {
            "name": "orangey",

            "window": {
                "applicationTheme": "light",
                "useMica": true,
            },
            "tabRow": {
                "background": "#ff8800",
            },
        },
        {
            "name": "WHITE",
            "window": {
                "applicationTheme": "dark",
                "useMica": true,
            },
            "tabRow": {
                "background": "#FFFFFF",
            },
        },
        {
            "name": "terminal",

            "window": {
                "applicationTheme": "dark",
                "useMica": false,
            },
            "tabRow": {
                "background": "terminalBackground",
            },
        },
    ]
```
    
</details>
This commit is contained in:
Mike Griese 2022-07-07 06:54:54 -05:00 committed by GitHub
parent 16028dee8b
commit 07d58a800c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1398 additions and 136 deletions

View File

@ -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

View File

@ -41,6 +41,7 @@
<ClCompile Include="DeserializationTests.cpp" />
<ClCompile Include="SerializationTests.cpp" />
<ClCompile Include="TerminalSettingsTests.cpp" />
<ClCompile Include="ThemeTests.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>

View File

@ -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 <defaults.h>
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<CascadiaSettings>(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<CascadiaSettings>(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;
}
}
}

View File

@ -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<winrt::Windows::UI::Xaml::FrameworkElement>() };
while (element)
{
element.RequestedTheme(theme);
element.RequestedTheme(requestedTheme);
element = element.Parent().try_as<winrt::Windows::UI::Xaml::FrameworkElement>();
}
} };
@ -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();
}
}

View File

@ -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<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> 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);

View File

@ -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<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();

View File

@ -3,8 +3,6 @@
#pragma once
#include "winrt/Microsoft.UI.Xaml.Controls.h"
#include "HighlightedTextSegment.g.h"
#include "HighlightedText.g.h"

View File

@ -242,6 +242,10 @@
<value>&#x2022; Found a keybinding that was missing a required parameter value. This keybinding will be ignored.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="UnknownTheme" xml:space="preserve">
<value>&#x2022; The specified "theme" was not found in the list of themes. Temporarily falling back to the default value.</value>
<comment>{Locked="&#x2022;"} This glyph is a bullet, used in a bulleted list.</comment>
</data>
<data name="LegacyGlobalsProperty" xml:space="preserve">
<value>The "globals" property is deprecated - your settings might need updating. </value>
<comment>{Locked="\"globals\""} </comment>

View File

@ -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)
{

View File

@ -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<winrt::Windows::UI::Xaml::ResourceDictionary>();
if (themeDictionary.HasKey(tabViewBackgroundKey))
{
const auto backgroundSolidBrush = themeDictionary.Lookup(tabViewBackgroundKey).as<Media::SolidColorBrush>();
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:
// - <none>
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:
// - <none>
// Return Value:
// - <none>
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
// - <none>
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<winrt::Windows::UI::Xaml::FrameworkElement>();
}
}
@ -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<winrt::hstring>(dictionaryKey) !=
winrt::unbox_value<winrt::hstring>(requestedThemeKey))
{
// No? skip it.
continue;
}
// Look for the requested resource in this dict.
const auto themeDictionary = dict.as<winrt::Windows::UI::Xaml::ResourceDictionary>();
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<Media::SolidColorBrush>();
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<TerminalApp::SettingsTab>())
{
return settingsTab.Content().try_as<Settings::Editor::MainPage>().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();
}
}

View File

@ -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<TerminalPage>; // for Xaml to bind events
std::optional<HWND> _hostingHwnd;
@ -196,6 +199,8 @@ namespace winrt::TerminalApp::implementation
std::optional<int> _rearrangeFrom{};
std::optional<int> _rearrangeTo{};
bool _removing{ false };
bool _activated{ false };
bool _visible{ true };
std::vector<std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>> _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

View File

@ -45,6 +45,9 @@ namespace TerminalApp
TaskbarState TaskbarState{ get; };
Windows.UI.Xaml.Media.Brush TitlebarBrush { get; };
void WindowActivated(Boolean activated);
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;

View File

@ -13,7 +13,6 @@
VerticalAlignment="Top"
d:DesignHeight="36"
d:DesignWidth="400"
Background="{ThemeResource TabViewBackground}"
SizeChanged="Root_SizeChanged"
mc:Ignorable="d">

View File

@ -568,6 +568,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
RootGrid().Background(solidColor);
}
BackgroundBrush(RootGrid().Background());
}
// Method Description:
@ -613,6 +615,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
solidColor.Color(bg);
}
BackgroundBrush(RootGrid().Background());
// Don't use the normal BackgroundBrush() Observable Property setter
// here. (e.g. `BackgroundBrush()`). The one from the macro will
// automatically ignore changes where the value doesn't _actually_
// change. In our case, most of the time when changing the colors of the
// background, the _Brush_ itself doesn't change, we simply change the
// Color() of the brush. This results in the event not getting bubbled
// up.
//
// Firing it manually makes sure it does.
_BackgroundBrush = RootGrid().Background();
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"BackgroundBrush" });
}
// Method Description:

View File

@ -130,6 +130,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void AdjustOpacity(const double opacity, const bool relative);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
// -------------------------------- WinRT Events ---------------------------------
// clang-format off
WINRT_CALLBACK(FontSizeChanged, Control::FontSizeChangedEventArgs);
@ -153,6 +155,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TYPED_EVENT(WarningBell, IInspectable, IInspectable);
// clang-format on
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers

View File

@ -14,7 +14,8 @@ namespace Microsoft.Terminal.Control
[default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl,
IDirectKeyListener,
IMouseWheelListener,
ICoreState
ICoreState,
Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TermControl(IControlSettings settings,
IControlAppearance unfocusedAppearance,
@ -90,5 +91,6 @@ namespace Microsoft.Terminal.Control
// opacity set by the settings should call this instead.
Double BackgroundOpacity { get; };
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}
}

View File

@ -41,17 +41,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
L"zh-Hant",
};
GlobalAppearance::GlobalAppearance()
constexpr std::wstring_view systemThemeName{ L"system" };
constexpr std::wstring_view darkThemeName{ L"dark" };
constexpr std::wstring_view lightThemeName{ L"light" };
GlobalAppearance::GlobalAppearance() :
_ThemeList{ single_threaded_observable_vector<Model::Theme>() }
{
InitializeComponent();
INITIALIZE_BINDABLE_ENUM_SETTING(Theme, ElementTheme, winrt::Windows::UI::Xaml::ElementTheme, L"Globals_Theme", L"Content");
INITIALIZE_BINDABLE_ENUM_SETTING(TabWidthMode, TabViewWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, L"Globals_TabWidthMode", L"Content");
}
void GlobalAppearance::OnNavigatedTo(const NavigationEventArgs& e)
{
_State = e.Parameter().as<Editor::GlobalAppearancePageNavigationState>();
_UpdateThemeList();
}
winrt::hstring GlobalAppearance::LanguageDisplayConverter(const winrt::hstring& tag)
@ -195,4 +200,61 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
// Function Description:
// - Updates the list of all themes available to choose from.
void GlobalAppearance::_UpdateThemeList()
{
// Surprisingly, though this is called every time we navigate to the page,
// the list does not keep growing on each navigation.
const auto& ThemeMap{ _State.Globals().Themes() };
for (const auto& pair : ThemeMap)
{
_ThemeList.Append(pair.Value());
}
}
winrt::Windows::Foundation::IInspectable GlobalAppearance::CurrentTheme()
{
return _State.Globals().CurrentTheme();
}
// Get the name out of the newly selected item, stash that as the Theme name
// set for the globals. That controls which theme is actually the current
// theme.
void GlobalAppearance::CurrentTheme(const winrt::Windows::Foundation::IInspectable& tag)
{
if (const auto& theme{ tag.try_as<Model::Theme>() })
{
_State.Globals().Theme(theme.Name());
}
}
// Method Description:
// - Convert the names of the inbox themes to some more descriptive,
// well-known values. If the passed in theme isn't an inbox one, then just
// return its set Name.
// - "light" becomes "Light"
// - "dark" becomes "Dark"
// - "system" becomes "Use Windows theme"
// - These values are all localized based on the app language.
// Arguments:
// - theme: the theme to get the display name for.
// Return Value:
// - the potentially localized name to use for this Theme.
winrt::hstring GlobalAppearance::ThemeNameConverter(const Model::Theme& theme)
{
if (theme.Name() == darkThemeName)
{
return RS_(L"Globals_ThemeDark/Content");
}
else if (theme.Name() == lightThemeName)
{
return RS_(L"Globals_ThemeLight/Content");
}
else if (theme.Name() == systemThemeName)
{
return RS_(L"Globals_ThemeSystem/Content");
}
return theme.Name();
}
}

View File

@ -26,9 +26,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e);
WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr);
GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals().Theme);
GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals().TabWidthMode);
WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector<Model::Theme>, ThemeList, nullptr);
public:
// LanguageDisplayConverter maps the given BCP 47 tag to a localized string.
// For instance "en-US" produces "English (United States)", while "de-DE" produces
@ -40,9 +41,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
winrt::Windows::Foundation::IInspectable CurrentLanguage();
void CurrentLanguage(const winrt::Windows::Foundation::IInspectable& tag);
winrt::Windows::Foundation::IInspectable CurrentTheme();
void CurrentTheme(const winrt::Windows::Foundation::IInspectable& tag);
static winrt::hstring ThemeNameConverter(const Model::Theme& theme);
private:
winrt::Windows::Foundation::Collections::IObservableVector<winrt::hstring> _languageList;
winrt::Windows::Foundation::IInspectable _currentLanguage;
winrt::Windows::Foundation::IInspectable _currentTheme;
void _UpdateThemeList();
};
}

View File

@ -21,7 +21,8 @@ namespace Microsoft.Terminal.Settings.Editor
IInspectable CurrentLanguage;
IInspectable CurrentTheme;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> ThemeList { get; };
static String ThemeNameConverter(Microsoft.Terminal.Settings.Model.Theme theme);
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Model.Theme> ThemeList { get; };
IInspectable CurrentTabWidthMode;
Windows.Foundation.Collections.IObservableVector<Microsoft.Terminal.Settings.Editor.EnumEntry> TabWidthModeList { get; };

View File

@ -8,6 +8,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:Microsoft.Terminal.Settings.Editor"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="using:Microsoft.Terminal.Settings.Model"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">
@ -42,10 +43,15 @@
<!-- Theme -->
<local:SettingContainer x:Uid="Globals_Theme">
<ComboBox AutomationProperties.AccessibilityView="Content"
ItemTemplate="{StaticResource EnumComboBoxTemplate}"
ItemsSource="{x:Bind ThemeList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentTheme, Mode=TwoWay}"
Style="{StaticResource ComboBoxSettingStyle}" />
Style="{StaticResource ComboBoxSettingStyle}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="model:Theme">
<TextBlock Text="{x:Bind local:GlobalAppearance.ThemeNameConverter((model:Theme)), Mode=OneWay}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</local:SettingContainer>
<!-- Always show tabs -->

View File

@ -609,4 +609,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
return _breadcrumbs;
}
winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush()
{
return SettingsNav().Background();
}
}

View File

@ -40,6 +40,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
bool TryPropagateHostingWindow(IInspectable object) noexcept;
uint64_t GetHostingWindow() const noexcept;
winrt::Windows::UI::Xaml::Media::Brush BackgroundBrush();
Windows::Foundation::Collections::IObservableVector<IInspectable> Breadcrumbs() noexcept;
TYPED_EVENT(OpenJson, Windows::Foundation::IInspectable, Model::SettingsTarget);

View File

@ -39,5 +39,7 @@ namespace Microsoft.Terminal.Settings.Editor
void SetHostingWindow(UInt64 window);
Windows.Foundation.Collections.IObservableVector<IInspectable> Breadcrumbs { get; };
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
}
}

View File

@ -409,6 +409,7 @@ void CascadiaSettings::_validateSettings()
_validateMediaResources();
_validateKeybindings();
_validateColorSchemesInCommands();
_validateThemeExists();
}
// Method Description:
@ -1152,3 +1153,14 @@ void CascadiaSettings::ExportFile(winrt::hstring path, winrt::hstring content)
}
CATCH_LOG();
}
void CascadiaSettings::_validateThemeExists()
{
if (!_globals->Themes().HasKey(_globals->Theme()))
{
_warnings.Append(SettingsLoadWarnings::UnknownTheme);
// safely fall back to system as the theme.
_globals->Theme(L"system");
}
}

View File

@ -73,6 +73,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
const Json::Value& colorSchemes;
const Json::Value& profileDefaults;
const Json::Value& profilesList;
const Json::Value& themes;
};
static std::pair<size_t, size_t> _lineAndColumnFromPosition(const std::string_view& string, const size_t position);
@ -158,6 +159,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
void _validateKeybindings() const;
void _validateColorSchemesInCommands() const;
bool _hasInvalidColorScheme(const Model::Command& command) const;
void _validateThemeExists();
// user settings
winrt::com_ptr<implementation::GlobalAppSettings> _globals = winrt::make_self<implementation::GlobalAppSettings>();

View File

@ -39,6 +39,7 @@ static constexpr std::string_view ProfilesKey{ "profiles" };
static constexpr std::string_view DefaultSettingsKey{ "defaults" };
static constexpr std::string_view ProfilesListKey{ "list" };
static constexpr std::string_view SchemesKey{ "schemes" };
static constexpr std::string_view ThemesKey{ "themes" };
static constexpr std::wstring_view jsonExtension{ L".json" };
static constexpr std::wstring_view FragmentsSubDirectory{ L"\\Fragments" };
@ -531,6 +532,25 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source
}
}
{
for (const auto& themeJson : json.themes)
{
if (const auto theme = Theme::FromJson(themeJson))
{
if (origin != OriginTag::InBox &&
(theme->Name() == L"system" || theme->Name() == L"light" || theme->Name() == L"dark"))
{
// If the theme didn't come from the in-box themes, and its
// name was one of the reserved names, then just ignore it.
// Themes don't support layering - we don't want the user
// versions of these themes overriding the built-in ones.
continue;
}
settings.globals->AddTheme(*theme);
}
}
}
{
settings.baseLayerProfile = Profile::FromJson(json.profileDefaults);
// Remove the `guid` member from the default settings.
@ -629,10 +649,11 @@ SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view&
{
auto root = content.empty() ? Json::Value{ Json::ValueType::objectValue } : _parseJSON(content);
const auto& colorSchemes = _getJSONValue(root, SchemesKey);
const auto& themes = _getJSONValue(root, ThemesKey);
const auto& profilesObject = _getJSONValue(root, ProfilesKey);
const auto& profileDefaults = _getJSONValue(profilesObject, DefaultSettingsKey);
const auto& profilesList = profilesObject.isArray() ? profilesObject : _getJSONValue(profilesObject, ProfilesListKey);
return JsonSettings{ std::move(root), colorSchemes, profileDefaults, profilesList };
return JsonSettings{ std::move(root), colorSchemes, profileDefaults, profilesList, themes };
}
// Just a common helper function between _parse and _parseFragment.
@ -1092,6 +1113,20 @@ Json::Value CascadiaSettings::ToJson() const
}
json[JsonKey(SchemesKey)] = schemes;
Json::Value themes{ Json::ValueType::arrayValue };
for (const auto& entry : _globals->Themes())
{
// Ignore the built in themes, when serializing the themes back out. We
// don't want to re-include them in the user settings file.
const auto theme{ winrt::get_self<Theme>(entry.Value()) };
if (theme->Name() == L"system" || theme->Name() == L"light" || theme->Name() == L"dark")
{
continue;
}
themes.append(theme->ToJson());
}
json[JsonKey(ThemesKey)] = themes;
return json;
}

View File

@ -17,6 +17,7 @@ using namespace winrt::Microsoft::UI::Xaml::Controls;
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
static constexpr std::string_view ActionsKey{ "actions" };
static constexpr std::string_view ThemeKey{ "theme" };
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
static constexpr std::string_view LegacyUseTabSwitcherModeKey{ "useTabSwitcher" };
@ -39,6 +40,14 @@ void GlobalAppSettings::_FinalizeInheritance()
_colorSchemes.Insert(k, v);
}
}
for (const auto& [k, v] : parent->_themes)
{
if (!_themes.HasKey(k))
{
_themes.Insert(k, v);
}
}
}
}
@ -65,6 +74,14 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_colorSchemes.Insert(kv.Key(), *schemeImpl->Copy());
}
}
if (_themes)
{
for (auto kv : _themes)
{
const auto themeImpl{ winrt::get_self<implementation::Theme>(kv.Value()) };
globals->_themes.Insert(kv.Key(), *themeImpl->Copy());
}
}
for (const auto& parent : _parents)
{
@ -192,3 +209,18 @@ Json::Value GlobalAppSettings::ToJson() const
json[JsonKey(ActionsKey)] = _actionMap->ToJson();
return json;
}
winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept
{
return _themes.TryLookup(Theme());
}
void GlobalAppSettings::AddTheme(const Model::Theme& theme)
{
_themes.Insert(theme.Name(), theme);
}
winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microsoft::Terminal::Settings::Model::Theme> GlobalAppSettings::Themes() noexcept
{
return _themes.GetView();
}

View File

@ -22,6 +22,7 @@ Author(s):
#include "ActionMap.h"
#include "Command.h"
#include "ColorScheme.h"
#include "Theme.h"
// fwdecl unittest classes
namespace SettingsModelLocalTests
@ -62,6 +63,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
DisableAnimations(!invertedDisableAnimationsValue);
}
Windows::Foundation::Collections::IMapView<hstring, Model::Theme> Themes() noexcept;
void AddTheme(const Model::Theme& theme);
Model::Theme CurrentTheme() noexcept;
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
@ -78,7 +83,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::guid _defaultProfile;
winrt::com_ptr<implementation::ActionMap> _actionMap{ winrt::make_self<implementation::ActionMap>() };
std::vector<SettingsLoadWarnings> _keybindingsWarnings;
Windows::Foundation::Collections::IMap<winrt::hstring, Model::ColorScheme> _colorSchemes{ winrt::single_threaded_map<winrt::hstring, Model::ColorScheme>() };
Windows::Foundation::Collections::IMap<winrt::hstring, Model::Theme> _themes{ winrt::single_threaded_map<winrt::hstring, Model::Theme>() };
};
}

View File

@ -3,6 +3,7 @@
#include "IInheritable.idl.h"
import "Theme.idl";
import "ColorScheme.idl";
import "ActionMap.idl";
@ -54,7 +55,6 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, ShowTitleInTitlebar);
INHERITABLE_SETTING(Boolean, ConfirmCloseAllTabs);
INHERITABLE_SETTING(String, Language);
INHERITABLE_SETTING(Windows.UI.Xaml.ElementTheme, Theme);
INHERITABLE_SETTING(Microsoft.UI.Xaml.Controls.TabViewWidthMode, TabWidthMode);
INHERITABLE_SETTING(Boolean, UseAcrylicInTabRow);
INHERITABLE_SETTING(Boolean, ShowTabsInTitlebar);
@ -94,5 +94,10 @@ namespace Microsoft.Terminal.Settings.Model
void RemoveColorScheme(String schemeName);
ActionMap ActionMap { get; };
Windows.Foundation.Collections.IMapView<String, Theme> Themes();
void AddTheme(Theme theme);
INHERITABLE_SETTING(String, Theme);
Theme CurrentTheme { get; };
}
}

View File

@ -33,8 +33,8 @@ Author(s):
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
X(hstring, Theme, "theme") \
X(hstring, Language, "language") \
X(winrt::Windows::UI::Xaml::ElementTheme, Theme, "theme", winrt::Windows::UI::Xaml::ElementTheme::Default) \
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
@ -114,3 +114,14 @@ Author(s):
// Intentionally omitted Appearance settings:
// * ForegroundKey, BackgroundKey, SelectionBackgroundKey, CursorColorKey: all optional colors
// * Opacity: needs special parsing
#define MTSM_THEME_SETTINGS(X) \
X(winrt::Microsoft::Terminal::Settings::Model::WindowTheme, Window, "window", nullptr) \
X(winrt::Microsoft::Terminal::Settings::Model::TabRowTheme, TabRow, "tabRow", nullptr)
#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)

View File

@ -48,6 +48,9 @@
<ClInclude Include="ColorScheme.h">
<DependentUpon>ColorScheme.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Theme.h">
<DependentUpon>Theme.idl</DependentUpon>
</ClInclude>
<ClInclude Include="Command.h">
<DependentUpon>Command.idl</DependentUpon>
</ClInclude>
@ -128,6 +131,9 @@
<ClCompile Include="ColorScheme.cpp">
<DependentUpon>ColorScheme.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Theme.cpp">
<DependentUpon>Theme.idl</DependentUpon>
</ClCompile>
<ClCompile Include="Command.cpp">
<DependentUpon>Command.idl</DependentUpon>
</ClCompile>
@ -173,6 +179,7 @@
<Midl Include="ApplicationState.idl" />
<Midl Include="CascadiaSettings.idl" />
<Midl Include="ColorScheme.idl" />
<Midl Include="Theme.idl" />
<Midl Include="Command.idl" />
<Midl Include="DefaultTerminal.idl" />
<Midl Include="GlobalAppSettings.idl" />

View File

@ -547,6 +547,79 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::InfoBarMessage)
};
};
template<>
struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<winrt::Microsoft::Terminal::Settings::Model::ThemeColor>
{
winrt::Microsoft::Terminal::Settings::Model::ThemeColor FromJson(const Json::Value& json)
{
if (json == Json::Value::null)
{
return nullptr;
}
const auto string{ Detail::GetStringView(json) };
if (string == "accent")
{
return winrt::Microsoft::Terminal::Settings::Model::ThemeColor::FromAccent();
}
else if (string == "terminalBackground")
{
return winrt::Microsoft::Terminal::Settings::Model::ThemeColor::FromTerminalBackground();
}
else
{
return winrt::Microsoft::Terminal::Settings::Model::ThemeColor::FromColor(::Microsoft::Console::Utils::ColorFromHexString(string));
}
}
bool CanConvert(const Json::Value& json)
{
if (json == Json::Value::null)
{
return true;
}
if (!json.isString())
{
return false;
}
const auto string{ Detail::GetStringView(json) };
const auto isColorSpec = (string.length() == 9 || string.length() == 7 || string.length() == 4) && string.front() == '#';
const auto isAccent = string == "accent";
const auto isTerminalBackground = string == "terminalBackground";
return isColorSpec || isAccent || isTerminalBackground;
}
Json::Value ToJson(const winrt::Microsoft::Terminal::Settings::Model::ThemeColor& val)
{
if (val == nullptr)
{
return Json::Value::null;
}
switch (val.ColorType())
{
case winrt::Microsoft::Terminal::Settings::Model::ThemeColorType::Accent:
{
return "accent";
}
case winrt::Microsoft::Terminal::Settings::Model::ThemeColorType::Color:
{
return til::u16u8(til::color{ val.Color() }.ToHexString(false));
}
case winrt::Microsoft::Terminal::Settings::Model::ThemeColorType::TerminalBackground:
{
return "terminalBackground";
}
}
return til::u16u8(til::color{ val.Color() }.ToHexString(false));
}
std::string TypeDescription() const
{
return "ThemeColor (#rrggbb, #rgb, #aarrggbb, accent, terminalBackground)";
}
};
// Possible ScrollToMarkDirection values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Control::ScrollToMarkDirection)
{

View File

@ -21,6 +21,7 @@ namespace Microsoft.Terminal.Settings.Model
InvalidSplitSize,
FailedToParseStartupActions,
FailedToParseSubCommands,
UnknownTheme,
WARNINGS_SIZE // IMPORTANT: This MUST be the last value in this enum. It's an unused placeholder.
};

View File

@ -0,0 +1,331 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Theme.h"
#include "../../types/inc/Utils.hpp"
#include "../../types/inc/colorTable.hpp"
#include "Utils.h"
#include "JsonUtils.h"
#include "TerminalSettingsSerializationHelpers.h"
#include "ThemeColor.g.cpp"
#include "WindowTheme.g.cpp"
#include "TabRowTheme.g.cpp"
#include "Theme.g.cpp"
using namespace ::Microsoft::Console;
using namespace Microsoft::Terminal::Settings::Model;
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
using namespace winrt::Windows::UI;
namespace winrt
{
namespace MUX = Microsoft::UI::Xaml;
namespace WUX = Windows::UI::Xaml;
}
static constexpr std::string_view NameKey{ "name" };
static constexpr wchar_t RegKeyDwm[] = L"Software\\Microsoft\\Windows\\DWM";
static constexpr wchar_t RegKeyAccentColor[] = L"AccentColor";
winrt::Microsoft::Terminal::Settings::Model::ThemeColor ThemeColor::FromColor(const winrt::Microsoft::Terminal::Core::Color& coreColor) noexcept
{
auto result = winrt::make_self<implementation::ThemeColor>();
result->_Color = coreColor;
result->_ColorType = ThemeColorType::Color;
return *result;
}
winrt::Microsoft::Terminal::Settings::Model::ThemeColor ThemeColor::FromAccent() noexcept
{
auto result = winrt::make_self<implementation::ThemeColor>();
result->_ColorType = ThemeColorType::Accent;
return *result;
}
winrt::Microsoft::Terminal::Settings::Model::ThemeColor ThemeColor::FromTerminalBackground() noexcept
{
auto result = winrt::make_self<implementation::ThemeColor>();
result->_ColorType = ThemeColorType::TerminalBackground;
return *result;
}
static wil::unique_hkey openDwmRegKey()
{
HKEY hKey{ nullptr };
if (RegOpenKeyEx(HKEY_CURRENT_USER, RegKeyDwm, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
{
return wil::unique_hkey{ hKey };
}
return nullptr;
}
static DWORD readDwmSubValue(const wil::unique_hkey& dwmRootKey, const wchar_t* key)
{
DWORD val{ 0 };
DWORD size{ sizeof(val) };
LOG_IF_FAILED(RegQueryValueExW(dwmRootKey.get(), key, nullptr, nullptr, reinterpret_cast<BYTE*>(&val), &size));
return val;
}
static til::color _getAccentColorForTitlebar()
{
// The color used for the "Use Accent color in the title bar" in DWM is
// stored in HKCU\Software\Microsoft\Windows\DWM\AccentColor.
return til::color{ static_cast<COLORREF>(readDwmSubValue(openDwmRegKey(), RegKeyAccentColor)) }.with_alpha(255);
}
til::color ThemeColor::ColorFromBrush(const winrt::WUX::Media::Brush& brush)
{
if (auto acrylic = brush.try_as<winrt::WUX::Media::AcrylicBrush>())
{
return acrylic.TintColor();
}
else if (auto solidColor = brush.try_as<winrt::WUX::Media::SolidColorBrush>())
{
return solidColor.Color();
}
return {};
}
winrt::WUX::Media::Brush ThemeColor::Evaluate(const winrt::WUX::ResourceDictionary& res,
const winrt::WUX::Media::Brush& terminalBackground,
const bool forTitlebar)
{
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:
{
til::color accentColor = forTitlebar ?
_getAccentColorForTitlebar() :
til::color{ winrt::unbox_value<winrt::Windows::UI::Color>(res.Lookup(accentColorKey)) };
const winrt::WUX::Media::SolidColorBrush accentBrush{ accentColor };
// _getAccentColorForTitlebar should have already filled the alpha
// channel in with 255
return accentBrush;
}
case ThemeColorType::Color:
{
return winrt::WUX::Media::SolidColorBrush{ forTitlebar ?
Color().with_alpha(255) :
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>())
{
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>())
{
winrt::WUX::Media::SolidColorBrush newBrush{};
newBrush.Color(til::color{ solidColor.Color() }.with_alpha(255));
return newBrush;
}
}
return terminalBackground;
}
}
return nullptr;
}
#define THEME_SETTINGS_FROM_JSON(type, name, jsonKey, ...) \
{ \
std::optional<type> _val; \
_val = JsonUtils::GetValueForKey<std::optional<type>>(json, jsonKey); \
if (_val) \
result->name(*_val); \
}
#define THEME_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
JsonUtils::SetValueForKey(json, jsonKey, val.name());
#define THEME_OBJECT_CONVERTER(nameSpace, name, macro) \
template<> \
struct ::Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait<nameSpace::name> \
{ \
nameSpace::name FromJson(const Json::Value& json) \
{ \
if (json == Json::Value::null) \
return nullptr; \
auto result = winrt::make_self<nameSpace::implementation::name>(); \
macro(THEME_SETTINGS_FROM_JSON); \
return *result; \
} \
\
bool CanConvert(const Json::Value& json) \
{ \
return json.isObject(); \
} \
\
Json::Value ToJson(const nameSpace::name& val) \
{ \
if (val == nullptr) \
return Json::Value::null; \
Json::Value json{ Json::ValueType::objectValue }; \
macro(THEME_SETTINGS_TO_JSON); \
return json; \
} \
\
std::string TypeDescription() const \
{ \
return "name (You should never see this)"; \
} \
};
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, WindowTheme, MTSM_THEME_WINDOW_SETTINGS);
THEME_OBJECT_CONVERTER(winrt::Microsoft::Terminal::Settings::Model, TabRowTheme, MTSM_THEME_TABROW_SETTINGS);
#undef THEME_SETTINGS_FROM_JSON
#undef THEME_SETTINGS_TO_JSON
#undef THEME_OBJECT_CONVERTER
Theme::Theme() noexcept :
Theme{ winrt::WUX::ElementTheme::Default }
{
}
Theme::Theme(const winrt::WUX::ElementTheme& requestedTheme) noexcept
{
auto window{ winrt::make_self<implementation::WindowTheme>() };
window->RequestedTheme(requestedTheme);
_Window = *window;
}
winrt::com_ptr<Theme> Theme::Copy() const
{
auto theme{ winrt::make_self<Theme>() };
theme->_Name = _Name;
if (_Window)
{
theme->_Window = *winrt::get_self<implementation::WindowTheme>(_Window)->Copy();
}
if (_TabRow)
{
theme->_TabRow = *winrt::get_self<implementation::TabRowTheme>(_TabRow)->Copy();
}
return theme;
}
// Method Description:
// - Create a new instance of this class from a serialized JsonObject.
// Arguments:
// - json: an object which should be a serialization of a ColorScheme object.
// Return Value:
// - Returns nullptr for invalid JSON.
winrt::com_ptr<Theme> Theme::FromJson(const Json::Value& json)
{
auto result = winrt::make_self<Theme>();
if (json.isString())
{
// We found a string, not an object. Just secretly promote that string
// to a theme object with just the applicationTheme set from that value.
JsonUtils::GetValue(json, result->_Name);
winrt::WUX::ElementTheme requestedTheme{ winrt::WUX::ElementTheme::Default };
JsonUtils::GetValue(json, requestedTheme);
auto window{ winrt::make_self<implementation::WindowTheme>() };
window->RequestedTheme(requestedTheme);
result->_Window = *window;
return result;
}
JsonUtils::GetValueForKey(json, NameKey, result->_Name);
// This will use each of the ConversionTrait's from above to quickly parse the sub-objects
#define THEME_SETTINGS_LAYER_JSON(type, name, jsonKey, ...) \
{ \
std::optional<type> _val; \
_val = JsonUtils::GetValueForKey<std::optional<type>>(json, jsonKey); \
if (_val) \
result->_##name = *_val; \
else \
result->_##name = nullptr; \
}
MTSM_THEME_SETTINGS(THEME_SETTINGS_LAYER_JSON)
#undef THEME_SETTINGS_LAYER_JSON
return result;
}
// Method Description:
// - Create a new serialized JsonObject from an instance of this class
// Arguments:
// - <none>
// Return Value:
// - the JsonObject representing this instance
Json::Value Theme::ToJson() const
{
Json::Value json{ Json::ValueType::objectValue };
JsonUtils::SetValueForKey(json, NameKey, _Name);
// Don't serialize anything if the object is null.
#define THEME_SETTINGS_TO_JSON(type, name, jsonKey, ...) \
if (_##name) \
JsonUtils::SetValueForKey(json, jsonKey, _##name);
MTSM_THEME_SETTINGS(THEME_SETTINGS_TO_JSON)
#undef THEME_SETTINGS_TO_JSON
return json;
}
winrt::hstring Theme::ToString()
{
return Name();
}
// Method Description:
// - A helper for retrieving the RequestedTheme out of the window property.
// There's a bunch of places throughout the app that all ask for the
// RequestedTheme, this saves some hassle. If there wasn't a `window` defined
// for this theme, this'll quickly just return `system`, to use the OS theme.
// Return Value:
// - the set applicationTheme for this Theme, otherwise the system theme.
winrt::WUX::ElementTheme Theme::RequestedTheme() const noexcept
{
return _Window ? _Window.RequestedTheme() : winrt::WUX::ElementTheme::Default;
}

View File

@ -0,0 +1,107 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- Theme.hpp
Abstract:
- A Theme represents a collection of settings which control the appearance of
the Terminal window itself. Things like the color of the titlebar, the style
of the tabs.
Author(s):
- Mike Griese - March 2022
--*/
#pragma once
#include "MTSMSettings.h"
#include "ThemeColor.g.h"
#include "WindowTheme.g.h"
#include "TabRowTheme.g.h"
#include "Theme.g.h"
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct ThemeColor : ThemeColorT<ThemeColor>
{
public:
ThemeColor() noexcept = default;
static winrt::Microsoft::Terminal::Settings::Model::ThemeColor FromColor(const winrt::Microsoft::Terminal::Core::Color& coreColor) noexcept;
static winrt::Microsoft::Terminal::Settings::Model::ThemeColor FromAccent() noexcept;
static winrt::Microsoft::Terminal::Settings::Model::ThemeColor FromTerminalBackground() noexcept;
static til::color ColorFromBrush(const winrt::Windows::UI::Xaml::Media::Brush& brush);
winrt::Windows::UI::Xaml::Media::Brush Evaluate(const winrt::Windows::UI::Xaml::ResourceDictionary& res,
const winrt::Windows::UI::Xaml::Media::Brush& terminalBackground,
const bool forTitlebar);
WINRT_PROPERTY(til::color, Color);
WINRT_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::ThemeColorType, ColorType);
};
#define THEME_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
WINRT_PROPERTY(type, name, ##__VA_ARGS__)
#define THEME_SETTINGS_COPY(type, name, jsonKey, ...) \
result->_##name = _##name;
#define COPY_THEME_OBJECT(T, macro) \
winrt::com_ptr<T> Copy() \
{ \
auto result{ winrt::make_self<T>() }; \
macro(THEME_SETTINGS_COPY); \
return result; \
}
struct WindowTheme : WindowThemeT<WindowTheme>
{
MTSM_THEME_WINDOW_SETTINGS(THEME_SETTINGS_INITIALIZE);
public:
COPY_THEME_OBJECT(WindowTheme, MTSM_THEME_WINDOW_SETTINGS);
};
struct TabRowTheme : TabRowThemeT<TabRowTheme>
{
MTSM_THEME_TABROW_SETTINGS(THEME_SETTINGS_INITIALIZE);
public:
COPY_THEME_OBJECT(TabRowTheme, MTSM_THEME_TABROW_SETTINGS);
};
struct Theme : ThemeT<Theme>
{
public:
Theme() noexcept;
Theme(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
com_ptr<Theme> Copy() const;
hstring ToString();
static com_ptr<Theme> FromJson(const Json::Value& json);
void LayerJson(const Json::Value& json);
Json::Value ToJson() const;
winrt::Windows::UI::Xaml::ElementTheme RequestedTheme() const noexcept;
WINRT_PROPERTY(winrt::hstring, Name);
MTSM_THEME_SETTINGS(THEME_SETTINGS_INITIALIZE)
private:
};
#undef THEME_SETTINGS_INITIALIZE
#undef THEME_SETTINGS_COPY
}
namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
{
BASIC_FACTORY(ThemeColor);
BASIC_FACTORY(Theme);
}

View File

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
namespace Microsoft.Terminal.Settings.Model
{
enum ThemeColorType
{
Accent,
Color,
TerminalBackground
};
runtimeclass ThemeColor
{
ThemeColor();
static ThemeColor FromColor(Microsoft.Terminal.Core.Color color);
static ThemeColor FromAccent();
static ThemeColor FromTerminalBackground();
Microsoft.Terminal.Core.Color Color { get; };
ThemeColorType ColorType;
static Microsoft.Terminal.Core.Color ColorFromBrush(Windows.UI.Xaml.Media.Brush brush);
Windows.UI.Xaml.Media.Brush Evaluate(Windows.UI.Xaml.ResourceDictionary res,
Windows.UI.Xaml.Media.Brush terminalBackground,
Boolean forTitlebar);
}
runtimeclass WindowTheme {
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
Boolean UseMica { get; };
}
runtimeclass TabRowTheme {
ThemeColor Background { get; };
}
[default_interface] runtimeclass Theme : Windows.Foundation.IStringable {
Theme();
Theme(Windows.UI.Xaml.ElementTheme requestedTheme);
String Name;
// window.* Namespace
WindowTheme Window { get; };
// tabRow.* Namespace
TabRowTheme TabRow { get; };
// A helper for retrieving the RequestedTheme out of the window property
Windows.UI.Xaml.ElementTheme RequestedTheme { get; };
}
}

View File

@ -280,6 +280,26 @@
"brightWhite": "#EEEEEC"
}
],
"themes": [
{
"name": "light",
"window":{
"applicationTheme": "light"
}
},
{
"name": "dark",
"window":{
"applicationTheme": "dark"
}
},
{
"name": "system",
"window":{
"applicationTheme": "system"
}
}
],
"actions":
[
// Application-level Keys

View File

@ -405,6 +405,11 @@ void AppHost::Initialize()
}
});
// Load bearing: make sure the PropertyChanged handler is added before we
// call Create, so that when the app sets up the titlebar brush, we're
// already prepared to listen for the change notification
_revokers.PropertyChanged = _logic.PropertyChanged(winrt::auto_revoke, { this, &AppHost::_PropertyChangedHandler });
_logic.Create();
_revokers.TitleChanged = _logic.TitleChanged(winrt::auto_revoke, { this, &AppHost::AppTitleChanged });
@ -698,8 +703,12 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta
{
if (_useNonClientArea)
{
(static_cast<NonClientIslandWindow*>(_window.get()))->SetTitlebarContent(arg);
auto nonClientWindow{ static_cast<NonClientIslandWindow*>(_window.get()) };
nonClientWindow->SetTitlebarContent(arg);
nonClientWindow->SetTitlebarBackground(_logic.TitlebarBrush());
}
_updateTheme();
}
// Method Description:
@ -710,9 +719,9 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta
// - arg: the ElementTheme to use as the new theme for the UI
// Return Value:
// - <none>
void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg)
void AppHost::_UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& /*arg*/)
{
_window->OnApplicationThemeChanged(arg);
_updateTheme();
}
void AppHost::_FocusModeChanged(const winrt::Windows::Foundation::IInspectable&,
@ -902,8 +911,15 @@ void AppHost::_FindTargetWindow(const winrt::Windows::Foundation::IInspectable&
args.ResultTargetWindowName(targetWindow.WindowName());
}
winrt::fire_and_forget AppHost::_WindowActivated()
winrt::fire_and_forget AppHost::_WindowActivated(bool activated)
{
_logic.WindowActivated(activated);
if (!activated)
{
co_return;
}
co_await winrt::resume_background();
if (auto peasant{ _windowManager.CurrentWindow() })
@ -1326,6 +1342,25 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou
}
}
void AppHost::_updateTheme()
{
auto theme = _logic.Theme();
_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));
}
void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
@ -1357,6 +1392,7 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta
}
_window->SetMinimizeToNotificationAreaBehavior(_logic.GetMinimizeToNotificationArea());
_updateTheme();
}
void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&,
@ -1578,3 +1614,13 @@ void AppHost::_CloseRequested(const winrt::Windows::Foundation::IInspectable& /*
const auto pos = _GetWindowLaunchPosition();
_logic.CloseWindow(pos);
}
void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& e)
{
if (e.PropertyName() == L"TitlebarBrush")
{
auto nonClientWindow{ static_cast<NonClientIslandWindow*>(_window.get()) };
nonClientWindow->SetTitlebarBackground(_logic.TitlebarBrush());
}
}

View File

@ -56,7 +56,7 @@ private:
void _RaiseVisualBell(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& arg);
void _WindowMouseWheeled(const til::point coord, const int32_t delta);
winrt::fire_and_forget _WindowActivated();
winrt::fire_and_forget _WindowActivated(bool activated);
void _WindowMoved();
void _DispatchCommandline(winrt::Windows::Foundation::IInspectable sender,
@ -122,6 +122,14 @@ private:
const winrt::Windows::Foundation::IInspectable& args);
void _HideNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
void _updateTheme();
void _PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
std::unique_ptr<NotificationIcon> _notificationIcon;
winrt::event_token _ReAddNotificationIconToken;
winrt::event_token _NotificationIconPressedToken;
@ -165,5 +173,6 @@ private:
winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested;
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
winrt::TerminalApp::AppLogic::PropertyChanged_revoker PropertyChanged;
} _revokers{};
};

View File

@ -439,10 +439,8 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
case WM_ACTIVATE:
{
// wparam = 0 indicates the window was deactivated
if (LOWORD(wparam) != 0)
{
_WindowActivatedHandlers();
}
const bool activated = LOWORD(wparam) != 0;
_WindowActivatedHandlers(activated);
break;
}

View File

@ -67,7 +67,7 @@ public:
WINRT_CALLBACK(DragRegionClicked, winrt::delegate<>);
WINRT_CALLBACK(WindowCloseButtonClicked, winrt::delegate<>);
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
WINRT_CALLBACK(WindowActivated, winrt::delegate<void()>);
WINRT_CALLBACK(WindowActivated, winrt::delegate<void(bool)>);
WINRT_CALLBACK(HotkeyPressed, winrt::delegate<void(long)>);
WINRT_CALLBACK(NotifyNotificationIconPressed, winrt::delegate<void()>);
WINRT_CALLBACK(NotifyWindowHidden, winrt::delegate<void()>);

View File

@ -1129,3 +1129,8 @@ bool NonClientIslandWindow::_IsTitlebarVisible() const
{
return !(_fullscreen || _borderless);
}
void NonClientIslandWindow::SetTitlebarBackground(winrt::Windows::UI::Xaml::Media::Brush brush)
{
_titlebar.Background(brush);
}

View File

@ -47,6 +47,8 @@ public:
void SetTitlebarContent(winrt::Windows::UI::Xaml::UIElement content);
void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) override;
void SetTitlebarBackground(winrt::Windows::UI::Xaml::Media::Brush brush);
private:
std::optional<til::point> _oldIslandPos;

View File

@ -64,6 +64,7 @@ Abstract:
#include <winrt/Windows.UI.Core.h>
#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Controls.Primitives.h>
#include <winrt/Windows.UI.Xaml.Data.h>
#include <winrt/Windows.ui.xaml.media.h>
#include <winrt/Windows.ApplicationModel.h>
#include <winrt/Windows.ApplicationModel.Resources.Core.h>

View File

@ -187,14 +187,13 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
wss << L"#" << std::uppercase << std::setfill(L'0') << std::hex;
// Force the compiler to promote from byte to int. Without it, the
// stringstream will try to write the components as chars
wss << std::setw(2) << static_cast<int>(r);
wss << std::setw(2) << static_cast<int>(g);
wss << std::setw(2) << static_cast<int>(b);
if (!omitAlpha)
{
wss << std::setw(2) << static_cast<int>(a);
}
wss << std::setw(2) << static_cast<int>(r);
wss << std::setw(2) << static_cast<int>(g);
wss << std::setw(2) << static_cast<int>(b);
return wss.str();
}
};

View File

@ -87,31 +87,43 @@ std::string Utils::ColorToHexString(const til::color color)
// the correct format, throws E_INVALIDARG
til::color Utils::ColorFromHexString(const std::string_view str)
{
THROW_HR_IF(E_INVALIDARG, str.size() != 7 && str.size() != 4);
THROW_HR_IF(E_INVALIDARG, str.size() != 9 && str.size() != 7 && str.size() != 4);
THROW_HR_IF(E_INVALIDARG, str.at(0) != '#');
std::string rStr;
std::string gStr;
std::string bStr;
std::string aStr;
if (str.size() == 4)
{
rStr = std::string(2, str.at(1));
gStr = std::string(2, str.at(2));
bStr = std::string(2, str.at(3));
aStr = "ff";
}
else
else if (str.size() == 7)
{
rStr = std::string(&str.at(1), 2);
gStr = std::string(&str.at(3), 2);
bStr = std::string(&str.at(5), 2);
aStr = "ff";
}
else if (str.size() == 9)
{
// #rrggbbaa
rStr = std::string(&str.at(1), 2);
gStr = std::string(&str.at(3), 2);
bStr = std::string(&str.at(5), 2);
aStr = std::string(&str.at(7), 2);
}
const auto r = gsl::narrow_cast<BYTE>(std::stoul(rStr, nullptr, 16));
const auto g = gsl::narrow_cast<BYTE>(std::stoul(gStr, nullptr, 16));
const auto b = gsl::narrow_cast<BYTE>(std::stoul(bStr, nullptr, 16));
const auto a = gsl::narrow_cast<BYTE>(std::stoul(aStr, nullptr, 16));
return til::color{ r, g, b };
return til::color{ r, g, b, a };
}
// Routine Description: