From cd1c057ca105176b1068c0622c7e4dbf7ab9c95d Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Wed, 24 Sep 2025 22:34:07 +0300 Subject: [PATCH 01/26] Created AccentExtractor projects --- .../OpenSolution.bat | 3 + .../samples/Assets/icon.png | Bin 0 -> 2216 bytes .../samples/Dependencies.props | 31 ++++ .../Extensions.AccentExtractor.Samples.csproj | 10 ++ .../samples/Extensions.AccentExtractor.md | 65 +++++++++ ...xtensions.AccentExtractorCustomSample.xaml | 25 ++++ ...nsions.AccentExtractorCustomSample.xaml.cs | 30 ++++ ...nsions.AccentExtractorTemplatedSample.xaml | 16 +++ ...ons.AccentExtractorTemplatedSample.xaml.cs | 21 +++ ...ntExtractorTemplatedStyleCustomSample.xaml | 26 ++++ ...xtractorTemplatedStyleCustomSample.xaml.cs | 21 +++ ...ions.AccentExtractorXbindBackedSample.xaml | 16 +++ ...s.AccentExtractorXbindBackedSample.xaml.cs | 21 +++ ...ExtractorXbindBackedStyleCustomSample.xaml | 26 ++++ ...ractorXbindBackedStyleCustomSample.xaml.cs | 21 +++ .../src/AccentExtractor.Clustering.cs | 10 ++ .../src/AccentExtractor.cs | 73 ++++++++++ ...it.WinUI.Extensions.AccentExtractor.csproj | 14 ++ .../src/Dependencies.props | 31 ++++ .../src/MultiTarget.props | 9 ++ .../src/Themes/Generic.xaml | 10 ++ ...mpleExtensions.AccentExtractorTestClass.cs | 134 ++++++++++++++++++ ...pleExtensions.AccentExtractorTestPage.xaml | 14 ++ ...Extensions.AccentExtractorTestPage.xaml.cs | 16 +++ ...Extensions.AccentExtractor.Tests.projitems | 23 +++ .../Extensions.AccentExtractor.Tests.shproj | 13 ++ 26 files changed, 679 insertions(+) create mode 100644 components/Extensions.AccentExtractor/OpenSolution.bat create mode 100644 components/Extensions.AccentExtractor/samples/Assets/icon.png create mode 100644 components/Extensions.AccentExtractor/samples/Dependencies.props create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.Samples.csproj create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml create mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs create mode 100644 components/Extensions.AccentExtractor/src/AccentExtractor.Clustering.cs create mode 100644 components/Extensions.AccentExtractor/src/AccentExtractor.cs create mode 100644 components/Extensions.AccentExtractor/src/CommunityToolkit.WinUI.Extensions.AccentExtractor.csproj create mode 100644 components/Extensions.AccentExtractor/src/Dependencies.props create mode 100644 components/Extensions.AccentExtractor/src/MultiTarget.props create mode 100644 components/Extensions.AccentExtractor/src/Themes/Generic.xaml create mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs create mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml create mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs create mode 100644 components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems create mode 100644 components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.shproj diff --git a/components/Extensions.AccentExtractor/OpenSolution.bat b/components/Extensions.AccentExtractor/OpenSolution.bat new file mode 100644 index 000000000..814a56d4b --- /dev/null +++ b/components/Extensions.AccentExtractor/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/Extensions.AccentExtractor/samples/Assets/icon.png b/components/Extensions.AccentExtractor/samples/Assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8435bcaa9fc371ca8e92db07ae596e0d57c8b9b0 GIT binary patch literal 2216 zcmV;Z2v_%sP);M1&0drDELIAGL9O(c600d`2O+f$vv5yP=YKs(Je9p7&Ka&|n*ecc!Pix~iV~>Yi6*wXL?Fq6O}_ef#!O2;q#X&d1-r zFW&c8*L5NOYWz*lA^xUE>kGNx-uL6v^z@vr)V_TA(vQ#Yoo1$I^Fv;IKkA`?U*Z6OoBe{XX@DLpL{F3+>RV2oP732rn@P@98F ziH~#f`tA7f<8t}(<$sLlUkdm_STvOKGYXY{9St3tGiu1>duN9F9aTe*kTXOCkoLYj zr)5cJP?i}f+kBoB8b~Q1rbJi`730DXGLx}aCV-6tC522QjZs)X03S%#=jXopQNe7G z@dlToiDbGDSy!a_DD)NYWiV?sGap0Dq-qgnA&~LHECw(NL8T=) z1ct(ga2;|14nD@qHwmVQ0;2|YUomW^!U#`7l>>&Bh;u)pT<|$jFoqk6%HTc~^RQ@( z5hZ5X^n7`vt!*nY9@rFRqF{^wF`}&H4I4JdfddC*W@e@$oS$Q04aThBn*gT3)URMp z>G{o@H*)RTHCb6%8B?H}yjcXcUm9p(K=nWD0vP!PaCP$@(k!31bkrIJ!2)-tl96*+&}@I6!3M8qT~Q2?u8 zcy@MHS+IzlwjvzRGx~+*l>(Gi6$!C8Jgi;2)=XVKe*CB(K745TS`)G2;nuBN9cqsP zh3?9y5yI0j3rZt%Q;V3i*L;-@bj}#EBDL zi-O{(`k1i!rOBH%ZPF-Qh^8PnZ{F0;pFa!x1_lMnUFbI&&8gFC}WVB;VU)DL&bgeyDBGT1Uw=yFE1xQ zlXdIXN%Xx|Sv6HKV+J+vFk{k20ni@>s(7s{7```cTQ%Vf8y`xM()#6VjL@l-2UZ)1 zMAlp(2n!()MJZ+A7DT29I(lXL!EfSTFx}nSy)d`h{3}%2w00Npq^YO)x9XqDcq0Kb`t5*xfQqpFjCI=5f3~jf78p^G}no2Fzdc;)IysSSZVr(fukQEprykDy< zqbZnxBN8(`!7W?1$e}}r1c|2>qh?{>d-v{@?c2BeJQ<2<$;_cXbnDiw*59|?yLU^< zSA=eeDMyH&Dn;O?U<@moWoql!uTK{z=_+aO*s+8Ar_R9^^Jcn6#~@GNDp>Q(&lq|B z{JGq_cdrQ3>E_6hBQiff?~J4|4F&T## ze2Ot?F7i1RStkmn!!cL^bKdFtd(+&zckeRXgNN#PA!`aD zjaC7J&2fZoFH{s(d8}#I6o7s`O)w?K`{bKiENsKV!h(MK^r(|LX> z*~rJxUHVoXzp-XhUqnbQUAiRq@89q5e?*H1Nj(o2FJ5%B>%JZY`Gw<0&+dhGuj!C7 z?uYbkO5XY{KIV*@{VRoX8s`fm<068)Y!=w) zn?l)IstDTf!(Iu(8i;vTaeMOuE%QV$RY7$1cP-aqS07(jX4W{bFHp!#3m}TTAaaF3L~n9Q)s^3xP5nw5 zM&HvBw5z{TbdA3!8Di)wIs`5S;W!fVdWDbiH|P}wlVfDMuB&#@so4j+uC6L7Q#M38 z*q?Pnc_gqfql3rn?qh)V@~B|(78+7U zKOCcocD&A`ELB-^?%cVvanNF%vtSH&^_LVuBqdkprWDoUyaLCf$s5zvc?rH#NGXk= qmFA_t_5FR}!i7I&wXL?Ful)xU?DJJ%Hwu*i0000 + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.Samples.csproj b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.Samples.csproj new file mode 100644 index 000000000..cebc59a68 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.Samples.csproj @@ -0,0 +1,10 @@ + + + + + Extensions.AccentExtractor + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md new file mode 100644 index 000000000..52f97f662 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md @@ -0,0 +1,65 @@ +--- +title: Extensions.AccentExtractor +author: githubaccount +description: TODO: Your experiment's description here +keywords: Extensions.AccentExtractor, Control, Layout +dev_langs: + - csharp +category: Controls +subcategory: Layout +discussion-id: 0 +issue-id: 0 +icon: assets/icon.png +--- + + + + + + + + + +# Extensions.AccentExtractor + +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 Extensions.AccentExtractorCustomSample] + +## 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 Extensions.AccentExtractorTemplatedSample] + +### Custom style + +> [!SAMPLE Extensions.AccentExtractorTemplatedStyleCustomSample] + +## 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 Extensions.AccentExtractorXbindBackedSample] + +### Custom style + +> [!SAMPLE Extensions.AccentExtractorXbindBackedStyleCustomSample] + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml new file mode 100644 index 000000000..bd630a7fb --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs new file mode 100644 index 000000000..a9115670a --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(Extensions.AccentExtractor)} custom control.")] +public sealed partial class Extensions.AccentExtractorCustomSample : Page +{ + public Extensions.AccentExtractorCustomSample() + { + 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/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml new file mode 100644 index 000000000..40280dd46 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs new file mode 100644 index 000000000..51e1b2482 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] +public sealed partial class Extensions.AccentExtractorTemplatedSample : Page +{ + public Extensions.AccentExtractorTemplatedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml new file mode 100644 index 000000000..8d85b0b58 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs new file mode 100644 index 000000000..408460a0b --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] +public sealed partial class Extensions.AccentExtractorTemplatedStyleCustomSample : Page +{ + public Extensions.AccentExtractorTemplatedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml new file mode 100644 index 000000000..8d3f8409d --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs new file mode 100644 index 000000000..5393c05e4 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorXbindBackedSample), "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 Extensions.AccentExtractorXbindBackedSample : Page +{ + public Extensions.AccentExtractorXbindBackedSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml new file mode 100644 index 000000000..f9ba83cb2 --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs new file mode 100644 index 000000000..802778c0d --- /dev/null +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorXbindBackedStyleCustomSample), "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 Extensions.AccentExtractorXbindBackedStyleCustomSample : Page +{ + public Extensions.AccentExtractorXbindBackedStyleCustomSample() + { + this.InitializeComponent(); + } +} diff --git a/components/Extensions.AccentExtractor/src/AccentExtractor.Clustering.cs b/components/Extensions.AccentExtractor/src/AccentExtractor.Clustering.cs new file mode 100644 index 000000000..09c974776 --- /dev/null +++ b/components/Extensions.AccentExtractor/src/AccentExtractor.Clustering.cs @@ -0,0 +1,10 @@ +// 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.Extensions; + +public static partial class AccentExtractor +{ + +} diff --git a/components/Extensions.AccentExtractor/src/AccentExtractor.cs b/components/Extensions.AccentExtractor/src/AccentExtractor.cs new file mode 100644 index 000000000..64b479b40 --- /dev/null +++ b/components/Extensions.AccentExtractor/src/AccentExtractor.cs @@ -0,0 +1,73 @@ +// 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 Microsoft.UI.Xaml.Media.Imaging; + +namespace CommunityToolkit.WinUI.Extensions; + +public static partial class AccentExtractor +{ + /// + /// Attached for the that enables or disables accent color calculation. + /// + public static readonly DependencyProperty ImageCalculateAccentProperty = + DependencyProperty.RegisterAttached("CalculateAccent", typeof(bool), typeof(Image), new PropertyMetadata(false, OnCalculateAccentChanged)); + + /// + /// Attached for the that enables or disables accent color calculation. + /// + public static readonly DependencyProperty ImageBrushCalculateAccentProperty = + DependencyProperty.RegisterAttached("CalculateAccent", typeof(bool), typeof(ImageBrush), new PropertyMetadata(false, OnCalculateAccentChanged)); + + private static void OnCalculateAccentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + // No change. Do nothing. + if ((bool)e.NewValue == (bool)e.OldValue) + return; + + bool assign = (bool)e.NewValue; + switch (d) + { + case Image image: + if (assign) + { + image.ImageOpened += OnImageOpened; + } + else + { + image.ImageOpened -= OnImageOpened; + } + break; + case ImageBrush imageBrush: + if (assign) + { + imageBrush.ImageOpened += OnImageOpened; + } + else + { + imageBrush.ImageOpened -= OnImageOpened; + } + break; + } + } + + private static void OnImageOpened(object sender, RoutedEventArgs e) + { + // Extract the ImageSource from the sender + ImageSource? source = null; + switch (sender) + { + case Image image: + source = image.Source; + break; + case ImageBrush imageBrush: + source = imageBrush.ImageSource; + break; + } + + // If the source is not a BitmapSource, we cannot process it + if (source is not BitmapSource bitmapSource) + return; + } +} diff --git a/components/Extensions.AccentExtractor/src/CommunityToolkit.WinUI.Extensions.AccentExtractor.csproj b/components/Extensions.AccentExtractor/src/CommunityToolkit.WinUI.Extensions.AccentExtractor.csproj new file mode 100644 index 000000000..115471511 --- /dev/null +++ b/components/Extensions.AccentExtractor/src/CommunityToolkit.WinUI.Extensions.AccentExtractor.csproj @@ -0,0 +1,14 @@ + + + + + Extensions.AccentExtractor + This package contains Extensions.AccentExtractor. + + + CommunityToolkit.WinUI.Controls.Extensions.AccentExtractorRns + + + + + diff --git a/components/Extensions.AccentExtractor/src/Dependencies.props b/components/Extensions.AccentExtractor/src/Dependencies.props new file mode 100644 index 000000000..e622e1df4 --- /dev/null +++ b/components/Extensions.AccentExtractor/src/Dependencies.props @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/src/MultiTarget.props b/components/Extensions.AccentExtractor/src/MultiTarget.props new file mode 100644 index 000000000..b11c19426 --- /dev/null +++ b/components/Extensions.AccentExtractor/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/Extensions.AccentExtractor/src/Themes/Generic.xaml b/components/Extensions.AccentExtractor/src/Themes/Generic.xaml new file mode 100644 index 000000000..3812e9a8c --- /dev/null +++ b/components/Extensions.AccentExtractor/src/Themes/Generic.xaml @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs new file mode 100644 index 000000000..0d9d705af --- /dev/null +++ b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.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 Extensions.AccentExtractorTests; + +[TestClass] +public partial class ExampleExtensions.AccentExtractorTestClass : VisualUITestBase +{ + // If you don't need access to UI objects directly or async code, use this pattern. + [TestMethod] + public void SimpleSynchronousExampleTest() + { + var assembly = typeof(Extensions.AccentExtractor).Assembly; + var type = assembly.GetType(typeof(Extensions.AccentExtractor).FullName ?? string.Empty); + + Assert.IsNotNull(type, "Could not find Extensions.AccentExtractor type."); + Assert.AreEqual(typeof(Extensions.AccentExtractor), type, "Type of Extensions.AccentExtractor 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 Extensions.AccentExtractor(); + 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(ExampleExtensions.AccentExtractorTestPage 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("Extensions.AccentExtractorControl"); + + Assert.IsNotNull(componentByName); + } + + // You can still do async work with a UIThreadTestMethod as well. + [UIThreadTestMethod] + public async Task SimpleAsyncUIExamplePageTest(ExampleExtensions.AccentExtractorTestPage 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 Extensions.AccentExtractor_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 Extensions.AccentExtractor_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 Extensions.AccentExtractor_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/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml new file mode 100644 index 000000000..80965fe83 --- /dev/null +++ b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs new file mode 100644 index 000000000..a00607bc8 --- /dev/null +++ b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.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 Extensions.AccentExtractorTests; + +/// +/// An empty page that can be used on its own or navigated to within a Frame. +/// +public sealed partial class ExampleExtensions.AccentExtractorTestPage : Page +{ + public ExampleExtensions.AccentExtractorTestPage() + { + this.InitializeComponent(); + } +} diff --git a/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems new file mode 100644 index 000000000..2f618d17c --- /dev/null +++ b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems @@ -0,0 +1,23 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + FA8C1D4D-4CEF-490E-A369-0CCCFBAA8909 + + + Extensions.AccentExtractorTests + + + + + ExampleExtensions.AccentExtractorTestPage.xaml + + + + + Designer + MSBuild:Compile + + + \ No newline at end of file diff --git a/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.shproj b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.shproj new file mode 100644 index 000000000..bc9df735c --- /dev/null +++ b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.shproj @@ -0,0 +1,13 @@ + + + + FA8C1D4D-4CEF-490E-A369-0CCCFBAA8909 + 14.0 + + + + + + + + From ae8fe9defe10da88f909b52be45725213c4e573c Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Thu, 25 Sep 2025 02:51:00 +0300 Subject: [PATCH 02/26] AccentExtractor proof of concept pixel retrieval --- .../samples/Extensions.AccentExtractor.md | 35 +---- ...xtensions.AccentExtractorCustomSample.xaml | 47 +++--- ...nsions.AccentExtractorCustomSample.xaml.cs | 17 +-- ...nsions.AccentExtractorTemplatedSample.xaml | 16 --- ...ons.AccentExtractorTemplatedSample.xaml.cs | 21 --- ...ntExtractorTemplatedStyleCustomSample.xaml | 26 ---- ...xtractorTemplatedStyleCustomSample.xaml.cs | 21 --- ...ions.AccentExtractorXbindBackedSample.xaml | 16 --- ...s.AccentExtractorXbindBackedSample.xaml.cs | 21 --- ...ExtractorXbindBackedStyleCustomSample.xaml | 26 ---- ...ractorXbindBackedStyleCustomSample.xaml.cs | 21 --- .../src/AccentExtractor.cs | 127 +++++++++++++---- .../src/MultiTarget.props | 4 +- .../src/Themes/Generic.xaml | 10 -- ...mpleExtensions.AccentExtractorTestClass.cs | 134 ------------------ ...pleExtensions.AccentExtractorTestPage.xaml | 14 -- ...Extensions.AccentExtractorTestPage.xaml.cs | 16 --- ...Extensions.AccentExtractor.Tests.projitems | 12 -- 18 files changed, 137 insertions(+), 447 deletions(-) delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml delete mode 100644 components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs delete mode 100644 components/Extensions.AccentExtractor/src/Themes/Generic.xaml delete mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs delete mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml delete mode 100644 components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md index 52f97f662..37c47bf62 100644 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractor.md @@ -29,37 +29,4 @@ TODO: Fill in information about this experiment and how to get started here... 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 Extensions.AccentExtractorCustomSample] - -## 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 Extensions.AccentExtractorTemplatedSample] - -### Custom style - -> [!SAMPLE Extensions.AccentExtractorTemplatedStyleCustomSample] - -## 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 Extensions.AccentExtractorXbindBackedSample] - -### Custom style - -> [!SAMPLE Extensions.AccentExtractorXbindBackedStyleCustomSample] - +> [!Sample AccentExtractorCustomSample] diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml index bd630a7fb..2bdd5f954 100644 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml @@ -1,25 +1,38 @@ - - + - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs index a9115670a..ab2961c62 100644 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml.cs @@ -9,22 +9,11 @@ namespace Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(Extensions.AccentExtractor)} custom control.")] -public sealed partial class Extensions.AccentExtractorCustomSample : Page +[ToolkitSample(id: nameof(AccentExtractorCustomSample), "Custom control", description: $"A sample for showing how ")] +public sealed partial class AccentExtractorCustomSample : Page { - public Extensions.AccentExtractorCustomSample() + public AccentExtractorCustomSample() { 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/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml deleted file mode 100644 index 40280dd46..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.xaml.cs deleted file mode 100644 index 51e1b2482..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorTemplatedSample), "Templated control", description: "A sample for showing how to create and use a templated control.")] -public sealed partial class Extensions.AccentExtractorTemplatedSample : Page -{ - public Extensions.AccentExtractorTemplatedSample() - { - this.InitializeComponent(); - } -} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml deleted file mode 100644 index 8d85b0b58..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.xaml.cs deleted file mode 100644 index 408460a0b..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorTemplatedStyleCustomSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorTemplatedStyleCustomSample), "Templated control (restyled)", description: "A sample for showing how to create a use and templated control with a custom style.")] -public sealed partial class Extensions.AccentExtractorTemplatedStyleCustomSample : Page -{ - public Extensions.AccentExtractorTemplatedStyleCustomSample() - { - this.InitializeComponent(); - } -} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml deleted file mode 100644 index 8d3f8409d..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.xaml.cs deleted file mode 100644 index 5393c05e4..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorXbindBackedSample), "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 Extensions.AccentExtractorXbindBackedSample : Page -{ - public Extensions.AccentExtractorXbindBackedSample() - { - this.InitializeComponent(); - } -} diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml deleted file mode 100644 index f9ba83cb2..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.xaml.cs deleted file mode 100644 index 802778c0d..000000000 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorXbindBackedStyleCustomSample.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 Extensions.AccentExtractorExperiment.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(Extensions.AccentExtractorXbindBackedStyleCustomSample), "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 Extensions.AccentExtractorXbindBackedStyleCustomSample : Page -{ - public Extensions.AccentExtractorXbindBackedStyleCustomSample() - { - this.InitializeComponent(); - } -} diff --git a/components/Extensions.AccentExtractor/src/AccentExtractor.cs b/components/Extensions.AccentExtractor/src/AccentExtractor.cs index 64b479b40..c78df32d2 100644 --- a/components/Extensions.AccentExtractor/src/AccentExtractor.cs +++ b/components/Extensions.AccentExtractor/src/AccentExtractor.cs @@ -2,31 +2,122 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.UI; +using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml.Media.Imaging; +using System.Buffers; +using Windows.Graphics.Imaging; +using Windows.UI; namespace CommunityToolkit.WinUI.Extensions; +/// +/// +/// public static partial class AccentExtractor { /// - /// Attached for the that enables or disables accent color calculation. + /// Attached that enables or disables accent color calculation. /// - public static readonly DependencyProperty ImageCalculateAccentProperty = - DependencyProperty.RegisterAttached("CalculateAccent", typeof(bool), typeof(Image), new PropertyMetadata(false, OnCalculateAccentChanged)); - + public static readonly DependencyProperty CalculateAccentProperty = + DependencyProperty.RegisterAttached("CalculateAccent", typeof(bool), typeof(AccentExtractor), new PropertyMetadata(false, OnCalculateAccentChanged)); + + /// + /// Attached that holds the calculated accent color. + /// + public static readonly DependencyProperty AccentColorProperty = + DependencyProperty.RegisterAttached("AccentColor", typeof(Color), typeof(AccentExtractor), new PropertyMetadata(Colors.Transparent)); + + /// + /// Gets a value indicating whether the accent calculation is enabled for the specified . + /// + /// The from which to retrieve the value. + /// if accent calculation is enabled for the specified ; otherwise, + /// . + public static bool GetCalculateAccent(UIElement obj) => (bool)obj.GetValue(CalculateAccentProperty); + + /// + /// Sets a value indicating whether the accent calculation is enabled for the specified . + /// + /// The on which to assign the value. + /// The value to assign. + public static void SetCalculateAccent(UIElement obj, bool value) => obj.SetValue(CalculateAccentProperty, value); + + /// + /// Gets the calculated accent color for the specified . + /// + /// The from which to retrieve the value. + /// The accent color calculated for the . + public static Color GetAccentColor(UIElement obj) => (Color)obj.GetValue(AccentColorProperty); + + /// + /// Sets the calculated accent color for the specified . + /// + /// + /// Can this be removed? The color is calculated automatically, so there's no need to set it manually. + /// + /// + /// + public static void SetAccentColor(UIElement obj, Color value) => obj.SetValue(AccentColorProperty, value); + /// - /// Attached for the that enables or disables accent color calculation. + /// Updates the attached property based on the dominant color of the . /// - public static readonly DependencyProperty ImageBrushCalculateAccentProperty = - DependencyProperty.RegisterAttached("CalculateAccent", typeof(bool), typeof(ImageBrush), new PropertyMetadata(false, OnCalculateAccentChanged)); + /// The to extract the accent color from. + public static async Task UpdateAccentAsync(this UIElement sender) + { + // TODO: Properly remove Uno support +#if !HAS_UNO + // Rerender the UIElement to a 64x64 bitmap + RenderTargetBitmap bitmap = new RenderTargetBitmap(); + await bitmap.RenderAsync(sender, 64, 64); + + // Create a stream from the bitmap + var pixels = await bitmap.GetPixelsAsync(); + var stream = pixels.AsStream(); + + // + if (stream.Length == 0) + return; + + // Read the stream into a a color array + int pos = 0; + Span colors = new Color[(int)stream.Length / 4]; // This should be 4096 (64x64), but it's good to be safe. + Span bytes = stackalloc byte[4]; + while (stream.Read(bytes) > 0) + { + // Safety check to avoid going out of bounds + // This should never happen, but it's good to be safe. + if (pos >= colors.Length) + break; + + colors[pos] = Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]); + pos++; + } + + var rand = Random.Shared.Next(colors.Length - 1); + var accent = colors[rand]; + + // Set the accent color on the UI thread + DispatcherQueue.GetForCurrentThread().TryEnqueue(() => SetAccentColor(sender, accent)); +#endif + } private static void OnCalculateAccentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { // No change. Do nothing. if ((bool)e.NewValue == (bool)e.OldValue) return; - + + // If true, calculate the accent color immediately. bool assign = (bool)e.NewValue; + if (assign && d is UIElement uie) + { + _ = uie.UpdateAccentAsync(); + } + + // If the element is an Image or ImageBrush, register for the image opened event. + // This ensures that the accent color is recalculated when the image source changes. switch (d) { case Image image: @@ -52,22 +143,6 @@ private static void OnCalculateAccentChanged(DependencyObject d, DependencyPrope } } - private static void OnImageOpened(object sender, RoutedEventArgs e) - { - // Extract the ImageSource from the sender - ImageSource? source = null; - switch (sender) - { - case Image image: - source = image.Source; - break; - case ImageBrush imageBrush: - source = imageBrush.ImageSource; - break; - } - - // If the source is not a BitmapSource, we cannot process it - if (source is not BitmapSource bitmapSource) - return; - } + private static async void OnImageOpened(object sender, RoutedEventArgs e) => await UpdateAccentAsync((UIElement)sender); } + diff --git a/components/Extensions.AccentExtractor/src/MultiTarget.props b/components/Extensions.AccentExtractor/src/MultiTarget.props index b11c19426..74c9103cf 100644 --- a/components/Extensions.AccentExtractor/src/MultiTarget.props +++ b/components/Extensions.AccentExtractor/src/MultiTarget.props @@ -4,6 +4,6 @@ MultiTarget is a custom property that indicates which target a project is designed to be built for / run on. Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages. --> - uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android; + uwp;wasdk;wpf; - \ No newline at end of file + diff --git a/components/Extensions.AccentExtractor/src/Themes/Generic.xaml b/components/Extensions.AccentExtractor/src/Themes/Generic.xaml deleted file mode 100644 index 3812e9a8c..000000000 --- a/components/Extensions.AccentExtractor/src/Themes/Generic.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.cs deleted file mode 100644 index 0d9d705af..000000000 --- a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestClass.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 Extensions.AccentExtractorTests; - -[TestClass] -public partial class ExampleExtensions.AccentExtractorTestClass : VisualUITestBase -{ - // If you don't need access to UI objects directly or async code, use this pattern. - [TestMethod] - public void SimpleSynchronousExampleTest() - { - var assembly = typeof(Extensions.AccentExtractor).Assembly; - var type = assembly.GetType(typeof(Extensions.AccentExtractor).FullName ?? string.Empty); - - Assert.IsNotNull(type, "Could not find Extensions.AccentExtractor type."); - Assert.AreEqual(typeof(Extensions.AccentExtractor), type, "Type of Extensions.AccentExtractor 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 Extensions.AccentExtractor(); - 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(ExampleExtensions.AccentExtractorTestPage 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("Extensions.AccentExtractorControl"); - - Assert.IsNotNull(componentByName); - } - - // You can still do async work with a UIThreadTestMethod as well. - [UIThreadTestMethod] - public async Task SimpleAsyncUIExamplePageTest(ExampleExtensions.AccentExtractorTestPage 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 Extensions.AccentExtractor_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 Extensions.AccentExtractor_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 Extensions.AccentExtractor_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/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml deleted file mode 100644 index 80965fe83..000000000 --- a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs b/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.xaml.cs deleted file mode 100644 index a00607bc8..000000000 --- a/components/Extensions.AccentExtractor/tests/ExampleExtensions.AccentExtractorTestPage.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 Extensions.AccentExtractorTests; - -/// -/// An empty page that can be used on its own or navigated to within a Frame. -/// -public sealed partial class ExampleExtensions.AccentExtractorTestPage : Page -{ - public ExampleExtensions.AccentExtractorTestPage() - { - this.InitializeComponent(); - } -} diff --git a/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems index 2f618d17c..81312c9c7 100644 --- a/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems +++ b/components/Extensions.AccentExtractor/tests/Extensions.AccentExtractor.Tests.projitems @@ -8,16 +8,4 @@ Extensions.AccentExtractorTests - - - - ExampleExtensions.AccentExtractorTestPage.xaml - - - - - Designer - MSBuild:Compile - - \ No newline at end of file From b9d756c20c8a7b8fea7438fd084ca8a90f5f738d Mon Sep 17 00:00:00 2001 From: Adam Dernis Date: Thu, 25 Sep 2025 06:13:34 +0300 Subject: [PATCH 03/26] Added colorfulness sorting --- ...xtensions.AccentExtractorCustomSample.xaml | 14 ++- ...nsions.AccentExtractorCustomSample.xaml.cs | 7 +- .../src/AccentExtractor.Clustering.cs | 115 +++++++++++++++++- .../src/AccentExtractor.cs | 20 +-- 4 files changed, 144 insertions(+), 12 deletions(-) diff --git a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml index 2bdd5f954..9e47ba45a 100644 --- a/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml +++ b/components/Extensions.AccentExtractor/samples/Extensions.AccentExtractorCustomSample.xaml @@ -13,6 +13,15 @@ + + + + + + + +