terminal/src/cascadia/TerminalSettingsEditor/SettingContainer.cpp
PankajBhojwani 9e83655b08
Add support for a profile to specify an "unfocused" appearance (#8392)
This pull request adds an appearance configuration object to our
settings model and app lib, allowing the control to be rendered
differently depending on its state, and then uses it to add support for
an "unfocused" appearance that the terminal will use when it's not in
focus.

To accomplish this, we isolated the appearance-related settings from
Profile (into AppearanceConfig) and TerminalSettings (into the
IControlAppearance and ICoreAppearance interfaces). A bunch of work was
done to make inheritance work.

The unfocused appearance inherits from the focused one _for that
profile_. This is important: If you define a
defaults.unfocusedAppearance, it will apply all of defaults' settings to
any leaf profile when a terminal in that profile is out of focus.

Specified in #8345 
Closes #3062
Closes #2316
2021-04-08 22:46:16 +00:00

217 lines
8.7 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "SettingContainer.h"
#include "SettingContainer.g.cpp"
#include "LibraryResources.h"
using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
DependencyProperty SettingContainer::_HeaderProperty{ nullptr };
DependencyProperty SettingContainer::_HelpTextProperty{ nullptr };
DependencyProperty SettingContainer::_HasSettingValueProperty{ nullptr };
DependencyProperty SettingContainer::_SettingOverrideSourceProperty{ nullptr };
SettingContainer::SettingContainer()
{
_InitializeProperties();
}
void SettingContainer::_InitializeProperties()
{
// Initialize any SettingContainer dependency properties here.
// This performs a lazy load on these properties, instead of
// initializing them when the DLL loads.
if (!_HeaderProperty)
{
_HeaderProperty =
DependencyProperty::Register(
L"Header",
xaml_typename<IInspectable>(),
xaml_typename<Editor::SettingContainer>(),
PropertyMetadata{ nullptr });
}
if (!_HelpTextProperty)
{
_HelpTextProperty =
DependencyProperty::Register(
L"HelpText",
xaml_typename<hstring>(),
xaml_typename<Editor::SettingContainer>(),
PropertyMetadata{ box_value(L"") });
}
if (!_HasSettingValueProperty)
{
_HasSettingValueProperty =
DependencyProperty::Register(
L"HasSettingValue",
xaml_typename<bool>(),
xaml_typename<Editor::SettingContainer>(),
PropertyMetadata{ box_value(false), PropertyChangedCallback{ &SettingContainer::_OnHasSettingValueChanged } });
}
if (!_SettingOverrideSourceProperty)
{
_SettingOverrideSourceProperty =
DependencyProperty::Register(
L"SettingOverrideSource",
xaml_typename<bool>(),
xaml_typename<Editor::SettingContainer>(),
PropertyMetadata{ nullptr });
}
}
void SettingContainer::_OnHasSettingValueChanged(DependencyObject const& d, DependencyPropertyChangedEventArgs const& /*args*/)
{
// update visibility for override message and reset button
const auto& obj{ d.try_as<Editor::SettingContainer>() };
get_self<SettingContainer>(obj)->_UpdateOverrideSystem();
}
void SettingContainer::OnApplyTemplate()
{
if (const auto& child{ GetTemplateChild(L"ResetButton") })
{
if (const auto& button{ child.try_as<Controls::Button>() })
{
// Apply click handler for the reset button.
// When clicked, we dispatch the bound ClearSettingValue event,
// resulting in inheriting the setting value from the parent.
button.Click([=](auto&&, auto&&) {
_ClearSettingValueHandlers(*this, nullptr);
// move the focus to the child control
if (const auto& content{ Content() })
{
if (const auto& control{ content.try_as<Controls::Control>() })
{
control.Focus(FocusState::Programmatic);
return;
}
else if (const auto& panel{ content.try_as<Controls::Panel>() })
{
for (const auto& panelChild : panel.Children())
{
if (const auto& panelControl{ panelChild.try_as<Controls::Control>() })
{
panelControl.Focus(FocusState::Programmatic);
return;
}
}
}
// if we get here, we didn't find something to reasonably focus to.
}
});
// apply name (automation property)
Automation::AutomationProperties::SetName(child, RS_(L"SettingContainer_OverrideMessageBaseLayer"));
}
}
_UpdateOverrideSystem();
if (const auto& content{ Content() })
{
if (const auto& obj{ content.try_as<DependencyObject>() })
{
// apply header text as name (automation property)
if (const auto& header{ Header() })
{
const auto headerText{ header.try_as<hstring>() };
if (headerText && !headerText->empty())
{
Automation::AutomationProperties::SetName(obj, *headerText);
}
}
// apply help text as tooltip and full description (automation property)
const auto& helpText{ HelpText() };
if (!helpText.empty())
{
Controls::ToolTipService::SetToolTip(obj, box_value(helpText));
Automation::AutomationProperties::SetFullDescription(obj, helpText);
}
}
}
}
// Method Description:
// - Updates the override system visibility and text
// Arguments:
// - <none>
void SettingContainer::_UpdateOverrideSystem()
{
if (const auto& child{ GetTemplateChild(L"ResetButton") })
{
if (const auto& button{ child.try_as<Controls::Button>() })
{
if (HasSettingValue())
{
// We want to be smart about showing the override system.
// Don't just show it if the user explicitly set the setting.
// If the tooltip is empty, we'll hide the entire override system.
hstring tooltip{};
const auto& settingSrc{ SettingOverrideSource() };
if (const auto& profile{ settingSrc.try_as<Model::Profile>() })
{
tooltip = _GenerateOverrideMessage(profile);
}
else if (const auto& appearanceConfig{ settingSrc.try_as<Model::AppearanceConfig>() })
{
tooltip = _GenerateOverrideMessage(appearanceConfig.SourceProfile());
}
Controls::ToolTipService::SetToolTip(button, box_value(tooltip));
button.Visibility(tooltip.empty() ? Visibility::Collapsed : Visibility::Visible);
}
else
{
// a value is not being overridden; hide the override system
button.Visibility(Visibility::Collapsed);
}
}
}
}
// Method Description:
// - Helper function for generating the override message
// Arguments:
// - profile: the profile that defines the setting (aka SettingOverrideSource)
// Return Value:
// - text specifying where the setting was defined. If empty, we don't want to show the system.
hstring SettingContainer::_GenerateOverrideMessage(const Model::Profile& profile)
{
const auto originTag{ profile.Origin() };
if (originTag == Model::OriginTag::InBox)
{
// in-box profile
return {};
}
else if (originTag == Model::OriginTag::Generated)
{
// from a dynamic profile generator
return {};
}
else if (originTag == Model::OriginTag::Fragment)
{
// from a fragment extension
return hstring{ fmt::format(std::wstring_view{ RS_(L"SettingContainer_OverrideMessageFragmentExtension") }, profile.Source()) };
}
else
{
// base layer
// TODO GH#3818: When we add profile inheritance as a setting,
// we'll need an extra conditional check to see if this
// is the base layer or some other profile
// GH#9539: Base Layer has been removed from the Settings UI.
// In the event that the Base Layer comes back,
// return RS_(L"SettingContainer_OverrideMessageBaseLayer") instead
return {};
}
}
}