diff --git a/components/Segmented/samples/SegmentedBasicSample.xaml b/components/Segmented/samples/SegmentedBasicSample.xaml
index f00f791e..f3307afa 100644
--- a/components/Segmented/samples/SegmentedBasicSample.xaml
+++ b/components/Segmented/samples/SegmentedBasicSample.xaml
@@ -14,6 +14,7 @@
[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/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs
index 3caa7cbf..88b4ba11 100644
--- a/components/Segmented/src/EqualPanel.cs
+++ b/components/Segmented/src/EqualPanel.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using Microsoft.UI.Xaml.Controls;
using System.Data;
namespace CommunityToolkit.WinUI.Controls;
@@ -14,15 +15,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 +24,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);
}
///
@@ -60,19 +79,39 @@ protected override Size MeasureOverride(Size availableSize)
if (_visibleItemsCount > 0)
{
- // 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))
+ bool stretch = Orientation switch
+ {
+ 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
+ ref double uSize = ref SelectAxis(Orientation, ref xSize, ref ySize, true);
+ ref double vSize = ref SelectAxis(Orientation, ref xSize, ref ySize, false);
+ ref double maxItemU = ref SelectAxis(Orientation, ref _maxItemWidth, ref _maxItemHeight, true);
+ ref double maxItemV = ref SelectAxis(Orientation, ref _maxItemWidth, ref _maxItemHeight, false);
+ double availableU = Orientation is Orientation.Horizontal ? availableSize.Width : availableSize.Height;
+
+ if (stretch)
{
- return new Size((_maxItemWidth * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)), _maxItemHeight);
+ // Adjust maxItemU to form equal rows/columns by available U space (adjust for spacing)
+ double totalU = availableU - (Spacing * (_visibleItemsCount - 1));
+ maxItemU = totalU / _visibleItemsCount;
+
+ // Set uSize/vSize for XY result construction
+ uSize = availableU;
+ vSize = maxItemV;
}
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);
+ uSize = (maxItemU * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1));
+ vSize = maxItemV;
}
+
+ return new Size(xSize, ySize);
}
else
{
@@ -83,31 +122,53 @@ protected override Size MeasureOverride(Size availableSize)
///
protected override Size ArrangeOverride(Size finalSize)
{
+ // Define X and Y
double x = 0;
+ double y = 0;
+ // Define UV axis
+ ref double u = ref x;
+ ref double maxItemU = ref _maxItemWidth;
+ double finalSizeU = finalSize.Width;
+ if (Orientation is Orientation.Vertical)
+ {
+ u = ref y;
+ 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));
+ 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();
}
+
+ private static ref double SelectAxis(Orientation orientation, ref double x, ref double y, bool u)
+ {
+ if ((orientation is Orientation.Horizontal && u) || (orientation is Orientation.Vertical && !u))
+ return ref x;
+ else
+ return ref y;
+ }
}
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..72d8dced 100644
--- a/components/Segmented/src/Segmented/Segmented.xaml
+++ b/components/Segmented/src/Segmented/Segmented.xaml
@@ -48,6 +48,7 @@
+
@@ -58,6 +59,7 @@
@@ -111,7 +113,8 @@
-
diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs
index 6fc91435..7d46eb63 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();
+ UpdateState();
}
///
@@ -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);
- }
+ UpdateState();
}
///
/// Handles changes to the Icon property.
///
- protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue)
+ protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue) => UpdateState();
+
+ internal void UpdateOrientation(Orientation orientation)
{
- OnIconChanged();
+ _isVertical = orientation is Orientation.Vertical;
+ UpdateState();
}
- private void OnIconChanged()
+ private void UpdateState()
{
- 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 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..14d436ee 100644
--- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml
+++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml
@@ -405,12 +405,37 @@
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
@@ -634,6 +659,10 @@
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+