9e83655b08
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
217 lines
8.7 KiB
C++
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 {};
|
|
}
|
|
}
|
|
}
|