Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SettingsControls] Bugfixes and improvements #373

Merged
merged 9 commits into from
Feb 26, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="SettingsControlsExperiment.Samples.SettingsExpanderSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Expand All @@ -11,7 +11,6 @@
<labs:SettingsExpander x:Name="settingsCard"
Description="The SettingsExpander has the same properties as a Card, and you can set SettingsCard as part of the Items collection."
Header="SettingsExpander"
IsEnabled="{x:Bind IsCardEnabled, Mode=OneWay}"
IsExpanded="{x:Bind IsCardExpanded, Mode=OneWay}">
<!-- TODO: This should be TwoWay bound but throws compile error in Uno. -->
<labs:SettingsExpander.HeaderIcon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace SettingsControlsExperiment.Samples;

[ToolkitSampleBoolOption("IsCardEnabled", true, Title = "Is Enabled")]
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
[ToolkitSampleBoolOption("IsCardExpanded", false, Title = "Is Expanded")]
// Single values without a colon are used for both label and value.
// To provide a different label for the value, separate with a colon surrounded by a single space on both sides ("label : value").
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<ToolkitComponentName>SettingsControls</ToolkitComponentName>
<Description>This package contains the SettingsCard and SettingsExpander controls.</Description>
<Version>0.0.15</Version>
<Version>0.0.16</Version>
<LangVersion>10.0</LangVersion>

<!-- Rns suffix is required for namespaces shared across projects. See https://github.com/CommunityToolkit/Labs-Windows/issues/152 -->
Expand Down
141 changes: 141 additions & 0 deletions components/SettingsControls/src/Helpers/IsNullOrEmptyStateTrigger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections;
using System.Collections.Specialized;

#if WINAPPSDK
using CommunityToolkit.WinUI.Helpers;
#else
using Microsoft.Toolkit.Uwp.Helpers;
#endif


namespace CommunityToolkit.Labs.WinUI;

/// <summary>
/// Enables a state if an Object is <c>null</c> or a String/IEnumerable is empty
/// </summary>
public class IsNullOrEmptyStateTrigger : StateTriggerBase
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
{
/// <summary>
/// Gets or sets the value used to check for <c>null</c> or empty.
/// </summary>
public object Value
{
get { return GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}

/// <summary>
/// Identifies the <see cref="Value"/> DependencyProperty
/// </summary>
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(object), typeof(IsNullOrEmptyStateTrigger), new PropertyMetadata(null, OnValuePropertyChanged));

public IsNullOrEmptyStateTrigger()
{
UpdateTrigger();
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
}

private void UpdateTrigger()
{
var val = Value;

SetActive(IsNullOrEmpty(val));

if (val == null)
{
return;
}

// Try to listen for various notification events
// Starting with INorifyCollectionChanged
#pragma warning disable CS8622 // Nullability of reference types
var valNotifyCollection = val as INotifyCollectionChanged;
if (valNotifyCollection != null)
{
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, NotifyCollectionChangedEventArgs>(this)
{
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
OnDetachAction = (weakEventListener) => valNotifyCollection.CollectionChanged -= weakEventListener.OnEvent
};

valNotifyCollection.CollectionChanged += weakEvent.OnEvent;
#pragma warning restore CS8622
return;
}

// Not INotifyCollectionChanged, try IObservableVector
var valObservableVector = val as IObservableVector<object>;
if (valObservableVector != null)
{
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, IVectorChangedEventArgs>(this)
{
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
OnDetachAction = (weakEventListener) => valObservableVector.VectorChanged -= weakEventListener.OnEvent
};

valObservableVector.VectorChanged += weakEvent.OnEvent;
return;
}

// Not INotifyCollectionChanged, try IObservableMap
var valObservableMap = val as IObservableMap<object, object>;
if (valObservableMap != null)
{
var weakEvent = new WeakEventListener<IsNullOrEmptyStateTrigger, object, IMapChangedEventArgs<object>>(this)
{
OnEventAction = static (instance, source, args) => instance.SetActive(IsNullOrEmpty(source)),
OnDetachAction = (weakEventListener) => valObservableMap.MapChanged -= weakEventListener.OnEvent
};

valObservableMap.MapChanged += weakEvent.OnEvent;
}
}

private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = (IsNullOrEmptyStateTrigger)d;
obj.UpdateTrigger();
}

private static bool IsNullOrEmpty(object val)
{
if (val == null)
{
return true;
}

// Object is not null, check for an empty string
var valString = val as string;
if (valString != null)
{
return valString.Length == 0;
}

// Object is not a string, check for an empty ICollection (faster)
var valCollection = val as ICollection;
if (valCollection != null)
{
return valCollection.Count == 0;
}

// Object is not an ICollection, check for an empty IEnumerable
var valEnumerable = val as IEnumerable;
if (valEnumerable != null)
{
foreach (var item in valEnumerable)
{
// Found an item, not empty
return false;
}

return true;
}

// Not null and not a known type to test for emptiness
return false;
}
}
53 changes: 38 additions & 15 deletions components/SettingsControls/src/SettingsCard/SettingsCard.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -294,22 +294,12 @@
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

