mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-20 05:54:23 +00:00
port over more WCT SettingsCard/Expander functionality
This commit is contained in:
139
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.cpp
Normal file
139
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "ControlSizeTrigger.h"
|
||||
#include "ControlSizeTrigger.g.cpp"
|
||||
|
||||
#include <limits>
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
DependencyProperty ControlSizeTrigger::_CanTriggerProperty{ nullptr };
|
||||
DependencyProperty ControlSizeTrigger::_MinWidthProperty{ nullptr };
|
||||
DependencyProperty ControlSizeTrigger::_MaxWidthProperty{ nullptr };
|
||||
DependencyProperty ControlSizeTrigger::_MinHeightProperty{ nullptr };
|
||||
DependencyProperty ControlSizeTrigger::_MaxHeightProperty{ nullptr };
|
||||
DependencyProperty ControlSizeTrigger::_TargetElementProperty{ nullptr };
|
||||
|
||||
ControlSizeTrigger::ControlSizeTrigger()
|
||||
{
|
||||
_InitializeProperties();
|
||||
}
|
||||
|
||||
void ControlSizeTrigger::_InitializeProperties()
|
||||
{
|
||||
// Defaults mirror the toolkit: trigger is always evaluatable, bounds
|
||||
// are wide open, no target element until one is bound.
|
||||
if (!_CanTriggerProperty)
|
||||
{
|
||||
_CanTriggerProperty = DependencyProperty::Register(
|
||||
L"CanTrigger",
|
||||
xaml_typename<bool>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ box_value(true), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
|
||||
}
|
||||
if (!_MinWidthProperty)
|
||||
{
|
||||
_MinWidthProperty = DependencyProperty::Register(
|
||||
L"MinWidth",
|
||||
xaml_typename<double>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ box_value(0.0), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
|
||||
}
|
||||
if (!_MaxWidthProperty)
|
||||
{
|
||||
_MaxWidthProperty = DependencyProperty::Register(
|
||||
L"MaxWidth",
|
||||
xaml_typename<double>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ box_value(std::numeric_limits<double>::infinity()), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
|
||||
}
|
||||
if (!_MinHeightProperty)
|
||||
{
|
||||
_MinHeightProperty = DependencyProperty::Register(
|
||||
L"MinHeight",
|
||||
xaml_typename<double>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ box_value(0.0), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
|
||||
}
|
||||
if (!_MaxHeightProperty)
|
||||
{
|
||||
_MaxHeightProperty = DependencyProperty::Register(
|
||||
L"MaxHeight",
|
||||
xaml_typename<double>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ box_value(std::numeric_limits<double>::infinity()), PropertyChangedCallback{ &ControlSizeTrigger::_OnTriggerInputChanged } });
|
||||
}
|
||||
if (!_TargetElementProperty)
|
||||
{
|
||||
_TargetElementProperty = DependencyProperty::Register(
|
||||
L"TargetElement",
|
||||
xaml_typename<FrameworkElement>(),
|
||||
xaml_typename<Editor::ControlSizeTrigger>(),
|
||||
PropertyMetadata{ nullptr, PropertyChangedCallback{ &ControlSizeTrigger::_OnTargetElementChanged } });
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSizeTrigger::_OnTriggerInputChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
{
|
||||
if (const auto obj{ d.try_as<Editor::ControlSizeTrigger>() })
|
||||
{
|
||||
get_self<ControlSizeTrigger>(obj)->_UpdateTrigger();
|
||||
}
|
||||
}
|
||||
|
||||
void ControlSizeTrigger::_OnTargetElementChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& e)
|
||||
{
|
||||
const auto obj{ d.try_as<Editor::ControlSizeTrigger>() };
|
||||
if (!obj)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const auto oldElement = e.OldValue().try_as<FrameworkElement>();
|
||||
const auto newElement = e.NewValue().try_as<FrameworkElement>();
|
||||
get_self<ControlSizeTrigger>(obj)->_UpdateTargetElement(oldElement, newElement);
|
||||
}
|
||||
|
||||
void ControlSizeTrigger::_UpdateTargetElement(const FrameworkElement& /*oldValue*/, const FrameworkElement& newValue)
|
||||
{
|
||||
// Revoking handles both unhooking the previous element and a null `newValue`.
|
||||
_sizeChangedRevoker.revoke();
|
||||
if (newValue)
|
||||
{
|
||||
_sizeChangedRevoker = newValue.SizeChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
strongThis->_UpdateTrigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
_UpdateTrigger();
|
||||
}
|
||||
|
||||
void ControlSizeTrigger::_UpdateTrigger()
|
||||
{
|
||||
const auto target = TargetElement();
|
||||
if (!target || !CanTrigger())
|
||||
{
|
||||
_isActive = false;
|
||||
SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto width = target.ActualWidth();
|
||||
const auto height = target.ActualHeight();
|
||||
|
||||
const bool activate =
|
||||
MinWidth() <= width &&
|
||||
width < MaxWidth() &&
|
||||
MinHeight() <= height &&
|
||||
height < MaxHeight();
|
||||
|
||||
_isActive = activate;
|
||||
SetActive(activate);
|
||||
}
|
||||
}
|
||||
64
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.h
Normal file
64
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- ControlSizeTrigger
|
||||
|
||||
Abstract:
|
||||
- A conditional state trigger that activates based on the size (width and/or
|
||||
height) of a target FrameworkElement. Lets XAML visual states swap based on
|
||||
the live size of a templated part. Ported from the Windows Community Toolkit
|
||||
primitive `CommunityToolkit.WinUI.ControlSizeTrigger`.
|
||||
|
||||
The trigger is "active" when:
|
||||
MinWidth <= TargetElement.ActualWidth < MaxWidth AND
|
||||
MinHeight <= TargetElement.ActualHeight < MaxHeight
|
||||
|
||||
Defaults: MinWidth = MinHeight = 0; MaxWidth = MaxHeight = +inf, which makes
|
||||
the trigger always active unless `CanTrigger` is false or `TargetElement` is
|
||||
null.
|
||||
|
||||
Author(s):
|
||||
- Carlos Zamora - May 2026 (port from CommunityToolkit.WinUI.ControlSizeTrigger)
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ControlSizeTrigger.g.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct ControlSizeTrigger : ControlSizeTriggerT<ControlSizeTrigger>
|
||||
{
|
||||
public:
|
||||
ControlSizeTrigger();
|
||||
|
||||
bool IsActive() const { return _isActive; }
|
||||
|
||||
DEPENDENCY_PROPERTY(bool, CanTrigger);
|
||||
DEPENDENCY_PROPERTY(double, MinWidth);
|
||||
DEPENDENCY_PROPERTY(double, MaxWidth);
|
||||
DEPENDENCY_PROPERTY(double, MinHeight);
|
||||
DEPENDENCY_PROPERTY(double, MaxHeight);
|
||||
DEPENDENCY_PROPERTY(Windows::UI::Xaml::FrameworkElement, TargetElement);
|
||||
|
||||
private:
|
||||
static void _InitializeProperties();
|
||||
static void _OnTriggerInputChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
static void _OnTargetElementChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
|
||||
void _UpdateTargetElement(const Windows::UI::Xaml::FrameworkElement& oldValue, const Windows::UI::Xaml::FrameworkElement& newValue);
|
||||
void _UpdateTrigger();
|
||||
|
||||
Windows::UI::Xaml::FrameworkElement::SizeChanged_revoker _sizeChangedRevoker;
|
||||
bool _isActive{ false };
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(ControlSizeTrigger);
|
||||
}
|
||||
30
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.idl
Normal file
30
src/cascadia/TerminalSettingsEditor/ControlSizeTrigger.idl
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass ControlSizeTrigger : Windows.UI.Xaml.StateTriggerBase
|
||||
{
|
||||
ControlSizeTrigger();
|
||||
|
||||
Boolean CanTrigger;
|
||||
static Windows.UI.Xaml.DependencyProperty CanTriggerProperty { get; };
|
||||
|
||||
Double MinWidth;
|
||||
static Windows.UI.Xaml.DependencyProperty MinWidthProperty { get; };
|
||||
|
||||
Double MaxWidth;
|
||||
static Windows.UI.Xaml.DependencyProperty MaxWidthProperty { get; };
|
||||
|
||||
Double MinHeight;
|
||||
static Windows.UI.Xaml.DependencyProperty MinHeightProperty { get; };
|
||||
|
||||
Double MaxHeight;
|
||||
static Windows.UI.Xaml.DependencyProperty MaxHeightProperty { get; };
|
||||
|
||||
Windows.UI.Xaml.FrameworkElement TargetElement;
|
||||
static Windows.UI.Xaml.DependencyProperty TargetElementProperty { get; };
|
||||
|
||||
Boolean IsActive { get; };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CornerRadiusFilterConverters.h"
|
||||
#include "CornerRadiusConverter.g.cpp"
|
||||
#include "TopCornerRadiusFilterConverter.g.cpp"
|
||||
#include "BottomCornerRadiusFilterConverter.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
winrt::Windows::Foundation::IInspectable CornerRadiusConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
|
||||
return box_value(CornerRadius{ 0, 0, cr.BottomRight, cr.BottomLeft });
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable CornerRadiusConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable TopCornerRadiusFilterConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
|
||||
return box_value(CornerRadius{ cr.TopLeft, cr.TopRight, 0, 0 });
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable TopCornerRadiusFilterConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable BottomCornerRadiusFilterConverter::Convert(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
if (!value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
const auto cr = unbox_value_or<CornerRadius>(value, CornerRadius{ 0, 0, 0, 0 });
|
||||
return box_value(CornerRadius{ 0, 0, cr.BottomRight, cr.BottomLeft });
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IInspectable BottomCornerRadiusFilterConverter::ConvertBack(const winrt::Windows::Foundation::IInspectable& value, const Interop::TypeName& /*targetType*/, const winrt::Windows::Foundation::IInspectable& /*parameter*/, const hstring& /*language*/)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CornerRadiusConverter.g.h"
|
||||
#include "TopCornerRadiusFilterConverter.g.h"
|
||||
#include "BottomCornerRadiusFilterConverter.g.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
struct CornerRadiusConverter : CornerRadiusConverterT<CornerRadiusConverter>
|
||||
{
|
||||
CornerRadiusConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
};
|
||||
|
||||
struct TopCornerRadiusFilterConverter : TopCornerRadiusFilterConverterT<TopCornerRadiusFilterConverter>
|
||||
{
|
||||
TopCornerRadiusFilterConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
};
|
||||
|
||||
struct BottomCornerRadiusFilterConverter : BottomCornerRadiusFilterConverterT<BottomCornerRadiusFilterConverter>
|
||||
{
|
||||
BottomCornerRadiusFilterConverter() = default;
|
||||
|
||||
Windows::Foundation::IInspectable Convert(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
Windows::Foundation::IInspectable ConvertBack(const Windows::Foundation::IInspectable& value, const Windows::UI::Xaml::Interop::TypeName& targetType, const Windows::Foundation::IInspectable& parameter, const hstring& language);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CornerRadiusConverter);
|
||||
BASIC_FACTORY(TopCornerRadiusFilterConverter);
|
||||
BASIC_FACTORY(BottomCornerRadiusFilterConverter);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Microsoft.Terminal.Settings.Editor
|
||||
{
|
||||
[default_interface] runtimeclass CornerRadiusConverter : Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
CornerRadiusConverter();
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass TopCornerRadiusFilterConverter : Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
TopCornerRadiusFilterConverter();
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass BottomCornerRadiusFilterConverter : Windows.UI.Xaml.Data.IValueConverter
|
||||
{
|
||||
BottomCornerRadiusFilterConverter();
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,12 @@
|
||||
<ClInclude Include="SettingsExpander.h">
|
||||
<DependentUpon>SettingsExpander.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ControlSizeTrigger.h">
|
||||
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CornerRadiusFilterConverters.h">
|
||||
<DependentUpon>CornerRadiusFilterConverters.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StringDefaultTemplateSelector.h">
|
||||
<DependentUpon>StringDefaultTemplateSelector.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
@@ -403,6 +409,12 @@
|
||||
<ClCompile Include="SettingsExpander.cpp">
|
||||
<DependentUpon>SettingsExpander.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ControlSizeTrigger.cpp">
|
||||
<DependentUpon>ControlSizeTrigger.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CornerRadiusFilterConverters.cpp">
|
||||
<DependentUpon>CornerRadiusFilterConverters.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StringDefaultTemplateSelector.cpp">
|
||||
<DependentUpon>StringDefaultTemplateSelector.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
@@ -519,6 +531,12 @@
|
||||
<Midl Include="SettingsExpander.idl">
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="ControlSizeTrigger.idl">
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="CornerRadiusFilterConverters.idl">
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="StringDefaultTemplateSelector.idl">
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
<Midl Include="SettingContainer.idl" />
|
||||
<Midl Include="SettingsCard.idl" />
|
||||
<Midl Include="SettingsExpander.idl" />
|
||||
<Midl Include="ControlSizeTrigger.idl" />
|
||||
<Midl Include="CornerRadiusFilterConverters.idl" />
|
||||
<Midl Include="StringDefaultTemplateSelector.idl" />
|
||||
<Midl Include="TerminalColorConverters.idl" />
|
||||
<Midl Include="NewTabMenuViewModel.idl" />
|
||||
|
||||
@@ -32,15 +32,38 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
static constexpr std::wstring_view PressedState{ L"Pressed" };
|
||||
static constexpr std::wstring_view DisabledState{ L"Disabled" };
|
||||
|
||||
static constexpr std::wstring_view BitmapHeaderIconEnabledState{ L"BitmapHeaderIconEnabled" };
|
||||
static constexpr std::wstring_view BitmapHeaderIconDisabledState{ L"BitmapHeaderIconDisabled" };
|
||||
|
||||
static constexpr std::wstring_view RightState{ L"Right" };
|
||||
static constexpr std::wstring_view LeftState{ L"Left" };
|
||||
static constexpr std::wstring_view VerticalState{ L"Vertical" };
|
||||
|
||||
static constexpr std::wstring_view NoContentSpacingState{ L"NoContentSpacing" };
|
||||
static constexpr std::wstring_view ContentSpacingState{ L"ContentSpacing" };
|
||||
|
||||
static constexpr std::wstring_view ContentAlignmentStatesGroup{ L"ContentAlignmentStates" };
|
||||
|
||||
static constexpr std::wstring_view ActionIconPresenterHolder{ L"PART_ActionIconPresenterHolder" };
|
||||
static constexpr std::wstring_view HeaderPresenter{ L"PART_HeaderPresenter" };
|
||||
static constexpr std::wstring_view DescriptionPresenter{ L"PART_DescriptionPresenter" };
|
||||
static constexpr std::wstring_view HeaderIconPresenterHolder{ L"PART_HeaderIconPresenterHolder" };
|
||||
|
||||
// Returns true if the given object is null, or is a string that is empty.
|
||||
// Non-string non-null objects (e.g. a TextBlock) are considered "non-empty".
|
||||
static bool _isNullOrEmpty(const winrt::Windows::Foundation::IInspectable& obj)
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (const auto pv{ obj.try_as<IPropertyValue>() }; pv && pv.Type() == PropertyType::String)
|
||||
{
|
||||
return unbox_value_or<hstring>(obj, hstring{}).empty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SettingsCard::SettingsCard()
|
||||
{
|
||||
_InitializeProperties();
|
||||
@@ -78,7 +101,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
L"ActionIcon",
|
||||
xaml_typename<IInspectable>(),
|
||||
xaml_typename<Editor::SettingsCard>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
PropertyMetadata{ box_value(hstring{ L"\uE974" }) });
|
||||
}
|
||||
if (!_ActionIconToolTipProperty)
|
||||
{
|
||||
@@ -123,6 +146,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
// Drop any handlers from a previous template.
|
||||
_isEnabledChangedRevoker.revoke();
|
||||
_contentAlignmentStatesChangedRevoker.revoke();
|
||||
_DisableButtonInteraction();
|
||||
if (_contentChangedToken != 0)
|
||||
{
|
||||
@@ -136,6 +160,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
_UpdateHeaderIconVisibility();
|
||||
// Initial visual states.
|
||||
_CheckInitialVisualState();
|
||||
_CheckHeaderIconState();
|
||||
_SetAccessibleContentName();
|
||||
|
||||
// Watch for Content changing later (we may need to refresh the AutomationProperties.Name on it).
|
||||
@@ -156,6 +181,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
strongThis->_GoToCommonState(strongThis->IsEnabled() ? NormalState : DisabledState, true);
|
||||
strongThis->_CheckHeaderIconState();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -164,6 +190,60 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
VisualStateManager::GoToState(*this, IsEnabled() ? hstring{ NormalState } : hstring{ DisabledState }, true);
|
||||
_UpdateContentAlignmentState();
|
||||
|
||||
// Subscribe to ContentAlignmentStates so we can drive ContentSpacingStates
|
||||
// whenever the alignment shifts to a stacked layout (Vertical, RightWrapped*).
|
||||
if (const auto child{ GetTemplateChild(hstring{ ContentAlignmentStatesGroup }) })
|
||||
{
|
||||
if (const auto group{ child.try_as<VisualStateGroup>() })
|
||||
{
|
||||
_CheckVerticalSpacingState(group.CurrentState());
|
||||
_contentAlignmentStatesChangedRevoker = group.CurrentStateChanged(winrt::auto_revoke, [weakThis = get_weak()](auto&&, const VisualStateChangedEventArgs& args) {
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
strongThis->_CheckVerticalSpacingState(args.NewState());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsCard::_CheckHeaderIconState()
|
||||
{
|
||||
// The Disabled common state recolors text/glyph foregrounds via the brush, but a
|
||||
// BitmapIcon is an image and won't pick up the disabled brush. Lower its opacity
|
||||
// instead, via the BitmapHeaderIconStates group. Mirrors the toolkit's
|
||||
// SettingsCard.cs::CheckHeaderIconState.
|
||||
if (HeaderIcon().try_as<BitmapIcon>())
|
||||
{
|
||||
VisualStateManager::GoToState(*this,
|
||||
hstring{ IsEnabled() ? BitmapHeaderIconEnabledState : BitmapHeaderIconDisabledState },
|
||||
true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset to the enabled state when a non-bitmap icon (or none) is present so the
|
||||
// opacity setter doesn't stick around from a previous bitmap icon.
|
||||
VisualStateManager::GoToState(*this, hstring{ BitmapHeaderIconEnabledState }, true);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsCard::_CheckVerticalSpacingState(const VisualState& state)
|
||||
{
|
||||
// Add row spacing whenever the content sits below the header (Vertical or RightWrapped*)
|
||||
// AND there's both Content and (Header or Description) to space apart.
|
||||
const auto stateName{ state ? state.Name() : hstring{} };
|
||||
const bool stackedLayout =
|
||||
stateName == VerticalState ||
|
||||
stateName == L"RightWrapped" ||
|
||||
stateName == L"RightWrappedNoIcon";
|
||||
|
||||
const bool hasContent{ static_cast<bool>(Content()) };
|
||||
const bool hasHeaderOrDescription = !_isNullOrEmpty(Header()) || !_isNullOrEmpty(Description());
|
||||
|
||||
VisualStateManager::GoToState(*this,
|
||||
hstring{ (stackedLayout && hasContent && hasHeaderOrDescription) ? ContentSpacingState : NoContentSpacingState },
|
||||
true);
|
||||
}
|
||||
|
||||
void SettingsCard::_SetAccessibleContentName()
|
||||
@@ -216,6 +296,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
strongThis->_GoToCommonState(NormalState, true);
|
||||
}
|
||||
});
|
||||
_pointerPressedRevoker = PointerPressed(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
strongThis->_GoToCommonState(PressedState, true);
|
||||
}
|
||||
});
|
||||
_pointerReleasedRevoker = PointerReleased(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
strongThis->_GoToCommonState(NormalState, true);
|
||||
}
|
||||
});
|
||||
_pointerCaptureLostRevoker = PointerCaptureLost(winrt::auto_revoke, [weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (const auto strongThis = weakThis.get())
|
||||
{
|
||||
@@ -264,6 +356,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
IsTabStop(false);
|
||||
_pointerEnteredRevoker.revoke();
|
||||
_pointerExitedRevoker.revoke();
|
||||
_pointerPressedRevoker.revoke();
|
||||
_pointerReleasedRevoker.revoke();
|
||||
_pointerCaptureLostRevoker.revoke();
|
||||
_pointerCanceledRevoker.revoke();
|
||||
_previewKeyDownRevoker.revoke();
|
||||
@@ -295,21 +389,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the given object is null, or is a string that is empty.
|
||||
// Non-string non-null objects (e.g. a TextBlock) are considered "non-empty".
|
||||
static bool _isNullOrEmpty(const winrt::Windows::Foundation::IInspectable& obj)
|
||||
{
|
||||
if (!obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (const auto pv{ obj.try_as<IPropertyValue>() }; pv && pv.Type() == PropertyType::String)
|
||||
{
|
||||
return unbox_value_or<hstring>(obj, hstring{}).empty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SettingsCard::_UpdateHeaderVisibility()
|
||||
{
|
||||
if (const auto child{ GetTemplateChild(hstring{ HeaderPresenter }) })
|
||||
@@ -378,7 +457,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void SettingsCard::_OnHeaderIconChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
{
|
||||
const auto obj{ d.try_as<Editor::SettingsCard>() };
|
||||
get_self<SettingsCard>(obj)->_UpdateHeaderIconVisibility();
|
||||
const auto self = get_self<SettingsCard>(obj);
|
||||
self->_UpdateHeaderIconVisibility();
|
||||
// HeaderIcon type may have flipped between BitmapIcon and other icon types — re-evaluate
|
||||
// the BitmapHeaderIcon visual state so the disabled-opacity setter is applied (or cleared).
|
||||
self->_CheckHeaderIconState();
|
||||
}
|
||||
|
||||
void SettingsCard::_OnIsClickEnabledChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
|
||||
@@ -59,6 +59,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
void _UpdateHeaderIconVisibility();
|
||||
void _UpdateContentAlignmentState();
|
||||
void _CheckInitialVisualState();
|
||||
void _CheckHeaderIconState();
|
||||
void _CheckVerticalSpacingState(const Windows::UI::Xaml::VisualState& state);
|
||||
void _SetAccessibleContentName();
|
||||
Windows::UI::Xaml::FrameworkElement _GetFocusedElement();
|
||||
|
||||
@@ -66,10 +68,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
Windows::UI::Xaml::Controls::Control::IsEnabledChanged_revoker _isEnabledChangedRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerEntered_revoker _pointerEnteredRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerExited_revoker _pointerExitedRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerPressed_revoker _pointerPressedRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerReleased_revoker _pointerReleasedRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerCaptureLost_revoker _pointerCaptureLostRevoker;
|
||||
Windows::UI::Xaml::UIElement::PointerCanceled_revoker _pointerCanceledRevoker;
|
||||
Windows::UI::Xaml::UIElement::PreviewKeyDown_revoker _previewKeyDownRevoker;
|
||||
Windows::UI::Xaml::UIElement::PreviewKeyUp_revoker _previewKeyUpRevoker;
|
||||
Windows::UI::Xaml::VisualStateGroup::CurrentStateChanged_revoker _contentAlignmentStatesChangedRevoker;
|
||||
int64_t _contentChangedToken{ 0 };
|
||||
};
|
||||
|
||||
|
||||
@@ -115,10 +115,32 @@
|
||||
<Thickness x:Key="SettingsCardHeaderIconMargin">2,0,20,0</Thickness>
|
||||
<Thickness x:Key="SettingsCardActionIconMargin">14,0,0,0</Thickness>
|
||||
<x:Double x:Key="SettingsCardActionIconMaxSize">13</x:Double>
|
||||
<x:Double x:Key="SettingsCardBitmapIconDisabledOpacity">0.4</x:Double>
|
||||
<x:Double x:Key="SettingsCardVerticalHeaderContentSpacing">8</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapThreshold">476</x:Double>
|
||||
<x:Double x:Key="SettingsCardWrapNoIconThreshold">286</x:Double>
|
||||
|
||||
<Thickness x:Key="SettingsExpanderHeaderPadding">16,16,4,16</Thickness>
|
||||
<Thickness x:Key="SettingsExpanderItemPadding">58,8,44,8</Thickness>
|
||||
<Thickness x:Key="ClickableSettingsExpanderItemPadding">58,8,16,8</Thickness>
|
||||
<Thickness x:Key="SettingsExpanderItemBorderThickness">0,1,0,0</Thickness>
|
||||
<x:Double x:Key="SettingsExpanderContentMinHeight">16</x:Double>
|
||||
<x:Double x:Key="SettingsExpanderChevronButtonWidth">32</x:Double>
|
||||
<x:Double x:Key="SettingsExpanderChevronButtonHeight">32</x:Double>
|
||||
<x:String x:Key="ExpanderChevronDownGlyph"></x:String>
|
||||
|
||||
<!--
|
||||
Hardcoded English string for the chevron tooltip on a collapsed SettingsExpander.
|
||||
Intentionally not localized: matches the Windows Community Toolkit's own ship pattern
|
||||
for SettingsExpander, where the tooltip lives as a static <x:String> resource in the
|
||||
control's default style. Upstream:
|
||||
https://github.com/CommunityToolkit/Windows/blob/b1d8231b5ee810f4e4264be17d9c343ee2785ede/components/SettingsControls/src/SettingsExpander/SettingsExpander.xaml#L14
|
||||
-->
|
||||
<x:String x:Key="SettingsExpanderChevronToolTip">Show all settings</x:String>
|
||||
|
||||
<local:CornerRadiusConverter x:Key="CornerRadiusConverter" />
|
||||
<local:TopCornerRadiusFilterConverter x:Key="TopCornerRadiusFilterConverter" />
|
||||
<local:BottomCornerRadiusFilterConverter x:Key="BottomCornerRadiusFilterConverter" />
|
||||
|
||||
<!-- ============ SettingsCard ============ -->
|
||||
|
||||
@@ -127,7 +149,7 @@
|
||||
no left/right rounded corners, a 1-pixel top border between siblings,
|
||||
deeper left padding to line up with the parent expander's content.
|
||||
-->
|
||||
<Style x:Key="SettingsExpanderItemCardStyle"
|
||||
<Style x:Key="DefaultSettingsExpanderItemStyle"
|
||||
BasedOn="{StaticResource DefaultSettingsCardStyle}"
|
||||
TargetType="local:SettingsCard">
|
||||
<Setter Property="BorderThickness" Value="{StaticResource SettingsExpanderItemBorderThickness}" />
|
||||
@@ -136,6 +158,18 @@
|
||||
<Setter Property="CornerRadius" Value="0" />
|
||||
</Style>
|
||||
|
||||
<!-- Variant of DefaultSettingsExpanderItemStyle for clickable items; trims the right padding to make room for the chevron. -->
|
||||
<Style x:Key="ClickableSettingsExpanderItemStyle"
|
||||
BasedOn="{StaticResource DefaultSettingsExpanderItemStyle}"
|
||||
TargetType="local:SettingsCard">
|
||||
<Setter Property="Padding" Value="{StaticResource ClickableSettingsExpanderItemPadding}" />
|
||||
</Style>
|
||||
|
||||
<!-- Selector that picks Default vs Clickable item style based on SettingsCard.IsClickEnabled. -->
|
||||
<local:SettingsExpanderItemStyleSelector x:Key="SettingsExpanderItemStyleSelector"
|
||||
ClickableStyle="{StaticResource ClickableSettingsExpanderItemStyle}"
|
||||
DefaultStyle="{StaticResource DefaultSettingsExpanderItemStyle}" />
|
||||
|
||||
<Style BasedOn="{StaticResource DefaultSettingsCardStyle}"
|
||||
TargetType="local:SettingsCard" />
|
||||
|
||||
@@ -159,7 +193,6 @@
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-3" />
|
||||
<Setter Property="ActionIcon" Value="" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:SettingsCard">
|
||||
@@ -315,8 +348,50 @@
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup x:Name="BitmapHeaderIconStates">
|
||||
<VisualState x:Name="BitmapHeaderIconEnabled" />
|
||||
<VisualState x:Name="BitmapHeaderIconDisabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_HeaderIconPresenterHolder.Opacity" Value="{StaticResource SettingsCardBitmapIconDisabledOpacity}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<VisualStateGroup x:Name="ContentAlignmentStates">
|
||||
<VisualState x:Name="Right" />
|
||||
|
||||
<!-- Whenever the control width is less than SettingsCardWrapThreshold, the Content is below the Header/Description. -->
|
||||
<VisualState x:Name="RightWrapped">
|
||||
<VisualState.StateTriggers>
|
||||
<local:ControlSizeTrigger MinWidth="{StaticResource SettingsCardWrapNoIconThreshold}"
|
||||
MaxWidth="{StaticResource SettingsCardWrapThreshold}"
|
||||
TargetElement="{Binding ElementName=PART_RootGrid}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Row)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Column)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="PART_ContentPresenter.HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Target="HeaderPanel.Margin" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<!-- For even smaller widths: the HeaderIcon is collapsed. -->
|
||||
<VisualState x:Name="RightWrappedNoIcon">
|
||||
<VisualState.StateTriggers>
|
||||
<local:ControlSizeTrigger MaxWidth="{StaticResource SettingsCardWrapNoIconThreshold}"
|
||||
TargetElement="{Binding ElementName=PART_RootGrid}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Row)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Column)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="PART_ContentPresenter.HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Target="HeaderPanel.Margin" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Left">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
|
||||
@@ -332,7 +407,16 @@
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Row)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.(Grid.Column)" Value="1" />
|
||||
<Setter Target="PART_ContentPresenter.HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Target="PART_RootGrid.RowSpacing" Value="8" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
<!-- Drives the row spacing between the header/description and the content when they share a column (Vertical or RightWrapped layouts). -->
|
||||
<VisualStateGroup x:Name="ContentSpacingStates">
|
||||
<VisualState x:Name="NoContentSpacing" />
|
||||
<VisualState x:Name="ContentSpacing">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="PART_RootGrid.RowSpacing" Value="{StaticResource SettingsCardVerticalHeaderContentSpacing}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
@@ -365,6 +449,7 @@
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontWeight" Value="Normal" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ItemContainerStyleSelector" Value="{StaticResource SettingsExpanderItemStyleSelector}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="local:SettingsExpander">
|
||||
@@ -375,10 +460,10 @@
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
IsExpanded="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}">
|
||||
IsExpanded="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Style="{StaticResource SettingsExpanderExpanderStyle}">
|
||||
<muxc:Expander.Header>
|
||||
<local:SettingsCard MinHeight="52"
|
||||
Padding="0,8,0,8"
|
||||
<local:SettingsCard Padding="{StaticResource SettingsExpanderHeaderPadding}"
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
@@ -393,10 +478,19 @@
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter Content="{TemplateBinding ItemsHeader}" />
|
||||
<ContentPresenter Grid.Row="1"
|
||||
<muxc:ItemsRepeater x:Name="PART_ItemsRepeater"
|
||||
Grid.Row="1"
|
||||
ItemTemplate="{Binding ItemTemplate, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
TabFocusNavigation="Local">
|
||||
<muxc:ItemsRepeater.Layout>
|
||||
<muxc:StackLayout Orientation="Vertical" />
|
||||
</muxc:ItemsRepeater.Layout>
|
||||
</muxc:ItemsRepeater>
|
||||
<ContentPresenter Grid.Row="2"
|
||||
Content="{TemplateBinding ItemsFooter}" />
|
||||
</Grid>
|
||||
</muxc:Expander.Content>
|
||||
@@ -406,4 +500,490 @@
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
Custom muxc:Expander template used inside SettingsExpander. Ported from
|
||||
the Windows Community Toolkit's SettingsExpanderExpanderStyle so that the
|
||||
Expander header/border match a sibling SettingsCard (hover, press, and
|
||||
disabled brushes), and so the expanded body visually "stitches" to the
|
||||
header via the CornerRadiusFilterConverters.
|
||||
|
||||
Notes vs. toolkit:
|
||||
- The ExpandDirection=Up branch is omitted: SettingsExpander in the
|
||||
Settings Editor always expands downward, and the toolkit's Up state
|
||||
references a WinUI 3 system style (ExpanderHeaderUpStyle) that doesn't
|
||||
ship in WinUI 2.
|
||||
- Theme resources prefixed with ExpanderHeader*/ExpanderChevron*/
|
||||
ExpanderContent* come from the WinUI 2 muxc:Expander default theme.
|
||||
-->
|
||||
<Style x:Key="SettingsExpanderExpanderStyle"
|
||||
TargetType="muxc:Expander">
|
||||
<Setter Property="Background" Value="{ThemeResource ExpanderContentBackground}" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="MinWidth" Value="{ThemeResource FlyoutThemeMinWidth}" />
|
||||
<Setter Property="MinHeight" Value="{ThemeResource ExpanderMinHeight}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ExpanderContentDownBorderThickness}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ExpanderContentBorderBrush}" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="CornerRadius" Value="{ThemeResource ControlCornerRadius}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="muxc:Expander">
|
||||
<Grid MinWidth="{TemplateBinding MinWidth}"
|
||||
MaxWidth="{TemplateBinding MaxWidth}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition x:Name="Row0"
|
||||
Height="Auto" />
|
||||
<RowDefinition x:Name="Row1"
|
||||
Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ToggleButton x:Name="ExpanderHeader"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
Padding="{StaticResource ExpanderHeaderPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="{StaticResource ExpanderHeaderHorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{StaticResource ExpanderHeaderVerticalContentAlignment}"
|
||||
AutomationProperties.AutomationId="ExpanderToggleButton"
|
||||
AutomationProperties.Name="{TemplateBinding AutomationProperties.Name}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{ThemeResource ExpanderHeaderBorderBrush}"
|
||||
BorderThickness="{ThemeResource ExpanderHeaderBorderThickness}"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}"
|
||||
IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
IsEnabled="{TemplateBinding IsEnabled}"
|
||||
IsTabStop="True"
|
||||
Style="{StaticResource SettingsExpanderHeaderDownStyle}" />
|
||||
|
||||
<!-- The clip is a composition clip applied in code (by muxc:Expander). -->
|
||||
<Border x:Name="ExpanderContentClip"
|
||||
Grid.Row="1">
|
||||
<Border x:Name="ExpanderContent"
|
||||
MinHeight="{StaticResource SettingsExpanderContentMinHeight}"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{StaticResource ExpanderContentDownBorderThickness}"
|
||||
CornerRadius="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BottomCornerRadiusFilterConverter}}"
|
||||
Visibility="Collapsed">
|
||||
<ContentPresenter Margin="0,-2,0,0"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
|
||||
<Border.RenderTransform>
|
||||
<CompositeTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
</Border>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="ExpandStates">
|
||||
<VisualState x:Name="ExpandUp">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpanderHeader.CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BottomCornerRadiusFilterConverter}}" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="Visible" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0"
|
||||
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ContentHeight}" />
|
||||
<SplineDoubleKeyFrame KeySpline="0.0, 0.0, 0.0, 1.0"
|
||||
KeyTime="0:0:0.333"
|
||||
Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CollapseDown">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0.2"
|
||||
Value="Collapsed" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0"
|
||||
Value="0" />
|
||||
<SplineDoubleKeyFrame KeySpline="1.0, 1.0, 0.0, 1.0"
|
||||
KeyTime="0:0:0.167"
|
||||
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.ContentHeight}" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="ExpandDown">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpanderHeader.CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
|
||||
</VisualState.Setters>
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="Visible" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0"
|
||||
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeContentHeight}" />
|
||||
<SplineDoubleKeyFrame KeySpline="0.0, 0.0, 0.0, 1.0"
|
||||
KeyTime="0:0:0.333"
|
||||
Value="0" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CollapseUp">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0:0:0.167"
|
||||
Value="Collapsed" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ExpanderContent"
|
||||
Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0"
|
||||
Value="0" />
|
||||
<SplineDoubleKeyFrame KeySpline="1.0, 1.0, 0.0, 1.0"
|
||||
KeyTime="0:0:0.167"
|
||||
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.NegativeContentHeight}" />
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<!--
|
||||
Up direction state intentionally omits the toolkit's
|
||||
"Setter Target=ExpanderHeader.Style = ExpanderHeaderUpStyle" because
|
||||
that style is a WinUI 3 system resource not shipped in WinUI 2.
|
||||
The remaining safe setters (corner radius / row placement) are kept
|
||||
so the muxc:Expander internal direction logic still has a state to
|
||||
land on when ExpandDirection=Up.
|
||||
-->
|
||||
<VisualStateGroup x:Name="ExpandDirectionStates">
|
||||
<VisualState x:Name="Down" />
|
||||
<VisualState x:Name="Up">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpanderContent.BorderThickness" Value="{ThemeResource ExpanderContentUpBorderThickness}" />
|
||||
<Setter Target="ExpanderContent.CornerRadius" Value="{Binding CornerRadius, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TopCornerRadiusFilterConverter}}" />
|
||||
<Setter Target="ExpanderHeader.(Grid.Row)" Value="1" />
|
||||
<Setter Target="ExpanderContentClip.(Grid.Row)" Value="0" />
|
||||
<Setter Target="Row0.Height" Value="*" />
|
||||
<Setter Target="Row1.Height" Value="Auto" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!--
|
||||
Header ToggleButton style for the custom Expander template above.
|
||||
Drives header coloring to match a sibling SettingsCard, and animates
|
||||
the chevron's AnimatedIcon between NormalOn/Off, PointerOverOn/Off,
|
||||
and PressedOn/Off states.
|
||||
-->
|
||||
<Style x:Key="SettingsExpanderHeaderDownStyle"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="BackgroundSizing" Value="InnerBorderEdge" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Grid x:Name="ToggleButtonGrid"
|
||||
Width="{TemplateBinding Width}"
|
||||
MinWidth="{TemplateBinding MinWidth}"
|
||||
MinHeight="{TemplateBinding MinHeight}"
|
||||
MaxWidth="{TemplateBinding MaxWidth}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
|
||||
Background="{TemplateBinding Background}"
|
||||
BackgroundSizing="{TemplateBinding BackgroundSizing}"
|
||||
BorderBrush="{ThemeResource ExpanderHeaderBorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<ContentPresenter x:Name="ContentPresenter"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}"
|
||||
Foreground="{TemplateBinding Foreground}" />
|
||||
|
||||
<ContentControl x:Name="ExpandCollapseChevronBorder"
|
||||
Grid.Column="1"
|
||||
Width="{StaticResource SettingsExpanderChevronButtonWidth}"
|
||||
Height="{StaticResource SettingsExpanderChevronButtonHeight}"
|
||||
Margin="0,0,8,0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Background="{ThemeResource ExpanderChevronBackground}"
|
||||
BorderBrush="{ThemeResource ExpanderChevronBorderBrush}"
|
||||
BorderThickness="{ThemeResource ExpanderChevronBorderThickness}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
FocusVisualMargin="-3"
|
||||
IsTabStop="False"
|
||||
ToolTipService.ToolTip="{StaticResource SettingsExpanderChevronToolTip}"
|
||||
UseSystemFocusVisuals="True">
|
||||
<!--
|
||||
Toolkit uses a muxc:AnimatedIcon wrapping AnimatedChevronUpDownSmallVisualSource.
|
||||
That visual source isn't available in the WinUI 2 build the editor targets, so
|
||||
we fall back to a static FontIcon. We rotate it 180° in the Checked* states to
|
||||
indicate the expanded state (mirrors the visual outcome of the animated icon,
|
||||
without the morph animation).
|
||||
-->
|
||||
<FontIcon x:Name="ExpandCollapseChevron"
|
||||
Width="16"
|
||||
Height="16"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
FontFamily="{ThemeResource SymbolThemeFontFamily}"
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource ExpanderChevronForeground}"
|
||||
Glyph="{StaticResource ExpanderChevronDownGlyph}"
|
||||
IsTextScaleFactorEnabled="False"
|
||||
RenderTransformOrigin="0.5, 0.5">
|
||||
<FontIcon.RenderTransform>
|
||||
<RotateTransform x:Name="ExpandCollapseChevronRotation" Angle="0" />
|
||||
</FontIcon.RenderTransform>
|
||||
</FontIcon>
|
||||
</ContentControl>
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualState x:Name="Normal">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBackground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronPointerOverForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Pressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronPressedForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Disabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderDisabledForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderDisabledForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Checked">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevronBorder"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronBorderBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevronBorder"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronBackground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpandCollapseChevronRotation.Angle" Value="180" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForegroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronPointerOverForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBackgroundPointerOver}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpandCollapseChevronRotation.Angle" Value="180" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedPressed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderForegroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderChevronPressedForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBackgroundPressed}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpandCollapseChevronRotation.Angle" Value="180" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="CheckedDisabled">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderDisabledForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpandCollapseChevron"
|
||||
Storyboard.TargetProperty="Foreground">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource ExpanderHeaderDisabledForeground}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ToggleButtonGrid"
|
||||
Storyboard.TargetProperty="BorderBrush">
|
||||
<DiscreteObjectKeyFrame KeyTime="0"
|
||||
Value="{ThemeResource SettingsCardBorderBrushDisabled}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ExpandCollapseChevronRotation.Angle" Value="180" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Indeterminate" />
|
||||
<VisualState x:Name="IndeterminatePointerOver" />
|
||||
<VisualState x:Name="IndeterminatePressed" />
|
||||
<VisualState x:Name="IndeterminateDisabled" />
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
#include "SettingsExpander.h"
|
||||
#include "SettingsExpander.g.cpp"
|
||||
#include "SettingsExpanderAutomationPeer.g.cpp"
|
||||
#include "SettingsExpanderItemStyleSelector.g.cpp"
|
||||
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation;
|
||||
using namespace winrt::Windows::UI::Xaml::Automation::Peers;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
|
||||
namespace MUXC = winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
{
|
||||
DependencyProperty SettingsExpander::_HeaderProperty{ nullptr };
|
||||
@@ -21,10 +25,18 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
DependencyProperty SettingsExpander::_IsExpandedProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemsHeaderProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemsFooterProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemsProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemsSourceProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemTemplateProperty{ nullptr };
|
||||
DependencyProperty SettingsExpander::_ItemContainerStyleSelectorProperty{ nullptr };
|
||||
|
||||
static constexpr std::wstring_view PART_ItemsRepeater{ L"PART_ItemsRepeater" };
|
||||
|
||||
SettingsExpander::SettingsExpander()
|
||||
{
|
||||
_InitializeProperties();
|
||||
|
||||
Items(single_threaded_vector<IInspectable>());
|
||||
}
|
||||
|
||||
void SettingsExpander::_InitializeProperties()
|
||||
@@ -85,6 +97,38 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
xaml_typename<Editor::SettingsExpander>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
}
|
||||
if (!_ItemsProperty)
|
||||
{
|
||||
_ItemsProperty = DependencyProperty::Register(
|
||||
L"Items",
|
||||
xaml_typename<IVector<IInspectable>>(),
|
||||
xaml_typename<Editor::SettingsExpander>(),
|
||||
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
|
||||
}
|
||||
if (!_ItemsSourceProperty)
|
||||
{
|
||||
_ItemsSourceProperty = DependencyProperty::Register(
|
||||
L"ItemsSource",
|
||||
xaml_typename<IInspectable>(),
|
||||
xaml_typename<Editor::SettingsExpander>(),
|
||||
PropertyMetadata{ nullptr, PropertyChangedCallback{ &SettingsExpander::_OnItemsConnectedPropertyChanged } });
|
||||
}
|
||||
if (!_ItemTemplateProperty)
|
||||
{
|
||||
_ItemTemplateProperty = DependencyProperty::Register(
|
||||
L"ItemTemplate",
|
||||
xaml_typename<IInspectable>(),
|
||||
xaml_typename<Editor::SettingsExpander>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
}
|
||||
if (!_ItemContainerStyleSelectorProperty)
|
||||
{
|
||||
_ItemContainerStyleSelectorProperty = DependencyProperty::Register(
|
||||
L"ItemContainerStyleSelector",
|
||||
xaml_typename<StyleSelector>(),
|
||||
xaml_typename<Editor::SettingsExpander>(),
|
||||
PropertyMetadata{ nullptr });
|
||||
}
|
||||
}
|
||||
|
||||
AutomationPeer SettingsExpander::OnCreateAutomationPeer()
|
||||
@@ -94,8 +138,77 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
|
||||
void SettingsExpander::OnApplyTemplate()
|
||||
{
|
||||
// No template-part lookups required: our template uses regular bindings
|
||||
// through TemplateBinding / x:Bind. The DPs do their own work.
|
||||
_SetAccessibleName();
|
||||
|
||||
// Drop the prior template's repeater hookups before locating the new one.
|
||||
_elementPreparedRevoker.revoke();
|
||||
_itemsRepeater = nullptr;
|
||||
|
||||
if (const auto child{ GetTemplateChild(hstring{ PART_ItemsRepeater }) })
|
||||
{
|
||||
_itemsRepeater = child.try_as<MUXC::ItemsRepeater>();
|
||||
}
|
||||
|
||||
if (_itemsRepeater)
|
||||
{
|
||||
_elementPreparedRevoker = _itemsRepeater.ElementPrepared(winrt::auto_revoke, { get_weak(), &SettingsExpander::_ItemsRepeater_ElementPrepared });
|
||||
|
||||
// Push our initial ItemsSource through to the repeater.
|
||||
_UpdateItemsSource();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsExpander::_SetAccessibleName()
|
||||
{
|
||||
if (!AutomationProperties::GetName(*this).empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (const auto headerString{ unbox_value_or<hstring>(Header(), hstring{}) }; !headerString.empty())
|
||||
{
|
||||
AutomationProperties::SetName(*this, headerString);
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsExpander::_UpdateItemsSource()
|
||||
{
|
||||
if (!_itemsRepeater)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ItemsSource wins when set; otherwise fall back to the inline Items collection.
|
||||
if (const auto source{ ItemsSource() })
|
||||
{
|
||||
_itemsRepeater.ItemsSource(source);
|
||||
}
|
||||
else
|
||||
{
|
||||
_itemsRepeater.ItemsSource(Items());
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsExpander::_OnItemsConnectedPropertyChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& /*e*/)
|
||||
{
|
||||
if (const auto obj{ d.try_as<Editor::SettingsExpander>() })
|
||||
{
|
||||
get_self<SettingsExpander>(obj)->_UpdateItemsSource();
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsExpander::_ItemsRepeater_ElementPrepared(const MUXC::ItemsRepeater& /*sender*/, const MUXC::ItemsRepeaterElementPreparedEventArgs& args)
|
||||
{
|
||||
const auto selector{ ItemContainerStyleSelector() };
|
||||
if (!selector)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (const auto element{ args.Element().try_as<FrameworkElement>() })
|
||||
{
|
||||
if (element.ReadLocalValue(FrameworkElement::StyleProperty()) == DependencyProperty::UnsetValue())
|
||||
{
|
||||
element.Style(selector.SelectStyle(nullptr, element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsExpander::_OnIsExpandedChanged(const DependencyObject& d, const DependencyPropertyChangedEventArgs& e)
|
||||
@@ -107,6 +220,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
const auto self = get_self<SettingsExpander>(obj);
|
||||
const auto newValue = unbox_value_or<bool>(e.NewValue(), false);
|
||||
|
||||
// Notify the automation peer so screen readers see the expand/collapse state change.
|
||||
if (const auto peer{ FrameworkElementAutomationPeer::FromElement(obj).try_as<Editor::SettingsExpanderAutomationPeer>() })
|
||||
{
|
||||
peer.RaiseExpandedChangedEvent(newValue);
|
||||
}
|
||||
|
||||
if (newValue)
|
||||
{
|
||||
self->Expanded.raise(obj, nullptr);
|
||||
@@ -147,4 +267,29 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void SettingsExpanderAutomationPeer::RaiseExpandedChangedEvent(bool newValue)
|
||||
{
|
||||
// Mirrors the toolkit's SettingsExpanderAutomationPeer.RaiseExpandedChangedEvent.
|
||||
// Narrator doesn't actually announce this today (microsoft/microsoft-ui-xaml#3469),
|
||||
// but other AT can subscribe and we keep parity with the toolkit.
|
||||
const auto newState = newValue ? ExpandCollapseState::Expanded : ExpandCollapseState::Collapsed;
|
||||
const auto oldState = newValue ? ExpandCollapseState::Collapsed : ExpandCollapseState::Expanded;
|
||||
RaisePropertyChangedEvent(ExpandCollapsePatternIdentifiers::ExpandCollapseStateProperty(), box_value(oldState), box_value(newState));
|
||||
}
|
||||
|
||||
Windows::UI::Xaml::Style SettingsExpanderItemStyleSelector::SelectStyleCore(const winrt::Windows::Foundation::IInspectable& /*item*/, const Windows::UI::Xaml::DependencyObject& container)
|
||||
{
|
||||
// When the prepared container is a clickable SettingsCard, give it the
|
||||
// clickable item style (which adds the right padding for the chevron).
|
||||
// Otherwise fall back to the default item style.
|
||||
if (const auto card{ container.try_as<Editor::SettingsCard>() })
|
||||
{
|
||||
if (card.IsClickEnabled())
|
||||
{
|
||||
return _ClickableStyle;
|
||||
}
|
||||
}
|
||||
return _DefaultStyle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ Abstract:
|
||||
on the Windows Community Toolkit's SettingsExpander.
|
||||
|
||||
Author(s):
|
||||
- Carlos Zamora - 2026 (port from CommunityToolkit.WinUI.Controls.SettingsExpander)
|
||||
- Carlos Zamora - May 2026 (port from CommunityToolkit.WinUI.Controls.SettingsExpander)
|
||||
|
||||
--*/
|
||||
|
||||
@@ -18,6 +18,7 @@ Author(s):
|
||||
|
||||
#include "SettingsExpander.g.h"
|
||||
#include "SettingsExpanderAutomationPeer.g.h"
|
||||
#include "SettingsExpanderItemStyleSelector.g.h"
|
||||
#include "Utils.h"
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
@@ -41,10 +42,22 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
DEPENDENCY_PROPERTY(bool, IsExpanded);
|
||||
DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsHeader);
|
||||
DEPENDENCY_PROPERTY(Windows::UI::Xaml::UIElement, ItemsFooter);
|
||||
DEPENDENCY_PROPERTY(Windows::Foundation::Collections::IVector<Windows::Foundation::IInspectable>, Items);
|
||||
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemsSource);
|
||||
DEPENDENCY_PROPERTY(Windows::Foundation::IInspectable, ItemTemplate);
|
||||
DEPENDENCY_PROPERTY(Windows::UI::Xaml::Controls::StyleSelector, ItemContainerStyleSelector);
|
||||
|
||||
private:
|
||||
static void _InitializeProperties();
|
||||
static void _OnIsExpandedChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
static void _OnItemsConnectedPropertyChanged(const Windows::UI::Xaml::DependencyObject& d, const Windows::UI::Xaml::DependencyPropertyChangedEventArgs& e);
|
||||
|
||||
void _SetAccessibleName();
|
||||
void _UpdateItemsSource();
|
||||
void _ItemsRepeater_ElementPrepared(const Microsoft::UI::Xaml::Controls::ItemsRepeater& sender, const Microsoft::UI::Xaml::Controls::ItemsRepeaterElementPreparedEventArgs& args);
|
||||
|
||||
Microsoft::UI::Xaml::Controls::ItemsRepeater _itemsRepeater{ nullptr };
|
||||
Microsoft::UI::Xaml::Controls::ItemsRepeater::ElementPrepared_revoker _elementPreparedRevoker;
|
||||
};
|
||||
|
||||
// AutomationPeer for SettingsExpander. Reports class name and falls back to
|
||||
@@ -57,6 +70,21 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationControlType GetAutomationControlTypeCore() const;
|
||||
hstring GetClassNameCore() const;
|
||||
hstring GetNameCore() const;
|
||||
|
||||
void RaiseExpandedChangedEvent(bool newValue);
|
||||
};
|
||||
|
||||
// StyleSelector used by SettingsExpander to choose between a clickable vs.
|
||||
// non-clickable SettingsCard container style for items in its ItemsRepeater.
|
||||
// Ported from the Windows Community Toolkit's SettingsExpanderItemStyleSelector.
|
||||
struct SettingsExpanderItemStyleSelector : SettingsExpanderItemStyleSelectorT<SettingsExpanderItemStyleSelector>
|
||||
{
|
||||
SettingsExpanderItemStyleSelector() = default;
|
||||
|
||||
Windows::UI::Xaml::Style SelectStyleCore(const Windows::Foundation::IInspectable& item, const Windows::UI::Xaml::DependencyObject& container);
|
||||
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::Style, DefaultStyle, nullptr);
|
||||
WINRT_PROPERTY(Windows::UI::Xaml::Style, ClickableStyle, nullptr);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,4 +92,5 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(SettingsExpander);
|
||||
BASIC_FACTORY(SettingsExpanderAutomationPeer);
|
||||
BASIC_FACTORY(SettingsExpanderItemStyleSelector);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,18 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
Windows.UI.Xaml.UIElement ItemsFooter;
|
||||
static Windows.UI.Xaml.DependencyProperty ItemsFooterProperty { get; };
|
||||
|
||||
Windows.Foundation.Collections.IVector<Object> Items;
|
||||
static Windows.UI.Xaml.DependencyProperty ItemsProperty { get; };
|
||||
|
||||
Object ItemsSource;
|
||||
static Windows.UI.Xaml.DependencyProperty ItemsSourceProperty { get; };
|
||||
|
||||
Object ItemTemplate;
|
||||
static Windows.UI.Xaml.DependencyProperty ItemTemplateProperty { get; };
|
||||
|
||||
Windows.UI.Xaml.Controls.StyleSelector ItemContainerStyleSelector;
|
||||
static Windows.UI.Xaml.DependencyProperty ItemContainerStyleSelectorProperty { get; };
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<SettingsExpander, Object> Expanded;
|
||||
event Windows.Foundation.TypedEventHandler<SettingsExpander, Object> Collapsed;
|
||||
};
|
||||
@@ -35,5 +47,15 @@ namespace Microsoft.Terminal.Settings.Editor
|
||||
[default_interface] runtimeclass SettingsExpanderAutomationPeer : Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer
|
||||
{
|
||||
SettingsExpanderAutomationPeer(SettingsExpander owner);
|
||||
|
||||
void RaiseExpandedChangedEvent(Boolean newValue);
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass SettingsExpanderItemStyleSelector : Windows.UI.Xaml.Controls.StyleSelector
|
||||
{
|
||||
SettingsExpanderItemStyleSelector();
|
||||
|
||||
Windows.UI.Xaml.Style DefaultStyle;
|
||||
Windows.UI.Xaml.Style ClickableStyle;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user