port over more WCT SettingsCard/Expander functionality

This commit is contained in:
Carlos Zamora
2026-05-18 15:06:28 -07:00
parent c64b2433da
commit 96d0762a77
14 changed files with 1264 additions and 27 deletions

View 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);
}
}

View 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);
}

View 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; };
}
}

View File

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

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

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

View File

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

View File

@@ -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*/)

View File

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

View File

@@ -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">&#xE972;</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="&#xE974;" />
<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>

View File

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

View File

@@ -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);
}

View File

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