From 95f38bc68533ce4fa7a2b5ca3c17c6396d26bdce Mon Sep 17 00:00:00 2001 From: Avishai Dernis Date: Mon, 12 Jan 2026 00:21:03 +0200 Subject: [PATCH 1/2] Carousel component creation frist commit --- components/Carousel/OpenSolution.bat | 3 + components/Carousel/samples/Assets/icon.png | Bin 0 -> 2216 bytes .../Carousel/samples/Carousel.Samples.csproj | 10 ++ components/Carousel/samples/Carousel.md | 65 +++++++++ .../samples/CarouselCustomSample.xaml | 25 ++++ .../samples/CarouselCustomSample.xaml.cs | 30 ++++ .../samples/CarouselTemplatedSample.xaml | 16 +++ .../samples/CarouselTemplatedSample.xaml.cs | 21 +++ .../CarouselTemplatedStyleCustomSample.xaml | 26 ++++ ...CarouselTemplatedStyleCustomSample.xaml.cs | 21 +++ .../samples/CarouselXbindBackedSample.xaml | 16 +++ .../samples/CarouselXbindBackedSample.xaml.cs | 21 +++ .../CarouselXbindBackedStyleCustomSample.xaml | 26 ++++ ...rouselXbindBackedStyleCustomSample.xaml.cs | 21 +++ .../Carousel/samples/Dependencies.props | 31 ++++ components/Carousel/src/Carousel.cs | 108 ++++++++++++++ .../src/CarouselStyle_ClassicBinding.xaml | 62 ++++++++ .../Carousel/src/CarouselStyle_xBind.xaml | 69 +++++++++ .../Carousel/src/CarouselStyle_xBind.xaml.cs | 20 +++ .../Carousel/src/Carousel_ClassicBinding.cs | 94 ++++++++++++ components/Carousel/src/Carousel_xBind.cs | 71 ++++++++++ ...nityToolkit.WinUI.Controls.Carousel.csproj | 14 ++ components/Carousel/src/Dependencies.props | 31 ++++ components/Carousel/src/MultiTarget.props | 9 ++ components/Carousel/src/Themes/Generic.xaml | 10 ++ .../Carousel/tests/Carousel.Tests.projitems | 23 +++ .../Carousel/tests/Carousel.Tests.shproj | 13 ++ .../tests/ExampleCarouselTestClass.cs | 134 ++++++++++++++++++ .../tests/ExampleCarouselTestPage.xaml | 14 ++ .../tests/ExampleCarouselTestPage.xaml.cs | 16 +++ 30 files changed, 1020 insertions(+) create mode 100644 components/Carousel/OpenSolution.bat create mode 100644 components/Carousel/samples/Assets/icon.png create mode 100644 components/Carousel/samples/Carousel.Samples.csproj create mode 100644 components/Carousel/samples/Carousel.md create mode 100644 components/Carousel/samples/CarouselCustomSample.xaml create mode 100644 components/Carousel/samples/CarouselCustomSample.xaml.cs create mode 100644 components/Carousel/samples/CarouselTemplatedSample.xaml create mode 100644 components/Carousel/samples/CarouselTemplatedSample.xaml.cs create mode 100644 components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml create mode 100644 components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs create mode 100644 components/Carousel/samples/CarouselXbindBackedSample.xaml create mode 100644 components/Carousel/samples/CarouselXbindBackedSample.xaml.cs create mode 100644 components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml create mode 100644 components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs create mode 100644 components/Carousel/samples/Dependencies.props create mode 100644 components/Carousel/src/Carousel.cs create mode 100644 components/Carousel/src/CarouselStyle_ClassicBinding.xaml create mode 100644 components/Carousel/src/CarouselStyle_xBind.xaml create mode 100644 components/Carousel/src/CarouselStyle_xBind.xaml.cs create mode 100644 components/Carousel/src/Carousel_ClassicBinding.cs create mode 100644 components/Carousel/src/Carousel_xBind.cs create mode 100644 components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj create mode 100644 components/Carousel/src/Dependencies.props create mode 100644 components/Carousel/src/MultiTarget.props create mode 100644 components/Carousel/src/Themes/Generic.xaml create mode 100644 components/Carousel/tests/Carousel.Tests.projitems create mode 100644 components/Carousel/tests/Carousel.Tests.shproj create mode 100644 components/Carousel/tests/ExampleCarouselTestClass.cs create mode 100644 components/Carousel/tests/ExampleCarouselTestPage.xaml create mode 100644 components/Carousel/tests/ExampleCarouselTestPage.xaml.cs diff --git a/components/Carousel/OpenSolution.bat b/components/Carousel/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/Carousel/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/components/Carousel/samples/Assets/icon.png b/components/Carousel/samples/Assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8435bcaa9fc371ca8e92db07ae596e0d57c8b9b0 GIT binary patch literal 2216 zcmV;Z2v_%sP);M1&0drDELIAGL9O(c600d`2O+f$vv5yP=YKs(Je9p7&Ka&|n*ecc!Pix~iV~>Yi6*wXL?Fq6O}_ef#!O2;q#X&d1-r zFW&c8*L5NOYWz*lA^xUE>kGNx-uL6v^z@vr)V_TA(vQ#Yoo1$I^Fv;IKkA`?U*Z6OoBe{XX@DLpL{F3+>RV2oP732rn@P@98F ziH~#f`tA7f<8t}(<$sLlUkdm_STvOKGYXY{9St3tGiu1>duN9F9aTe*kTXOCkoLYj zr)5cJP?i}f+kBoB8b~Q1rbJi`730DXGLx}aCV-6tC522QjZs)X03S%#=jXopQNe7G z@dlToiDbGDSy!a_DD)NYWiV?sGap0Dq-qgnA&~LHECw(NL8T=) z1ct(ga2;|14nD@qHwmVQ0;2|YUomW^!U#`7l>>&Bh;u)pT<|$jFoqk6%HTc~^RQ@( z5hZ5X^n7`vt!*nY9@rFRqF{^wF`}&H4I4JdfddC*W@e@$oS$Q04aThBn*gT3)URMp z>G{o@H*)RTHCb6%8B?H}yjcXcUm9p(K=nWD0vP!PaCP$@(k!31bkrIJ!2)-tl96*+&}@I6!3M8qT~Q2?u8 zcy@MHS+IzlwjvzRGx~+*l>(Gi6$!C8Jgi;2)=XVKe*CB(K745TS`)G2;nuBN9cqsP zh3?9y5yI0j3rZt%Q;V3i*L;-@bj}#EBDL zi-O{(`k1i!rOBH%ZPF-Qh^8PnZ{F0;pFa!x1_lMnUFbI&&8gFC}WVB;VU)DL&bgeyDBGT1Uw=yFE1xQ zlXdIXN%Xx|Sv6HKV+J+vFk{k20ni@>s(7s{7```cTQ%Vf8y`xM()#6VjL@l-2UZ)1 zMAlp(2n!()MJZ+A7DT29I(lXL!EfSTFx}nSy)d`h{3}%2w00Npq^YO)x9XqDcq0Kb`t5*xfQqpFjCI=5f3~jf78p^G}no2Fzdc;)IysSSZVr(fukQEprykDy< zqbZnxBN8(`!7W?1$e}}r1c|2>qh?{>d-v{@?c2BeJQ<2<$;_cXbnDiw*59|?yLU^< zSA=eeDMyH&Dn;O?U<@moWoql!uTK{z=_+aO*s+8Ar_R9^^Jcn6#~@GNDp>Q(&lq|B z{JGq_cdrQ3>E_6hBQiff?~J4|4F&T## ze2Ot?F7i1RStkmn!!cL^bKdFtd(+&zckeRXgNN#PA!`aD zjaC7J&2fZoFH{s(d8}#I6o7s`O)w?K`{bKiENsKV!h(MK^r(|LX> z*~rJxUHVoXzp-XhUqnbQUAiRq@89q5e?*H1Nj(o2FJ5%B>%JZY`Gw<0&+dhGuj!C7 z?uYbkO5XY{KIV*@{VRoX8s`fm<068)Y!=w) zn?l)IstDTf!(Iu(8i;vTaeMOuE%QV$RY7$1cP-aqS07(jX4W{bFHp!#3m}TTAaaF3L~n9Q)s^3xP5nw5 zM&HvBw5z{TbdA3!8Di)wIs`5S;W!fVdWDbiH|P}wlVfDMuB&#@so4j+uC6L7Q#M38 z*q?Pnc_gqfql3rn?qh)V@~B|(78+7U zKOCcocD&A`ELB-^?%cVvanNF%vtSH&^_LVuBqdkprWDoUyaLCf$s5zvc?rH#NGXk= qmFA_t_5FR}!i7I&wXL?Ful)xU?DJJ%Hwu*i0000 + + + + Carousel + + + + + diff --git a/components/Carousel/samples/Carousel.md b/components/Carousel/samples/Carousel.md new file mode 100644 index 00000000..070c8677 --- /dev/null +++ b/components/Carousel/samples/Carousel.md @@ -0,0 +1,65 @@ +--- +title: Carousel +author: githubaccount +description: TODO: Your experiment's description here +keywords: Carousel, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + + + + + + + + + +# Carousel + +TODO: Fill in information about this experiment and how to get started here... + +## Custom Control + +You can inherit from an existing component as well, like `Panel`, this example shows a control without a +XAML Style that will be more light-weight to consume by an app developer: + +> [!Sample CarouselCustomSample] + +## Templated Controls + +The Toolkit is built with templated controls. This provides developers a flexible way to restyle components +easily while still inheriting the general functionality a control provides. The examples below show +how a component can use a default style and then get overridden by the end developer. + +TODO: Two types of templated control building methods are shown. Delete these if you're building a custom component. +Otherwise, pick one method for your component and delete the files related to the unchosen `_ClassicBinding` or `_xBind` +classes (and the custom non-suffixed one as well). Then, rename your component to just be your component name. + +The `_ClassicBinding` class shows the traditional method used to develop components with best practices. + +### Implict style + +> [!SAMPLE CarouselTemplatedSample] + +### Custom style + +> [!SAMPLE CarouselTemplatedStyleCustomSample] + +## Templated Controls with x:Bind + +This is an _experimental_ new way to define components which allows for the use of x:Bind within the style. + +### Implict style + +> [!SAMPLE CarouselXbindBackedSample] + +### Custom style + +> [!SAMPLE CarouselXbindBackedStyleCustomSample] + diff --git a/components/Carousel/samples/CarouselCustomSample.xaml b/components/Carousel/samples/CarouselCustomSample.xaml new file mode 100644 index 00000000..392e630e --- /dev/null +++ b/components/Carousel/samples/CarouselCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselCustomSample.xaml.cs b/components/Carousel/samples/CarouselCustomSample.xaml.cs new file mode 100644 index 00000000..c70ff312 --- /dev/null +++ b/components/Carousel/samples/CarouselCustomSample.xaml.cs @@ -0,0 +1,30 @@ +// 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 CommunityToolkit.WinUI.Controls; + +namespace CarouselExperiment.Samples; + +/// +/// An example sample page of a custom control inheriting from Panel. +/// +[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")] +[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")] + +[ToolkitSample(id: nameof(CarouselCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(Carousel)} custom control.")] +public sealed partial class CarouselCustomSample : Page +{ + public CarouselCustomSample() + { + this.InitializeComponent(); + } + + // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149 + public static Orientation ConvertStringToOrientation(string orientation) => orientation switch + { + "Vertical" => Orientation.Vertical, + "Horizontal" => Orientation.Horizontal, + _ => throw new System.NotImplementedException(), + }; +} diff --git a/components/Carousel/samples/CarouselTemplatedSample.xaml b/components/Carousel/samples/CarouselTemplatedSample.xaml new file mode 100644 index 00000000..70ded607 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Carousel/samples/CarouselTemplatedSample.xaml.cs b/components/Carousel/samples/CarouselTemplatedSample.xaml.cs new file mode 100644 index 00000000..86f32c20 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedSample.xaml.cs @@ -0,0 +1,21 @@ +// 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 CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// 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"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] +public sealed partial class CarouselTemplatedSample : Page +{ + public CarouselTemplatedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml new file mode 100644 index 00000000..08dff9a7 --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..3fc9960b --- /dev/null +++ b/components/Carousel/samples/CarouselTemplatedStyleCustomSample.xaml.cs @@ -0,0 +1,21 @@ +// 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 CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// 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"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, true, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] +public sealed partial class CarouselTemplatedStyleCustomSample : Page +{ + public CarouselTemplatedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselXbindBackedSample.xaml b/components/Carousel/samples/CarouselXbindBackedSample.xaml new file mode 100644 index 00000000..1c195009 --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs b/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs new file mode 100644 index 00000000..a3d9c35a --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedSample.xaml.cs @@ -0,0 +1,21 @@ +// 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 CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// 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"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, false, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselXbindBackedSample), "Backed templated control", description: "A sample for showing how to create and use a templated control with a backed resource dictionary.")] +public sealed partial class CarouselXbindBackedSample : Page +{ + public CarouselXbindBackedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml new file mode 100644 index 00000000..d860f3dd --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..6123f44a --- /dev/null +++ b/components/Carousel/samples/CarouselXbindBackedStyleCustomSample.xaml.cs @@ -0,0 +1,21 @@ +// 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 CarouselExperiment.Samples; + +[ToolkitSampleBoolOption("IsTextVisible", true, Title = "IsVisible")] +// 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"). +[ToolkitSampleNumericOption("TextSize", 12, 8, 48, 2, true, Title = "FontSize")] +[ToolkitSampleMultiChoiceOption("TextFontFamily", "Segoe UI", "Arial", "Consolas", Title = "Font family")] +[ToolkitSampleMultiChoiceOption("TextForeground", "Teal : #0ddc8c", "Sand : #e7a676", "Dull green : #5d7577", Title = "Text foreground")] + +[ToolkitSample(id: nameof(CarouselXbindBackedStyleCustomSample), "Backed templated control (restyled)", description: "A sample for showing how to create and use a templated control with a backed resource dictionary and a custom style.")] +public sealed partial class CarouselXbindBackedStyleCustomSample : Page +{ + public CarouselXbindBackedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/samples/Dependencies.props b/components/Carousel/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/Carousel/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/src/Carousel.cs b/components/Carousel/src/Carousel.cs new file mode 100644 index 00000000..be988660 --- /dev/null +++ b/components/Carousel/src/Carousel.cs @@ -0,0 +1,108 @@ +// 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; + +/// +/// This is an example control based off of the BoxPanel sample here: https://docs.microsoft.com/windows/apps/design/layout/boxpanel-example-custom-panel. If you need this similar sort of layout component for an application, see UniformGrid in the Toolkit. +/// It is provided as an example of how to inherit from another control like . +/// You can choose to start here or from the or example components. Remove unused components and rename as appropriate. +/// +public partial class Carousel : Panel +{ + /// + /// Identifies the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Carousel), new PropertyMetadata(null, OnOrientationChanged)); + + /// + /// Gets the preference of the rows/columns when there are a non-square number of children. Defaults to Vertical. + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + // Invalidate our layout when the property changes. + private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + { + if (dependencyObject is Carousel panel) + { + panel.InvalidateMeasure(); + } + } + + // Store calculations we want to use between the Measure and Arrange methods. + int _columnCount; + double _cellWidth, _cellHeight; + + protected override Size MeasureOverride(Size availableSize) + { + // Determine the square that can contain this number of items. + var maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count)); + // Get an aspect ratio from availableSize, decides whether to trim row or column. + var aspectratio = availableSize.Width / availableSize.Height; + if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; } + + int rowcount; + + // Now trim this square down to a rect, many times an entire row or column can be omitted. + if (aspectratio > 1) + { + rowcount = maxrc; + _columnCount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; + } + else + { + rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; + _columnCount = maxrc; + } + + // Now that we have a column count, divide available horizontal, that's our cell width. + _cellWidth = (int)Math.Floor(availableSize.Width / _columnCount); + // Next get a cell height, same logic of dividing available vertical by rowcount. + _cellHeight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount; + + double maxcellheight = 0; + + foreach (UIElement child in Children) + { + child.Measure(new Size(_cellWidth, _cellHeight)); + maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight; + } + + return LimitUnboundedSize(availableSize, maxcellheight); + } + + // This method limits the panel height when no limit is imposed by the panel's parent. + // That can happen to height if the panel is close to the root of main app window. + // In this case, base the height of a cell on the max height from desired size + // and base the height of the panel on that number times the #rows. + Size LimitUnboundedSize(Size input, double maxcellheight) + { + if (Double.IsInfinity(input.Height)) + { + input.Height = maxcellheight * _columnCount; + _cellHeight = maxcellheight; + } + return input; + } + + protected override Size ArrangeOverride(Size finalSize) + { + int count = 1; + double x, y; + foreach (UIElement child in Children) + { + x = (count - 1) % _columnCount * _cellWidth; + y = ((int)(count - 1) / _columnCount) * _cellHeight; + Point anchorPoint = new Point(x, y); + child.Arrange(new Rect(anchorPoint, child.DesiredSize)); + count++; + } + return finalSize; + } +} diff --git a/components/Carousel/src/CarouselStyle_ClassicBinding.xaml b/components/Carousel/src/CarouselStyle_ClassicBinding.xaml new file mode 100644 index 00000000..fe838453 --- /dev/null +++ b/components/Carousel/src/CarouselStyle_ClassicBinding.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/components/Carousel/src/CarouselStyle_xBind.xaml b/components/Carousel/src/CarouselStyle_xBind.xaml new file mode 100644 index 00000000..248404c8 --- /dev/null +++ b/components/Carousel/src/CarouselStyle_xBind.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/components/Carousel/src/CarouselStyle_xBind.xaml.cs b/components/Carousel/src/CarouselStyle_xBind.xaml.cs new file mode 100644 index 00000000..06eac824 --- /dev/null +++ b/components/Carousel/src/CarouselStyle_xBind.xaml.cs @@ -0,0 +1,20 @@ +// 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; + +/// +/// Backing code for this resource dictionary. +/// +public sealed partial class CarouselStyle_xBind : ResourceDictionary +{ + // NOTICE + // This file only exists to enable x:Bind in the resource dictionary. + // Do not add code here. + // Instead, add code-behind to your templated control. + public CarouselStyle_xBind() + { + this.InitializeComponent(); + } +} diff --git a/components/Carousel/src/Carousel_ClassicBinding.cs b/components/Carousel/src/Carousel_ClassicBinding.cs new file mode 100644 index 00000000..47c234bf --- /dev/null +++ b/components/Carousel/src/Carousel_ClassicBinding.cs @@ -0,0 +1,94 @@ +// 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; + +/// +/// An example templated control. +/// +[TemplatePart(Name = nameof(PART_HelloWorld), Type = typeof(TextBlock))] +public partial class Carousel_ClassicBinding : Control +{ + /// + /// Creates a new instance of the class. + /// + public Carousel_ClassicBinding() + { + this.DefaultStyleKey = typeof(Carousel_ClassicBinding); + } + + /// + /// The primary text block that displays "Hello world". + /// + protected TextBlock? PART_HelloWorld { get; private set; } + + /// + protected override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Detach all attached events when a new template is applied. + if (PART_HelloWorld is not null) + { + PART_HelloWorld.PointerEntered -= Element_PointerEntered; + } + + // Attach events when the template is applied and the control is loaded. + PART_HelloWorld = GetTemplateChild(nameof(PART_HelloWorld)) as TextBlock; + + if (PART_HelloWorld is not null) + { + PART_HelloWorld.PointerEntered += Element_PointerEntered; + } + } + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemPaddingProperty = DependencyProperty.Register( + nameof(ItemPadding), + typeof(Thickness), + typeof(Carousel_ClassicBinding), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(Carousel_ClassicBinding), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((Carousel_ClassicBinding)d).OnMyPropertyChanged((string)e.OldValue, (string)e.NewValue))); + + /// + /// Gets or sets an example string. A basic DependencyProperty example. + /// + public string MyProperty + { + get => (string)GetValue(MyPropertyProperty); + set => SetValue(MyPropertyProperty, value); + } + + /// + /// Gets or sets a padding for an item. A basic DependencyProperty example. + /// + public Thickness ItemPadding + { + get => (Thickness)GetValue(ItemPaddingProperty); + set => SetValue(ItemPaddingProperty, value); + } + + protected virtual void OnMyPropertyChanged(string oldValue, string newValue) + { + // Do something with the changed value. + } + + public void Element_PointerEntered(object sender, PointerRoutedEventArgs e) + { + if (sender is TextBlock text) + { + text.Opacity = 1; + } + } +} diff --git a/components/Carousel/src/Carousel_xBind.cs b/components/Carousel/src/Carousel_xBind.cs new file mode 100644 index 00000000..c7fdb048 --- /dev/null +++ b/components/Carousel/src/Carousel_xBind.cs @@ -0,0 +1,71 @@ +// 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; + +/// +/// An example templated control. +/// +public partial class Carousel_xBind: Control +{ + /// + /// Creates a new instance of the class. + /// + public Carousel_xBind() + { + this.DefaultStyleKey = typeof(Carousel_xBind); + + // Allows directly using this control as the x:DataType in the template. + this.DataContext = this; + } + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemPaddingProperty = DependencyProperty.Register( + nameof(ItemPadding), + typeof(Thickness), + typeof(Carousel_xBind), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(Carousel_xBind), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((Carousel_xBind)d).OnMyPropertyChanged((string)e.OldValue, (string)e.NewValue))); + + /// + /// Gets or sets an example string. A basic DependencyProperty example. + /// + public string MyProperty + { + get => (string)GetValue(MyPropertyProperty); + set => SetValue(MyPropertyProperty, value); + } + + /// + /// Gets or sets a padding for an item. A basic DependencyProperty example. + /// + public Thickness ItemPadding + { + get => (Thickness)GetValue(ItemPaddingProperty); + set => SetValue(ItemPaddingProperty, value); + } + + protected virtual void OnMyPropertyChanged(string oldValue, string newValue) + { + // Do something with the changed value. + } + + public void Element_PointerEntered(object sender, PointerRoutedEventArgs e) + { + if (sender is TextBlock text) + { + text.Opacity = 1; + } + } +} diff --git a/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj b/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj new file mode 100644 index 00000000..84430ea1 --- /dev/null +++ b/components/Carousel/src/CommunityToolkit.WinUI.Controls.Carousel.csproj @@ -0,0 +1,14 @@ + + + + + Carousel + This package contains Carousel. + + + CommunityToolkit.WinUI.Controls.CarouselRns + + + + + diff --git a/components/Carousel/src/Dependencies.props b/components/Carousel/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/Carousel/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Carousel/src/MultiTarget.props b/components/Carousel/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/components/Carousel/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/components/Carousel/src/Themes/Generic.xaml b/components/Carousel/src/Themes/Generic.xaml new file mode 100644 index 00000000..301137bb --- /dev/null +++ b/components/Carousel/src/Themes/Generic.xaml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/components/Carousel/tests/Carousel.Tests.projitems b/components/Carousel/tests/Carousel.Tests.projitems new file mode 100644 index 00000000..378417eb --- /dev/null +++ b/components/Carousel/tests/Carousel.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 4FFF589B-BBD2-47F1-A5D6-0568F4801D79 + + + CarouselTests + + + + + ExampleCarouselTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/Carousel/tests/Carousel.Tests.shproj b/components/Carousel/tests/Carousel.Tests.shproj new file mode 100644 index 00000000..8ad0ea28 --- /dev/null +++ b/components/Carousel/tests/Carousel.Tests.shproj @@ -0,0 +1,13 @@ + + + + 4FFF589B-BBD2-47F1-A5D6-0568F4801D79 + 14.0 + + + + + + + + diff --git a/components/Carousel/tests/ExampleCarouselTestClass.cs b/components/Carousel/tests/ExampleCarouselTestClass.cs new file mode 100644 index 00000000..8e47c4ca --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestClass.cs @@ -0,0 +1,134 @@ +// 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 CommunityToolkit.Tooling.TestGen; +using CommunityToolkit.Tests; +using CommunityToolkit.WinUI.Controls; + +namespace CarouselTests; + +[TestClass] +public partial class ExampleCarouselTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(Carousel).Assembly; + var type = assembly.GetType(typeof(Carousel).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find Carousel type."); + Assert.AreEqual(typeof(Carousel), type, "Type of Carousel does not match expected type."); + } + + // If you don't need access to UI objects directly, use this pattern. + [TestMethod] + public async Task SimpleAsyncExampleTest() + { + await Task.Delay(250); + + Assert.IsTrue(true); + } + + // Example that shows how to check for exception throwing. + [TestMethod] + public void SimpleExceptionCheckTest() + { + // If you need to check exceptions occur for invalid inputs, etc... + // Use Assert.ThrowsException to limit the scope to where you expect the error to occur. + // Otherwise, using the ExpectedException attribute could swallow or + // catch other issues in setup code. + Assert.ThrowsException(() => throw new NotImplementedException()); + } + + // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects. + [UIThreadTestMethod] + public void SimpleUIAttributeExampleTest() + { + var component = new Carousel(); + Assert.IsNotNull(component); + } + + // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter. + // This lets us actually test a control as it would behave within an actual application. + // The page will already be loaded by the time your test is called. + [UIThreadTestMethod] + public void SimpleUIExamplePageTest(ExampleCarouselTestPage page) + { + // You can use the Toolkit Visual Tree helpers here to find the component by type or name: + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + + var componentByName = page.FindDescendant("CarouselControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleCarouselTestPage page) + { + // This helper can be used to wait for a rendering pass to complete. + // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper. + await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); + + var component = page.FindDescendant(); + + Assert.IsNotNull(component); + } + + //// ----------------------------- ADVANCED TEST SCENARIOS ----------------------------- + + // If you need to use DataRow, you can use this pattern with the UI dispatch still. + // Otherwise, checkout the UIThreadTestMethod attribute above. + // See https://github.com/CommunityToolkit/Labs-Windows/issues/186 + [TestMethod] + public async Task ComplexAsyncUIExampleTest() + { + await EnqueueAsync(() => + { + var component = new Carousel_ClassicBinding(); + Assert.IsNotNull(component); + }); + } + + // If you want to load other content not within a XAML page using the UIThreadTestMethod above. + // Then you can do that using the Load/UnloadTestContentAsync methods. + [TestMethod] + public async Task ComplexAsyncLoadUIExampleTest() + { + await EnqueueAsync(async () => + { + var component = new Carousel_ClassicBinding(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + }); + } + + // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well: + [UIThreadTestMethod] + public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest() + { + var component = new Carousel_ClassicBinding(); + Assert.IsNotNull(component); + Assert.IsFalse(component.IsLoaded); + + await LoadTestContentAsync(component); + + Assert.IsTrue(component.IsLoaded); + + await UnloadTestContentAsync(component); + + Assert.IsFalse(component.IsLoaded); + } +} diff --git a/components/Carousel/tests/ExampleCarouselTestPage.xaml b/components/Carousel/tests/ExampleCarouselTestPage.xaml new file mode 100644 index 00000000..86719c0f --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs b/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs new file mode 100644 index 00000000..766077ea --- /dev/null +++ b/components/Carousel/tests/ExampleCarouselTestPage.xaml.cs @@ -0,0 +1,16 @@ +// 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 CarouselTests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleCarouselTestPage : Page +{ + public ExampleCarouselTestPage() + { + this.InitializeComponent(); + } +} From 0526049415844b21581af7f3d05cf1ef5642828f Mon Sep 17 00:00:00 2001 From: Avishai Dernis Date: Mon, 12 Jan 2026 02:18:01 +0200 Subject: [PATCH 2/2] WIP Replace template with Carousel --- .../Carousel/src/Carousel.Properties.cs | 151 ++++++++++++++++++ components/Carousel/src/Carousel.cs | 103 ++---------- components/Carousel/src/CarouselItem.cs | 72 +++++++++ components/Carousel/src/CarouselStyle.xaml | 66 ++++++++ .../src/CarouselStyle_ClassicBinding.xaml | 62 ------- .../Carousel/src/CarouselStyle_xBind.xaml | 69 -------- .../Carousel/src/CarouselStyle_xBind.xaml.cs | 20 --- .../Carousel/src/Carousel_ClassicBinding.cs | 94 ----------- components/Carousel/src/Carousel_xBind.cs | 71 -------- .../tests/ExampleCarouselTestClass.cs | 10 +- 10 files changed, 307 insertions(+), 411 deletions(-) create mode 100644 components/Carousel/src/Carousel.Properties.cs create mode 100644 components/Carousel/src/CarouselItem.cs create mode 100644 components/Carousel/src/CarouselStyle.xaml delete mode 100644 components/Carousel/src/CarouselStyle_ClassicBinding.xaml delete mode 100644 components/Carousel/src/CarouselStyle_xBind.xaml delete mode 100644 components/Carousel/src/CarouselStyle_xBind.xaml.cs delete mode 100644 components/Carousel/src/Carousel_ClassicBinding.cs delete mode 100644 components/Carousel/src/Carousel_xBind.cs diff --git a/components/Carousel/src/Carousel.Properties.cs b/components/Carousel/src/Carousel.Properties.cs new file mode 100644 index 00000000..99ace3e9 --- /dev/null +++ b/components/Carousel/src/Carousel.Properties.cs @@ -0,0 +1,151 @@ +// 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.Numerics; + +namespace CommunityToolkit.WinUI.Controls; + +public partial class Carousel +{ + /// + /// The backing for the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Carousel), new PropertyMetadata(Orientation.Horizontal)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty SelectedIndexProperty = + DependencyProperty.Register(nameof(SelectedIndex), typeof(int), typeof(Carousel), new PropertyMetadata(-1, OnCarouselSelectionPropertyChanged)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(Carousel), new PropertyMetadata(null, OnCarouselSelectionPropertyChanged)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemSpacingProperty = + DependencyProperty.Register(nameof(ItemSpacing), typeof(double), typeof(Carousel), new PropertyMetadata(0)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty TransitionDurationProperty = + DependencyProperty.Register(nameof(TransitionDuration), typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromMilliseconds(200))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty EasingFunctionProperty = + DependencyProperty.Register(nameof(TransitionDuration), typeof(EasingFunctionBase), typeof(Carousel), new PropertyMetadata(null)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemDepthProperty = + DependencyProperty.Register(nameof(ItemDepth), typeof(double), typeof(Carousel), new PropertyMetadata(0)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty ItemRotationProperty = + DependencyProperty.Register(nameof(ItemRotation), typeof(double), typeof(Carousel), new PropertyMetadata(Vector3.Zero)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty InvertPositiveProperty = + DependencyProperty.Register(nameof(InvertPositive), typeof(bool), typeof(Carousel), new PropertyMetadata(true)); + + /// + /// Gets or sets the orientation to render items. + /// + public Orientation Orientation + { + get => (Orientation)GetValue(OrientationProperty); + set => SetValue(OrientationProperty, value); + } + + /// + /// Gets or sets the index of the selected item. + /// + public int SelectedIndex + { + get => (int)GetValue(SelectedIndexProperty); + set => SetValue(SelectedIndexProperty, value); + } + + /// + /// Gets or sets the selected item. + /// + public object? SelectedItem + { + get => (object?)GetValue(SelectedItemProperty); + set => SetValue(SelectedItemProperty, value); + } + + /// + /// Gets or sets the selected item. + /// + public double? ItemSpacing + { + get => (double?)GetValue(ItemSpacingProperty); + set => SetValue(ItemSpacingProperty, value); + } + + /// + /// Gets or sets the transition animation duration. + /// + public TimeSpan TransitionDuration + { + get => (TimeSpan)GetValue(TransitionDurationProperty); + set => SetValue(TransitionDurationProperty, value); + } + + /// + /// Gets or sets the transition animation duration. + /// + public EasingFunctionBase? EasingFunction + { + get => (EasingFunctionBase?)GetValue(EasingFunctionProperty); + set => SetValue(EasingFunctionProperty, value); + } + + /// + /// Gets or sets the depth for unselected items. + /// + public double ItemDepth + { + get => (double)GetValue(TransitionDurationProperty); + set => SetValue(TransitionDurationProperty, value); + } + + /// + /// Gets or sets the rotation to apply to carousel items. + /// + public Vector3 ItemRotation + { + get => (Vector3)GetValue(ItemRotationProperty); + set => SetValue(ItemRotationProperty, value); + } + + /// + /// Gets or sets the rotation to apply to carousel items. + /// + public bool InvertPositive + { + get => (bool)GetValue(InvertPositiveProperty); + set => SetValue(InvertPositiveProperty, value); + } + + private static void OnCarouselSelectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not Carousel carousel) + return; + } +} diff --git a/components/Carousel/src/Carousel.cs b/components/Carousel/src/Carousel.cs index be988660..7160f8d3 100644 --- a/components/Carousel/src/Carousel.cs +++ b/components/Carousel/src/Carousel.cs @@ -5,104 +5,27 @@ namespace CommunityToolkit.WinUI.Controls; /// -/// This is an example control based off of the BoxPanel sample here: https://docs.microsoft.com/windows/apps/design/layout/boxpanel-example-custom-panel. If you need this similar sort of layout component for an application, see UniformGrid in the Toolkit. -/// It is provided as an example of how to inherit from another control like . -/// You can choose to start here or from the or example components. Remove unused components and rename as appropriate. +/// An example templated control. /// -public partial class Carousel : Panel +public partial class Carousel : ItemsControl { /// - /// Identifies the property. + /// Occurs when the selected item changes. /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(Carousel), new PropertyMetadata(null, OnOrientationChanged)); + public event SelectionChangedEventHandler? SelectionChanged; /// - /// Gets the preference of the rows/columns when there are a non-square number of children. Defaults to Vertical. + /// Creates a new instance of the class. /// - public Orientation Orientation + public Carousel() { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } + this.DefaultStyleKey = typeof(Carousel); } - // Invalidate our layout when the property changes. - private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) - { - if (dependencyObject is Carousel panel) - { - panel.InvalidateMeasure(); - } - } - - // Store calculations we want to use between the Measure and Arrange methods. - int _columnCount; - double _cellWidth, _cellHeight; - - protected override Size MeasureOverride(Size availableSize) - { - // Determine the square that can contain this number of items. - var maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count)); - // Get an aspect ratio from availableSize, decides whether to trim row or column. - var aspectratio = availableSize.Width / availableSize.Height; - if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; } - - int rowcount; - - // Now trim this square down to a rect, many times an entire row or column can be omitted. - if (aspectratio > 1) - { - rowcount = maxrc; - _columnCount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; - } - else - { - rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; - _columnCount = maxrc; - } - - // Now that we have a column count, divide available horizontal, that's our cell width. - _cellWidth = (int)Math.Floor(availableSize.Width / _columnCount); - // Next get a cell height, same logic of dividing available vertical by rowcount. - _cellHeight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount; - - double maxcellheight = 0; - - foreach (UIElement child in Children) - { - child.Measure(new Size(_cellWidth, _cellHeight)); - maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight; - } - - return LimitUnboundedSize(availableSize, maxcellheight); - } - - // This method limits the panel height when no limit is imposed by the panel's parent. - // That can happen to height if the panel is close to the root of main app window. - // In this case, base the height of a cell on the max height from desired size - // and base the height of the panel on that number times the #rows. - Size LimitUnboundedSize(Size input, double maxcellheight) - { - if (Double.IsInfinity(input.Height)) - { - input.Height = maxcellheight * _columnCount; - _cellHeight = maxcellheight; - } - return input; - } - - protected override Size ArrangeOverride(Size finalSize) - { - int count = 1; - double x, y; - foreach (UIElement child in Children) - { - x = (count - 1) % _columnCount * _cellWidth; - y = ((int)(count - 1) / _columnCount) * _cellHeight; - Point anchorPoint = new Point(x, y); - child.Arrange(new Rect(anchorPoint, child.DesiredSize)); - count++; - } - return finalSize; - } + /// + /// Returns the container used for each item + /// + /// Returns always a CarouselItem + protected override DependencyObject GetContainerForItemOverride() + => new CarouselItem(); } diff --git a/components/Carousel/src/CarouselItem.cs b/components/Carousel/src/CarouselItem.cs new file mode 100644 index 00000000..b2689f45 --- /dev/null +++ b/components/Carousel/src/CarouselItem.cs @@ -0,0 +1,72 @@ +// 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; + +/// +/// Represents the container for an item in a control +/// +public partial class CarouselItem : SelectorItem +{ + private const string PointerOverState = "PointerOver"; + private const string PointerOverSelectedState = "PointerOverSelected"; + private const string PressedState = "Pressed"; + private const string PressedSelectedState = "PressedSelected"; + private const string SelectedState = "Selected"; + private const string NormalState = "Normal"; + + /// + /// Fired when the is selected. + /// + public event EventHandler? Selected; + + /// + /// Initializes a new instance of the class. + /// + public CarouselItem() + { + DefaultStyleKey = typeof(CarouselItem); + + RegisterPropertyChangedCallback(IsSelectedProperty, OnIsSelectedChanged); + } + + /// + protected override void OnPointerEntered(PointerRoutedEventArgs e) + { + base.OnPointerEntered(e); + + VisualStateManager.GoToState(this, IsSelected ? PointerOverSelectedState : PointerOverState, true); + } + + /// + protected override void OnPointerExited(PointerRoutedEventArgs e) + { + base.OnPointerExited(e); + + VisualStateManager.GoToState(this, IsSelected ? SelectedState : NormalState, true); + } + + /// + protected override void OnPointerPressed(PointerRoutedEventArgs e) + { + base.OnPointerPressed(e); + + VisualStateManager.GoToState(this, IsSelected ? PressedSelectedState : PressedState, true); + } + + private void OnIsSelectedChanged(DependencyObject sender, DependencyProperty dp) + { + var item = (CarouselItem)sender; + + if (item.IsSelected) + { + Selected?.Invoke(this, EventArgs.Empty); + VisualStateManager.GoToState(item, SelectedState, true); + } + else + { + VisualStateManager.GoToState(item, NormalState, true); + } + } +} diff --git a/components/Carousel/src/CarouselStyle.xaml b/components/Carousel/src/CarouselStyle.xaml new file mode 100644 index 00000000..61d2b81d --- /dev/null +++ b/components/Carousel/src/CarouselStyle.xaml @@ -0,0 +1,66 @@ + + + + + + + + diff --git a/components/Carousel/src/CarouselStyle_ClassicBinding.xaml b/components/Carousel/src/CarouselStyle_ClassicBinding.xaml deleted file mode 100644 index fe838453..00000000 --- a/components/Carousel/src/CarouselStyle_ClassicBinding.xaml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - - - - - - - - - 4,4,4,4 - - - - - - - - diff --git a/components/Carousel/src/CarouselStyle_xBind.xaml b/components/Carousel/src/CarouselStyle_xBind.xaml deleted file mode 100644 index 248404c8..00000000 --- a/components/Carousel/src/CarouselStyle_xBind.xaml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - 4,4,4,4 - - - - - - - - diff --git a/components/Carousel/src/CarouselStyle_xBind.xaml.cs b/components/Carousel/src/CarouselStyle_xBind.xaml.cs deleted file mode 100644 index 06eac824..00000000 --- a/components/Carousel/src/CarouselStyle_xBind.xaml.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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; - -/// -/// Backing code for this resource dictionary. -/// -public sealed partial class CarouselStyle_xBind : ResourceDictionary -{ - // NOTICE - // This file only exists to enable x:Bind in the resource dictionary. - // Do not add code here. - // Instead, add code-behind to your templated control. - public CarouselStyle_xBind() - { - this.InitializeComponent(); - } -} diff --git a/components/Carousel/src/Carousel_ClassicBinding.cs b/components/Carousel/src/Carousel_ClassicBinding.cs deleted file mode 100644 index 47c234bf..00000000 --- a/components/Carousel/src/Carousel_ClassicBinding.cs +++ /dev/null @@ -1,94 +0,0 @@ -// 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; - -/// -/// An example templated control. -/// -[TemplatePart(Name = nameof(PART_HelloWorld), Type = typeof(TextBlock))] -public partial class Carousel_ClassicBinding : Control -{ - /// - /// Creates a new instance of the class. - /// - public Carousel_ClassicBinding() - { - this.DefaultStyleKey = typeof(Carousel_ClassicBinding); - } - - /// - /// The primary text block that displays "Hello world". - /// - protected TextBlock? PART_HelloWorld { get; private set; } - - /// - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - // Detach all attached events when a new template is applied. - if (PART_HelloWorld is not null) - { - PART_HelloWorld.PointerEntered -= Element_PointerEntered; - } - - // Attach events when the template is applied and the control is loaded. - PART_HelloWorld = GetTemplateChild(nameof(PART_HelloWorld)) as TextBlock; - - if (PART_HelloWorld is not null) - { - PART_HelloWorld.PointerEntered += Element_PointerEntered; - } - } - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty ItemPaddingProperty = DependencyProperty.Register( - nameof(ItemPadding), - typeof(Thickness), - typeof(Carousel_ClassicBinding), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(Carousel_ClassicBinding), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((Carousel_ClassicBinding)d).OnMyPropertyChanged((string)e.OldValue, (string)e.NewValue))); - - /// - /// Gets or sets an example string. A basic DependencyProperty example. - /// - public string MyProperty - { - get => (string)GetValue(MyPropertyProperty); - set => SetValue(MyPropertyProperty, value); - } - - /// - /// Gets or sets a padding for an item. A basic DependencyProperty example. - /// - public Thickness ItemPadding - { - get => (Thickness)GetValue(ItemPaddingProperty); - set => SetValue(ItemPaddingProperty, value); - } - - protected virtual void OnMyPropertyChanged(string oldValue, string newValue) - { - // Do something with the changed value. - } - - public void Element_PointerEntered(object sender, PointerRoutedEventArgs e) - { - if (sender is TextBlock text) - { - text.Opacity = 1; - } - } -} diff --git a/components/Carousel/src/Carousel_xBind.cs b/components/Carousel/src/Carousel_xBind.cs deleted file mode 100644 index c7fdb048..00000000 --- a/components/Carousel/src/Carousel_xBind.cs +++ /dev/null @@ -1,71 +0,0 @@ -// 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; - -/// -/// An example templated control. -/// -public partial class Carousel_xBind: Control -{ - /// - /// Creates a new instance of the class. - /// - public Carousel_xBind() - { - this.DefaultStyleKey = typeof(Carousel_xBind); - - // Allows directly using this control as the x:DataType in the template. - this.DataContext = this; - } - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty ItemPaddingProperty = DependencyProperty.Register( - nameof(ItemPadding), - typeof(Thickness), - typeof(Carousel_xBind), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(Carousel_xBind), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((Carousel_xBind)d).OnMyPropertyChanged((string)e.OldValue, (string)e.NewValue))); - - /// - /// Gets or sets an example string. A basic DependencyProperty example. - /// - public string MyProperty - { - get => (string)GetValue(MyPropertyProperty); - set => SetValue(MyPropertyProperty, value); - } - - /// - /// Gets or sets a padding for an item. A basic DependencyProperty example. - /// - public Thickness ItemPadding - { - get => (Thickness)GetValue(ItemPaddingProperty); - set => SetValue(ItemPaddingProperty, value); - } - - protected virtual void OnMyPropertyChanged(string oldValue, string newValue) - { - // Do something with the changed value. - } - - public void Element_PointerEntered(object sender, PointerRoutedEventArgs e) - { - if (sender is TextBlock text) - { - text.Opacity = 1; - } - } -} diff --git a/components/Carousel/tests/ExampleCarouselTestClass.cs b/components/Carousel/tests/ExampleCarouselTestClass.cs index 8e47c4ca..cc205524 100644 --- a/components/Carousel/tests/ExampleCarouselTestClass.cs +++ b/components/Carousel/tests/ExampleCarouselTestClass.cs @@ -57,7 +57,7 @@ public void SimpleUIAttributeExampleTest() public void SimpleUIExamplePageTest(ExampleCarouselTestPage page) { // You can use the Toolkit Visual Tree helpers here to find the component by type or name: - var component = page.FindDescendant(); + var component = page.FindDescendant(); Assert.IsNotNull(component); @@ -74,7 +74,7 @@ public async Task SimpleAsyncUIExamplePageTest(ExampleCarouselTestPage page) // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper. await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { }); - var component = page.FindDescendant(); + var component = page.FindDescendant(); Assert.IsNotNull(component); } @@ -89,7 +89,7 @@ public async Task ComplexAsyncUIExampleTest() { await EnqueueAsync(() => { - var component = new Carousel_ClassicBinding(); + var component = new Carousel(); Assert.IsNotNull(component); }); } @@ -101,7 +101,7 @@ public async Task ComplexAsyncLoadUIExampleTest() { await EnqueueAsync(async () => { - var component = new Carousel_ClassicBinding(); + var component = new Carousel(); Assert.IsNotNull(component); Assert.IsFalse(component.IsLoaded); @@ -119,7 +119,7 @@ await EnqueueAsync(async () => [UIThreadTestMethod] public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest() { - var component = new Carousel_ClassicBinding(); + var component = new Carousel(); Assert.IsNotNull(component); Assert.IsFalse(component.IsLoaded);