<VisualStateGroup x:Name="ContentAlignmentStates">
<!-- Default -->
<VisualState x:Name="Right" />
<VisualState x:Name="Left">
<VisualState.StateTriggers>
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
To="Left" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
<Setter Target="PART_DescriptionPresenter.Visibility" Value="Collapsed" />
<Setter Target="PART_HeaderPresenter.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="Left" />
</VisualState.Setters>
</VisualState>

<!-- Whenever the control width is less than SettingsCardWrapThreshold, the Content is below the Header/Description -->
<VisualState x:Name="RightWrapped">
<VisualState.StateTriggers>
<labs:ControlSizeTrigger MinWidth="{ThemeResource SettingsCardWrapNoIconThreshold}"
Expand All @@ -325,6 +315,8 @@
<Setter Target="HeaderPanel.Margin" Value="0" />
</VisualState.Setters>
</VisualState>

<!-- For even smaller widths: the HeaderIcon is collapsed. -->
<VisualState x:Name="RightWrappedNoIcon">
<VisualState.StateTriggers>
<labs:ControlSizeTrigger MaxWidth="{ThemeResource SettingsCardWrapNoIconThreshold}"
Expand All @@ -340,6 +332,24 @@
<Setter Target="HeaderPanel.Margin" Value="0" />
</VisualState.Setters>
</VisualState>

<!-- Header/Description/Icon collapsed, content is to the left. Great for e.g. Checkboxes -->
<VisualState x:Name="Left">
<VisualState.StateTriggers>
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
To="Left" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PART_HeaderIconPresenterHolder.Visibility" Value="Collapsed" />
<Setter Target="PART_DescriptionPresenter.Visibility" Value="Collapsed" />
<Setter Target="PART_HeaderPresenter.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="Left" />
</VisualState.Setters>
</VisualState>

<!-- Similiar to Left, but the HeaderIcon/Header/Description is visible -->
<VisualState x:Name="Vertical">
<VisualState.StateTriggers>
<labs:IsEqualStateTrigger Value="{Binding ContentAlignment, RelativeSource={RelativeSource TemplatedParent}}"
Expand All @@ -354,6 +364,19 @@
</VisualState.Setters>
</VisualState>
</VisualStateGroup>

<!-- Collapsing the Content presenter whenever it's empty -->
<VisualStateGroup x:Name="ContentVisibilityStates">
<VisualState x:Name="Visible" />
<VisualState x:Name="Collapsed">
<VisualState.StateTriggers>
<labs:IsNullOrEmptyStateTrigger Value="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PART_ContentPresenter.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>

<Viewbox x:Name="PART_HeaderIconPresenterHolder"
Expand Down Expand Up @@ -463,6 +486,7 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
Expand Down Expand Up @@ -964,7 +988,6 @@
Grid.ColumnSpan="3"
Margin="0,5"
Background="{ThemeResource ToggleSwitchContainerBackground}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}" />
<ContentPresenter x:Name="OffContentPresenter"
Grid.RowSpan="3"
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.Labs.WinUI.Internal;

namespace CommunityToolkit.Labs.WinUI;

//// Note: ItemsRepeater will request all the available horizontal space: https://github.com/microsoft/microsoft-ui-xaml/issues/3842
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Thickness x:Key="SettingsExpanderItemPadding">58,8,44,8</Thickness>
<Thickness x:Key="SettingsExpanderItemBorderThickness">0,1,0,0</Thickness>
<Thickness x:Key="ClickableSettingsExpanderItemPadding">58,8,16,8</Thickness>
<x:Double x:Key="SettingsExpanderContentMinHeight">16</x:Double>

<Style x:Key="DefaultSettingsExpanderItemStyle"
BasedOn="{StaticResource DefaultSettingsCardStyle}"
Expand Down Expand Up @@ -101,9 +102,7 @@
ItemTemplate="{Binding ItemTemplate, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"
TabFocusNavigation="Local">
<muxc:ItemsRepeater.Layout>
<muxc:UniformGridLayout ItemsStretch="Fill"
MaximumRowsOrColumns="1"
Orientation="Horizontal" />
<muxc:StackLayout Orientation="Vertical" />
</muxc:ItemsRepeater.Layout>
</muxc:ItemsRepeater>
<ContentPresenter Grid.Row="2"
Expand Down Expand Up @@ -263,7 +262,7 @@
<Border x:Name="ExpanderContentClip"
Grid.Row="1">
<Border x:Name="ExpanderContent"
MinHeight="{TemplateBinding MinHeight}"
MinHeight="{ThemeResource SettingsExpanderContentMinHeight}"
niels9001 marked this conversation as resolved.
Show resolved Hide resolved
Padding="{TemplateBinding Padding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Expand Down