From fb5d7f7d7fe5a4a209706eb343f97c50bf4ba5b8 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 1 May 2023 13:02:58 +0200 Subject: [PATCH 01/15] Init --- RadialGauge/OpenSolution.bat | 3 + RadialGauge/samples/Dependencies.props | 31 ++++ .../samples/RadialGauge.Samples.csproj | 8 ++ RadialGauge/samples/RadialGauge.md | 64 +++++++++ .../samples/RadialGaugeCustomSample.xaml | 25 ++++ .../samples/RadialGaugeCustomSample.xaml.cs | 30 ++++ .../samples/RadialGaugeTemplatedSample.xaml | 16 +++ .../RadialGaugeTemplatedSample.xaml.cs | 21 +++ ...RadialGaugeTemplatedStyleCustomSample.xaml | 26 ++++ ...ialGaugeTemplatedStyleCustomSample.xaml.cs | 21 +++ .../samples/RadialGaugeXbindBackedSample.xaml | 16 +++ .../RadialGaugeXbindBackedSample.xaml.cs | 21 +++ ...dialGaugeXbindBackedStyleCustomSample.xaml | 26 ++++ ...lGaugeXbindBackedStyleCustomSample.xaml.cs | 21 +++ RadialGauge/src/AdditionalAssemblyInfo.cs | 13 ++ ...yToolkit.WinUI.Controls.RadialGauge.csproj | 13 ++ RadialGauge/src/Dependencies.props | 31 ++++ RadialGauge/src/MultiTarget.props | 9 ++ RadialGauge/src/RadialGauge.cs | 108 ++++++++++++++ .../src/RadialGaugeStyle_ClassicBinding.xaml | 62 ++++++++ RadialGauge/src/RadialGaugeStyle_xBind.xaml | 69 +++++++++ .../src/RadialGaugeStyle_xBind.xaml.cs | 20 +++ RadialGauge/src/RadialGauge_ClassicBinding.cs | 94 ++++++++++++ RadialGauge/src/RadialGauge_xBind.cs | 71 ++++++++++ RadialGauge/src/Themes/Generic.xaml | 10 ++ .../tests/ExampleRadialGaugeTestClass.cs | 134 ++++++++++++++++++ .../tests/ExampleRadialGaugeTestPage.xaml | 14 ++ .../tests/ExampleRadialGaugeTestPage.xaml.cs | 16 +++ RadialGauge/tests/RadialGauge.Tests.projitems | 23 +++ RadialGauge/tests/RadialGauge.Tests.shproj | 13 ++ components/RadialGauge/OpenSolution.bat | 3 + .../RadialGauge/samples/Dependencies.props | 31 ++++ .../samples/RadialGauge.Samples.csproj | 8 ++ components/RadialGauge/samples/RadialGauge.md | 64 +++++++++ .../samples/RadialGaugeCustomSample.xaml | 25 ++++ .../samples/RadialGaugeCustomSample.xaml.cs | 30 ++++ .../samples/RadialGaugeTemplatedSample.xaml | 16 +++ .../RadialGaugeTemplatedSample.xaml.cs | 21 +++ ...RadialGaugeTemplatedStyleCustomSample.xaml | 26 ++++ ...ialGaugeTemplatedStyleCustomSample.xaml.cs | 21 +++ .../samples/RadialGaugeXbindBackedSample.xaml | 16 +++ .../RadialGaugeXbindBackedSample.xaml.cs | 21 +++ ...dialGaugeXbindBackedStyleCustomSample.xaml | 26 ++++ ...lGaugeXbindBackedStyleCustomSample.xaml.cs | 21 +++ .../RadialGauge/src/AdditionalAssemblyInfo.cs | 13 ++ ...yToolkit.WinUI.Controls.RadialGauge.csproj | 13 ++ components/RadialGauge/src/Dependencies.props | 31 ++++ components/RadialGauge/src/MultiTarget.props | 9 ++ components/RadialGauge/src/RadialGauge.cs | 108 ++++++++++++++ .../src/RadialGaugeStyle_ClassicBinding.xaml | 62 ++++++++ .../src/RadialGaugeStyle_xBind.xaml | 69 +++++++++ .../src/RadialGaugeStyle_xBind.xaml.cs | 20 +++ .../src/RadialGauge_ClassicBinding.cs | 94 ++++++++++++ .../RadialGauge/src/RadialGauge_xBind.cs | 71 ++++++++++ .../RadialGauge/src/Themes/Generic.xaml | 10 ++ .../tests/ExampleRadialGaugeTestClass.cs | 134 ++++++++++++++++++ .../tests/ExampleRadialGaugeTestPage.xaml | 14 ++ .../tests/ExampleRadialGaugeTestPage.xaml.cs | 16 +++ .../tests/RadialGauge.Tests.projitems | 23 +++ .../tests/RadialGauge.Tests.shproj | 13 ++ 60 files changed, 2058 insertions(+) create mode 100644 RadialGauge/OpenSolution.bat create mode 100644 RadialGauge/samples/Dependencies.props create mode 100644 RadialGauge/samples/RadialGauge.Samples.csproj create mode 100644 RadialGauge/samples/RadialGauge.md create mode 100644 RadialGauge/samples/RadialGaugeCustomSample.xaml create mode 100644 RadialGauge/samples/RadialGaugeCustomSample.xaml.cs create mode 100644 RadialGauge/samples/RadialGaugeTemplatedSample.xaml create mode 100644 RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs create mode 100644 RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml create mode 100644 RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs create mode 100644 RadialGauge/samples/RadialGaugeXbindBackedSample.xaml create mode 100644 RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs create mode 100644 RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml create mode 100644 RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs create mode 100644 RadialGauge/src/AdditionalAssemblyInfo.cs create mode 100644 RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj create mode 100644 RadialGauge/src/Dependencies.props create mode 100644 RadialGauge/src/MultiTarget.props create mode 100644 RadialGauge/src/RadialGauge.cs create mode 100644 RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml create mode 100644 RadialGauge/src/RadialGaugeStyle_xBind.xaml create mode 100644 RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs create mode 100644 RadialGauge/src/RadialGauge_ClassicBinding.cs create mode 100644 RadialGauge/src/RadialGauge_xBind.cs create mode 100644 RadialGauge/src/Themes/Generic.xaml create mode 100644 RadialGauge/tests/ExampleRadialGaugeTestClass.cs create mode 100644 RadialGauge/tests/ExampleRadialGaugeTestPage.xaml create mode 100644 RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs create mode 100644 RadialGauge/tests/RadialGauge.Tests.projitems create mode 100644 RadialGauge/tests/RadialGauge.Tests.shproj create mode 100644 components/RadialGauge/OpenSolution.bat create mode 100644 components/RadialGauge/samples/Dependencies.props create mode 100644 components/RadialGauge/samples/RadialGauge.Samples.csproj create mode 100644 components/RadialGauge/samples/RadialGauge.md create mode 100644 components/RadialGauge/samples/RadialGaugeCustomSample.xaml create mode 100644 components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs create mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml create mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs create mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml create mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs create mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml create mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs create mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml create mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs create mode 100644 components/RadialGauge/src/AdditionalAssemblyInfo.cs create mode 100644 components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj create mode 100644 components/RadialGauge/src/Dependencies.props create mode 100644 components/RadialGauge/src/MultiTarget.props create mode 100644 components/RadialGauge/src/RadialGauge.cs create mode 100644 components/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml create mode 100644 components/RadialGauge/src/RadialGaugeStyle_xBind.xaml create mode 100644 components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs create mode 100644 components/RadialGauge/src/RadialGauge_ClassicBinding.cs create mode 100644 components/RadialGauge/src/RadialGauge_xBind.cs create mode 100644 components/RadialGauge/src/Themes/Generic.xaml create mode 100644 components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs create mode 100644 components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml create mode 100644 components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs create mode 100644 components/RadialGauge/tests/RadialGauge.Tests.projitems create mode 100644 components/RadialGauge/tests/RadialGauge.Tests.shproj diff --git a/RadialGauge/OpenSolution.bat b/RadialGauge/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/RadialGauge/OpenSolution.bat @@ -0,0 +1,3 @@ +@ECHO OFF + +powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %* \ No newline at end of file diff --git a/RadialGauge/samples/Dependencies.props b/RadialGauge/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/RadialGauge/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/RadialGauge/samples/RadialGauge.Samples.csproj b/RadialGauge/samples/RadialGauge.Samples.csproj new file mode 100644 index 00000000..7ee8a5b1 --- /dev/null +++ b/RadialGauge/samples/RadialGauge.Samples.csproj @@ -0,0 +1,8 @@ + + + RadialGauge + + + + + diff --git a/RadialGauge/samples/RadialGauge.md b/RadialGauge/samples/RadialGauge.md new file mode 100644 index 00000000..3498d9dd --- /dev/null +++ b/RadialGauge/samples/RadialGauge.md @@ -0,0 +1,64 @@ +--- +title: RadialGauge +author: githubaccount +description: TODO: Your experiment's description here +keywords: RadialGauge, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +--- + + + + + + + + + +# RadialGauge + +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 RadialGaugeCustomSample] + +## 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 RadialGaugeTemplatedSample] + +### Custom style + +> [!SAMPLE RadialGaugeTemplatedStyleCustomSample] + +## 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 RadialGaugeXbindBackedSample] + +### Custom style + +> [!SAMPLE RadialGaugeXbindBackedStyleCustomSample] + diff --git a/RadialGauge/samples/RadialGaugeCustomSample.xaml b/RadialGauge/samples/RadialGaugeCustomSample.xaml new file mode 100644 index 00000000..f08b0cb8 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs b/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs new file mode 100644 index 00000000..9592ad96 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeCustomSample.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 RadialGaugeExperiment.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(RadialGaugeCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(RadialGauge)} custom control.")] +public sealed partial class RadialGaugeCustomSample : Page +{ + public RadialGaugeCustomSample() + { + 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/RadialGauge/samples/RadialGaugeTemplatedSample.xaml b/RadialGauge/samples/RadialGaugeTemplatedSample.xaml new file mode 100644 index 00000000..f6929b36 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeTemplatedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs b/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs new file mode 100644 index 00000000..45811343 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeTemplatedSample.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 RadialGaugeExperiment.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(RadialGaugeTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] +public sealed partial class RadialGaugeTemplatedSample : Page +{ + public RadialGaugeTemplatedSample() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml new file mode 100644 index 00000000..b5763d48 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..90e3df32 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.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 RadialGaugeExperiment.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(RadialGaugeTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] +public sealed partial class RadialGaugeTemplatedStyleCustomSample : Page +{ + public RadialGaugeTemplatedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml b/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml new file mode 100644 index 00000000..346f5b23 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs b/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs new file mode 100644 index 00000000..7f982e26 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeXbindBackedSample.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 RadialGaugeExperiment.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(RadialGaugeXbindBackedSample), "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 RadialGaugeXbindBackedSample : Page +{ + public RadialGaugeXbindBackedSample() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml new file mode 100644 index 00000000..482ea47f --- /dev/null +++ b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..465e0053 --- /dev/null +++ b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.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 RadialGaugeExperiment.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(RadialGaugeXbindBackedStyleCustomSample), "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 RadialGaugeXbindBackedStyleCustomSample : Page +{ + public RadialGaugeXbindBackedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/src/AdditionalAssemblyInfo.cs b/RadialGauge/src/AdditionalAssemblyInfo.cs new file mode 100644 index 00000000..343502a1 --- /dev/null +++ b/RadialGauge/src/AdditionalAssemblyInfo.cs @@ -0,0 +1,13 @@ +// 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.Runtime.CompilerServices; + +// These `InternalsVisibleTo` calls are intended to make it easier for +// for any internal code to be testable in all the different test projects +// used with the Labs infrastructure. +[assembly: InternalsVisibleTo("RadialGauge.Tests.Uwp")] +[assembly: InternalsVisibleTo("RadialGauge.Tests.WinAppSdk")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")] diff --git a/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj b/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj new file mode 100644 index 00000000..de26976d --- /dev/null +++ b/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj @@ -0,0 +1,13 @@ + + + RadialGauge + This package contains RadialGauge. + 0.0.1 + + + CommunityToolkit.WinUI.Controls.RadialGaugeRns + + + + + diff --git a/RadialGauge/src/Dependencies.props b/RadialGauge/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/RadialGauge/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/RadialGauge/src/MultiTarget.props b/RadialGauge/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/RadialGauge/src/MultiTarget.props @@ -0,0 +1,9 @@ + + + + uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + + \ No newline at end of file diff --git a/RadialGauge/src/RadialGauge.cs b/RadialGauge/src/RadialGauge.cs new file mode 100644 index 00000000..145eb8ab --- /dev/null +++ b/RadialGauge/src/RadialGauge.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 RadialGauge : Panel +{ + /// + /// Identifies the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(RadialGauge), 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 RadialGauge 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/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml b/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml new file mode 100644 index 00000000..363d3a6b --- /dev/null +++ b/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/RadialGauge/src/RadialGaugeStyle_xBind.xaml b/RadialGauge/src/RadialGaugeStyle_xBind.xaml new file mode 100644 index 00000000..95701222 --- /dev/null +++ b/RadialGauge/src/RadialGaugeStyle_xBind.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs b/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs new file mode 100644 index 00000000..d33464f1 --- /dev/null +++ b/RadialGauge/src/RadialGaugeStyle_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 RadialGaugeStyle_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 RadialGaugeStyle_xBind() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/src/RadialGauge_ClassicBinding.cs b/RadialGauge/src/RadialGauge_ClassicBinding.cs new file mode 100644 index 00000000..507692ec --- /dev/null +++ b/RadialGauge/src/RadialGauge_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 RadialGauge_ClassicBinding : Control +{ + /// + /// Creates a new instance of the class. + /// + public RadialGauge_ClassicBinding() + { + this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_ClassicBinding), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(RadialGauge_ClassicBinding), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/RadialGauge_xBind.cs b/RadialGauge/src/RadialGauge_xBind.cs new file mode 100644 index 00000000..fae7278e --- /dev/null +++ b/RadialGauge/src/RadialGauge_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 RadialGauge_xBind: Control +{ + /// + /// Creates a new instance of the class. + /// + public RadialGauge_xBind() + { + this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_xBind), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(RadialGauge_xBind), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/Themes/Generic.xaml b/RadialGauge/src/Themes/Generic.xaml new file mode 100644 index 00000000..cb041611 --- /dev/null +++ b/RadialGauge/src/Themes/Generic.xaml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/RadialGauge/tests/ExampleRadialGaugeTestClass.cs b/RadialGauge/tests/ExampleRadialGaugeTestClass.cs new file mode 100644 index 00000000..275bbd4a --- /dev/null +++ b/RadialGauge/tests/ExampleRadialGaugeTestClass.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 RadialGaugeExperiment.Tests; + +[TestClass] +public partial class ExampleRadialGaugeTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(RadialGauge).Assembly; + var type = assembly.GetType(typeof(RadialGauge).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find RadialGauge type."); + Assert.AreEqual(typeof(RadialGauge), type, "Type of RadialGauge 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 RadialGauge(); + 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(ExampleRadialGaugeTestPage 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("RadialGaugeControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleRadialGaugeTestPage 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 RadialGauge_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 RadialGauge_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 RadialGauge_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/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml b/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml new file mode 100644 index 00000000..c47247ce --- /dev/null +++ b/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs b/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs new file mode 100644 index 00000000..640396fe --- /dev/null +++ b/RadialGauge/tests/ExampleRadialGaugeTestPage.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 RadialGaugeExperiment.Tests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleRadialGaugeTestPage : Page +{ + public ExampleRadialGaugeTestPage() + { + this.InitializeComponent(); + } +} diff --git a/RadialGauge/tests/RadialGauge.Tests.projitems b/RadialGauge/tests/RadialGauge.Tests.projitems new file mode 100644 index 00000000..6845b557 --- /dev/null +++ b/RadialGauge/tests/RadialGauge.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 4E3AF150-B9AE-422D-9E1C-18AE541D52D0 + + + RadialGaugeExperiment.Tests + + + + + ExampleRadialGaugeTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/RadialGauge/tests/RadialGauge.Tests.shproj b/RadialGauge/tests/RadialGauge.Tests.shproj new file mode 100644 index 00000000..58f4d434 --- /dev/null +++ b/RadialGauge/tests/RadialGauge.Tests.shproj @@ -0,0 +1,13 @@ + + + + 4E3AF150-B9AE-422D-9E1C-18AE541D52D0 + 14.0 + + + + + + + + diff --git a/components/RadialGauge/OpenSolution.bat b/components/RadialGauge/OpenSolution.bat new file mode 100644 index 00000000..814a56d4 --- /dev/null +++ b/components/RadialGauge/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/RadialGauge/samples/Dependencies.props b/components/RadialGauge/samples/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/RadialGauge/samples/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/RadialGauge/samples/RadialGauge.Samples.csproj b/components/RadialGauge/samples/RadialGauge.Samples.csproj new file mode 100644 index 00000000..7ee8a5b1 --- /dev/null +++ b/components/RadialGauge/samples/RadialGauge.Samples.csproj @@ -0,0 +1,8 @@ + + + RadialGauge + + + + + diff --git a/components/RadialGauge/samples/RadialGauge.md b/components/RadialGauge/samples/RadialGauge.md new file mode 100644 index 00000000..3498d9dd --- /dev/null +++ b/components/RadialGauge/samples/RadialGauge.md @@ -0,0 +1,64 @@ +--- +title: RadialGauge +author: githubaccount +description: TODO: Your experiment's description here +keywords: RadialGauge, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +--- + + + + + + + + + +# RadialGauge + +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 RadialGaugeCustomSample] + +## 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 RadialGaugeTemplatedSample] + +### Custom style + +> [!SAMPLE RadialGaugeTemplatedStyleCustomSample] + +## 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 RadialGaugeXbindBackedSample] + +### Custom style + +> [!SAMPLE RadialGaugeXbindBackedStyleCustomSample] + diff --git a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml new file mode 100644 index 00000000..f08b0cb8 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs new file mode 100644 index 00000000..9592ad96 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeCustomSample.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 RadialGaugeExperiment.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(RadialGaugeCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(RadialGauge)} custom control.")] +public sealed partial class RadialGaugeCustomSample : Page +{ + public RadialGaugeCustomSample() + { + 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/RadialGauge/samples/RadialGaugeTemplatedSample.xaml b/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml new file mode 100644 index 00000000..f6929b36 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs new file mode 100644 index 00000000..45811343 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeTemplatedSample.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 RadialGaugeExperiment.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(RadialGaugeTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] +public sealed partial class RadialGaugeTemplatedSample : Page +{ + public RadialGaugeTemplatedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml new file mode 100644 index 00000000..b5763d48 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..90e3df32 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.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 RadialGaugeExperiment.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(RadialGaugeTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] +public sealed partial class RadialGaugeTemplatedStyleCustomSample : Page +{ + public RadialGaugeTemplatedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml new file mode 100644 index 00000000..346f5b23 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs new file mode 100644 index 00000000..7f982e26 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.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 RadialGaugeExperiment.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(RadialGaugeXbindBackedSample), "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 RadialGaugeXbindBackedSample : Page +{ + public RadialGaugeXbindBackedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml new file mode 100644 index 00000000..482ea47f --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs new file mode 100644 index 00000000..465e0053 --- /dev/null +++ b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.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 RadialGaugeExperiment.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(RadialGaugeXbindBackedStyleCustomSample), "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 RadialGaugeXbindBackedStyleCustomSample : Page +{ + public RadialGaugeXbindBackedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/src/AdditionalAssemblyInfo.cs b/components/RadialGauge/src/AdditionalAssemblyInfo.cs new file mode 100644 index 00000000..343502a1 --- /dev/null +++ b/components/RadialGauge/src/AdditionalAssemblyInfo.cs @@ -0,0 +1,13 @@ +// 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.Runtime.CompilerServices; + +// These `InternalsVisibleTo` calls are intended to make it easier for +// for any internal code to be testable in all the different test projects +// used with the Labs infrastructure. +[assembly: InternalsVisibleTo("RadialGauge.Tests.Uwp")] +[assembly: InternalsVisibleTo("RadialGauge.Tests.WinAppSdk")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")] +[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")] diff --git a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj new file mode 100644 index 00000000..de26976d --- /dev/null +++ b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj @@ -0,0 +1,13 @@ + + + RadialGauge + This package contains RadialGauge. + 0.0.1 + + + CommunityToolkit.WinUI.Controls.RadialGaugeRns + + + + + diff --git a/components/RadialGauge/src/Dependencies.props b/components/RadialGauge/src/Dependencies.props new file mode 100644 index 00000000..e622e1df --- /dev/null +++ b/components/RadialGauge/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/RadialGauge/src/MultiTarget.props b/components/RadialGauge/src/MultiTarget.props new file mode 100644 index 00000000..b11c1942 --- /dev/null +++ b/components/RadialGauge/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/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs new file mode 100644 index 00000000..145eb8ab --- /dev/null +++ b/components/RadialGauge/src/RadialGauge.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 RadialGauge : Panel +{ + /// + /// Identifies the property. + /// + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(RadialGauge), 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 RadialGauge 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/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml b/components/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml new file mode 100644 index 00000000..363d3a6b --- /dev/null +++ b/components/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml b/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml new file mode 100644 index 00000000..95701222 --- /dev/null +++ b/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + 4,4,4,4 + + + + + + + + diff --git a/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs b/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs new file mode 100644 index 00000000..d33464f1 --- /dev/null +++ b/components/RadialGauge/src/RadialGaugeStyle_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 RadialGaugeStyle_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 RadialGaugeStyle_xBind() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/src/RadialGauge_ClassicBinding.cs b/components/RadialGauge/src/RadialGauge_ClassicBinding.cs new file mode 100644 index 00000000..507692ec --- /dev/null +++ b/components/RadialGauge/src/RadialGauge_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 RadialGauge_ClassicBinding : Control +{ + /// + /// Creates a new instance of the class. + /// + public RadialGauge_ClassicBinding() + { + this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_ClassicBinding), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(RadialGauge_ClassicBinding), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/RadialGauge_xBind.cs b/components/RadialGauge/src/RadialGauge_xBind.cs new file mode 100644 index 00000000..fae7278e --- /dev/null +++ b/components/RadialGauge/src/RadialGauge_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 RadialGauge_xBind: Control +{ + /// + /// Creates a new instance of the class. + /// + public RadialGauge_xBind() + { + this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_xBind), + new PropertyMetadata(defaultValue: new Thickness(0))); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( + nameof(MyProperty), + typeof(string), + typeof(RadialGauge_xBind), + new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/Themes/Generic.xaml b/components/RadialGauge/src/Themes/Generic.xaml new file mode 100644 index 00000000..cb041611 --- /dev/null +++ b/components/RadialGauge/src/Themes/Generic.xaml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs b/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs new file mode 100644 index 00000000..275bbd4a --- /dev/null +++ b/components/RadialGauge/tests/ExampleRadialGaugeTestClass.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 RadialGaugeExperiment.Tests; + +[TestClass] +public partial class ExampleRadialGaugeTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(RadialGauge).Assembly; + var type = assembly.GetType(typeof(RadialGauge).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find RadialGauge type."); + Assert.AreEqual(typeof(RadialGauge), type, "Type of RadialGauge 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 RadialGauge(); + 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(ExampleRadialGaugeTestPage 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("RadialGaugeControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleRadialGaugeTestPage 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 RadialGauge_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 RadialGauge_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 RadialGauge_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/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml new file mode 100644 index 00000000..c47247ce --- /dev/null +++ b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs new file mode 100644 index 00000000..640396fe --- /dev/null +++ b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.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 RadialGaugeExperiment.Tests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleRadialGaugeTestPage : Page +{ + public ExampleRadialGaugeTestPage() + { + this.InitializeComponent(); + } +} diff --git a/components/RadialGauge/tests/RadialGauge.Tests.projitems b/components/RadialGauge/tests/RadialGauge.Tests.projitems new file mode 100644 index 00000000..cc1e6d95 --- /dev/null +++ b/components/RadialGauge/tests/RadialGauge.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 2D8A9068-EF5F-4569-982C-D7147398B174 + + + RadialGaugeExperiment.Tests + + + + + ExampleRadialGaugeTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/RadialGauge/tests/RadialGauge.Tests.shproj b/components/RadialGauge/tests/RadialGauge.Tests.shproj new file mode 100644 index 00000000..728d1a2a --- /dev/null +++ b/components/RadialGauge/tests/RadialGauge.Tests.shproj @@ -0,0 +1,13 @@ + + + + 2D8A9068-EF5F-4569-982C-D7147398B174 + 14.0 + + + + + + + + From dd68c0e96d0c9f534eec0a67803e32d7480eb77d Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 1 May 2023 14:25:03 +0200 Subject: [PATCH 02/15] Removing unused files --- components/RadialGauge/src/RadialGauge.cs | 897 ++++++++++++++++-- ...e_ClassicBinding.xaml => RadialGauge.xaml} | 0 .../src/RadialGaugeStyle_xBind.xaml | 69 -- .../src/RadialGaugeStyle_xBind.xaml.cs | 20 - .../src/RadialGauge_ClassicBinding.cs | 94 -- .../RadialGauge/src/RadialGauge_xBind.cs | 71 -- .../RadialGauge/src/Themes/Generic.xaml | 5 +- 7 files changed, 838 insertions(+), 318 deletions(-) rename components/RadialGauge/src/{RadialGaugeStyle_ClassicBinding.xaml => RadialGauge.xaml} (100%) delete mode 100644 components/RadialGauge/src/RadialGaugeStyle_xBind.xaml delete mode 100644 components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs delete mode 100644 components/RadialGauge/src/RadialGauge_ClassicBinding.cs delete mode 100644 components/RadialGauge/src/RadialGauge_xBind.cs diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index 145eb8ab..026fdf54 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -3,106 +3,881 @@ // 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. +/// A Modern UI Radial Gauge using XAML and Composition API. +/// The scale of the gauge is a clockwise arc that sweeps from MinAngle (default lower left, at -150°) to MaxAngle (default lower right, at +150°). /// -public partial class RadialGauge : Panel +//// All calculations are for a 200x200 square. The viewbox will do the rest. +[TemplatePart(Name = ContainerPartName, Type = typeof(Grid))] +[TemplatePart(Name = ScalePartName, Type = typeof(Path))] +[TemplatePart(Name = TrailPartName, Type = typeof(Path))] +[TemplatePart(Name = ValueTextPartName, Type = typeof(TextBlock))] +public class RadialGauge : RangeBase { /// - /// Identifies the property. + /// Identifies the optional StepSize property. + /// + public static readonly DependencyProperty StepSizeProperty = + DependencyProperty.Register(nameof(StepSize), typeof(double), typeof(RadialGauge), new PropertyMetadata(0.0)); + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty IsInteractiveProperty = + DependencyProperty.Register(nameof(IsInteractive), typeof(bool), typeof(RadialGauge), new PropertyMetadata(false, OnInteractivityChanged)); + + /// + /// Identifies the ScaleWidth dependency property. + /// + public static readonly DependencyProperty ScaleWidthProperty = + DependencyProperty.Register(nameof(ScaleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(26.0, OnScaleChanged)); + + /// + /// Identifies the NeedleBrush dependency property. + /// + public static readonly DependencyProperty NeedleBrushProperty = + DependencyProperty.Register(nameof(NeedleBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the Unit dependency property. + /// + public static readonly DependencyProperty UnitProperty = + DependencyProperty.Register(nameof(Unit), typeof(string), typeof(RadialGauge), new PropertyMetadata(string.Empty)); + + /// + /// Identifies the TrailBrush dependency property. + /// + public static readonly DependencyProperty TrailBrushProperty = + DependencyProperty.Register(nameof(TrailBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); + + /// + /// Identifies the ScaleBrush dependency property. + /// + public static readonly DependencyProperty ScaleBrushProperty = + DependencyProperty.Register(nameof(ScaleBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); + + /// + /// Identifies the ScaleTickBrush dependency property. + /// + public static readonly DependencyProperty ScaleTickBrushProperty = + DependencyProperty.Register(nameof(ScaleTickBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the TickBrush dependency property. + /// + public static readonly DependencyProperty TickBrushProperty = + DependencyProperty.Register(nameof(TickBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the ValueStringFormat dependency property. + /// + public static readonly DependencyProperty ValueStringFormatProperty = + DependencyProperty.Register(nameof(ValueStringFormat), typeof(string), typeof(RadialGauge), new PropertyMetadata("N0", (s, e) => OnValueChanged(s))); + + /// + /// Identifies the TickSpacing dependency property. + /// + public static readonly DependencyProperty TickSpacingProperty = + DependencyProperty.Register(nameof(TickSpacing), typeof(int), typeof(RadialGauge), new PropertyMetadata(10, OnFaceChanged)); + + /// + /// Identifies the NeedleLength dependency property. + /// + public static readonly DependencyProperty NeedleLengthProperty = + DependencyProperty.Register(nameof(NeedleLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(100d, OnFaceChanged)); + + /// + /// Identifies the NeedleWidth dependency property. + /// + public static readonly DependencyProperty NeedleWidthProperty = + DependencyProperty.Register(nameof(NeedleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(5d, OnFaceChanged)); + + /// + /// Identifies the ScalePadding dependency property. + /// + public static readonly DependencyProperty ScalePaddingProperty = + DependencyProperty.Register(nameof(ScalePadding), typeof(double), typeof(RadialGauge), new PropertyMetadata(23d, OnFaceChanged)); + + /// + /// Identifies the ScaleTickWidth dependency property. + /// + public static readonly DependencyProperty ScaleTickWidthProperty = + DependencyProperty.Register(nameof(ScaleTickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(2.5, OnFaceChanged)); + + /// + /// Identifies the TickWidth dependency property. + /// + public static readonly DependencyProperty TickWidthProperty = + DependencyProperty.Register(nameof(TickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(5d, OnFaceChanged)); + + /// + /// Identifies the TickLength dependency property. + /// + public static readonly DependencyProperty TickLengthProperty = + DependencyProperty.Register(nameof(TickLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(18d, OnFaceChanged)); + + /// + /// Identifies the MinAngle dependency property. + /// + public static readonly DependencyProperty MinAngleProperty = + DependencyProperty.Register(nameof(MinAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(-150, OnScaleChanged)); + + /// + /// Identifies the MaxAngle dependency property. + /// + public static readonly DependencyProperty MaxAngleProperty = + DependencyProperty.Register(nameof(MaxAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(150, OnScaleChanged)); + + /// + /// Identifies the ValueAngle dependency property. + /// + protected static readonly DependencyProperty ValueAngleProperty = + DependencyProperty.Register(nameof(ValueAngle), typeof(double), typeof(RadialGauge), new PropertyMetadata(null)); + + // Template Parts. + private const string ContainerPartName = "PART_Container"; + private const string ScalePartName = "PART_Scale"; + private const string TrailPartName = "PART_Trail"; + private const string ValueTextPartName = "PART_ValueText"; + + // For convenience. + private const double Degrees2Radians = Math.PI / 180; + + // High-contrast accessibility + private static readonly ThemeListener ThemeListener = new ThemeListener(); + private SolidColorBrush _needleBrush; + private Brush _trailBrush; + private Brush _scaleBrush; + private SolidColorBrush _scaleTickBrush; + private SolidColorBrush _tickBrush; + private Brush _foreground; + + private double _normalizedMinAngle; + private double _normalizedMaxAngle; + + private Compositor _compositor; + private ContainerVisual _root; + private SpriteVisual _needle; + + /// + /// Initializes a new instance of the class. + /// Create a default radial gauge control. + /// + public RadialGauge() + { + DefaultStyleKey = typeof(RadialGauge); + + SmallChange = 1; + LargeChange = 10; + + // Small step + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Left, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Up, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Right, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Down, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + // Large step + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Left, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Up, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Right, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Down, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + } + + private void ThemeListener_ThemeChanged(ThemeListener sender) + { + OnColorsChanged(); + } + + private void RadialGauge_Unloaded(object sender, RoutedEventArgs e) + { + // TODO: We should just use a WeakEventListener for ThemeChanged here, but ours currently doesn't support it. + // See proposal for general helper here: https://github.com/CommunityToolkit/dotnet/issues/404 + ThemeListener.ThemeChanged -= ThemeListener_ThemeChanged; + PointerReleased -= RadialGauge_PointerReleased; + Unloaded -= RadialGauge_Unloaded; + } + + /// + /// Gets or sets the rounding interval for the Value. + /// + public double StepSize + { + get { return (double)GetValue(StepSizeProperty); } + set { SetValue(StepSizeProperty, value); } + } + + /// + /// Gets or sets a value indicating whether the control accepts setting its value through interaction. + /// + public bool IsInteractive + { + get { return (bool)GetValue(IsInteractiveProperty); } + set { SetValue(IsInteractiveProperty, value); } + } + + /// + /// Gets or sets the width of the scale, in percentage of the gauge radius. + /// + public double ScaleWidth + { + get { return (double)GetValue(ScaleWidthProperty); } + set { SetValue(ScaleWidthProperty, value); } + } + + /// + /// Gets or sets the displayed unit measure. + /// + public string Unit + { + get { return (string)GetValue(UnitProperty); } + set { SetValue(UnitProperty, value); } + } + + /// + /// Gets or sets the needle brush. + /// + public SolidColorBrush NeedleBrush + { + get { return (SolidColorBrush)GetValue(NeedleBrushProperty); } + set { SetValue(NeedleBrushProperty, value); } + } + + /// + /// Gets or sets the trail brush. + /// + public Brush TrailBrush + { + get { return (Brush)GetValue(TrailBrushProperty); } + set { SetValue(TrailBrushProperty, value); } + } + + /// + /// Gets or sets the scale brush. + /// + public Brush ScaleBrush + { + get { return (Brush)GetValue(ScaleBrushProperty); } + set { SetValue(ScaleBrushProperty, value); } + } + + /// + /// Gets or sets the scale tick brush. + /// + public SolidColorBrush ScaleTickBrush + { + get { return (SolidColorBrush)GetValue(ScaleTickBrushProperty); } + set { SetValue(ScaleTickBrushProperty, value); } + } + + /// + /// Gets or sets the outer tick brush. + /// + public SolidColorBrush TickBrush + { + get { return (SolidColorBrush)GetValue(TickBrushProperty); } + set { SetValue(TickBrushProperty, value); } + } + + /// + /// Gets or sets the value string format. + /// + public string ValueStringFormat + { + get { return (string)GetValue(ValueStringFormatProperty); } + set { SetValue(ValueStringFormatProperty, value); } + } + + /// + /// Gets or sets the tick spacing, in units. Values of zero or less will be ignored when drawing. + /// + public int TickSpacing + { + get { return (int)GetValue(TickSpacingProperty); } + set { SetValue(TickSpacingProperty, value); } + } + + /// + /// Gets or sets the needle length, in percentage of the gauge radius. + /// + public double NeedleLength + { + get { return (double)GetValue(NeedleLengthProperty); } + set { SetValue(NeedleLengthProperty, value); } + } + + /// + /// Gets or sets the needle width, in percentage of the gauge radius. + /// + public double NeedleWidth + { + get { return (double)GetValue(NeedleWidthProperty); } + set { SetValue(NeedleWidthProperty, value); } + } + + /// + /// Gets or sets the distance of the scale from the outside of the control, in percentage of the gauge radius. /// - public static readonly DependencyProperty OrientationProperty = - DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(RadialGauge), new PropertyMetadata(null, OnOrientationChanged)); + public double ScalePadding + { + get { return (double)GetValue(ScalePaddingProperty); } + set { SetValue(ScalePaddingProperty, value); } + } + + /// + /// Gets or sets the width of the scale ticks, in percentage of the gauge radius. + /// + public double ScaleTickWidth + { + get { return (double)GetValue(ScaleTickWidthProperty); } + set { SetValue(ScaleTickWidthProperty, value); } + } + + /// + /// Gets or sets the length of the ticks, in percentage of the gauge radius. + /// + public double TickLength + { + get { return (double)GetValue(TickLengthProperty); } + set { SetValue(TickLengthProperty, value); } + } + + /// + /// Gets or sets the width of the ticks, in percentage of the gauge radius. + /// + public double TickWidth + { + get { return (double)GetValue(TickWidthProperty); } + set { SetValue(TickWidthProperty, value); } + } + + /// + /// Gets or sets the start angle of the scale, which corresponds with the Minimum value, in degrees. + /// + /// Changing MinAngle may require retemplating the control. + public int MinAngle + { + get { return (int)GetValue(MinAngleProperty); } + set { SetValue(MinAngleProperty, value); } + } + + /// + /// Gets or sets the end angle of the scale, which corresponds with the Maximum value, in degrees. + /// + /// Changing MaxAngle may require retemplating the control. + public int MaxAngle + { + get { return (int)GetValue(MaxAngleProperty); } + set { SetValue(MaxAngleProperty, value); } + } + + /// + /// Gets or sets the current angle of the needle (between MinAngle and MaxAngle). Setting the angle will update the Value. + /// + protected double ValueAngle + { + get { return (double)GetValue(ValueAngleProperty); } + set { SetValue(ValueAngleProperty, value); } + } + + /// + /// Gets the normalized minimum angle. + /// + /// The minimum angle in the range from -180 to 180. + protected double NormalizedMinAngle => _normalizedMinAngle; + + /// + /// Gets the normalized maximum angle. + /// + /// The maximum angle, in the range from -180 to 540. + protected double NormalizedMaxAngle => _normalizedMaxAngle; + + /// + protected override AutomationPeer OnCreateAutomationPeer() + { + return new RadialGaugeAutomationPeer(this); + } /// - /// Gets the preference of the rows/columns when there are a non-square number of children. Defaults to Vertical. + /// Update the visual state of the control when its template is changed. /// - public Orientation Orientation + protected override void OnApplyTemplate() + { + PointerReleased -= RadialGauge_PointerReleased; + ThemeListener.ThemeChanged -= ThemeListener_ThemeChanged; + Unloaded -= RadialGauge_Unloaded; + + // Remember local brushes. + _needleBrush = ReadLocalValue(NeedleBrushProperty) as SolidColorBrush; + _trailBrush = ReadLocalValue(TrailBrushProperty) as SolidColorBrush; + _scaleBrush = ReadLocalValue(ScaleBrushProperty) as SolidColorBrush; + _scaleTickBrush = ReadLocalValue(ScaleTickBrushProperty) as SolidColorBrush; + _tickBrush = ReadLocalValue(TickBrushProperty) as SolidColorBrush; + _foreground = ReadLocalValue(ForegroundProperty) as SolidColorBrush; + + PointerReleased += RadialGauge_PointerReleased; + ThemeListener.ThemeChanged += ThemeListener_ThemeChanged; + Unloaded += RadialGauge_Unloaded; + + // Apply color scheme. + OnColorsChanged(); + + base.OnApplyTemplate(); + } + + /// + protected override void OnMinimumChanged(double oldMinimum, double newMinimum) + { + base.OnMinimumChanged(oldMinimum, newMinimum); + OnScaleChanged(this); + } + + /// + protected override void OnMaximumChanged(double oldMaximum, double newMaximum) + { + base.OnMaximumChanged(oldMaximum, newMaximum); + OnScaleChanged(this); + } + + /// + protected override void OnValueChanged(double oldValue, double newValue) + { + OnValueChanged(this); + base.OnValueChanged(oldValue, newValue); + if (AutomationPeer.ListenerExists(AutomationEvents.LiveRegionChanged)) + { + var peer = FrameworkElementAutomationPeer.FromElement(this) as RadialGaugeAutomationPeer; + peer?.RaiseValueChangedEvent(oldValue, newValue); + } + } + + private static void OnValueChanged(DependencyObject d) { - get { return (Orientation)GetValue(OrientationProperty); } - set { SetValue(OrientationProperty, value); } + RadialGauge radialGauge = (RadialGauge)d; + if (!double.IsNaN(radialGauge.Value)) + { + if (radialGauge.StepSize != 0) + { + radialGauge.Value = radialGauge.RoundToMultiple(radialGauge.Value, radialGauge.StepSize); + } + + var middleOfScale = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + var valueText = radialGauge.GetTemplateChild(ValueTextPartName) as TextBlock; + radialGauge.ValueAngle = radialGauge.ValueToAngle(radialGauge.Value); + + // Needle + if (radialGauge._needle != null) + { + radialGauge._needle.RotationAngleInDegrees = (float)radialGauge.ValueAngle; + } + + // Trail + var trail = radialGauge.GetTemplateChild(TrailPartName) as Path; + if (trail != null) + { + if (radialGauge.ValueAngle > radialGauge.NormalizedMinAngle) + { + trail.Visibility = Visibility.Visible; + + if (radialGauge.ValueAngle - radialGauge.NormalizedMinAngle == 360) + { + // Draw full circle. + var eg = new EllipseGeometry(); + eg.Center = new Point(100, 100); + eg.RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + eg.RadiusY = eg.RadiusX; + trail.Data = eg; + } + else + { + // Draw arc. + var pg = new PathGeometry(); + var pf = new PathFigure(); + pf.IsClosed = false; + pf.StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale); + var seg = new ArcSegment(); + seg.SweepDirection = SweepDirection.Clockwise; + seg.IsLargeArc = radialGauge.ValueAngle > (180 + radialGauge.NormalizedMinAngle); + seg.Size = new Size(middleOfScale, middleOfScale); + seg.Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale); // On overflow, stop trail at MaxAngle. + pf.Segments.Add(seg); + pg.Figures.Add(pf); + trail.Data = pg; + } + } + else + { + trail.Visibility = Visibility.Collapsed; + } + } + + // Value Text + if (valueText != null) + { + valueText.Text = radialGauge.Value.ToString(radialGauge.ValueStringFormat); + } + } } - // Invalidate our layout when the property changes. - private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + private static void OnInteractivityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (dependencyObject is RadialGauge panel) + RadialGauge radialGauge = (RadialGauge)d; + + if (radialGauge.IsInteractive) + { + radialGauge.Tapped += radialGauge.RadialGauge_Tapped; + radialGauge.ManipulationDelta += radialGauge.RadialGauge_ManipulationDelta; + radialGauge.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; + } + else { - panel.InvalidateMeasure(); + radialGauge.Tapped -= radialGauge.RadialGauge_Tapped; + radialGauge.ManipulationDelta -= radialGauge.RadialGauge_ManipulationDelta; + radialGauge.ManipulationMode = ManipulationModes.None; } } - // Store calculations we want to use between the Measure and Arrange methods. - int _columnCount; - double _cellWidth, _cellHeight; + private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OnScaleChanged(d); + } - protected override Size MeasureOverride(Size availableSize) + private static void OnScaleChanged(DependencyObject d) { - // 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; } + RadialGauge radialGauge = (RadialGauge)d; + + radialGauge.UpdateNormalizedAngles(); + + var scale = radialGauge.GetTemplateChild(ScalePartName) as Path; + if (scale != null) + { + if (radialGauge.NormalizedMaxAngle - radialGauge.NormalizedMinAngle == 360) + { + // Draw full circle. + var eg = new EllipseGeometry(); + eg.Center = new Point(100, 100); + eg.RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + eg.RadiusY = eg.RadiusX; + scale.Data = eg; + } + else + { + // Draw arc. + var pg = new PathGeometry(); + var pf = new PathFigure(); + pf.IsClosed = false; + var middleOfScale = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + pf.StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale); + var seg = new ArcSegment(); + seg.SweepDirection = SweepDirection.Clockwise; + seg.IsLargeArc = radialGauge.NormalizedMaxAngle > (radialGauge.NormalizedMinAngle + 180); + seg.Size = new Size(middleOfScale, middleOfScale); + seg.Point = radialGauge.ScalePoint(radialGauge.NormalizedMaxAngle, middleOfScale); + pf.Segments.Add(seg); + pg.Figures.Add(pf); + scale.Data = pg; + } - int rowcount; + if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode) + { + OnFaceChanged(radialGauge); + } + } + } - // Now trim this square down to a rect, many times an entire row or column can be omitted. - if (aspectratio > 1) + private static void OnFaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode) { - rowcount = maxrc; - _columnCount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; + OnFaceChanged(d); + } + } + + private static void OnFaceChanged(DependencyObject d) + { + RadialGauge radialGauge = (RadialGauge)d; + + var container = radialGauge.GetTemplateChild(ContainerPartName) as Grid; + if (container == null || DesignTimeHelpers.IsRunningInLegacyDesignerMode) + { + // Bad template. + return; + } + + radialGauge._root = container.GetVisual(); + radialGauge._root.Children.RemoveAll(); + radialGauge._compositor = radialGauge._root.Compositor; + + if (radialGauge.TickSpacing > 0) + { + // Ticks. + SpriteVisual tick; + for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing) + { + tick = radialGauge._compositor.CreateSpriteVisual(); + tick.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength); + tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color); + tick.Opacity = (float)radialGauge.TickBrush.Opacity; + tick.Offset = new Vector3(100 - ((float)radialGauge.TickWidth / 2), 0.0f, 0); + tick.CenterPoint = new Vector3((float)radialGauge.TickWidth / 2, 100.0f, 0); + tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); + radialGauge._root.Children.InsertAtTop(tick); + } + + // Scale Ticks. + for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing) + { + tick = radialGauge._compositor.CreateSpriteVisual(); + tick.Size = new Vector2((float)radialGauge.ScaleTickWidth, (float)radialGauge.ScaleWidth); + tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.ScaleTickBrush.Color); + tick.Opacity = (float)radialGauge.ScaleTickBrush.Opacity; + tick.Offset = new Vector3(100 - ((float)radialGauge.ScaleTickWidth / 2), (float)radialGauge.ScalePadding, 0); + tick.CenterPoint = new Vector3((float)radialGauge.ScaleTickWidth / 2, 100 - (float)radialGauge.ScalePadding, 0); + tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); + radialGauge._root.Children.InsertAtTop(tick); + } + } + + // Needle. + radialGauge._needle = radialGauge._compositor.CreateSpriteVisual(); + radialGauge._needle.Size = new Vector2((float)radialGauge.NeedleWidth, (float)radialGauge.NeedleLength); + radialGauge._needle.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.NeedleBrush.Color); + radialGauge._needle.Opacity = (float)radialGauge.NeedleBrush.Opacity; + radialGauge._needle.CenterPoint = new Vector3((float)radialGauge.NeedleWidth / 2, (float)radialGauge.NeedleLength, 0); + radialGauge._needle.Offset = new Vector3(100 - ((float)radialGauge.NeedleWidth / 2), 100 - (float)radialGauge.NeedleLength, 0); + radialGauge._root.Children.InsertAtTop(radialGauge._needle); + + OnValueChanged(radialGauge); + } + + private void OnColorsChanged() + { + if (ThemeListener.IsHighContrast) + { + // Apply High Contrast Theme. + ClearBrush(_needleBrush, NeedleBrushProperty); + ClearBrush(_trailBrush, TrailBrushProperty); + ClearBrush(_scaleBrush, ScaleBrushProperty); + ClearBrush(_scaleTickBrush, ScaleTickBrushProperty); + ClearBrush(_tickBrush, TickBrushProperty); + ClearBrush(_foreground, ForegroundProperty); } else { - rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc; - _columnCount = maxrc; + // Apply User Defined or Default Theme. + RestoreBrush(_needleBrush, NeedleBrushProperty); + RestoreBrush(_trailBrush, TrailBrushProperty); + RestoreBrush(_scaleBrush, ScaleBrushProperty); + RestoreBrush(_scaleTickBrush, ScaleTickBrushProperty); + RestoreBrush(_tickBrush, TickBrushProperty); + RestoreBrush(_foreground, ForegroundProperty); } - // 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; + OnScaleChanged(this); + } - double maxcellheight = 0; + private void ClearBrush(Brush brush, DependencyProperty prop) + { + if (brush != null) + { + ClearValue(prop); + } + } - foreach (UIElement child in Children) + private void RestoreBrush(Brush source, DependencyProperty prop) + { + if (source != null) { - child.Measure(new Size(_cellWidth, _cellHeight)); - maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight; + SetValue(prop, source); } + } + + private void RadialGauge_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + SetGaugeValueFromPoint(e.Position); + } - return LimitUnboundedSize(availableSize, maxcellheight); + private void RadialGauge_Tapped(object sender, TappedRoutedEventArgs e) + { + SetGaugeValueFromPoint(e.GetPosition(this)); } - // 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)) + private void RadialGauge_PointerReleased(object sender, PointerRoutedEventArgs e) + { + if (IsInteractive) { - input.Height = maxcellheight * _columnCount; - _cellHeight = maxcellheight; + e.Handled = true; } - return input; } - protected override Size ArrangeOverride(Size finalSize) + private void UpdateNormalizedAngles() { - int count = 1; - double x, y; - foreach (UIElement child in Children) + var result = Mod(MinAngle, 360); + + if (result >= 180) + { + result = result - 360; + } + + _normalizedMinAngle = result; + + result = Mod(MaxAngle, 360); + + if (result < 180) + { + result = result + 360; + } + + if (result > NormalizedMinAngle + 360) { - 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++; + result = result - 360; } - return finalSize; + + _normalizedMaxAngle = result; + } + + private void SetGaugeValueFromPoint(Point p) + { + var pt = new Point(p.X - (ActualWidth / 2), -p.Y + (ActualHeight / 2)); + + var angle = Math.Atan2(pt.X, pt.Y) / Degrees2Radians; + var divider = Mod(NormalizedMaxAngle - NormalizedMinAngle, 360); + if (divider == 0) + { + divider = 360; + } + + var value = Minimum + ((Maximum - Minimum) * Mod(angle - NormalizedMinAngle, 360) / divider); + if (value < Minimum || value > Maximum) + { + // Ignore positions outside the scale angle. + return; + } + + Value = RoundToMultiple(value, StepSize); + } + + private Point ScalePoint(double angle, double middleOfScale) + { + return new Point(100 + (Math.Sin(Degrees2Radians * angle) * middleOfScale), 100 - (Math.Cos(Degrees2Radians * angle) * middleOfScale)); + } + + private double ValueToAngle(double value) + { + // Off-scale on the left. + if (value < Minimum) + { + return MinAngle; + } + + // Off-scale on the right. + if (value > Maximum) + { + return MaxAngle; + } + + return ((value - Minimum) / (Maximum - Minimum) * (NormalizedMaxAngle - NormalizedMinAngle)) + NormalizedMinAngle; + } + + private double Mod(double number, double divider) + { + var result = number % divider; + result = result < 0 ? result + divider : result; + return result; + } + + private double RoundToMultiple(double number, double multiple) + { + double modulo = number % multiple; + if (double.IsNaN(modulo)) + { + return number; + } + + if ((multiple - modulo) <= modulo) + { + modulo = multiple - modulo; + } + else + { + modulo *= -1; + } + + return number + modulo; + } + + private void AddKeyboardAccelerator( + VirtualKeyModifiers keyModifiers, + VirtualKey key, + TypedEventHandler handler) + { + var accelerator = new KeyboardAccelerator() + { + Modifiers = keyModifiers, + Key = key + }; + accelerator.Invoked += handler; + KeyboardAccelerators.Add(accelerator); } } diff --git a/components/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml b/components/RadialGauge/src/RadialGauge.xaml similarity index 100% rename from components/RadialGauge/src/RadialGaugeStyle_ClassicBinding.xaml rename to components/RadialGauge/src/RadialGauge.xaml diff --git a/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml b/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml deleted file mode 100644 index 95701222..00000000 --- a/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - 4,4,4,4 - - - - - - - - diff --git a/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs b/components/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs deleted file mode 100644 index d33464f1..00000000 --- a/components/RadialGauge/src/RadialGaugeStyle_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 RadialGaugeStyle_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 RadialGaugeStyle_xBind() - { - this.InitializeComponent(); - } -} diff --git a/components/RadialGauge/src/RadialGauge_ClassicBinding.cs b/components/RadialGauge/src/RadialGauge_ClassicBinding.cs deleted file mode 100644 index 507692ec..00000000 --- a/components/RadialGauge/src/RadialGauge_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 RadialGauge_ClassicBinding : Control -{ - /// - /// Creates a new instance of the class. - /// - public RadialGauge_ClassicBinding() - { - this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_ClassicBinding), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(RadialGauge_ClassicBinding), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/RadialGauge_xBind.cs b/components/RadialGauge/src/RadialGauge_xBind.cs deleted file mode 100644 index fae7278e..00000000 --- a/components/RadialGauge/src/RadialGauge_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 RadialGauge_xBind: Control -{ - /// - /// Creates a new instance of the class. - /// - public RadialGauge_xBind() - { - this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_xBind), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(RadialGauge_xBind), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/Themes/Generic.xaml b/components/RadialGauge/src/Themes/Generic.xaml index cb041611..d47de3c6 100644 --- a/components/RadialGauge/src/Themes/Generic.xaml +++ b/components/RadialGauge/src/Themes/Generic.xaml @@ -1,10 +1,9 @@ - - - + From e30e83ac517610130914dcd4d917d62823fa3109 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Mon, 1 May 2023 17:17:56 +0200 Subject: [PATCH 03/15] Update CommunityToolkit.WinUI.Controls.RadialGauge.csproj --- .../src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj index de26976d..bb9ee034 100644 --- a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj +++ b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj @@ -10,4 +10,10 @@ +<<<<<<< Updated upstream +======= + + + +>>>>>>> Stashed changes From 3d78873ebc9b759c4980e2559bc2c1b3e9b543cd Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 5 May 2023 11:37:17 +0200 Subject: [PATCH 04/15] Remove unused files --- .../samples/RadialGaugeTemplatedSample.xaml | 16 ------------ .../RadialGaugeTemplatedSample.xaml.cs | 21 --------------- ...RadialGaugeTemplatedStyleCustomSample.xaml | 26 ------------------- ...ialGaugeTemplatedStyleCustomSample.xaml.cs | 21 --------------- .../samples/RadialGaugeXbindBackedSample.xaml | 16 ------------ .../RadialGaugeXbindBackedSample.xaml.cs | 21 --------------- ...dialGaugeXbindBackedStyleCustomSample.xaml | 26 ------------------- ...lGaugeXbindBackedStyleCustomSample.xaml.cs | 21 --------------- 8 files changed, 168 deletions(-) delete mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml delete mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs delete mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml delete mode 100644 components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs delete mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml delete mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs delete mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml delete mode 100644 components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml b/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml deleted file mode 100644 index f6929b36..00000000 --- a/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs deleted file mode 100644 index 45811343..00000000 --- a/components/RadialGauge/samples/RadialGaugeTemplatedSample.xaml.cs +++ /dev/null @@ -1,21 +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 RadialGaugeExperiment.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(RadialGaugeTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] -public sealed partial class RadialGaugeTemplatedSample : Page -{ - public RadialGaugeTemplatedSample() - { - this.InitializeComponent(); - } -} diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml deleted file mode 100644 index b5763d48..00000000 --- a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs deleted file mode 100644 index 90e3df32..00000000 --- a/components/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml.cs +++ /dev/null @@ -1,21 +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 RadialGaugeExperiment.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(RadialGaugeTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] -public sealed partial class RadialGaugeTemplatedStyleCustomSample : Page -{ - public RadialGaugeTemplatedStyleCustomSample() - { - this.InitializeComponent(); - } -} diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml deleted file mode 100644 index 346f5b23..00000000 --- a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs deleted file mode 100644 index 7f982e26..00000000 --- a/components/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml.cs +++ /dev/null @@ -1,21 +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 RadialGaugeExperiment.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(RadialGaugeXbindBackedSample), "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 RadialGaugeXbindBackedSample : Page -{ - public RadialGaugeXbindBackedSample() - { - this.InitializeComponent(); - } -} diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml deleted file mode 100644 index 482ea47f..00000000 --- a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs deleted file mode 100644 index 465e0053..00000000 --- a/components/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml.cs +++ /dev/null @@ -1,21 +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 RadialGaugeExperiment.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(RadialGaugeXbindBackedStyleCustomSample), "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 RadialGaugeXbindBackedStyleCustomSample : Page -{ - public RadialGaugeXbindBackedStyleCustomSample() - { - this.InitializeComponent(); - } -} From 6d03435589ef04597003d6179bad88f9145b485d Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 5 May 2023 11:37:53 +0200 Subject: [PATCH 05/15] Adding AutomationPeer --- components/RadialGauge/samples/RadialGauge.md | 32 ------- .../samples/RadialGaugeCustomSample.xaml | 32 ++++--- ...yToolkit.WinUI.Controls.RadialGauge.csproj | 11 +-- components/RadialGauge/src/RadialGauge.cs | 39 +++++--- .../src/RadialGaugeAutomationPeer.cs | 93 +++++++++++++++++++ .../tests/ExampleRadialGaugeTestClass.cs | 47 +--------- .../tests/ExampleRadialGaugeTestPage.xaml | 3 +- 7 files changed, 150 insertions(+), 107 deletions(-) create mode 100644 components/RadialGauge/src/RadialGaugeAutomationPeer.cs diff --git a/components/RadialGauge/samples/RadialGauge.md b/components/RadialGauge/samples/RadialGauge.md index 3498d9dd..f7dca2d3 100644 --- a/components/RadialGauge/samples/RadialGauge.md +++ b/components/RadialGauge/samples/RadialGauge.md @@ -30,35 +30,3 @@ XAML Style that will be more light-weight to consume by an app developer: > [!Sample RadialGaugeCustomSample] -## 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 RadialGaugeTemplatedSample] - -### Custom style - -> [!SAMPLE RadialGaugeTemplatedStyleCustomSample] - -## 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 RadialGaugeXbindBackedSample] - -### Custom style - -> [!SAMPLE RadialGaugeXbindBackedStyleCustomSample] - diff --git a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml index f08b0cb8..595def10 100644 --- a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml +++ b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml @@ -1,4 +1,4 @@ - + - + - - - - - - - - + diff --git a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj index bb9ee034..fb80b23e 100644 --- a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj +++ b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj @@ -8,12 +8,11 @@ CommunityToolkit.WinUI.Controls.RadialGaugeRns + + + + + -<<<<<<< Updated upstream -======= - - - ->>>>>>> Stashed changes diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index 026fdf54..036f0d2a 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -2,6 +2,18 @@ // 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.Helpers; +using CommunityToolkit.WinUI.Extensions; +using System.Numerics; +using Windows.UI.Composition; +#if WINAPPSDK +using Path = Microsoft.UI.Xaml.Shapes.Path; +#else +using Path = Windows.UI.Xaml.Shapes.Path; +#endif +using VirtualKey = Windows.System.VirtualKey; +using VirtualKeyModifiers = Windows.System.VirtualKeyModifiers; + namespace CommunityToolkit.WinUI.Controls; /// /// A Modern UI Radial Gauge using XAML and Composition API. @@ -145,19 +157,19 @@ public class RadialGauge : RangeBase // High-contrast accessibility private static readonly ThemeListener ThemeListener = new ThemeListener(); - private SolidColorBrush _needleBrush; - private Brush _trailBrush; - private Brush _scaleBrush; - private SolidColorBrush _scaleTickBrush; - private SolidColorBrush _tickBrush; - private Brush _foreground; + private SolidColorBrush? _needleBrush; + private Brush? _trailBrush; + private Brush? _scaleBrush; + private SolidColorBrush? _scaleTickBrush; + private SolidColorBrush? _tickBrush; + private Brush? _foreground; private double _normalizedMinAngle; private double _normalizedMaxAngle; - private Compositor _compositor; - private ContainerVisual _root; - private SpriteVisual _needle; + private Compositor? _compositor; + private ContainerVisual? _root; + private SpriteVisual? _needle; /// /// Initializes a new instance of the class. @@ -609,9 +621,8 @@ private static void OnScaleChanged(DependencyObject d) radialGauge.UpdateNormalizedAngles(); - var scale = radialGauge.GetTemplateChild(ScalePartName) as Path; - if (scale != null) - { + if (radialGauge.GetTemplateChild(ScalePartName) is Path scale) + { if (radialGauge.NormalizedMaxAngle - radialGauge.NormalizedMinAngle == 360) { // Draw full circle. @@ -737,7 +748,7 @@ private void OnColorsChanged() OnScaleChanged(this); } - private void ClearBrush(Brush brush, DependencyProperty prop) + private void ClearBrush(Brush? brush, DependencyProperty prop) { if (brush != null) { @@ -745,7 +756,7 @@ private void ClearBrush(Brush brush, DependencyProperty prop) } } - private void RestoreBrush(Brush source, DependencyProperty prop) + private void RestoreBrush(Brush? source, DependencyProperty prop) { if (source != null) { diff --git a/components/RadialGauge/src/RadialGaugeAutomationPeer.cs b/components/RadialGauge/src/RadialGaugeAutomationPeer.cs new file mode 100644 index 00000000..2550a8ca --- /dev/null +++ b/components/RadialGauge/src/RadialGaugeAutomationPeer.cs @@ -0,0 +1,93 @@ +// 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. + +#if WINAPPSDK +using Microsoft.UI.Xaml.Automation.Provider; +#else +using Windows.UI.Xaml.Automation.Provider; +#endif + +namespace CommunityToolkit.WinUI.Controls; + +/// +/// Exposes to Microsoft UI Automation. +/// +public class RadialGaugeAutomationPeer : + RangeBaseAutomationPeer, + IRangeValueProvider +{ + /// + /// Initializes a new instance of the class. + /// + /// The owner element to create for. + public RadialGaugeAutomationPeer(RadialGauge owner) + : base(owner) + { + } + + /// + public new bool IsReadOnly => !((RadialGauge)Owner).IsInteractive; + + /// + public new double LargeChange => ((RadialGauge)Owner).StepSize; + + /// + public new double Maximum => ((RadialGauge)Owner).Maximum; + + /// + public new double Minimum => ((RadialGauge)Owner).Minimum; + + /// + public new double SmallChange => ((RadialGauge)Owner).StepSize; + + /// + public new double Value => ((RadialGauge)Owner).Value; + + /// + public new void SetValue(double value) + { + ((RadialGauge)Owner).Value = value; + } + + /// + protected override IList? GetChildrenCore() + { + return null; + } + + /// + protected override string GetNameCore() + { + var gauge = (RadialGauge)Owner; + return "radial gauge. " + (string.IsNullOrWhiteSpace(gauge.Unit) ? "no unit specified, " : "unit " + gauge.Unit + ", ") + Value; + } + + /// + protected override object GetPatternCore(PatternInterface patternInterface) + { + if (patternInterface == PatternInterface.RangeValue) + { + // Expose RangeValue properties. + return this; + } + + return base.GetPatternCore(patternInterface); + } + + /// + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Custom; + } + + /// + /// Raises the property changed event for this AutomationPeer for the provided identifier. + /// + /// Old value + /// New value + public void RaiseValueChangedEvent(double oldValue, double newValue) + { + RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, PropertyValue.CreateDouble(oldValue), PropertyValue.CreateDouble(newValue)); + } +} diff --git a/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs b/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs index 275bbd4a..f5c48fbb 100644 --- a/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs +++ b/components/RadialGauge/tests/ExampleRadialGaugeTestClass.cs @@ -57,13 +57,7 @@ public void SimpleUIAttributeExampleTest() public void SimpleUIExamplePageTest(ExampleRadialGaugeTestPage 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("RadialGaugeControl"); - - Assert.IsNotNull(componentByName); + } // You can still do async work with a UIThreadTestMethod as well. @@ -72,11 +66,7 @@ public async Task SimpleAsyncUIExamplePageTest(ExampleRadialGaugeTestPage 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 ----------------------------- @@ -87,11 +77,7 @@ public async Task SimpleAsyncUIExamplePageTest(ExampleRadialGaugeTestPage page) [TestMethod] public async Task ComplexAsyncUIExampleTest() { - await EnqueueAsync(() => - { - var component = new RadialGauge_ClassicBinding(); - Assert.IsNotNull(component); - }); + } // If you want to load other content not within a XAML page using the UIThreadTestMethod above. @@ -99,36 +85,13 @@ await EnqueueAsync(() => [TestMethod] public async Task ComplexAsyncLoadUIExampleTest() { - await EnqueueAsync(async () => - { - var component = new RadialGauge_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 RadialGauge_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/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml index c47247ce..d25a7d35 100644 --- a/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml +++ b/components/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml @@ -1,4 +1,4 @@ - + - From f0423f04928f38101a489abc57a10c94376f4b91 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 5 May 2023 12:05:29 +0200 Subject: [PATCH 06/15] Initial port --- components/RadialGauge/src/RadialGauge.cs | 13 +- components/RadialGauge/src/RadialGauge.xaml | 146 ++++++++++++++------ 2 files changed, 112 insertions(+), 47 deletions(-) diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index 036f0d2a..7e4656b7 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -3,13 +3,15 @@ // See the LICENSE file in the project root for more information. using CommunityToolkit.WinUI.Helpers; -using CommunityToolkit.WinUI.Extensions; using System.Numerics; -using Windows.UI.Composition; #if WINAPPSDK using Path = Microsoft.UI.Xaml.Shapes.Path; +using Microsoft.UI.Xaml.Hosting; +using Microsoft.UI.Composition; #else using Path = Windows.UI.Xaml.Shapes.Path; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Composition; #endif using VirtualKey = Windows.System.VirtualKey; using VirtualKeyModifiers = Windows.System.VirtualKeyModifiers; @@ -676,7 +678,12 @@ private static void OnFaceChanged(DependencyObject d) return; } - radialGauge._root = container.GetVisual(); + // TO DO: Replace with _radialGauge._root = container.GetVisual(); + var hostVisual = ElementCompositionPreview.GetElementVisual(container); + var root = hostVisual.Compositor.CreateContainerVisual(); + ElementCompositionPreview.SetElementChildVisual(container, root); + + radialGauge._root = root; radialGauge._root.Children.RemoveAll(); radialGauge._compositor = radialGauge._root.Compositor; diff --git a/components/RadialGauge/src/RadialGauge.xaml b/components/RadialGauge/src/RadialGauge.xaml index 363d3a6b..88dd6201 100644 --- a/components/RadialGauge/src/RadialGauge.xaml +++ b/components/RadialGauge/src/RadialGauge.xaml @@ -1,62 +1,120 @@ - + + + + + + + + + + + - + + + + + + + - + + + + + + + - + + + + + + + - 4,4,4,4 - - - - - - From 28569cf2433aaf05cbe656dce8d60a6d891d998b Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Sat, 6 May 2023 00:28:13 +0200 Subject: [PATCH 07/15] Various design improvements --- .../samples/RadialGaugeTemplatedSample.xaml | 6 +- ...RadialGaugeTemplatedStyleCustomSample.xaml | 8 +- .../samples/RadialGaugeXbindBackedSample.xaml | 6 +- ...dialGaugeXbindBackedStyleCustomSample.xaml | 8 +- .../samples/RadialGaugeCustomSample.xaml | 51 +- .../samples/RadialGaugeCustomSample.xaml.cs | 17 +- .../RadialGauge/src/RadialGauge.Input.cs | 119 ++++ .../RadialGauge/src/RadialGauge.Properties.cs | 421 ++++++++++++ components/RadialGauge/src/RadialGauge.cs | 598 ++++-------------- components/RadialGauge/src/RadialGauge.xaml | 108 ++-- .../tests/ExampleRadialGaugeTestPage.xaml | 5 +- 11 files changed, 760 insertions(+), 587 deletions(-) create mode 100644 components/RadialGauge/src/RadialGauge.Input.cs create mode 100644 components/RadialGauge/src/RadialGauge.Properties.cs diff --git a/RadialGauge/samples/RadialGaugeTemplatedSample.xaml b/RadialGauge/samples/RadialGaugeTemplatedSample.xaml index f6929b36..6eee0085 100644 --- a/RadialGauge/samples/RadialGaugeTemplatedSample.xaml +++ b/RadialGauge/samples/RadialGaugeTemplatedSample.xaml @@ -9,8 +9,8 @@ + FontSize="{x:Bind TextSize, Mode=OneWay}" + Foreground="{x:Bind TextForeground, Mode=OneWay}" + Visibility="{x:Bind IsTextVisible, Mode=OneWay}" /> diff --git a/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml index b5763d48..fe621e99 100644 --- a/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml +++ b/RadialGauge/samples/RadialGaugeTemplatedStyleCustomSample.xaml @@ -18,9 +18,9 @@ + FontSize="{x:Bind TextSize, Mode=OneWay}" + Foreground="{x:Bind TextForeground, Mode=OneWay}" + Style="{StaticResource CustomClassicBindingRadialGaugeStyle}" + Visibility="{x:Bind IsTextVisible, Mode=OneWay}" /> diff --git a/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml b/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml index 346f5b23..3ac538a0 100644 --- a/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml +++ b/RadialGauge/samples/RadialGaugeXbindBackedSample.xaml @@ -9,8 +9,8 @@ + FontSize="{x:Bind TextSize, Mode=OneWay}" + Foreground="{x:Bind TextForeground, Mode=OneWay}" + Visibility="{x:Bind IsTextVisible, Mode=OneWay}" /> diff --git a/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml index 482ea47f..2647884d 100644 --- a/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml +++ b/RadialGauge/samples/RadialGaugeXbindBackedStyleCustomSample.xaml @@ -18,9 +18,9 @@ + FontSize="{x:Bind TextSize, Mode=OneWay}" + Foreground="{x:Bind TextForeground, Mode=OneWay}" + Style="{StaticResource CustomxBindRadialGaugeStyle}" + Visibility="{x:Bind IsTextVisible, Mode=OneWay}" /> diff --git a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml index 595def10..24aa352f 100644 --- a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml +++ b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml @@ -1,4 +1,4 @@ - + - - - - - + + + diff --git a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs index 9592ad96..70acaad7 100644 --- a/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs +++ b/components/RadialGauge/samples/RadialGaugeCustomSample.xaml.cs @@ -9,8 +9,21 @@ namespace RadialGaugeExperiment.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")] +[ToolkitSampleBoolOption("Enabled", true, Title = "IsEnabled")] +[ToolkitSampleNumericOption("Value", 120, 0, 240, 1, false, Title = "Value")] +[ToolkitSampleNumericOption("StepSize", 30, 5, 30, 1, false, Title = "StepSize")] +[ToolkitSampleBoolOption("IsInteractive", true, Title = "IsInteractive")] +[ToolkitSampleNumericOption("TickSpacing", 30, 10, 30, 1, false, Title = "TickSpacing")] +[ToolkitSampleNumericOption("ScaleWidth", 12, 4, 50, 1, false, Title = "ScaleWidth")] +[ToolkitSampleNumericOption("MinAngle", 210, 0, 360, 1, false, Title = "MinAngle")] +[ToolkitSampleNumericOption("MaxAngle", 150, 0, 360, 1, false, Title = "MaxAngle")] +[ToolkitSampleNumericOption("NeedleWidth", 0, 1, 10, 1, false, Title = "NeedleWidth")] +[ToolkitSampleNumericOption("NeedleLength", 0, 20, 100, 1, false, Title = "NeedleLength")] +[ToolkitSampleNumericOption("TickLength", 8, 0, 30, 1, false, Title = "TickLength")] +[ToolkitSampleNumericOption("TickWidth", 2, 0, 30, 1, false, Title = "TickWidth")] +[ToolkitSampleNumericOption("ScalePadding", 0, 0, 100, 1, false, Title = "ScalePadding")] +[ToolkitSampleNumericOption("TickPadding", 16, 0, 100, 1, false, Title = "TickPadding")] +[ToolkitSampleNumericOption("ScaleTickWidth", 4, 2, 20, 1, false, Title = "ScaleTickWidth")] [ToolkitSample(id: nameof(RadialGaugeCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(RadialGauge)} custom control.")] public sealed partial class RadialGaugeCustomSample : Page diff --git a/components/RadialGauge/src/RadialGauge.Input.cs b/components/RadialGauge/src/RadialGauge.Input.cs new file mode 100644 index 00000000..f7873b47 --- /dev/null +++ b/components/RadialGauge/src/RadialGauge.Input.cs @@ -0,0 +1,119 @@ +// 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 VirtualKey = Windows.System.VirtualKey; +using VirtualKeyModifiers = Windows.System.VirtualKeyModifiers; + +namespace CommunityToolkit.WinUI.Controls; +public partial class RadialGauge : RangeBase +{ + private void RadialGauge_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) + { + SetGaugeValueFromPoint(e.Position); + } + + private void RadialGauge_Tapped(object sender, TappedRoutedEventArgs e) + { + SetGaugeValueFromPoint(e.GetPosition(this)); + } + + private void RadialGauge_PointerReleased(object sender, PointerRoutedEventArgs e) + { + if (IsInteractive) + { + e.Handled = true; + } + } + + private void SetKeyboardAccelerators() + { + // Small step + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Left, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Up, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Right, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Down, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); + kaea.Handled = true; + } + }); + + // Large step + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Left, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Up, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Right, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + + AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Down, static (_, kaea) => + { + if (kaea.Element is RadialGauge gauge) + { + gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); + kaea.Handled = true; + } + }); + } + + private void AddKeyboardAccelerator( + VirtualKeyModifiers keyModifiers, + VirtualKey key, + TypedEventHandler handler) + { + var accelerator = new KeyboardAccelerator() + { + Modifiers = keyModifiers, + Key = key + }; + accelerator.Invoked += handler; + KeyboardAccelerators.Add(accelerator); + } +} diff --git a/components/RadialGauge/src/RadialGauge.Properties.cs b/components/RadialGauge/src/RadialGauge.Properties.cs new file mode 100644 index 00000000..a496f63e --- /dev/null +++ b/components/RadialGauge/src/RadialGauge.Properties.cs @@ -0,0 +1,421 @@ +// 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.Helpers; + +namespace CommunityToolkit.WinUI.Controls; +public partial class RadialGauge : RangeBase +{ + /// + /// Identifies the optional StepSize property. + /// + public static readonly DependencyProperty StepSizeProperty = + DependencyProperty.Register(nameof(StepSize), typeof(double), typeof(RadialGauge), new PropertyMetadata(0.0)); + + /// + /// Identifies the property. + /// + public static readonly DependencyProperty IsInteractiveProperty = + DependencyProperty.Register(nameof(IsInteractive), typeof(bool), typeof(RadialGauge), new PropertyMetadata(true, OnInteractivityChanged)); + + /// + /// Identifies the ScaleWidth dependency property. + /// + public static readonly DependencyProperty ScaleWidthProperty = + DependencyProperty.Register(nameof(ScaleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(12.0, OnScaleChanged)); + + /// + /// Identifies the NeedleBrush dependency property. + /// + public static readonly DependencyProperty NeedleBrushProperty = + DependencyProperty.Register(nameof(NeedleBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the NeedleBrush dependency property. + /// + public static readonly DependencyProperty NeedleBorderBrushProperty = + DependencyProperty.Register(nameof(NeedleBorderBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the Unit dependency property. + /// + public static readonly DependencyProperty UnitProperty = + DependencyProperty.Register(nameof(Unit), typeof(string), typeof(RadialGauge), new PropertyMetadata(string.Empty)); + + /// + /// Identifies the TrailBrush dependency property. + /// + public static readonly DependencyProperty TrailBrushProperty = + DependencyProperty.Register(nameof(TrailBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); + + /// + /// Identifies the ScaleBrush dependency property. + /// + public static readonly DependencyProperty ScaleBrushProperty = + DependencyProperty.Register(nameof(ScaleBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); + + /// + /// Identifies the ScaleTickBrush dependency property. + /// + public static readonly DependencyProperty ScaleTickBrushProperty = + DependencyProperty.Register(nameof(ScaleTickBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the TickBrush dependency property. + /// + public static readonly DependencyProperty TickBrushProperty = + DependencyProperty.Register(nameof(TickBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); + + /// + /// Identifies the ValueStringFormat dependency property. + /// + public static readonly DependencyProperty ValueStringFormatProperty = + DependencyProperty.Register(nameof(ValueStringFormat), typeof(string), typeof(RadialGauge), new PropertyMetadata("N0", (s, e) => OnValueChanged(s))); + + /// + /// Identifies the TickSpacing dependency property. + /// + public static readonly DependencyProperty TickSpacingProperty = + DependencyProperty.Register(nameof(TickSpacing), typeof(int), typeof(RadialGauge), new PropertyMetadata(12, OnFaceChanged)); + + /// + /// Identifies the NeedleLength dependency property. + /// + public static readonly DependencyProperty NeedleLengthProperty = + DependencyProperty.Register(nameof(NeedleLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(100d, OnFaceChanged)); + + + /// + /// Identifies the NeedleLength dependency property. + /// + public static readonly DependencyProperty NeedleBorderThicknessProperty = + DependencyProperty.Register(nameof(NeedleBorderThickness), typeof(double), typeof(RadialGauge), new PropertyMetadata(1d, OnFaceChanged)); + + + /// + /// Identifies the NeedleWidth dependency property. + /// + public static readonly DependencyProperty NeedleWidthProperty = + DependencyProperty.Register(nameof(NeedleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(5d, OnFaceChanged)); + + /// + /// Identifies the ScalePadding dependency property. + /// + public static readonly DependencyProperty ScalePaddingProperty = + DependencyProperty.Register(nameof(ScalePadding), typeof(double), typeof(RadialGauge), new PropertyMetadata(0d, OnFaceChanged)); + + /// + /// Identifies the TickPadding dependency property. + /// + public static readonly DependencyProperty TickPaddingProperty = + DependencyProperty.Register(nameof(TickPadding), typeof(double), typeof(RadialGauge), new PropertyMetadata(24d, OnFaceChanged)); + + + + /// + /// Identifies the ScaleTickWidth dependency property. + /// + public static readonly DependencyProperty ScaleTickWidthProperty = + DependencyProperty.Register(nameof(ScaleTickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(2d, OnFaceChanged)); + + /// + /// Identifies the ScaleTickWidth dependency property. + /// + public static readonly DependencyProperty ScaleTickLengthProperty = + DependencyProperty.Register(nameof(ScaleTickLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(12d, OnFaceChanged)); + + + /// + /// Identifies the ScaleTickWidth dependency property. + /// + public static readonly DependencyProperty ScaleTickCornerRadiusProperty = + DependencyProperty.Register(nameof(ScaleTickCornerRadius), typeof(double), typeof(RadialGauge), new PropertyMetadata(2d, OnFaceChanged)); + + /// + /// Identifies the TickWidth dependency property. + /// + public static readonly DependencyProperty TickWidthProperty = + DependencyProperty.Register(nameof(TickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(2d, OnFaceChanged)); + + /// + /// Identifies the TickLength dependency property. + /// + public static readonly DependencyProperty TickLengthProperty = + DependencyProperty.Register(nameof(TickLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(6d, OnFaceChanged)); + + /// + /// Identifies the TickCornerRadius dependency property. + /// + public static readonly DependencyProperty TickCornerRadiusProperty = + DependencyProperty.Register(nameof(TickCornerRadius), typeof(double), typeof(RadialGauge), new PropertyMetadata(2d, OnFaceChanged)); + + /// + /// Identifies the MinAngle dependency property. + /// + public static readonly DependencyProperty MinAngleProperty = + DependencyProperty.Register(nameof(MinAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(-150, OnScaleChanged)); + + /// + /// Identifies the MaxAngle dependency property. + /// + public static readonly DependencyProperty MaxAngleProperty = + DependencyProperty.Register(nameof(MaxAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(150, OnScaleChanged)); + + /// + /// Identifies the ValueAngle dependency property. + /// + protected static readonly DependencyProperty ValueAngleProperty = + DependencyProperty.Register(nameof(ValueAngle), typeof(double), typeof(RadialGauge), new PropertyMetadata(null)); + + /// + /// Gets or sets the rounding interval for the Value. + /// + public double StepSize + { + get { return (double)GetValue(StepSizeProperty); } + set { SetValue(StepSizeProperty, value); } + } + + /// + /// Gets or sets a value indicating whether the control accepts setting its value through interaction. + /// + public bool IsInteractive + { + get { return (bool)GetValue(IsInteractiveProperty); } + set { SetValue(IsInteractiveProperty, value); } + } + + /// + /// Gets or sets the width of the scale, in percentage of the gauge radius. + /// + public double ScaleWidth + { + get { return (double)GetValue(ScaleWidthProperty); } + set { SetValue(ScaleWidthProperty, value); } + } + + /// + /// Gets or sets the displayed unit measure. + /// + public string Unit + { + get { return (string)GetValue(UnitProperty); } + set { SetValue(UnitProperty, value); } + } + + /// + /// Gets or sets the needle brush. + /// + public SolidColorBrush NeedleBrush + { + get { return (SolidColorBrush)GetValue(NeedleBrushProperty); } + set { SetValue(NeedleBrushProperty, value); } + } + + /// + /// Gets or sets the needle border brush. + /// + public SolidColorBrush NeedleBorderBrush + { + get { return (SolidColorBrush)GetValue(NeedleBorderBrushProperty); } + set { SetValue(NeedleBorderBrushProperty, value); } + } + + /// + /// Gets or sets the trail brush. + /// + public Brush TrailBrush + { + get { return (Brush)GetValue(TrailBrushProperty); } + set { SetValue(TrailBrushProperty, value); } + } + + /// + /// Gets or sets the scale brush. + /// + public Brush ScaleBrush + { + get { return (Brush)GetValue(ScaleBrushProperty); } + set { SetValue(ScaleBrushProperty, value); } + } + + /// + /// Gets or sets the scale tick brush. + /// + public SolidColorBrush ScaleTickBrush + { + get { return (SolidColorBrush)GetValue(ScaleTickBrushProperty); } + set { SetValue(ScaleTickBrushProperty, value); } + } + + /// + /// Gets or sets the scale tick cornerradius. + /// + public double ScaleTickCornerRadius + { + get { return (double)GetValue(ScaleTickCornerRadiusProperty); } + set { SetValue(ScaleTickCornerRadiusProperty, value); } + } + + + /// + /// Gets or sets the outer tick brush. + /// + public SolidColorBrush TickBrush + { + get { return (SolidColorBrush)GetValue(TickBrushProperty); } + set { SetValue(TickBrushProperty, value); } + } + + /// + /// Gets or sets the value string format. + /// + public string ValueStringFormat + { + get { return (string)GetValue(ValueStringFormatProperty); } + set { SetValue(ValueStringFormatProperty, value); } + } + + /// + /// Gets or sets the tick spacing, in units. Values of zero or less will be ignored when drawing. + /// + public int TickSpacing + { + get { return (int)GetValue(TickSpacingProperty); } + set { SetValue(TickSpacingProperty, value); } + } + + /// + /// Gets or sets the needle length, in percentage of the gauge radius. + /// + public double NeedleLength + { + get { return (double)GetValue(NeedleLengthProperty); } + set { SetValue(NeedleLengthProperty, value); } + } + + + /// + /// Gets or sets the needle length, in percentage of the gauge radius. + /// + public double NeedleBorderThickness + { + get { return (double)GetValue(NeedleBorderThicknessProperty); } + set { SetValue(NeedleBorderThicknessProperty, value); } + } + + /// + /// Gets or sets the needle width, in percentage of the gauge radius. + /// + public double NeedleWidth + { + get { return (double)GetValue(NeedleWidthProperty); } + set { SetValue(NeedleWidthProperty, value); } + } + + /// + /// Gets or sets the distance of the scale from the outside of the control, in percentage of the gauge radius. + /// + public double ScalePadding + { + get { return (double)GetValue(ScalePaddingProperty); } + set { SetValue(ScalePaddingProperty, value); } + } + + /// + /// Gets or sets the distance of the ticks from the outside of the control, in percentage of the gauge radius. + /// + public double TickPadding + { + get { return (double)GetValue(TickPaddingProperty); } + set { SetValue(TickPaddingProperty, value); } + } + + /// + /// Gets or sets the width of the scale ticks, in percentage of the gauge radius. + /// + public double ScaleTickWidth + { + get { return (double)GetValue(ScaleTickWidthProperty); } + set { SetValue(ScaleTickWidthProperty, value); } + } + + /// + /// Gets or sets the length of the ticks, in percentage of the gauge radius. + /// + public double ScaleTickLength + { + get { return (double)GetValue(ScaleTickLengthProperty); } + set { SetValue(ScaleTickLengthProperty, value); } + } + + /// + /// Gets or sets the length of the ticks, in percentage of the gauge radius. + /// + public double TickLength + { + get { return (double)GetValue(TickLengthProperty); } + set { SetValue(TickLengthProperty, value); } + } + + /// + /// Gets or sets the width of the ticks, in percentage of the gauge radius. + /// + public double TickWidth + { + get { return (double)GetValue(TickWidthProperty); } + set { SetValue(TickWidthProperty, value); } + } + + /// + /// Gets or sets the CornerRadius of the ticks, in percentage of the gauge radius. + /// + public double TickCornerRadius + { + get { return (double)GetValue(TickCornerRadiusProperty); } + set { SetValue(TickCornerRadiusProperty, value); } + } + + /// + /// Gets or sets the start angle of the scale, which corresponds with the Minimum value, in degrees. + /// + /// Changing MinAngle may require retemplating the control. + public int MinAngle + { + get { return (int)GetValue(MinAngleProperty); } + set { SetValue(MinAngleProperty, value); } + } + + /// + /// Gets or sets the end angle of the scale, which corresponds with the Maximum value, in degrees. + /// + /// Changing MaxAngle may require retemplating the control. + public int MaxAngle + { + get { return (int)GetValue(MaxAngleProperty); } + set { SetValue(MaxAngleProperty, value); } + } + + /// + /// Gets or sets the current angle of the needle (between MinAngle and MaxAngle). Setting the angle will update the Value. + /// + protected double ValueAngle + { + get { return (double)GetValue(ValueAngleProperty); } + set { SetValue(ValueAngleProperty, value); } + } + + private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OnScaleChanged(d); + } + + private static void OnFaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode) + { + OnFaceChanged(d); + } + } +} diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index 7e4656b7..cb3211f5 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -13,8 +13,6 @@ using Windows.UI.Xaml.Hosting; using Windows.UI.Composition; #endif -using VirtualKey = Windows.System.VirtualKey; -using VirtualKeyModifiers = Windows.System.VirtualKeyModifiers; namespace CommunityToolkit.WinUI.Controls; /// @@ -26,128 +24,8 @@ namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = ScalePartName, Type = typeof(Path))] [TemplatePart(Name = TrailPartName, Type = typeof(Path))] [TemplatePart(Name = ValueTextPartName, Type = typeof(TextBlock))] -public class RadialGauge : RangeBase +public partial class RadialGauge : RangeBase { - /// - /// Identifies the optional StepSize property. - /// - public static readonly DependencyProperty StepSizeProperty = - DependencyProperty.Register(nameof(StepSize), typeof(double), typeof(RadialGauge), new PropertyMetadata(0.0)); - - /// - /// Identifies the property. - /// - public static readonly DependencyProperty IsInteractiveProperty = - DependencyProperty.Register(nameof(IsInteractive), typeof(bool), typeof(RadialGauge), new PropertyMetadata(false, OnInteractivityChanged)); - - /// - /// Identifies the ScaleWidth dependency property. - /// - public static readonly DependencyProperty ScaleWidthProperty = - DependencyProperty.Register(nameof(ScaleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(26.0, OnScaleChanged)); - - /// - /// Identifies the NeedleBrush dependency property. - /// - public static readonly DependencyProperty NeedleBrushProperty = - DependencyProperty.Register(nameof(NeedleBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); - - /// - /// Identifies the Unit dependency property. - /// - public static readonly DependencyProperty UnitProperty = - DependencyProperty.Register(nameof(Unit), typeof(string), typeof(RadialGauge), new PropertyMetadata(string.Empty)); - - /// - /// Identifies the TrailBrush dependency property. - /// - public static readonly DependencyProperty TrailBrushProperty = - DependencyProperty.Register(nameof(TrailBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); - - /// - /// Identifies the ScaleBrush dependency property. - /// - public static readonly DependencyProperty ScaleBrushProperty = - DependencyProperty.Register(nameof(ScaleBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null)); - - /// - /// Identifies the ScaleTickBrush dependency property. - /// - public static readonly DependencyProperty ScaleTickBrushProperty = - DependencyProperty.Register(nameof(ScaleTickBrush), typeof(Brush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); - - /// - /// Identifies the TickBrush dependency property. - /// - public static readonly DependencyProperty TickBrushProperty = - DependencyProperty.Register(nameof(TickBrush), typeof(SolidColorBrush), typeof(RadialGauge), new PropertyMetadata(null, OnFaceChanged)); - - /// - /// Identifies the ValueStringFormat dependency property. - /// - public static readonly DependencyProperty ValueStringFormatProperty = - DependencyProperty.Register(nameof(ValueStringFormat), typeof(string), typeof(RadialGauge), new PropertyMetadata("N0", (s, e) => OnValueChanged(s))); - - /// - /// Identifies the TickSpacing dependency property. - /// - public static readonly DependencyProperty TickSpacingProperty = - DependencyProperty.Register(nameof(TickSpacing), typeof(int), typeof(RadialGauge), new PropertyMetadata(10, OnFaceChanged)); - - /// - /// Identifies the NeedleLength dependency property. - /// - public static readonly DependencyProperty NeedleLengthProperty = - DependencyProperty.Register(nameof(NeedleLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(100d, OnFaceChanged)); - - /// - /// Identifies the NeedleWidth dependency property. - /// - public static readonly DependencyProperty NeedleWidthProperty = - DependencyProperty.Register(nameof(NeedleWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(5d, OnFaceChanged)); - - /// - /// Identifies the ScalePadding dependency property. - /// - public static readonly DependencyProperty ScalePaddingProperty = - DependencyProperty.Register(nameof(ScalePadding), typeof(double), typeof(RadialGauge), new PropertyMetadata(23d, OnFaceChanged)); - - /// - /// Identifies the ScaleTickWidth dependency property. - /// - public static readonly DependencyProperty ScaleTickWidthProperty = - DependencyProperty.Register(nameof(ScaleTickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(2.5, OnFaceChanged)); - - /// - /// Identifies the TickWidth dependency property. - /// - public static readonly DependencyProperty TickWidthProperty = - DependencyProperty.Register(nameof(TickWidth), typeof(double), typeof(RadialGauge), new PropertyMetadata(5d, OnFaceChanged)); - - /// - /// Identifies the TickLength dependency property. - /// - public static readonly DependencyProperty TickLengthProperty = - DependencyProperty.Register(nameof(TickLength), typeof(double), typeof(RadialGauge), new PropertyMetadata(18d, OnFaceChanged)); - - /// - /// Identifies the MinAngle dependency property. - /// - public static readonly DependencyProperty MinAngleProperty = - DependencyProperty.Register(nameof(MinAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(-150, OnScaleChanged)); - - /// - /// Identifies the MaxAngle dependency property. - /// - public static readonly DependencyProperty MaxAngleProperty = - DependencyProperty.Register(nameof(MaxAngle), typeof(int), typeof(RadialGauge), new PropertyMetadata(150, OnScaleChanged)); - - /// - /// Identifies the ValueAngle dependency property. - /// - protected static readonly DependencyProperty ValueAngleProperty = - DependencyProperty.Register(nameof(ValueAngle), typeof(double), typeof(RadialGauge), new PropertyMetadata(null)); - // Template Parts. private const string ContainerPartName = "PART_Container"; private const string ScalePartName = "PART_Scale"; @@ -160,6 +38,7 @@ public class RadialGauge : RangeBase // High-contrast accessibility private static readonly ThemeListener ThemeListener = new ThemeListener(); private SolidColorBrush? _needleBrush; + private SolidColorBrush? _needleBorderBrush; private Brush? _trailBrush; private Brush? _scaleBrush; private SolidColorBrush? _scaleTickBrush; @@ -171,7 +50,7 @@ public class RadialGauge : RangeBase private Compositor? _compositor; private ContainerVisual? _root; - private SpriteVisual? _needle; + private CompositionSpriteShape? _needle; /// /// Initializes a new instance of the class. @@ -184,79 +63,7 @@ public RadialGauge() SmallChange = 1; LargeChange = 10; - // Small step - AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Left, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Up, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Right, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.SmallChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.None, VirtualKey.Down, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.SmallChange)); - kaea.Handled = true; - } - }); - - // Large step - AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Left, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Up, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Right, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Min(gauge.Maximum, gauge.Value + Math.Max(gauge.StepSize, gauge.LargeChange)); - kaea.Handled = true; - } - }); - - AddKeyboardAccelerator(VirtualKeyModifiers.Control, VirtualKey.Down, static (_, kaea) => - { - if (kaea.Element is RadialGauge gauge) - { - gauge.Value = Math.Max(gauge.Minimum, gauge.Value - Math.Max(gauge.StepSize, gauge.LargeChange)); - kaea.Handled = true; - } - }); + SetKeyboardAccelerators(); } private void ThemeListener_ThemeChanged(ThemeListener sender) @@ -273,206 +80,6 @@ private void RadialGauge_Unloaded(object sender, RoutedEventArgs e) Unloaded -= RadialGauge_Unloaded; } - /// - /// Gets or sets the rounding interval for the Value. - /// - public double StepSize - { - get { return (double)GetValue(StepSizeProperty); } - set { SetValue(StepSizeProperty, value); } - } - - /// - /// Gets or sets a value indicating whether the control accepts setting its value through interaction. - /// - public bool IsInteractive - { - get { return (bool)GetValue(IsInteractiveProperty); } - set { SetValue(IsInteractiveProperty, value); } - } - - /// - /// Gets or sets the width of the scale, in percentage of the gauge radius. - /// - public double ScaleWidth - { - get { return (double)GetValue(ScaleWidthProperty); } - set { SetValue(ScaleWidthProperty, value); } - } - - /// - /// Gets or sets the displayed unit measure. - /// - public string Unit - { - get { return (string)GetValue(UnitProperty); } - set { SetValue(UnitProperty, value); } - } - - /// - /// Gets or sets the needle brush. - /// - public SolidColorBrush NeedleBrush - { - get { return (SolidColorBrush)GetValue(NeedleBrushProperty); } - set { SetValue(NeedleBrushProperty, value); } - } - - /// - /// Gets or sets the trail brush. - /// - public Brush TrailBrush - { - get { return (Brush)GetValue(TrailBrushProperty); } - set { SetValue(TrailBrushProperty, value); } - } - - /// - /// Gets or sets the scale brush. - /// - public Brush ScaleBrush - { - get { return (Brush)GetValue(ScaleBrushProperty); } - set { SetValue(ScaleBrushProperty, value); } - } - - /// - /// Gets or sets the scale tick brush. - /// - public SolidColorBrush ScaleTickBrush - { - get { return (SolidColorBrush)GetValue(ScaleTickBrushProperty); } - set { SetValue(ScaleTickBrushProperty, value); } - } - - /// - /// Gets or sets the outer tick brush. - /// - public SolidColorBrush TickBrush - { - get { return (SolidColorBrush)GetValue(TickBrushProperty); } - set { SetValue(TickBrushProperty, value); } - } - - /// - /// Gets or sets the value string format. - /// - public string ValueStringFormat - { - get { return (string)GetValue(ValueStringFormatProperty); } - set { SetValue(ValueStringFormatProperty, value); } - } - - /// - /// Gets or sets the tick spacing, in units. Values of zero or less will be ignored when drawing. - /// - public int TickSpacing - { - get { return (int)GetValue(TickSpacingProperty); } - set { SetValue(TickSpacingProperty, value); } - } - - /// - /// Gets or sets the needle length, in percentage of the gauge radius. - /// - public double NeedleLength - { - get { return (double)GetValue(NeedleLengthProperty); } - set { SetValue(NeedleLengthProperty, value); } - } - - /// - /// Gets or sets the needle width, in percentage of the gauge radius. - /// - public double NeedleWidth - { - get { return (double)GetValue(NeedleWidthProperty); } - set { SetValue(NeedleWidthProperty, value); } - } - - /// - /// Gets or sets the distance of the scale from the outside of the control, in percentage of the gauge radius. - /// - public double ScalePadding - { - get { return (double)GetValue(ScalePaddingProperty); } - set { SetValue(ScalePaddingProperty, value); } - } - - /// - /// Gets or sets the width of the scale ticks, in percentage of the gauge radius. - /// - public double ScaleTickWidth - { - get { return (double)GetValue(ScaleTickWidthProperty); } - set { SetValue(ScaleTickWidthProperty, value); } - } - - /// - /// Gets or sets the length of the ticks, in percentage of the gauge radius. - /// - public double TickLength - { - get { return (double)GetValue(TickLengthProperty); } - set { SetValue(TickLengthProperty, value); } - } - - /// - /// Gets or sets the width of the ticks, in percentage of the gauge radius. - /// - public double TickWidth - { - get { return (double)GetValue(TickWidthProperty); } - set { SetValue(TickWidthProperty, value); } - } - - /// - /// Gets or sets the start angle of the scale, which corresponds with the Minimum value, in degrees. - /// - /// Changing MinAngle may require retemplating the control. - public int MinAngle - { - get { return (int)GetValue(MinAngleProperty); } - set { SetValue(MinAngleProperty, value); } - } - - /// - /// Gets or sets the end angle of the scale, which corresponds with the Maximum value, in degrees. - /// - /// Changing MaxAngle may require retemplating the control. - public int MaxAngle - { - get { return (int)GetValue(MaxAngleProperty); } - set { SetValue(MaxAngleProperty, value); } - } - - /// - /// Gets or sets the current angle of the needle (between MinAngle and MaxAngle). Setting the angle will update the Value. - /// - protected double ValueAngle - { - get { return (double)GetValue(ValueAngleProperty); } - set { SetValue(ValueAngleProperty, value); } - } - - /// - /// Gets the normalized minimum angle. - /// - /// The minimum angle in the range from -180 to 180. - protected double NormalizedMinAngle => _normalizedMinAngle; - - /// - /// Gets the normalized maximum angle. - /// - /// The maximum angle, in the range from -180 to 540. - protected double NormalizedMaxAngle => _normalizedMaxAngle; - - /// - protected override AutomationPeer OnCreateAutomationPeer() - { - return new RadialGaugeAutomationPeer(this); - } - /// /// Update the visual state of the control when its template is changed. /// @@ -484,6 +91,7 @@ protected override void OnApplyTemplate() // Remember local brushes. _needleBrush = ReadLocalValue(NeedleBrushProperty) as SolidColorBrush; + _needleBorderBrush = ReadLocalValue(NeedleBorderBrushProperty) as SolidColorBrush; _trailBrush = ReadLocalValue(TrailBrushProperty) as SolidColorBrush; _scaleBrush = ReadLocalValue(ScaleBrushProperty) as SolidColorBrush; _scaleTickBrush = ReadLocalValue(ScaleTickBrushProperty) as SolidColorBrush; @@ -500,6 +108,24 @@ protected override void OnApplyTemplate() base.OnApplyTemplate(); } + /// + /// Gets the normalized minimum angle. + /// + /// The minimum angle in the range from -180 to 180. + protected double NormalizedMinAngle => _normalizedMinAngle; + + /// + /// Gets the normalized maximum angle. + /// + /// The maximum angle, in the range from -180 to 540. + protected double NormalizedMaxAngle => _normalizedMaxAngle; + + /// + protected override AutomationPeer OnCreateAutomationPeer() + { + return new RadialGaugeAutomationPeer(this); + } + /// protected override void OnMinimumChanged(double oldMinimum, double newMinimum) { @@ -557,9 +183,11 @@ private static void OnValueChanged(DependencyObject d) if (radialGauge.ValueAngle - radialGauge.NormalizedMinAngle == 360) { // Draw full circle. - var eg = new EllipseGeometry(); - eg.Center = new Point(100, 100); - eg.RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + var eg = new EllipseGeometry + { + Center = new Point(100, 100), + RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2) + }; eg.RadiusY = eg.RadiusX; trail.Data = eg; } @@ -567,14 +195,18 @@ private static void OnValueChanged(DependencyObject d) { // Draw arc. var pg = new PathGeometry(); - var pf = new PathFigure(); - pf.IsClosed = false; - pf.StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale); - var seg = new ArcSegment(); - seg.SweepDirection = SweepDirection.Clockwise; - seg.IsLargeArc = radialGauge.ValueAngle > (180 + radialGauge.NormalizedMinAngle); - seg.Size = new Size(middleOfScale, middleOfScale); - seg.Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale); // On overflow, stop trail at MaxAngle. + var pf = new PathFigure + { + IsClosed = false, + StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale) + }; + var seg = new ArcSegment + { + SweepDirection = SweepDirection.Clockwise, + IsLargeArc = radialGauge.ValueAngle > (180 + radialGauge.NormalizedMinAngle), + Size = new Size(middleOfScale, middleOfScale), + Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale) // On overflow, stop trail at MaxAngle. + }; pf.Segments.Add(seg); pg.Figures.Add(pf); trail.Data = pg; @@ -612,11 +244,6 @@ private static void OnInteractivityChanged(DependencyObject d, DependencyPropert } } - private static void OnScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - OnScaleChanged(d); - } - private static void OnScaleChanged(DependencyObject d) { RadialGauge radialGauge = (RadialGauge)d; @@ -628,9 +255,11 @@ private static void OnScaleChanged(DependencyObject d) if (radialGauge.NormalizedMaxAngle - radialGauge.NormalizedMinAngle == 360) { // Draw full circle. - var eg = new EllipseGeometry(); - eg.Center = new Point(100, 100); - eg.RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); + var eg = new EllipseGeometry + { + Center = new Point(100, 100), + RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2) + }; eg.RadiusY = eg.RadiusX; scale.Data = eg; } @@ -638,15 +267,19 @@ private static void OnScaleChanged(DependencyObject d) { // Draw arc. var pg = new PathGeometry(); - var pf = new PathFigure(); - pf.IsClosed = false; + var pf = new PathFigure + { + IsClosed = false + }; var middleOfScale = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); pf.StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale); - var seg = new ArcSegment(); - seg.SweepDirection = SweepDirection.Clockwise; - seg.IsLargeArc = radialGauge.NormalizedMaxAngle > (radialGauge.NormalizedMinAngle + 180); - seg.Size = new Size(middleOfScale, middleOfScale); - seg.Point = radialGauge.ScalePoint(radialGauge.NormalizedMaxAngle, middleOfScale); + var seg = new ArcSegment + { + SweepDirection = SweepDirection.Clockwise, + IsLargeArc = radialGauge.NormalizedMaxAngle > (radialGauge.NormalizedMinAngle + 180), + Size = new Size(middleOfScale, middleOfScale), + Point = radialGauge.ScalePoint(radialGauge.NormalizedMaxAngle, middleOfScale) + }; pf.Segments.Add(seg); pg.Figures.Add(pf); scale.Data = pg; @@ -659,19 +292,12 @@ private static void OnScaleChanged(DependencyObject d) } } - private static void OnFaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (!DesignTimeHelpers.IsRunningInLegacyDesignerMode) - { - OnFaceChanged(d); - } - } - private static void OnFaceChanged(DependencyObject d) { RadialGauge radialGauge = (RadialGauge)d; var container = radialGauge.GetTemplateChild(ContainerPartName) as Grid; + if (container == null || DesignTimeHelpers.IsRunningInLegacyDesignerMode) { // Bad template. @@ -682,49 +308,75 @@ private static void OnFaceChanged(DependencyObject d) var hostVisual = ElementCompositionPreview.GetElementVisual(container); var root = hostVisual.Compositor.CreateContainerVisual(); ElementCompositionPreview.SetElementChildVisual(container, root); - radialGauge._root = root; + // + radialGauge._root.Children.RemoveAll(); radialGauge._compositor = radialGauge._root.Compositor; if (radialGauge.TickSpacing > 0) { // Ticks. - SpriteVisual tick; + var tick = radialGauge._compositor.CreateShapeVisual(); + tick.Size = new Vector2((float)(radialGauge.Height), (float)(radialGauge.Width)); + tick.BorderMode = CompositionBorderMode.Soft; + tick.Opacity = (float)radialGauge.TickBrush.Opacity; + + var roundedTickRectangle = radialGauge._compositor.CreateRoundedRectangleGeometry(); + roundedTickRectangle.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength); + roundedTickRectangle.CornerRadius = new Vector2((float)radialGauge.TickCornerRadius, (float)radialGauge.TickCornerRadius); + for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing) { - tick = radialGauge._compositor.CreateSpriteVisual(); - tick.Size = new Vector2((float)radialGauge.TickWidth, (float)radialGauge.TickLength); - tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color); - tick.Opacity = (float)radialGauge.TickBrush.Opacity; - tick.Offset = new Vector3(100 - ((float)radialGauge.TickWidth / 2), 0.0f, 0); - tick.CenterPoint = new Vector3((float)radialGauge.TickWidth / 2, 100.0f, 0); - tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); - radialGauge._root.Children.InsertAtTop(tick); + var tickSpriteShape = radialGauge._compositor.CreateSpriteShape(roundedTickRectangle); + tickSpriteShape.FillBrush = radialGauge._compositor.CreateColorBrush(radialGauge.TickBrush.Color); + tickSpriteShape.Offset = new Vector2(100 - ((float)radialGauge.TickWidth / 2), (float)radialGauge.TickPadding); + tickSpriteShape.CenterPoint = new Vector2((float)radialGauge.TickWidth / 2, 100 - (float)radialGauge.TickPadding); + tickSpriteShape.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); + tick.Shapes.Add(tickSpriteShape); } + radialGauge._root.Children.InsertAtTop(tick); + // Scale Ticks. + var scaleTick = radialGauge._compositor.CreateShapeVisual(); + scaleTick.Size = new Vector2((float)(radialGauge.Height), (float)(radialGauge.Width)); + scaleTick.BorderMode = CompositionBorderMode.Soft; + scaleTick.Opacity = (float)radialGauge.ScaleTickBrush.Opacity; + + var roundedScaleTickRectangle = radialGauge._compositor.CreateRoundedRectangleGeometry(); + roundedScaleTickRectangle.Size = new Vector2((float)radialGauge.ScaleTickWidth, (float)radialGauge.ScaleTickLength); + roundedScaleTickRectangle.CornerRadius = new Vector2((float)radialGauge.ScaleTickCornerRadius, (float)radialGauge.ScaleTickCornerRadius); + for (double i = radialGauge.Minimum; i <= radialGauge.Maximum; i += radialGauge.TickSpacing) { - tick = radialGauge._compositor.CreateSpriteVisual(); - tick.Size = new Vector2((float)radialGauge.ScaleTickWidth, (float)radialGauge.ScaleWidth); - tick.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.ScaleTickBrush.Color); - tick.Opacity = (float)radialGauge.ScaleTickBrush.Opacity; - tick.Offset = new Vector3(100 - ((float)radialGauge.ScaleTickWidth / 2), (float)radialGauge.ScalePadding, 0); - tick.CenterPoint = new Vector3((float)radialGauge.ScaleTickWidth / 2, 100 - (float)radialGauge.ScalePadding, 0); - tick.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); - radialGauge._root.Children.InsertAtTop(tick); + var scaleTickSpriteShape = radialGauge._compositor.CreateSpriteShape(roundedScaleTickRectangle); + scaleTickSpriteShape.FillBrush = radialGauge._compositor.CreateColorBrush(radialGauge.ScaleTickBrush.Color); + scaleTickSpriteShape.Offset = new Vector2(100 - ((float)radialGauge.ScaleTickWidth / 2), (float)radialGauge.ScalePadding); + scaleTickSpriteShape.CenterPoint = new Vector2((float)radialGauge.ScaleTickWidth / 2, 100 - (float)radialGauge.ScalePadding); + scaleTickSpriteShape.RotationAngleInDegrees = (float)radialGauge.ValueToAngle(i); + scaleTick.Shapes.Add(scaleTickSpriteShape); } + radialGauge._root.Children.InsertAtTop(scaleTick); } // Needle. - radialGauge._needle = radialGauge._compositor.CreateSpriteVisual(); - radialGauge._needle.Size = new Vector2((float)radialGauge.NeedleWidth, (float)radialGauge.NeedleLength); - radialGauge._needle.Brush = radialGauge._compositor.CreateColorBrush(radialGauge.NeedleBrush.Color); - radialGauge._needle.Opacity = (float)radialGauge.NeedleBrush.Opacity; - radialGauge._needle.CenterPoint = new Vector3((float)radialGauge.NeedleWidth / 2, (float)radialGauge.NeedleLength, 0); - radialGauge._needle.Offset = new Vector3(100 - ((float)radialGauge.NeedleWidth / 2), 100 - (float)radialGauge.NeedleLength, 0); - radialGauge._root.Children.InsertAtTop(radialGauge._needle); + var shapeVisual = radialGauge._compositor.CreateShapeVisual(); + shapeVisual.Size = new Vector2((float)radialGauge.Height, (float)radialGauge.Width); + shapeVisual.BorderMode = CompositionBorderMode.Soft; + shapeVisual.Opacity = (float)radialGauge.NeedleBrush.Opacity; + var roundedNeedleRectangle = radialGauge._compositor.CreateRoundedRectangleGeometry(); + roundedNeedleRectangle.Size = new Vector2((float)radialGauge.NeedleWidth, (float)radialGauge.NeedleLength); + roundedNeedleRectangle.CornerRadius = new Vector2((float)radialGauge.NeedleWidth / 2, (float)radialGauge.NeedleWidth / 2); + radialGauge._needle = radialGauge._compositor.CreateSpriteShape(roundedNeedleRectangle); + radialGauge._needle.FillBrush = radialGauge._compositor.CreateColorBrush(radialGauge.NeedleBrush.Color); + radialGauge._needle.CenterPoint = new Vector2((float)radialGauge.NeedleWidth / 2, (float)radialGauge.NeedleLength); + radialGauge._needle.Offset = new Vector2(100 - ((float)radialGauge.NeedleWidth / 2), 100 - (float)radialGauge.NeedleLength); + radialGauge._needle.StrokeThickness = (float)radialGauge.NeedleBorderThickness; + radialGauge._needle.StrokeBrush = radialGauge._compositor.CreateColorBrush(radialGauge.NeedleBorderBrush.Color); + shapeVisual.Shapes.Add(radialGauge._needle); + + radialGauge._root.Children.InsertAtTop(shapeVisual); OnValueChanged(radialGauge); } @@ -735,6 +387,7 @@ private void OnColorsChanged() { // Apply High Contrast Theme. ClearBrush(_needleBrush, NeedleBrushProperty); + ClearBrush(_needleBorderBrush, NeedleBorderBrushProperty); ClearBrush(_trailBrush, TrailBrushProperty); ClearBrush(_scaleBrush, ScaleBrushProperty); ClearBrush(_scaleTickBrush, ScaleTickBrushProperty); @@ -745,6 +398,7 @@ private void OnColorsChanged() { // Apply User Defined or Default Theme. RestoreBrush(_needleBrush, NeedleBrushProperty); + RestoreBrush(_needleBorderBrush, NeedleBorderBrushProperty); RestoreBrush(_trailBrush, TrailBrushProperty); RestoreBrush(_scaleBrush, ScaleBrushProperty); RestoreBrush(_scaleTickBrush, ScaleTickBrushProperty); @@ -771,24 +425,6 @@ private void RestoreBrush(Brush? source, DependencyProperty prop) } } - private void RadialGauge_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e) - { - SetGaugeValueFromPoint(e.Position); - } - - private void RadialGauge_Tapped(object sender, TappedRoutedEventArgs e) - { - SetGaugeValueFromPoint(e.GetPosition(this)); - } - - private void RadialGauge_PointerReleased(object sender, PointerRoutedEventArgs e) - { - if (IsInteractive) - { - e.Handled = true; - } - } - private void UpdateNormalizedAngles() { var result = Mod(MinAngle, 360); @@ -884,18 +520,4 @@ private double RoundToMultiple(double number, double multiple) return number + modulo; } - - private void AddKeyboardAccelerator( - VirtualKeyModifiers keyModifiers, - VirtualKey key, - TypedEventHandler handler) - { - var accelerator = new KeyboardAccelerator() - { - Modifiers = keyModifiers, - Key = key - }; - accelerator.Invoked += handler; - KeyboardAccelerators.Add(accelerator); - } } diff --git a/components/RadialGauge/src/RadialGauge.xaml b/components/RadialGauge/src/RadialGauge.xaml index 88dd6201..230389ee 100644 --- a/components/RadialGauge/src/RadialGauge.xaml +++ b/components/RadialGauge/src/RadialGauge.xaml @@ -1,66 +1,56 @@ - + - - - - - - - - - - - - - - - + + + + + + + Color="{ThemeResource TextFillColorPrimaryBrush}" /> - - - - - + + + + + + + Color="{ThemeResource TextFillColorPrimaryBrush}" /> - - - - - + + + + + + @@ -68,11 +58,12 @@ - - - - - diff --git a/RadialGauge/src/RadialGaugeStyle_xBind.xaml b/RadialGauge/src/RadialGaugeStyle_xBind.xaml deleted file mode 100644 index 95701222..00000000 --- a/RadialGauge/src/RadialGaugeStyle_xBind.xaml +++ /dev/null @@ -1,69 +0,0 @@ - - - - - - - - - - - - - - - - 4,4,4,4 - - - - - - - - diff --git a/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs b/RadialGauge/src/RadialGaugeStyle_xBind.xaml.cs deleted file mode 100644 index d33464f1..00000000 --- a/RadialGauge/src/RadialGaugeStyle_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 RadialGaugeStyle_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 RadialGaugeStyle_xBind() - { - this.InitializeComponent(); - } -} diff --git a/RadialGauge/src/RadialGauge_ClassicBinding.cs b/RadialGauge/src/RadialGauge_ClassicBinding.cs deleted file mode 100644 index 507692ec..00000000 --- a/RadialGauge/src/RadialGauge_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 RadialGauge_ClassicBinding : Control -{ - /// - /// Creates a new instance of the class. - /// - public RadialGauge_ClassicBinding() - { - this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_ClassicBinding), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(RadialGauge_ClassicBinding), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/RadialGauge_xBind.cs b/RadialGauge/src/RadialGauge_xBind.cs deleted file mode 100644 index fae7278e..00000000 --- a/RadialGauge/src/RadialGauge_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 RadialGauge_xBind: Control -{ - /// - /// Creates a new instance of the class. - /// - public RadialGauge_xBind() - { - this.DefaultStyleKey = typeof(RadialGauge_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(RadialGauge_xBind), - new PropertyMetadata(defaultValue: new Thickness(0))); - - /// - /// The backing for the property. - /// - public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register( - nameof(MyProperty), - typeof(string), - typeof(RadialGauge_xBind), - new PropertyMetadata(defaultValue: string.Empty, (d, e) => ((RadialGauge_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/RadialGauge/src/Themes/Generic.xaml b/RadialGauge/src/Themes/Generic.xaml deleted file mode 100644 index cb041611..00000000 --- a/RadialGauge/src/Themes/Generic.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/RadialGauge/tests/ExampleRadialGaugeTestClass.cs b/RadialGauge/tests/ExampleRadialGaugeTestClass.cs deleted file mode 100644 index 275bbd4a..00000000 --- a/RadialGauge/tests/ExampleRadialGaugeTestClass.cs +++ /dev/null @@ -1,134 +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. - -using CommunityToolkit.Tooling.TestGen; -using CommunityToolkit.Tests; -using CommunityToolkit.WinUI.Controls; - -namespace RadialGaugeExperiment.Tests; - -[TestClass] -public partial class ExampleRadialGaugeTestClass : VisualUITestBase -{ - // If you don't need access to UI objects directly or async code, use this pattern. - [TestMethod] - public void SimpleSynchronousExampleTest() - { - var assembly = typeof(RadialGauge).Assembly; - var type = assembly.GetType(typeof(RadialGauge).FullName ?? string.Empty); - - Assert.IsNotNull(type, "Could not find RadialGauge type."); - Assert.AreEqual(typeof(RadialGauge), type, "Type of RadialGauge 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 RadialGauge(); - 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(ExampleRadialGaugeTestPage 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("RadialGaugeControl"); - - Assert.IsNotNull(componentByName); - } - - // You can still do async work with a UIThreadTestMethod as well. - [UIThreadTestMethod] - public async Task SimpleAsyncUIExamplePageTest(ExampleRadialGaugeTestPage 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 RadialGauge_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 RadialGauge_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 RadialGauge_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/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml b/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml deleted file mode 100644 index c47247ce..00000000 --- a/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs b/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs deleted file mode 100644 index 640396fe..00000000 --- a/RadialGauge/tests/ExampleRadialGaugeTestPage.xaml.cs +++ /dev/null @@ -1,16 +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 RadialGaugeExperiment.Tests; - -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// -public sealed partial class ExampleRadialGaugeTestPage : Page -{ - public ExampleRadialGaugeTestPage() - { - this.InitializeComponent(); - } -} diff --git a/RadialGauge/tests/RadialGauge.Tests.projitems b/RadialGauge/tests/RadialGauge.Tests.projitems deleted file mode 100644 index 6845b557..00000000 --- a/RadialGauge/tests/RadialGauge.Tests.projitems +++ /dev/null @@ -1,23 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 4E3AF150-B9AE-422D-9E1C-18AE541D52D0 - - - RadialGaugeExperiment.Tests - - - - - ExampleRadialGaugeTestPage.xaml - - - - - Designer - MSBuild:Compile - - - \ No newline at end of file diff --git a/RadialGauge/tests/RadialGauge.Tests.shproj b/RadialGauge/tests/RadialGauge.Tests.shproj deleted file mode 100644 index 58f4d434..00000000 --- a/RadialGauge/tests/RadialGauge.Tests.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 4E3AF150-B9AE-422D-9E1C-18AE541D52D0 - 14.0 - - - - - - - - From 3b5cf03757fbc50a3ac9cd74351a01f5565bd9db Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Thu, 11 May 2023 15:58:17 +0200 Subject: [PATCH 13/15] Catching negative number --- components/RadialGauge/src/RadialGauge.cs | 97 ++++++++++++----------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index 4217641e..e1ac570e 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -4,7 +4,6 @@ using CommunityToolkit.WinUI.Helpers; using System.Numerics; -using Microsoft.UI.Xaml; #if WINAPPSDK using Path = Microsoft.UI.Xaml.Shapes.Path; using Microsoft.UI.Xaml.Hosting; @@ -181,66 +180,70 @@ private static void OnValueChanged(DependencyObject d) } var middleOfScale = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2); - var valueText = radialGauge.GetTemplateChild(ValueTextPartName) as TextBlock; - radialGauge.ValueAngle = radialGauge.ValueToAngle(radialGauge.Value); - - // Needle - if (radialGauge._needle != null) + if (middleOfScale >= 0) { - radialGauge._needle.RotationAngleInDegrees = (float)radialGauge.ValueAngle; - } + var valueText = radialGauge.GetTemplateChild(ValueTextPartName) as TextBlock; + radialGauge.ValueAngle = radialGauge.ValueToAngle(radialGauge.Value); - // Trail - var trail = radialGauge.GetTemplateChild(TrailPartName) as Path; - if (trail != null) - { - if (radialGauge.ValueAngle > radialGauge.NormalizedMinAngle) + // Needle + if (radialGauge._needle != null) { - trail.Visibility = Visibility.Visible; + radialGauge._needle.RotationAngleInDegrees = (float)radialGauge.ValueAngle; + } - if (radialGauge.ValueAngle - radialGauge.NormalizedMinAngle == 360) + // Trail + var trail = radialGauge.GetTemplateChild(TrailPartName) as Path; + if (trail != null) + { + if (radialGauge.ValueAngle > radialGauge.NormalizedMinAngle) { - // Draw full circle. - var eg = new EllipseGeometry + trail.Visibility = Visibility.Visible; + + if (radialGauge.ValueAngle - radialGauge.NormalizedMinAngle == 360) + { + // Draw full circle. + var eg = new EllipseGeometry + { + Center = new Point(100, 100), + RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2) + }; + eg.RadiusY = eg.RadiusX; + trail.Data = eg; + } + else { - Center = new Point(100, 100), - RadiusX = 100 - radialGauge.ScalePadding - (radialGauge.ScaleWidth / 2) - }; - eg.RadiusY = eg.RadiusX; - trail.Data = eg; + // Draw arc. + var pg = new PathGeometry(); + var pf = new PathFigure + { + IsClosed = false, + StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale) + }; + var seg = new ArcSegment + { + SweepDirection = SweepDirection.Clockwise, + IsLargeArc = radialGauge.ValueAngle > (180 + radialGauge.NormalizedMinAngle), + Size = new Size(middleOfScale, middleOfScale), + Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale) // On overflow, stop trail at MaxAngle. + }; + pf.Segments.Add(seg); + pg.Figures.Add(pf); + trail.Data = pg; + } } else { - // Draw arc. - var pg = new PathGeometry(); - var pf = new PathFigure - { - IsClosed = false, - StartPoint = radialGauge.ScalePoint(radialGauge.NormalizedMinAngle, middleOfScale) - }; - var seg = new ArcSegment - { - SweepDirection = SweepDirection.Clockwise, - IsLargeArc = radialGauge.ValueAngle > (180 + radialGauge.NormalizedMinAngle), - Size = new Size(middleOfScale, middleOfScale), - Point = radialGauge.ScalePoint(Math.Min(radialGauge.ValueAngle, radialGauge.NormalizedMaxAngle), middleOfScale) // On overflow, stop trail at MaxAngle. - }; - pf.Segments.Add(seg); - pg.Figures.Add(pf); - trail.Data = pg; + trail.Visibility = Visibility.Collapsed; } + } - else + + // Value Text + if (valueText != null) { - trail.Visibility = Visibility.Collapsed; + valueText.Text = radialGauge.Value.ToString(radialGauge.ValueStringFormat); } } - - // Value Text - if (valueText != null) - { - valueText.Text = radialGauge.Value.ToString(radialGauge.ValueStringFormat); - } } } From 3566edbd850d62db00ac0500b4515ac87c66d30d Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Fri, 12 May 2023 17:54:27 +0200 Subject: [PATCH 14/15] Addressing feedback --- components/RadialGauge/samples/RadialGaugeSample.xaml.cs | 2 +- components/RadialGauge/src/RadialGauge.cs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/components/RadialGauge/samples/RadialGaugeSample.xaml.cs b/components/RadialGauge/samples/RadialGaugeSample.xaml.cs index 83858b52..d73d2def 100644 --- a/components/RadialGauge/samples/RadialGaugeSample.xaml.cs +++ b/components/RadialGauge/samples/RadialGaugeSample.xaml.cs @@ -12,7 +12,7 @@ namespace RadialGaugeExperiment.Samples; [ToolkitSampleBoolOption("Enabled", true, Title = "IsEnabled")] [ToolkitSampleNumericOption("Value", 120, 0, 240, 1, false, Title = "Value")] [ToolkitSampleNumericOption("StepSize", 30, 5, 30, 1, false, Title = "StepSize")] -[ToolkitSampleBoolOption("IsInteractive", false, Title = "IsInteractive")] +[ToolkitSampleBoolOption("IsInteractive", true, Title = "IsInteractive")] [ToolkitSampleNumericOption("TickSpacing", 15, 10, 30, 1, false, Title = "TickSpacing")] [ToolkitSampleNumericOption("ScaleWidth", 12, 4, 50, 1, false, Title = "ScaleWidth")] [ToolkitSampleNumericOption("MinAngle", -150, -150, 360, 1, false, Title = "MinAngle")] diff --git a/components/RadialGauge/src/RadialGauge.cs b/components/RadialGauge/src/RadialGauge.cs index e1ac570e..abb84889 100644 --- a/components/RadialGauge/src/RadialGauge.cs +++ b/components/RadialGauge/src/RadialGauge.cs @@ -112,6 +112,7 @@ protected override void OnApplyTemplate() OnColorsChanged(); OnUnitChanged(this); OnEnabledChanged(); + OnInteractivityChanged(this); base.OnApplyTemplate(); } @@ -248,6 +249,11 @@ private static void OnValueChanged(DependencyObject d) } private static void OnInteractivityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + OnInteractivityChanged(d); + } + + private static void OnInteractivityChanged(DependencyObject d) { RadialGauge radialGauge = (RadialGauge)d; From 62e55341a3b905407e18955dbc8048e9e4cc4450 Mon Sep 17 00:00:00 2001 From: Arlo Godfrey Date: Mon, 15 May 2023 16:27:40 -0500 Subject: [PATCH 15/15] Fixed PackageId --- .../CommunityToolkit.WinUI.Controls.RadialGauge.csproj | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj index fb80b23e..c2745c5c 100644 --- a/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj +++ b/components/RadialGauge/src/CommunityToolkit.WinUI.Controls.RadialGauge.csproj @@ -8,11 +8,15 @@ CommunityToolkit.WinUI.Controls.RadialGaugeRns - - - + + + + + + $(PackageIdPrefix).$(PackageIdVariant).Controls.$(ToolkitComponentName) +