diff --git a/components/Segmented/samples/SegmentedBasicSample.xaml b/components/Segmented/samples/SegmentedBasicSample.xaml
index f00f791e..51ef53b1 100644
--- a/components/Segmented/samples/SegmentedBasicSample.xaml
+++ b/components/Segmented/samples/SegmentedBasicSample.xaml
@@ -1,4 +1,4 @@
-
+
[ToolkitSampleMultiChoiceOption("SelectionMode", "Single", "Multiple", Title = "Selection mode")]
[ToolkitSampleMultiChoiceOption("Alignment", "Left", "Center", "Right", "Stretch", Title = "Horizontal alignment")]
+[ToolkitSampleMultiChoiceOption("OrientationMode", "Horizontal", "Vertical", Title = "Orientation")]
[ToolkitSample(id: nameof(SegmentedBasicSample), "Basics", description: $"A sample for showing how to create and use a {nameof(Segmented)} custom control.")]
public sealed partial class SegmentedBasicSample : Page
@@ -36,5 +37,12 @@ public SegmentedBasicSample()
"Stretch" => HorizontalAlignment.Stretch,
_ => throw new System.NotImplementedException(),
};
+
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Horizontal" => Orientation.Horizontal,
+ "Vertical" => Orientation.Vertical,
+ _ => throw new System.NotImplementedException(),
+ };
}
diff --git a/components/Segmented/samples/SegmentedStylesSample.xaml b/components/Segmented/samples/SegmentedStylesSample.xaml
index 72b1b75b..7a2b20d0 100644
--- a/components/Segmented/samples/SegmentedStylesSample.xaml
+++ b/components/Segmented/samples/SegmentedStylesSample.xaml
@@ -1,4 +1,4 @@
-
+
-
Item 1
@@ -31,7 +32,8 @@
-
[ToolkitSampleMultiChoiceOption("SelectionMode", "Single", "Multiple", Title = "Selection mode")]
+[ToolkitSampleMultiChoiceOption("OrientationMode", "Horizontal", "Vertical", Title = "Orientation")]
[ToolkitSample(id: nameof(SegmentedStylesSample), "Additional styles", description: "A sample on how to use different built-in styles.")]
public sealed partial class SegmentedStylesSample : Page
@@ -22,4 +23,11 @@ public SegmentedStylesSample()
"Multiple" => ListViewSelectionMode.Multiple,
_ => throw new System.NotImplementedException(),
};
+
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Horizontal" => Orientation.Horizontal,
+ "Vertical" => Orientation.Vertical,
+ _ => throw new System.NotImplementedException(),
+ };
}
diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs
index 3caa7cbf..4f01131f 100644
--- a/components/Segmented/src/EqualPanel.cs
+++ b/components/Segmented/src/EqualPanel.cs
@@ -14,15 +14,6 @@ public partial class EqualPanel : Panel
private double _maxItemWidth = 0;
private double _maxItemHeight = 0;
private int _visibleItemsCount = 0;
-
- ///
- /// Gets or sets the spacing between items.
- ///
- public double Spacing
- {
- get { return (double)GetValue(SpacingProperty); }
- set { SetValue(SpacingProperty, value); }
- }
///
/// Identifies the Spacing dependency property.
@@ -32,14 +23,41 @@ public double Spacing
nameof(Spacing),
typeof(double),
typeof(EqualPanel),
- new PropertyMetadata(default(double), OnSpacingChanged));
+ new PropertyMetadata(default(double), OnPropertyChanged));
+
+ ///
+ /// Backing for the property.
+ ///
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
+ nameof(Orientation),
+ typeof(Orientation),
+ typeof(EqualPanel),
+ new PropertyMetadata(default(Orientation), OnPropertyChanged));
+
+ ///
+ /// Gets or sets the spacing between items.
+ ///
+ public double Spacing
+ {
+ get => (double)GetValue(SpacingProperty);
+ set => SetValue(SpacingProperty, value);
+ }
+
+ ///
+ /// Gets or sets the panel orientation.
+ ///
+ public Orientation Orientation
+ {
+ get => (Orientation)GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
///
/// Creates a new instance of the class.
///
public EqualPanel()
{
- RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnHorizontalAlignmentChanged);
+ RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnAlignmentChanged);
}
///
@@ -58,56 +76,113 @@ protected override Size MeasureOverride(Size availableSize)
_maxItemHeight = Math.Max(_maxItemHeight, child.DesiredSize.Height);
}
- if (_visibleItemsCount > 0)
+ // No children, no space taken
+ if (_visibleItemsCount <= 0)
+ return new Size(0, 0);
+
+ // Determine if the desired alignment is stretched.
+ // Don't stretch if infinite space is available though. Attempting to divide infinite space will result in a crash.
+ bool stretch = Orientation switch
{
- // Return equal widths based on the widest item
- // In very specific edge cases the AvailableWidth might be infinite resulting in a crash.
- if (HorizontalAlignment != HorizontalAlignment.Stretch || double.IsInfinity(availableSize.Width))
- {
- return new Size((_maxItemWidth * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)), _maxItemHeight);
- }
- else
- {
- // Equal columns based on the available width, adjust for spacing
- double totalWidth = availableSize.Width - (Spacing * (_visibleItemsCount - 1));
- _maxItemWidth = totalWidth / _visibleItemsCount;
- return new Size(availableSize.Width, _maxItemHeight);
- }
+ Orientation.Horizontal => HorizontalAlignment is HorizontalAlignment.Stretch && !double.IsInfinity(availableSize.Width),
+ Orientation.Vertical or _ => VerticalAlignment is VerticalAlignment.Stretch && !double.IsInfinity(availableSize.Height),
+ };
+
+ // Define XY coords
+ double xSize = 0, ySize = 0;
+
+ // Define UV coords for orientation agnostic XY manipulation
+ var uvSize = new UVCoord(ref xSize, ref ySize, Orientation);
+ var maxItemSize = new UVCoord(ref _maxItemWidth, ref _maxItemHeight, Orientation);
+ double availableU = Orientation is Orientation.Horizontal ? availableSize.Width : availableSize.Height;
+
+ if (stretch)
+ {
+ // Adjust maxItemU to form equal rows/columns by available U space (adjust for spacing)
+ double totalU = availableU - (Spacing * (_visibleItemsCount - 1));
+ maxItemSize.U = totalU / _visibleItemsCount;
+
+ // Set uSize/vSize for XY result construction
+ uvSize.U = availableU;
+ uvSize.V = maxItemSize.V;
}
else
{
- return new Size(0, 0);
+ uvSize.U = (maxItemSize.U * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1));
+ uvSize.V = maxItemSize.V;
}
+
+ return new Size(xSize, ySize);
}
///
protected override Size ArrangeOverride(Size finalSize)
{
+ // Define X and Y
double x = 0;
+ double y = 0;
+ // Define UV axis
+ var pos = new UVCoord(ref x, ref y, Orientation);
+ ref double maxItemU = ref _maxItemWidth;
+ double finalSizeU = finalSize.Width;
+ if (Orientation is Orientation.Vertical)
+ {
+ maxItemU = ref _maxItemHeight;
+ finalSizeU = finalSize.Height;
+ }
+
// Check if there's more (little) width available - if so, set max item width to the maximum possible as we have an almost perfect height.
- if (finalSize.Width > _visibleItemsCount * _maxItemWidth + (Spacing * (_visibleItemsCount - 1)))
+ if (finalSizeU > _visibleItemsCount * maxItemU + (Spacing * (_visibleItemsCount - 1)))
{
- _maxItemWidth = (finalSize.Width - (Spacing * (_visibleItemsCount - 1))) / _visibleItemsCount;
+ maxItemU = (finalSizeU - (Spacing * (_visibleItemsCount - 1))) / _visibleItemsCount;
}
var elements = Children.Where(static e => e.Visibility == Visibility.Visible);
foreach (var child in elements)
{
- child.Arrange(new Rect(x, 0, _maxItemWidth, _maxItemHeight));
- x += _maxItemWidth + Spacing;
+ // NOTE: The arrange method is still in X/Y coordinate system
+ child.Arrange(new Rect(x, y, _maxItemWidth, _maxItemHeight));
+ pos.U += maxItemU + Spacing;
}
return finalSize;
}
- private void OnHorizontalAlignmentChanged(DependencyObject sender, DependencyProperty dp)
+ private void OnAlignmentChanged(DependencyObject sender, DependencyProperty dp)
{
InvalidateMeasure();
}
- private static void OnSpacingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var panel = (EqualPanel)d;
panel.InvalidateMeasure();
}
+
+ ///
+ /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system.
+ ///
+ private readonly ref struct UVCoord
+ {
+ private readonly ref double _u;
+ private readonly ref double _v;
+
+ public UVCoord(ref double x, ref double y, Orientation orientation)
+ {
+ if (orientation is Orientation.Horizontal)
+ {
+ _u = ref x;
+ _v = ref y;
+ }
+ else
+ {
+ _u = ref y;
+ _v = ref x;
+ }
+ }
+
+ public readonly ref double U => ref _u;
+
+ public readonly ref double V => ref _v;
+ }
}
diff --git a/components/Segmented/src/Segmented/Segmented.Properties.cs b/components/Segmented/src/Segmented/Segmented.Properties.cs
new file mode 100644
index 00000000..c28d7a4e
--- /dev/null
+++ b/components/Segmented/src/Segmented/Segmented.Properties.cs
@@ -0,0 +1,26 @@
+// 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.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+public partial class Segmented
+{
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
+ nameof(Orientation),
+ typeof(Orientation),
+ typeof(Segmented),
+ new PropertyMetadata(Orientation.Horizontal, (d, e) => ((Segmented)d).OnOrientationChanged()));
+
+ ///
+ /// Gets or sets the orientation.
+ ///
+ public Orientation Orientation
+ {
+ get => (Orientation)GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+}
diff --git a/components/Segmented/src/Segmented/Segmented.cs b/components/Segmented/src/Segmented/Segmented.cs
index d649ce46..938dca8e 100644
--- a/components/Segmented/src/Segmented/Segmented.cs
+++ b/components/Segmented/src/Segmented/Segmented.cs
@@ -22,6 +22,7 @@ public Segmented()
this.DefaultStyleKey = typeof(Segmented);
RegisterPropertyChangedCallback(SelectedIndexProperty, OnSelectedIndexChanged);
+ RegisterPropertyChangedCallback(OrientationProperty, OnSelectedIndexChanged);
}
///
@@ -154,4 +155,15 @@ private void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty
_internalSelectedIndex = SelectedIndex;
}
}
+
+ private void OnOrientationChanged()
+ {
+ for (int i = 0; i < Items.Count; i++)
+ {
+ if (ContainerFromIndex(i) is SegmentedItem item)
+ {
+ item.UpdateOrientation(Orientation);
+ }
+ }
+ }
}
diff --git a/components/Segmented/src/Segmented/Segmented.xaml b/components/Segmented/src/Segmented/Segmented.xaml
index ffc5460b..b7166894 100644
--- a/components/Segmented/src/Segmented/Segmented.xaml
+++ b/components/Segmented/src/Segmented/Segmented.xaml
@@ -1,4 +1,4 @@
-
+
@@ -58,6 +59,7 @@
@@ -91,7 +93,8 @@
-
@@ -111,7 +114,8 @@
-
diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs
index 6fc91435..346d4dd9 100644
--- a/components/Segmented/src/SegmentedItem/SegmentedItem.cs
+++ b/components/Segmented/src/SegmentedItem/SegmentedItem.cs
@@ -11,9 +11,15 @@ namespace CommunityToolkit.WinUI.Controls;
public partial class SegmentedItem : ListViewItem
{
internal const string IconLeftState = "IconLeft";
+ internal const string IconTopState = "IconTop";
internal const string IconOnlyState = "IconOnly";
internal const string ContentOnlyState = "ContentOnly";
+ internal const string HorizontalState = "Horizontal";
+ internal const string VerticalState = "Vertical";
+
+ private bool _isVertical = false;
+
///
/// Creates a new instance of .
///
@@ -26,8 +32,7 @@ public SegmentedItem()
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
- OnIconChanged();
- ContentChanged();
+ UpdateVisualStates();
}
///
@@ -36,38 +41,32 @@ protected override void OnApplyTemplate()
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
- ContentChanged();
- }
-
- private void ContentChanged()
- {
- if (Content != null)
- {
- VisualStateManager.GoToState(this, IconLeftState, true);
- }
- else
- {
- VisualStateManager.GoToState(this, IconOnlyState, true);
- }
+ UpdateVisualStates();
}
///
/// Handles changes to the Icon property.
///
- protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue)
+ protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue) => UpdateVisualStates();
+
+ internal void UpdateOrientation(Orientation orientation)
{
- OnIconChanged();
+ _isVertical = orientation is Orientation.Vertical;
+ UpdateVisualStates();
}
- private void OnIconChanged()
+ private void UpdateVisualStates()
{
- if (Icon != null)
+ string contentState = (Icon is null, Content is null) switch
{
- VisualStateManager.GoToState(this, IconLeftState, true);
- }
- else
- {
- VisualStateManager.GoToState(this, ContentOnlyState, true);
- }
+ (false, false) => _isVertical ? IconTopState : IconLeftState,
+ (false, true) => IconOnlyState,
+ (true, false) => ContentOnlyState,
+ (true, true) => ContentOnlyState, // Invalid state. Treat as content only
+ };
+
+ // Update visual states
+ VisualStateManager.GoToState(this, contentState, true);
+ VisualStateManager.GoToState(this, _isVertical ? VerticalState : HorizontalState, true);
}
}
diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml
index f0ee77f5..6f7d97c5 100644
--- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml
+++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml
@@ -1,4 +1,4 @@
-
@@ -405,19 +405,42 @@
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -625,15 +648,12 @@
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -693,19 +712,26 @@
Control.IsTemplateFocusTarget="True"
CornerRadius="{TemplateBinding CornerRadius}">
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
@@ -858,15 +884,11 @@
-
-
-
-
-
+
-
-
-
-
-
-
+
+
+
+
+
+
+