diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp index 1935b0a5f7c..cf9503878c8 100644 --- a/src/cascadia/TerminalApp/Profile.cpp +++ b/src/cascadia/TerminalApp/Profile.cpp @@ -12,7 +12,6 @@ using namespace winrt::TerminalApp; using namespace winrt::Windows::Data::Json; using namespace ::Microsoft::Console; - static constexpr std::wstring_view NAME_KEY{ L"name" }; static constexpr std::wstring_view GUID_KEY{ L"guid" }; static constexpr std::wstring_view COLORSCHEME_KEY{ L"colorscheme" }; @@ -36,6 +35,9 @@ static constexpr std::wstring_view CLOSEONEXIT_KEY{ L"closeOnExit" }; static constexpr std::wstring_view PADDING_KEY{ L"padding" }; static constexpr std::wstring_view STARTINGDIRECTORY_KEY{ L"startingDirectory" }; static constexpr std::wstring_view ICON_KEY{ L"icon" }; +static constexpr std::wstring_view BACKGROUNDIMAGE_KEY{ L"backgroundImage" }; +static constexpr std::wstring_view BACKGROUNDIMAGEOPACITY_KEY{ L"backgroundImageOpacity" }; +static constexpr std::wstring_view BACKGROUNDIMAGESTRETCHMODE_KEY{ L"backgroundImageStretchMode" }; // Possible values for Scrollbar state static constexpr std::wstring_view ALWAYS_VISIBLE{ L"visible" }; @@ -48,6 +50,12 @@ static constexpr std::wstring_view CURSORSHAPE_UNDERSCORE{ L"underscore" }; static constexpr std::wstring_view CURSORSHAPE_FILLEDBOX{ L"filledBox" }; static constexpr std::wstring_view CURSORSHAPE_EMPTYBOX{ L"emptyBox" }; +// Possible values for Image Stretch Mode +static constexpr std::wstring_view IMAGESTRETCHMODE_NONE{ L"none" }; +static constexpr std::wstring_view IMAGESTRETCHMODE_FILL{ L"fill" }; +static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORM{ L"uniform" }; +static constexpr std::wstring_view IMAGESTRETCHMODE_UNIFORMTOFILL{ L"uniformToFill" }; + Profile::Profile() : Profile(Utils::CreateGuid()) { @@ -76,7 +84,10 @@ Profile::Profile(const winrt::guid& guid): _scrollbarState{ }, _closeOnExit{ true }, _padding{ DEFAULT_PADDING }, - _icon{ } + _icon{ }, + _backgroundImage{ }, + _backgroundImageOpacity{ }, + _backgroundImageStretchMode{ } { } @@ -173,6 +184,21 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector& terminalSettings.ScrollState(result); } + if (_backgroundImage) + { + terminalSettings.BackgroundImage(_backgroundImage.value()); + } + + if (_backgroundImageOpacity) + { + terminalSettings.BackgroundImageOpacity(_backgroundImageOpacity.value()); + } + + if (_backgroundImageStretchMode) + { + terminalSettings.BackgroundImageStretchMode(_backgroundImageStretchMode.value()); + } + return terminalSettings; } @@ -274,6 +300,25 @@ JsonObject Profile::ToJson() const jsonObject.Insert(ICON_KEY, icon); } + if (_backgroundImage) + { + const auto backgroundImage = JsonValue::CreateStringValue(_backgroundImage.value()); + jsonObject.Insert(BACKGROUNDIMAGE_KEY, backgroundImage); + } + + if (_backgroundImageOpacity) + { + const auto opacity = JsonValue::CreateNumberValue(_backgroundImageOpacity.value()); + jsonObject.Insert(BACKGROUNDIMAGEOPACITY_KEY, opacity); + } + + if (_backgroundImageStretchMode) + { + const auto imageStretchMode = JsonValue::CreateStringValue( + SerializeImageStretchMode(_backgroundImageStretchMode.value())); + jsonObject.Insert(BACKGROUNDIMAGESTRETCHMODE_KEY, imageStretchMode); + } + return jsonObject; } @@ -405,6 +450,19 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json) { result._icon = json.GetNamedString(ICON_KEY); } + if (json.HasKey(BACKGROUNDIMAGE_KEY)) + { + result._backgroundImage = json.GetNamedString(BACKGROUNDIMAGE_KEY); + } + if (json.HasKey(BACKGROUNDIMAGEOPACITY_KEY)) + { + result._backgroundImageOpacity = json.GetNamedNumber(BACKGROUNDIMAGEOPACITY_KEY); + } + if (json.HasKey(BACKGROUNDIMAGESTRETCHMODE_KEY)) + { + const auto stretchMode = json.GetNamedString(BACKGROUNDIMAGESTRETCHMODE_KEY); + result._backgroundImageStretchMode = ParseImageStretchMode(stretchMode.c_str()); + } return result; } @@ -551,6 +609,58 @@ ScrollbarState Profile::ParseScrollbarState(const std::wstring& scrollbarState) } } +// Method Description: +// - Helper function for converting a user-specified image stretch mode +// to the appropriate enum value +// Arguments: +// - The value from the profiles.json file +// Return Value: +// - The corresponding enum value which maps to the string provided by the user +winrt::Windows::UI::Xaml::Media::Stretch Profile::ParseImageStretchMode(const std::wstring& imageStretchMode) +{ + if (imageStretchMode == IMAGESTRETCHMODE_NONE) + { + return winrt::Windows::UI::Xaml::Media::Stretch::None; + } + else if (imageStretchMode == IMAGESTRETCHMODE_FILL) + { + return winrt::Windows::UI::Xaml::Media::Stretch::Fill; + } + else if (imageStretchMode == IMAGESTRETCHMODE_UNIFORM) + { + return winrt::Windows::UI::Xaml::Media::Stretch::Uniform; + } + else // Fall through to default behavior + { + return winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill; + } +} + +// Method Description: +// - Helper function for converting an ImageStretchMode to the +// correct string value. +// Arguments: +// - imageStretchMode: The enum value to convert to a string. +// Return Value: +// - The string value for the given ImageStretchMode +std::wstring_view Profile::SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode) +{ + switch (imageStretchMode) + { + case winrt::Windows::UI::Xaml::Media::Stretch::None: + return IMAGESTRETCHMODE_NONE; + case winrt::Windows::UI::Xaml::Media::Stretch::Fill: + return IMAGESTRETCHMODE_FILL; + case winrt::Windows::UI::Xaml::Media::Stretch::Uniform: + return IMAGESTRETCHMODE_UNIFORM; + default: + case winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill: + return IMAGESTRETCHMODE_UNIFORMTOFILL; + } +} + + + // Method Description: // - Helper function for converting a user-specified cursor style corresponding // CursorStyle enum value diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h index 12ec99dfaf1..c4e861c6aa4 100644 --- a/src/cascadia/TerminalApp/Profile.h +++ b/src/cascadia/TerminalApp/Profile.h @@ -59,6 +59,8 @@ class TerminalApp::Profile final static std::wstring EvaluateStartingDirectory(const std::wstring& directory); static winrt::Microsoft::Terminal::Settings::ScrollbarState ParseScrollbarState(const std::wstring& scrollbarState); + static winrt::Windows::UI::Xaml::Media::Stretch ParseImageStretchMode(const std::wstring& imageStretchMode); + static std::wstring_view SerializeImageStretchMode(const winrt::Windows::UI::Xaml::Media::Stretch imageStretchMode); static winrt::Microsoft::Terminal::Settings::CursorStyle _ParseCursorShape(const std::wstring& cursorShapeString); static std::wstring_view _SerializeCursorStyle(const winrt::Microsoft::Terminal::Settings::CursorStyle cursorShape); @@ -84,6 +86,10 @@ class TerminalApp::Profile final double _acrylicTransparency; bool _useAcrylic; + std::optional _backgroundImage; + std::optional _backgroundImageOpacity; + std::optional _backgroundImageStretchMode; + std::optional _scrollbarState; bool _closeOnExit; std::wstring _padding; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 0eacbc9bbbe..ca3835b5be3 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -158,6 +158,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - Style our UI elements based on the values in our _settings, and set up // other control-specific settings. This method will be called whenever // the settings are reloaded. + // * Calls _InitializeBackgroundBrush to set up the Xaml brush responsible + // for the control's background // * Calls _BackgroundColorChanged to style the background of the control // - Core settings will be passed to the terminal in _InitializeTerminal // Arguments: @@ -166,6 +168,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // - void TermControl::_ApplyUISettings() { + _InitializeBackgroundBrush(); + uint32_t bg = _settings.DefaultBackground(); _BackgroundColorChanged(bg); @@ -186,9 +190,96 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _desiredFont = { _actualFont }; } + // Method Description: + // - Set up the brush used to display the control's background. + // - Respects the settings for acrylic, background image and opacity from + // _settings. + // * Prioritizes the acrylic background if chosen, respecting acrylicOpacity + // from _settings. + // * If acrylic is not enabled and a backgroundImage is present, it is used, + // respecting the opacity and stretch mode settings from _settings. + // * Falls back to a solid color background from _settings if acrylic is not + // enabled and no background image is present. + // - Avoids image flickering and acrylic brush redraw if settings are changed + // but the appropriate brush is still in place. + // - Does not apply background color; _BackgroundColorChanged must be called + // to do so. + // Arguments: + // - + // Return Value: + // - + void TermControl::_InitializeBackgroundBrush() + { + if (_settings.UseAcrylic()) + { + // See if we've already got an acrylic background brush + // to avoid the flicker when setting up a new one + auto acrylic = _root.Background().try_as(); + + // Instantiate a brush if there's not already one there + if (acrylic == nullptr) + { + acrylic = Media::AcrylicBrush{}; + acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop); + } + + // Apply brush settings + acrylic.TintOpacity(_settings.TintOpacity()); + + // Apply brush to control if it's not already there + if (_root.Background() != acrylic) + { + _root.Background(acrylic); + } + } + else if (!_settings.BackgroundImage().empty()) + { + Windows::Foundation::Uri imageUri{ _settings.BackgroundImage() }; + + // Check if the existing brush is an image brush, and if not + // construct a new one + auto brush = _root.Background().try_as(); + + if (brush == nullptr) + { + brush = Media::ImageBrush{}; + } + + // Check if the image brush is already pointing to the image + // in the modified settings; if it isn't (or isn't there), + // set a new image source for the brush + auto imageSource = brush.ImageSource().try_as(); + + if (imageSource == nullptr || imageSource.UriSource() == nullptr + || imageSource.UriSource().RawUri() != imageUri.RawUri()) + { + // Note that BitmapImage handles the image load asynchronously, + // which is especially important since the image + // may well be both large and somewhere out on the + // internet. + Media::Imaging::BitmapImage image(imageUri); + brush.ImageSource(image); + } + + // Apply stretch and opacity settings + brush.Stretch(_settings.BackgroundImageStretchMode()); + brush.Opacity(_settings.BackgroundImageOpacity()); + + // Apply brush if it isn't already there + if (_root.Background() != brush) + { + _root.Background(brush); + } + } + else + { + Media::SolidColorBrush solidColor{}; + _root.Background(solidColor); + } + } + // Method Description: // - Style the background of the control with the provided background color - // - Respects the settings for acrylic and opacity from _settings // Arguments: // - color: The background color to use as a uint32 (aka DWORD COLORREF) // Return Value: @@ -208,23 +299,33 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation if (_settings.UseAcrylic()) { - Media::AcrylicBrush acrylic{}; - acrylic.BackgroundSource(Media::AcrylicBackgroundSource::HostBackdrop); - acrylic.FallbackColor(bgColor); - acrylic.TintColor(bgColor); - acrylic.TintOpacity(_settings.TintOpacity()); - _root.Background(acrylic); + if (auto acrylic = _root.Background().try_as()) + { + acrylic.FallbackColor(bgColor); + acrylic.TintColor(bgColor); + } // If we're acrylic, we want to make sure that the default BG color // is transparent, so we can see the acrylic effect on text with the // default BG color. _settings.DefaultBackground(ARGB(0, R, G, B)); } + else if (!_settings.BackgroundImage().empty()) + { + // This currently applies no changes to the image background + // brush itself. + + // Set the default background as transparent to prevent the + // DX layer from overwriting the background image + _settings.DefaultBackground(ARGB(0, R, G, B)); + } else { - Media::SolidColorBrush solidColor{}; - solidColor.Color(bgColor); - _root.Background(solidColor); + if (auto solidColor = _root.Background().try_as()) + { + solidColor.Color(bgColor); + } + _settings.DefaultBackground(RGB(R, G, B)); } }); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 29bfc91b8ab..7a7de08d166 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -95,6 +95,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void _Create(); void _ApplyUISettings(); + void _InitializeBackgroundBrush(); void _BackgroundColorChanged(const uint32_t color); void _ApplyConnectionSettings(); void _InitializeTerminal(); diff --git a/src/cascadia/TerminalControl/pch.h b/src/cascadia/TerminalControl/pch.h index 7f48d39a5dc..516572299a5 100644 --- a/src/cascadia/TerminalControl/pch.h +++ b/src/cascadia/TerminalControl/pch.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/cascadia/TerminalSettings/IControlSettings.idl b/src/cascadia/TerminalSettings/IControlSettings.idl index e552e3ac4cf..e0ad1278753 100644 --- a/src/cascadia/TerminalSettings/IControlSettings.idl +++ b/src/cascadia/TerminalSettings/IControlSettings.idl @@ -34,5 +34,8 @@ namespace Microsoft.Terminal.Settings String StartingDirectory; String EnvironmentVariables; + String BackgroundImage; + Double BackgroundImageOpacity; + Windows.UI.Xaml.Media.Stretch BackgroundImageStretchMode; }; } diff --git a/src/cascadia/TerminalSettings/TerminalSettings.cpp b/src/cascadia/TerminalSettings/TerminalSettings.cpp index 858ec3160aa..26ca1d3d387 100644 --- a/src/cascadia/TerminalSettings/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettings/TerminalSettings.cpp @@ -26,6 +26,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _padding{ DEFAULT_PADDING }, _fontFace{ DEFAULT_FONT_FACE }, _fontSize{ DEFAULT_FONT_SIZE }, + _backgroundImage{}, + _backgroundImageOpacity{ 1.0 }, + _backgroundImageStretchMode{ winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill }, _keyBindings{ nullptr }, _scrollbarState{ ScrollbarState::Visible } { @@ -193,6 +196,36 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _fontSize = value; } + void TerminalSettings::BackgroundImage(hstring const& value) + { + _backgroundImage = value; + } + + hstring TerminalSettings::BackgroundImage() + { + return _backgroundImage; + } + + void TerminalSettings::BackgroundImageOpacity(double value) + { + _backgroundImageOpacity = value; + } + + double TerminalSettings::BackgroundImageOpacity() + { + return _backgroundImageOpacity; + } + + winrt::Windows::UI::Xaml::Media::Stretch TerminalSettings::BackgroundImageStretchMode() + { + return _backgroundImageStretchMode; + } + + void TerminalSettings::BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value) + { + _backgroundImageStretchMode = value; + } + Settings::IKeyBindings TerminalSettings::KeyBindings() { return _keyBindings; diff --git a/src/cascadia/TerminalSettings/terminalsettings.h b/src/cascadia/TerminalSettings/terminalsettings.h index 847da3a767f..49b0d3dd852 100644 --- a/src/cascadia/TerminalSettings/terminalsettings.h +++ b/src/cascadia/TerminalSettings/terminalsettings.h @@ -61,6 +61,13 @@ namespace winrt::Microsoft::Terminal::Settings::implementation int32_t FontSize(); void FontSize(int32_t value); + hstring BackgroundImage(); + void BackgroundImage(hstring const& value); + double BackgroundImageOpacity(); + void BackgroundImageOpacity(double value); + winrt::Windows::UI::Xaml::Media::Stretch BackgroundImageStretchMode(); + void BackgroundImageStretchMode(winrt::Windows::UI::Xaml::Media::Stretch value); + winrt::Microsoft::Terminal::Settings::IKeyBindings KeyBindings(); void KeyBindings(winrt::Microsoft::Terminal::Settings::IKeyBindings const& value); @@ -94,6 +101,9 @@ namespace winrt::Microsoft::Terminal::Settings::implementation hstring _fontFace; int32_t _fontSize; hstring _padding; + hstring _backgroundImage; + double _backgroundImageOpacity; + winrt::Windows::UI::Xaml::Media::Stretch _backgroundImageStretchMode; hstring _commandline; hstring _startingDir; hstring _envVars;