From 25106dcd908652abf4f8de2d4853084aac1c33f2 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Mon, 17 Nov 2025 17:08:12 +0200 Subject: [PATCH 01/14] Added Orientation property to EqualPanel --- components/Segmented/src/EqualPanel.cs | 112 +++++++++++++++++++------ 1 file changed, 87 insertions(+), 25 deletions(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index 3caa7cbf..d2befe6f 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,42 @@ 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); + RegisterPropertyChangedCallback(VerticalAlignmentProperty, OnAlignmentChanged); } /// @@ -60,19 +80,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 contstruction + 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 +123,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; + } } From 4d35ca1cf80b45dbc916c3374df55075304fa46a Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Mon, 17 Nov 2025 19:24:59 +0200 Subject: [PATCH 02/14] Added orientation to default Segmented style --- .../samples/SegmentedBasicSample.xaml | 2 + .../samples/SegmentedBasicSample.xaml.cs | 8 +++ .../src/Segmented/Segmented.Properties.cs | 26 ++++++++ .../Segmented/src/Segmented/Segmented.cs | 13 ++++ .../Segmented/src/Segmented/Segmented.xaml | 3 +- .../src/SegmentedItem/SegmentedItem.cs | 51 +++++++-------- .../src/SegmentedItem/SegmentedItem.xaml | 63 +++++++++++++------ 7 files changed, 121 insertions(+), 45 deletions(-) create mode 100644 components/Segmented/src/Segmented/Segmented.Properties.cs 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/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..982eaa94 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,16 @@ private void OnSelectedIndexChanged(DependencyObject sender, DependencyProperty _internalSelectedIndex = SelectedIndex; } } + + private void OnOrientationChanged() + { + for (int i = 0; i < Items.Count; i++) + { + var container = ContainerFromIndex(i) as SegmentedItem; + if (container is null) + continue; + + container.UpdateOrientation(Orientation); + } + } } diff --git a/components/Segmented/src/Segmented/Segmented.xaml b/components/Segmented/src/Segmented/Segmented.xaml index ffc5460b..960bcfc6 100644 --- a/components/Segmented/src/Segmented/Segmented.xaml +++ b/components/Segmented/src/Segmented/Segmented.xaml @@ -58,6 +58,7 @@ @@ -111,7 +112,7 @@ - diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs index 6fc91435..a6145ee4 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.cs +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Windows.Media.Devices; + namespace CommunityToolkit.WinUI.Controls; /// @@ -11,9 +13,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 +34,7 @@ public SegmentedItem() protected override void OnApplyTemplate() { base.OnApplyTemplate(); - OnIconChanged(); - ContentChanged(); + UpdateState(); } /// @@ -36,38 +43,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. Treast 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..c17de8be 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml @@ -1,4 +1,4 @@ - @@ -405,12 +405,33 @@ + + + + + + + + + + - + + + + + + + + + + + + + - @@ -634,6 +655,10 @@ + + + + - - - - - + + + + + + From 99738b14262f71c2c96f5e55f946eede62d66a2a Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Mon, 17 Nov 2025 23:10:23 +0200 Subject: [PATCH 03/14] Fixed orientation binding issue in ButtonSegmentedStyle --- components/Segmented/src/Segmented/Segmented.xaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/Segmented/src/Segmented/Segmented.xaml b/components/Segmented/src/Segmented/Segmented.xaml index 960bcfc6..72d8dced 100644 --- a/components/Segmented/src/Segmented/Segmented.xaml +++ b/components/Segmented/src/Segmented/Segmented.xaml @@ -48,6 +48,7 @@ + @@ -112,7 +113,8 @@ - From c4e0ae3bfb8cafed8186f3519e6150266ad700ae Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Mon, 17 Nov 2025 23:11:41 +0200 Subject: [PATCH 04/14] Refined default SegmentedItem style --- components/Segmented/src/SegmentedItem/SegmentedItem.xaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml index c17de8be..14d436ee 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml @@ -1,4 +1,4 @@ - @@ -419,10 +419,14 @@ + + - + + + From 093c4b22cde48326199940027eb4bf70afcba43d Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Tue, 18 Nov 2025 17:00:19 +0200 Subject: [PATCH 05/14] Removed unnecessary namespace reference and simplified OnOrientationChanged logic --- components/Segmented/src/Segmented/Segmented.cs | 9 ++++----- components/Segmented/src/SegmentedItem/SegmentedItem.cs | 2 -- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/components/Segmented/src/Segmented/Segmented.cs b/components/Segmented/src/Segmented/Segmented.cs index 982eaa94..938dca8e 100644 --- a/components/Segmented/src/Segmented/Segmented.cs +++ b/components/Segmented/src/Segmented/Segmented.cs @@ -160,11 +160,10 @@ private void OnOrientationChanged() { for (int i = 0; i < Items.Count; i++) { - var container = ContainerFromIndex(i) as SegmentedItem; - if (container is null) - continue; - - container.UpdateOrientation(Orientation); + if (ContainerFromIndex(i) is SegmentedItem item) + { + item.UpdateOrientation(Orientation); + } } } } diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs index a6145ee4..61cdb58b 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.cs +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.cs @@ -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 Windows.Media.Devices; - namespace CommunityToolkit.WinUI.Controls; /// From 9924e892fd760c4be999cef46427013d49c37432 Mon Sep 17 00:00:00 2001 From: Avishai Dernis Date: Wed, 19 Nov 2025 02:41:33 +0200 Subject: [PATCH 06/14] Apply typo fixes from code review Co-authored-by: Andrew KeepCoding --- components/Segmented/src/EqualPanel.cs | 2 +- components/Segmented/src/SegmentedItem/SegmentedItem.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index d2befe6f..86ed8491 100644 --- a/components/Segmented/src/EqualPanel.cs +++ b/components/Segmented/src/EqualPanel.cs @@ -102,7 +102,7 @@ protected override Size MeasureOverride(Size availableSize) double totalU = availableU - (Spacing * (_visibleItemsCount - 1)); maxItemU = totalU / _visibleItemsCount; - // Set uSize/vSize for XY result contstruction + // Set uSize/vSize for XY result construction uSize = availableU; vSize = maxItemV; } diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs index 61cdb58b..7d46eb63 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.cs +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.cs @@ -62,7 +62,7 @@ private void UpdateState() (false, false) => _isVertical ? IconTopState : IconLeftState, (false, true) => IconOnlyState, (true, false) => ContentOnlyState, - (true, true) => ContentOnlyState, // Invalid state. Treast as content only + (true, true) => ContentOnlyState, // Invalid state. Treat as content only }; // Update states From 00de4bcae5d5e04edac927bfd01a9bb6bfdb189e Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Wed, 19 Nov 2025 02:42:42 +0200 Subject: [PATCH 07/14] Removed unneccesary callback registration EqualPanel constructor --- components/Segmented/src/EqualPanel.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index 86ed8491..88b4ba11 100644 --- a/components/Segmented/src/EqualPanel.cs +++ b/components/Segmented/src/EqualPanel.cs @@ -59,7 +59,6 @@ public Orientation Orientation public EqualPanel() { RegisterPropertyChangedCallback(HorizontalAlignmentProperty, OnAlignmentChanged); - RegisterPropertyChangedCallback(VerticalAlignmentProperty, OnAlignmentChanged); } /// From fccfc847b21feb5675c18b903fba7cb3bd6f4a43 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Wed, 26 Nov 2025 17:30:09 +0200 Subject: [PATCH 08/14] Added orientation option to styles sample --- components/Segmented/samples/SegmentedStylesSample.xaml | 8 +++++--- .../Segmented/samples/SegmentedStylesSample.xaml.cs | 8 ++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) 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(), + }; } From db22dd1aba2c205309013e57ac20100b7ec1fc6e Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Wed, 26 Nov 2025 17:58:38 +0200 Subject: [PATCH 09/14] Adjusted DefaultSegmentedStyle IconTop style --- components/Segmented/src/SegmentedItem/SegmentedItem.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml index 14d436ee..9def1779 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml @@ -419,14 +419,14 @@ - - + + - + - + From aa68ff5712be22423617eabb1a0131b702eb92ff Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Wed, 26 Nov 2025 23:50:32 +0200 Subject: [PATCH 10/14] Fixed alignment issues in SegmentedItem IconTop VisualState --- .../samples/SegmentedBasicSample.xaml | 2 +- .../src/SegmentedItem/SegmentedItem.xaml | 31 +++++++------------ 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/components/Segmented/samples/SegmentedBasicSample.xaml b/components/Segmented/samples/SegmentedBasicSample.xaml index f3307afa..51ef53b1 100644 --- a/components/Segmented/samples/SegmentedBasicSample.xaml +++ b/components/Segmented/samples/SegmentedBasicSample.xaml @@ -1,4 +1,4 @@ - + - - + + + + - - @@ -436,13 +436,11 @@ - - @@ -650,19 +648,12 @@ - - - - - - - - - + - + Date: Thu, 27 Nov 2025 00:00:19 +0200 Subject: [PATCH 11/14] Added vertical behavior for the PivotSegmentedStyle --- .../Segmented/src/Segmented/Segmented.xaml | 5 +- .../src/SegmentedItem/SegmentedItem.xaml | 67 ++++++++++--------- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/components/Segmented/src/Segmented/Segmented.xaml b/components/Segmented/src/Segmented/Segmented.xaml index 72d8dced..b7166894 100644 --- a/components/Segmented/src/Segmented/Segmented.xaml +++ b/components/Segmented/src/Segmented/Segmented.xaml @@ -1,4 +1,4 @@ - - diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml index d7cdd238..6f7d97c5 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.xaml +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.xaml @@ -1,4 +1,4 @@ - @@ -665,7 +665,6 @@ + + + + + + + + + + - + - - - @@ -878,15 +884,11 @@ - - - - - + - - - - - - + + + + + + + From 4b3fb0eb699254463451e9b111de43f8b853eaf8 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Thu, 27 Nov 2025 00:18:12 +0200 Subject: [PATCH 12/14] Addressed simple code style suggestions from Arlo --- components/Segmented/src/EqualPanel.cs | 70 +++++++++---------- .../src/SegmentedItem/SegmentedItem.cs | 12 ++-- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index 88b4ba11..75e6fe95 100644 --- a/components/Segmented/src/EqualPanel.cs +++ b/components/Segmented/src/EqualPanel.cs @@ -2,7 +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 Microsoft.UI.Xaml.Controls; using System.Data; namespace CommunityToolkit.WinUI.Controls; @@ -77,46 +76,45 @@ 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 { - 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) - { - // 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 - { - uSize = (maxItemU * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)); - vSize = maxItemV; - } - - return new Size(xSize, ySize); + 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) + { + // 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 { - return new Size(0, 0); + uSize = (maxItemU * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)); + vSize = maxItemV; } + + return new Size(xSize, ySize); } /// diff --git a/components/Segmented/src/SegmentedItem/SegmentedItem.cs b/components/Segmented/src/SegmentedItem/SegmentedItem.cs index 7d46eb63..346d4dd9 100644 --- a/components/Segmented/src/SegmentedItem/SegmentedItem.cs +++ b/components/Segmented/src/SegmentedItem/SegmentedItem.cs @@ -32,7 +32,7 @@ public SegmentedItem() protected override void OnApplyTemplate() { base.OnApplyTemplate(); - UpdateState(); + UpdateVisualStates(); } /// @@ -41,21 +41,21 @@ protected override void OnApplyTemplate() protected override void OnContentChanged(object oldContent, object newContent) { base.OnContentChanged(oldContent, newContent); - UpdateState(); + UpdateVisualStates(); } /// /// Handles changes to the Icon property. /// - protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue) => UpdateState(); + protected virtual void OnIconPropertyChanged(IconElement oldValue, IconElement newValue) => UpdateVisualStates(); internal void UpdateOrientation(Orientation orientation) { _isVertical = orientation is Orientation.Vertical; - UpdateState(); + UpdateVisualStates(); } - private void UpdateState() + private void UpdateVisualStates() { string contentState = (Icon is null, Content is null) switch { @@ -65,7 +65,7 @@ private void UpdateState() (true, true) => ContentOnlyState, // Invalid state. Treat as content only }; - // Update states + // Update visual states VisualStateManager.GoToState(this, contentState, true); VisualStateManager.GoToState(this, _isVertical ? VerticalState : HorizontalState, true); } From 05c7684e74c916239b26f7df21684a17688134f4 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Thu, 27 Nov 2025 00:39:20 +0200 Subject: [PATCH 13/14] Replaced SelectAxis method with UVCoord ref struct for mapping coordinates --- components/Segmented/src/EqualPanel.cs | 75 ++++++++++++++++++++------ 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index 75e6fe95..9b26e4b9 100644 --- a/components/Segmented/src/EqualPanel.cs +++ b/components/Segmented/src/EqualPanel.cs @@ -92,26 +92,24 @@ protected override Size MeasureOverride(Size availableSize) 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); + 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)); - maxItemU = totalU / _visibleItemsCount; + maxItemSize.U = totalU / _visibleItemsCount; // Set uSize/vSize for XY result construction - uSize = availableU; - vSize = maxItemV; + uvSize.U = availableU; + uvSize.V = maxItemSize.V; } else { - uSize = (maxItemU * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)); - vSize = maxItemV; + uvSize.U = (maxItemSize.U * _visibleItemsCount) + (Spacing * (_visibleItemsCount - 1)); + uvSize.V = maxItemSize.V; } return new Size(xSize, ySize); @@ -125,12 +123,11 @@ protected override Size ArrangeOverride(Size finalSize) double y = 0; // Define UV axis - ref double u = ref x; + var pos = new UVCoord(ref x, ref y, Orientation); ref double maxItemU = ref _maxItemWidth; double finalSizeU = finalSize.Width; if (Orientation is Orientation.Vertical) { - u = ref y; maxItemU = ref _maxItemHeight; finalSizeU = finalSize.Height; } @@ -146,7 +143,7 @@ protected override Size ArrangeOverride(Size finalSize) { // NOTE: The arrange method is still in X/Y coordinate system child.Arrange(new Rect(x, y, _maxItemWidth, _maxItemHeight)); - u += maxItemU + Spacing; + pos.U += maxItemU + Spacing; } return finalSize; } @@ -162,11 +159,55 @@ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChan panel.InvalidateMeasure(); } - private static ref double SelectAxis(Orientation orientation, ref double x, ref double y, bool u) + /// + /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system. + /// + private ref struct UVCoord { - if ((orientation is Orientation.Horizontal && u) || (orientation is Orientation.Vertical && !u)) - return ref x; - else - return ref y; + private readonly bool _vertical; + + private ref double _x; + private ref double _y; + + public UVCoord(ref double x, ref double y, Orientation orientation) + { + _x = ref x; + _y = ref y; + _vertical = orientation is Orientation.Vertical; + } + + public ref double X => ref _x; + + public ref double Y => ref _y; + + public ref double U + { + get + { + if (_vertical) + { + return ref Y; + } + else + { + return ref X; + } + } + } + + public ref double V + { + get + { + if (_vertical) + { + return ref X; + } + else + { + return ref Y; + } + } + } } } From bb11f0567d7b6bc49531ac7294151509372c7e53 Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Fri, 28 Nov 2025 10:06:07 +0200 Subject: [PATCH 14/14] Removed X/Y coords from UVCoord and changed U/V refs to precalculate --- components/Segmented/src/EqualPanel.cs | 51 +++++++------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/components/Segmented/src/EqualPanel.cs b/components/Segmented/src/EqualPanel.cs index 9b26e4b9..4f01131f 100644 --- a/components/Segmented/src/EqualPanel.cs +++ b/components/Segmented/src/EqualPanel.cs @@ -162,52 +162,27 @@ private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChan /// /// A struct for mapping X/Y coordinates to an orientation adjusted U/V coordinate system. /// - private ref struct UVCoord + private readonly ref struct UVCoord { - private readonly bool _vertical; - - private ref double _x; - private ref double _y; + private readonly ref double _u; + private readonly ref double _v; public UVCoord(ref double x, ref double y, Orientation orientation) { - _x = ref x; - _y = ref y; - _vertical = orientation is Orientation.Vertical; - } - - public ref double X => ref _x; - - public ref double Y => ref _y; - - public ref double U - { - get + if (orientation is Orientation.Horizontal) { - if (_vertical) - { - return ref Y; - } - else - { - return ref X; - } + _u = ref x; + _v = ref y; } - } - - public ref double V - { - get + else { - if (_vertical) - { - return ref X; - } - else - { - return ref Y; - } + _u = ref y; + _v = ref x; } } + + public readonly ref double U => ref _u; + + public readonly ref double V => ref _v; } }