diff --git a/.editorconfig b/.editorconfig index 490397a..71d45df 100644 --- a/.editorconfig +++ b/.editorconfig @@ -2,13 +2,19 @@ root = true [*] -end_of_line = CRLF +end_of_line = crlf +indent_style = space +max_line_length = 190 ; 4-column tab indentation -[*.{cs,csproj,xaml,xml,props,targets}] -indent_style = space +[*.{cs,csproj,xaml,xml,props,targets,nuspec}] indent_size = 4 -[*.{md,yml}] -indent_style = space +[*.{yml,json}] +indent_size = 2 +max_line_length = off + +[*.md] indent_size = 2 +max_line_length = off +trim_trailing_whitespace = false diff --git a/src/AvaloniaApp/AvaloniaApp.csproj b/src/AvaloniaApp/AvaloniaApp.csproj index 06e00f7..ba2ee52 100644 --- a/src/AvaloniaApp/AvaloniaApp.csproj +++ b/src/AvaloniaApp/AvaloniaApp.csproj @@ -1,24 +1,25 @@  - - WinExe - net8.0;net6.0 - enable - true - app.manifest - true - - - - - - - - - + + WinExe + net8.0;net6.0 + enable + true + app.manifest + true + - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AvaloniaApp/MainWindow.axaml b/src/AvaloniaApp/MainWindow.axaml index c4653f7..b3e3485 100644 --- a/src/AvaloniaApp/MainWindow.axaml +++ b/src/AvaloniaApp/MainWindow.axaml @@ -2,8 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:iconPacks="clr-namespace:MahApps.Metro.IconPacks;assembly=AvaloniaIconPacks" xmlns:avaloniaApp="clr-namespace:AvaloniaApp" + xmlns:iconPacks="urn:iconpacks-avalonia" + xmlns:iconPacks1="clr-namespace:MahApps.Metro.IconPacks;assembly=AvaloniaIconPacks" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="AvaloniaApp.MainWindow" Title="AvaloniaApp" @@ -15,10 +16,10 @@ - + - @@ -30,13 +31,13 @@ - - - - - net8.0;net6.0;netstandard2.0 - $(DefineConstants);AVALONIA - latest - disable - - - - - - - - + + + net8.0;net6.0;netstandard2.0 + $(DefineConstants);AVALONIA + latest + disable + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AvaloniaIconPacks/BoxIcons.xaml b/src/AvaloniaIconPacks/BoxIcons.xaml index 4258d6e..22f6ae8 100644 --- a/src/AvaloniaIconPacks/BoxIcons.xaml +++ b/src/AvaloniaIconPacks/BoxIcons.xaml @@ -1,11 +1,10 @@ - - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/src/AvaloniaIconPacks/Core/PackIconBase.cs b/src/AvaloniaIconPacks/Core/PackIconBase.cs deleted file mode 100644 index f44c400..0000000 --- a/src/AvaloniaIconPacks/Core/PackIconBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -#if (NETFX_CORE || WINDOWS_UWP) -using Windows.UI.Xaml.Controls; -#elif AVALONIA -using Avalonia.Controls; - -#else -using System.Windows.Controls; -#endif - -namespace MahApps.Metro.IconPacks -{ -#if AVALONIA - public abstract class PackIconBase : PathIcon -#else - public abstract class PackIconBase : Control -#endif - { - protected internal abstract void SetKind(TKind iconKind); - protected abstract void UpdateData(); - } -} \ No newline at end of file diff --git a/src/AvaloniaIconPacks/Core/PackIconControlBase.cs b/src/AvaloniaIconPacks/Core/PackIconControlBase.cs deleted file mode 100644 index dfa9c89..0000000 --- a/src/AvaloniaIconPacks/Core/PackIconControlBase.cs +++ /dev/null @@ -1,639 +0,0 @@ -using System; - -#if NETFX_CORE || WINDOWS_UWP -using System.Linq; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Media.Animation; -#elif AVALONIA -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Avalonia; -using Avalonia.Animation; -using Avalonia.Animation.Easings; -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Data; -using Avalonia.Media; -using Avalonia.Styling; - -#else -using System.ComponentModel; -using System.Linq; -using System.Windows; -using System.Windows.Media; -using System.Windows.Media.Animation; -#endif - -namespace MahApps.Metro.IconPacks -{ - /// - /// Class PackIconControlBase which is the base class for any PackIcon control. - /// - public abstract class PackIconControlBase : PackIconBase - { -#if NETFX_CORE || WINDOWS_UWP - private long _opacityRegisterToken; - private long _visibilityRegisterToken; - - public PackIconControlBase() - { - this.Loaded += (sender, args) => - { - this._opacityRegisterToken = this.RegisterPropertyChangedCallback(OpacityProperty, this.CoerceSpinProperty); - this._visibilityRegisterToken = this.RegisterPropertyChangedCallback(VisibilityProperty, this.CoerceSpinProperty); - }; - this.Unloaded += (sender, args) => - { - this.UnregisterPropertyChangedCallback(OpacityProperty, this._opacityRegisterToken); - this.UnregisterPropertyChangedCallback(VisibilityProperty, this._visibilityRegisterToken); - }; - } - - private void CoerceSpinProperty(DependencyObject sender, DependencyProperty dp) - { - var packIcon = sender as PackIconControlBase; - if (packIcon != null && (dp == OpacityProperty || dp == VisibilityProperty)) - { - var spin = this.Spin && packIcon.Visibility == Visibility.Visible && packIcon.SpinDuration > 0 && packIcon.Opacity > 0; - packIcon.ToggleSpinAnimation(spin); - } - } -#elif AVALONIA - public PackIconControlBase() - { - AffectsRender(SpinProperty); - AffectsRender(SpinDurationProperty); - AffectsRender(OpacityProperty); - AffectsRender(SpinEasingFunctionProperty); - AffectsRender(FlipProperty); - AffectsRender(RotationAngleProperty); - - Observable.CombineLatest( - this.GetObservable(SpinProperty).Select(_ => Unit.Default), - this.GetObservable(IsVisibleProperty).Select(_ => Unit.Default), - this.GetObservable(SpinDurationProperty).Select(_ => Unit.Default), - this.GetObservable(OpacityProperty).Select(_ => Unit.Default), - this.GetObservable(SpinEasingFunctionProperty).Select(_ => Unit.Default)) - .Select(_ => this.CanSpin()) - .Subscribe(spin => - { - this.StopSpinAnimation(); - if (spin) - { - this.BeginSpinAnimation(); - } - }); - } - - private bool CanSpin() - { - return this.Spin - && this.IsVisible - && this.SpinDuration > 0 - && this.Opacity > 0 - && this.SpinEasingFunction != null; - } -#else - static PackIconControlBase() - { - OpacityProperty.OverrideMetadata(typeof(PackIconControlBase), new UIPropertyMetadata(1d, (d, e) => { d.CoerceValue(SpinProperty); })); - VisibilityProperty.OverrideMetadata(typeof(PackIconControlBase), new UIPropertyMetadata(Visibility.Visible, (d, e) => { d.CoerceValue(SpinProperty); })); - } -#endif - -#if (NETFX_CORE || WINDOWS_UWP) - protected static readonly DependencyProperty DataProperty - = DependencyProperty.Register(nameof(Data), typeof(string), typeof(PackIconControlBase), new PropertyMetadata("")); -#elif !AVALONIA - private static readonly DependencyPropertyKey DataPropertyKey - = DependencyProperty.RegisterReadOnly(nameof(Data), typeof(string), typeof(PackIconControlBase), new PropertyMetadata("")); - - // ReSharper disable once StaticMemberInGenericType - public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty; -#endif - - /// - /// Gets the path data for the current icon kind. - /// -#if !(NETFX_CORE || WINDOWS_UWP || AVALONIA) - [TypeConverter(typeof(GeometryConverter))] - public string Data - { - get { return (string)GetValue(DataProperty); } - protected set { SetValue(DataPropertyKey, value); } - } -#endif - -#if NETFX_CORE || WINDOWS_UWP - protected override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - this.UpdateData(); - - this.CoerceSpinProperty(this, SpinProperty); - - if (this.Spin) - { - this.StopSpinAnimation(); - this.BeginSpinAnimation(); - } - } -#elif AVALONIA - private Grid innerGrid; - - private ScaleTransform scaleTransform; - private RotateTransform rotateTransform; - - /// - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - base.OnApplyTemplate(e); - - this.innerGrid = e.NameScope.Find("PART_InnerGrid"); - - if (this.innerGrid != null) - { - var transformGroup = new TransformGroup(); - this.scaleTransform = new ScaleTransform(); - this.rotateTransform = new RotateTransform(); - transformGroup.Children.Add(scaleTransform); - transformGroup.Children.Add(rotateTransform); - this.innerGrid.RenderTransform = transformGroup; - } - - this.UpdateScaleTransformation(this.Flip); - this.UpdateRotateTransformation(this.RotationAngle); - this.UpdateData(); - - var spin = CanSpin(); - if (spin) - { - this.StopSpinAnimation(); - this.BeginSpinAnimation(); - } - } - - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - if (change.Property == FlipProperty) - { - if (change.NewValue != null && change.NewValue != change.OldValue) - { - this.UpdateScaleTransformation(change.GetNewValue()); - } - } - else if (change.Property == RotationAngleProperty) - { - if (change.NewValue != null && change.NewValue != change.OldValue) - { - this.UpdateRotateTransformation(change.GetNewValue()); - } - } - } - - private void UpdateScaleTransformation(PackIconFlipOrientation flipOrientation) - { - if (this.scaleTransform != null) - { - var scaleX = flipOrientation is PackIconFlipOrientation.Horizontal or PackIconFlipOrientation.Both - ? -1 - : 1; - var scaleY = flipOrientation is PackIconFlipOrientation.Vertical or PackIconFlipOrientation.Both - ? -1 - : 1; - this.scaleTransform.ScaleX = scaleX; - this.scaleTransform.ScaleY = scaleY; - } - } - - private void UpdateRotateTransformation(double angle) - { - if (this.rotateTransform != null) - { - this.rotateTransform.Angle = angle; - } - } -#else - public override void OnApplyTemplate() - { - base.OnApplyTemplate(); - - this.UpdateData(); - - this.CoerceValue(SpinProperty); - - if (this.Spin) - { - this.StopSpinAnimation(); - this.BeginSpinAnimation(); - } - } -#endif - - /// - /// Identifies the Flip dependency property. - /// -#if AVALONIA - public static readonly StyledProperty FlipProperty - = AvaloniaProperty.Register(nameof(Flip)); -#else - public static readonly DependencyProperty FlipProperty - = DependencyProperty.Register( - nameof(Flip), - typeof(PackIconFlipOrientation), - typeof(PackIconControlBase), - new PropertyMetadata(PackIconFlipOrientation.Normal)); -#endif - - /// - /// Gets or sets the flip orientation. - /// - public PackIconFlipOrientation Flip - { - get { return (PackIconFlipOrientation)this.GetValue(FlipProperty); } - set { this.SetValue(FlipProperty, value); } - } - - /// - /// Identifies the RotationAngle dependency property. - /// -#if NETFX_CORE || WINDOWS_UWP - public static readonly DependencyProperty RotationAngleProperty - = DependencyProperty.Register( - nameof(RotationAngle), - typeof(double), - typeof(PackIconControlBase), - new PropertyMetadata(0d)); -#elif AVALONIA - public static readonly StyledProperty RotationAngleProperty - = AvaloniaProperty.Register( - nameof(RotationAngle), - 0d, - false, - BindingMode.OneWay, - null, - (packIcon, value) => - { - if (value < 0) - { - return 0d; - } - - return value > 360 ? 360d : value; - }); -#else - public static readonly DependencyProperty RotationAngleProperty - = DependencyProperty.Register( - nameof(RotationAngle), - typeof(double), - typeof(PackIconControlBase), - new PropertyMetadata(0d, null, (dependencyObject, value) => - { - var val = (double)value; - if (val < 0) - { - return 0d; - } - - return val > 360 ? 360d : val; - })); -#endif - - /// - /// Gets or sets the rotation (angle). - /// - /// The rotation. - public double RotationAngle - { - get { return (double)this.GetValue(RotationAngleProperty); } - set { this.SetValue(RotationAngleProperty, value); } - } - - /// - /// Identifies the Spin dependency property. - /// -#if NETFX_CORE || WINDOWS_UWP - public static readonly DependencyProperty SpinProperty - = DependencyProperty.Register( - nameof(Spin), - typeof(bool), - typeof(PackIconControlBase), - new PropertyMetadata(default(bool), SpinPropertyChangedCallback)); - - private static void SpinPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && e.NewValue is bool) - { - packIcon.ToggleSpinAnimation((bool)e.NewValue); - } - } -#elif AVALONIA - public static readonly StyledProperty SpinProperty - = AvaloniaProperty.Register(nameof(Spin)); -#else - public static readonly DependencyProperty SpinProperty - = DependencyProperty.Register( - nameof(Spin), - typeof(bool), - typeof(PackIconControlBase), - new PropertyMetadata(default(bool), SpinPropertyChangedCallback, SpinPropertyCoerceValueCallback)); - - private static object SpinPropertyCoerceValueCallback(DependencyObject dependencyObject, object value) - { - if (dependencyObject is PackIconControlBase packIcon && (!packIcon.IsVisible || packIcon.Opacity <= 0 || packIcon.SpinDuration <= 0.0)) - { - return false; - } - return value; - } - - private static void SpinPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && e.NewValue is bool) - { - packIcon.ToggleSpinAnimation((bool)e.NewValue); - } - } -#endif - - /// - /// Gets or sets a value indicating whether the inner icon is spinning. - /// - /// true if spin; otherwise, false. - public bool Spin - { - get { return (bool)this.GetValue(SpinProperty); } - set { this.SetValue(SpinProperty, value); } - } - -#if AVALONIA - private Animation spinAnimation = null; - private Task spinAnimationTask = null; - - private void BeginSpinAnimation() - { - if (this.innerGrid is null) - { - return; - } - - var animation = spinAnimation ?? new Animation - { - Children = - { - new KeyFrame() - { - Cue = new Cue(0), - Setters = { new Setter(RotateTransform.AngleProperty, 0d) } - }, - new KeyFrame() - { - Cue = new Cue(1), - Setters = { new Setter(RotateTransform.AngleProperty, 360d) } - } - } - }; - - animation.Duration = TimeSpan.FromSeconds(this.SpinDuration); - animation.Easing = this.SpinEasingFunction; - animation.IterationCount = IterationCount.Infinite; - this.spinAnimation = animation; - this.spinAnimationTask = animation.RunAsync(this.innerGrid); - } - - private void StopSpinAnimation() - { - if (this.spinAnimation != null) - { - this.spinAnimation.IterationCount = new IterationCount(0); - this.spinAnimationTask?.Dispose(); - this.spinAnimationTask = null; - } - } -#else - private void ToggleSpinAnimation(bool spin) - { - if (spin) - { - this.BeginSpinAnimation(); - } - else - { - this.StopSpinAnimation(); - } - } - - private Storyboard spinningStoryboard; - private FrameworkElement _innerGrid; - private FrameworkElement InnerGrid => this._innerGrid ?? (this._innerGrid = - this.GetTemplateChild("PART_InnerGrid") as FrameworkElement); - - private void BeginSpinAnimation() - { - var element = this.InnerGrid; - if (null == element) - { - return; - } - var transformGroup = element.RenderTransform as TransformGroup ?? new TransformGroup(); - var rotateTransform = transformGroup.Children.OfType().LastOrDefault(); - - if (rotateTransform != null) - { - rotateTransform.Angle = 0; - } - else - { - transformGroup.Children.Add(new RotateTransform()); - element.RenderTransform = transformGroup; - } - - var animation = new DoubleAnimation - { - From = 0, - To = 360, - AutoReverse = this.SpinAutoReverse, - EasingFunction = this.SpinEasingFunction, - RepeatBehavior = RepeatBehavior.Forever, - Duration = new Duration(TimeSpan.FromSeconds(this.SpinDuration)) - }; - - var storyboard = new Storyboard(); - storyboard.Children.Add(animation); - Storyboard.SetTarget(animation, element); - -#if NETFX_CORE || WINDOWS_UWP - Storyboard.SetTargetProperty(animation, $"(RenderTransform).(TransformGroup.Children)[{transformGroup.Children.Count - 1}].(Angle)"); -#else - Storyboard.SetTargetProperty(animation, new PropertyPath($"(0).(1)[{transformGroup.Children.Count - 1}].(2)", RenderTransformProperty, TransformGroup.ChildrenProperty, RotateTransform.AngleProperty)); -#endif - - spinningStoryboard = storyboard; - storyboard.Begin(); - } - - private void StopSpinAnimation() - { - var storyboard = spinningStoryboard; - storyboard?.Stop(); - spinningStoryboard = null; - } -#endif - - /// - /// Identifies the SpinDuration dependency property. - /// -#if NETFX_CORE || WINDOWS_UWP - public static readonly DependencyProperty SpinDurationProperty - = DependencyProperty.Register( - nameof(SpinDuration), - typeof(double), - typeof(PackIconControlBase), - new PropertyMetadata(1d, SpinDurationPropertyChangedCallback)); -#elif AVALONIA - public static readonly StyledProperty SpinDurationProperty - = AvaloniaProperty.Register( - nameof(SpinDuration), - 1d, - false, - BindingMode.OneWay, - null, - (iconPack, value) => value < 0 ? 0d : value); -#else - public static readonly DependencyProperty SpinDurationProperty - = DependencyProperty.Register( - nameof(SpinDuration), - typeof(double), - typeof(PackIconControlBase), - new PropertyMetadata(1d, SpinDurationPropertyChangedCallback, (dependencyObject, value) => - { - var val = (double)value; - return val < 0 ? 0d : value; - })); - - private static void SpinDurationPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && packIcon.Spin && e.NewValue is double) - { - packIcon.StopSpinAnimation(); - packIcon.BeginSpinAnimation(); - } - } -#endif - - /// - /// Gets or sets the duration of the spinning animation (in seconds). This will also restart the spin animation. - /// - /// The duration of the spin in seconds. - public double SpinDuration - { - get { return (double)this.GetValue(SpinDurationProperty); } - set { this.SetValue(SpinDurationProperty, value); } - } - - /// - /// Identifies the SpinEasingFunction dependency property. - /// -#if NETFX_CORE || WINDOWS_UWP - public static readonly DependencyProperty SpinEasingFunctionProperty - = DependencyProperty.Register( - nameof(SpinEasingFunction), - typeof(EasingFunctionBase), - typeof(PackIconControlBase), - new PropertyMetadata(null, SpinEasingFunctionPropertyChangedCallback)); - - private static void SpinEasingFunctionPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && packIcon.Spin) - { - packIcon.StopSpinAnimation(); - packIcon.BeginSpinAnimation(); - } - } -#elif AVALONIA - public static readonly StyledProperty SpinEasingFunctionProperty - = AvaloniaProperty.Register( - nameof(SpinEasingFunction), - new LinearEasing()); -#else - public static readonly DependencyProperty SpinEasingFunctionProperty - = DependencyProperty.Register( - nameof(SpinEasingFunction), - typeof(IEasingFunction), - typeof(PackIconControlBase), - new PropertyMetadata(null, SpinEasingFunctionPropertyChangedCallback)); - - private static void SpinEasingFunctionPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && packIcon.Spin) - { - packIcon.StopSpinAnimation(); - packIcon.BeginSpinAnimation(); - } - } -#endif - - /// - /// Gets or sets the EasingFunction of the spinning animation. This will also restart the spin animation. - /// - /// The spin easing function. -#if NETFX_CORE || WINDOWS_UWP - public EasingFunctionBase SpinEasingFunction - { - get { return (EasingFunctionBase)this.GetValue(SpinEasingFunctionProperty); } - set { this.SetValue(SpinEasingFunctionProperty, value); } - } -#elif AVALONIA - public Easing SpinEasingFunction - { - get { return (Easing)this.GetValue(SpinEasingFunctionProperty); } - set { this.SetValue(SpinEasingFunctionProperty, value); } - } -#else - public IEasingFunction SpinEasingFunction - { - get { return (IEasingFunction)this.GetValue(SpinEasingFunctionProperty); } - set { this.SetValue(SpinEasingFunctionProperty, value); } - } -#endif - -#if AVALONIA - public static readonly StyledProperty SpinAutoReverseProperty - = AvaloniaProperty.Register(nameof(SpinAutoReverse)); -#else - /// - /// Identifies the SpinAutoReverse dependency property. - /// - public static readonly DependencyProperty SpinAutoReverseProperty - = DependencyProperty.Register( - nameof(SpinAutoReverse), - typeof(bool), - typeof(PackIconControlBase), - new PropertyMetadata(default(bool), SpinAutoReversePropertyChangedCallback)); - - private static void SpinAutoReversePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (dependencyObject is PackIconControlBase packIcon && e.OldValue != e.NewValue && packIcon.Spin && e.NewValue is bool) - { - packIcon.StopSpinAnimation(); - packIcon.BeginSpinAnimation(); - } - } -#endif - - /// - /// Gets or sets the AutoReverse of the spinning animation. This will also restart the spin animation. - /// - /// true if [spin automatic reverse]; otherwise, false. - public bool SpinAutoReverse - { - get { return (bool)this.GetValue(SpinAutoReverseProperty); } - set { this.SetValue(SpinAutoReverseProperty, value); } - } - } -} \ No newline at end of file diff --git a/src/AvaloniaIconPacks/PackIconBoxIcons.cs b/src/AvaloniaIconPacks/PackIconBoxIcons.cs index 8d3f17b..4280be4 100644 --- a/src/AvaloniaIconPacks/PackIconBoxIcons.cs +++ b/src/AvaloniaIconPacks/PackIconBoxIcons.cs @@ -1,13 +1,6 @@ -using System; using Avalonia.Media; -#if (NETFX_CORE || WINDOWS_UWP) -using Windows.UI.Xaml; -using Windows.UI.Xaml.Data; -#elif AVALONIA +using IconPacks.Avalonia; using Avalonia; -#else -using System.Windows; -#endif namespace MahApps.Metro.IconPacks { @@ -17,73 +10,38 @@ namespace MahApps.Metro.IconPacks /// public class PackIconBoxIcons : PackIconControlBase { -#if AVALONIA public static readonly StyledProperty KindProperty = AvaloniaProperty.Register(nameof(Kind)); -#else - public static readonly DependencyProperty KindProperty - = DependencyProperty.Register( - nameof(Kind), - typeof(PackIconBoxIconsKind), - typeof(PackIconBoxIcons), - new PropertyMetadata(default(PackIconBoxIconsKind), KindPropertyChangedCallback)); - - private static void KindPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) - { - if (e.NewValue != e.OldValue) - { - ((PackIconBoxIcons)dependencyObject).UpdateData(); - } - } -#endif /// /// Gets or sets the icon to display. /// public PackIconBoxIconsKind Kind { - get { return (PackIconBoxIconsKind) GetValue(KindProperty); } + get { return GetValue(KindProperty); } set { SetValue(KindProperty, value); } } -#if NETFX_CORE || WINDOWS_UWP - public PackIconBoxIcons() - { - this.DefaultStyleKey = typeof(PackIconBoxIcons); - } -#elif AVALONIA // We override OnPropertyChanged of the base class. That way we can react on property changes protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - // if the changed property is the NumberOfStarsProperty, we need to update the stars + // if the changed property is the KindProperty, we need to update the stars if (change.Property == KindProperty) { UpdateData(); } } -#else - static PackIconBoxIcons() - { - DefaultStyleKeyProperty.OverrideMetadata(typeof(PackIconBoxIcons), new FrameworkPropertyMetadata(typeof(PackIconBoxIcons))); - } -#endif - protected internal override void SetKind(TKind iconKind) + protected override void SetKind(TKind iconKind) { -#if NETFX_CORE || WINDOWS_UWP - BindingOperations.SetBinding(this, PackIconBoxIcons.KindProperty, new Binding() { Source = iconKind, Mode = BindingMode.OneTime }); -#elif AVALONIA this.SetValue(KindProperty, iconKind); -#else - this.SetCurrentValue(KindProperty, iconKind); -#endif } protected override void UpdateData() { - if (Kind != default(PackIconBoxIconsKind)) + if (Kind != default) { string data = null; PackIconBoxIconsDataFactory.DataIndex.Value?.TryGetValue(Kind, out data); diff --git a/src/IconPacks.Avalonia.Core/Attributes/MetaDataAttribute.cs b/src/IconPacks.Avalonia.Core/Attributes/MetaDataAttribute.cs new file mode 100644 index 0000000..4c1cee5 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Attributes/MetaDataAttribute.cs @@ -0,0 +1,28 @@ +using System; + +namespace IconPacks.Avalonia.Attributes +{ + /// + /// Specifies meta data for a class. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class MetaDataAttribute : Attribute + { + public MetaDataAttribute() + { + } + + public MetaDataAttribute(string name, string projectUrl, string licenseUrl) + { + Name = name; + ProjectUrl = projectUrl; + LicenseUrl = licenseUrl; + } + + public string Name { get; } + + public string ProjectUrl { get; } + + public string LicenseUrl { get; } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Converter/DataTypeValueConverter.cs b/src/IconPacks.Avalonia.Core/Converter/DataTypeValueConverter.cs new file mode 100644 index 0000000..b1bf660 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Converter/DataTypeValueConverter.cs @@ -0,0 +1,39 @@ +using Avalonia.Data; +using System; +using System.Globalization; + +namespace IconPacks.Avalonia.Converter +{ + public class DataTypeValueConverter : MarkupConverter + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static DataTypeValueConverter() + { + } + + private DataTypeValueConverter() + { + } + + public static DataTypeValueConverter Instance { get; } = new(); + + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + + /// + protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value?.GetType(); + } + + /// + protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return BindingNotification.UnsetValue; + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Converter/FlipToScaleXValueConverter.cs b/src/IconPacks.Avalonia.Core/Converter/FlipToScaleXValueConverter.cs new file mode 100644 index 0000000..0367bb2 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Converter/FlipToScaleXValueConverter.cs @@ -0,0 +1,49 @@ +using Avalonia.Data; +using System; +using System.Globalization; + +namespace IconPacks.Avalonia.Converter +{ + /// + /// ValueConverter which converts the PackIconFlipOrientation enumeration value to ScaleX value of a ScaleTransformation. + /// + public class FlipToScaleXValueConverter : MarkupConverter + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static FlipToScaleXValueConverter() + { + } + + private FlipToScaleXValueConverter() + { + } + + public static FlipToScaleXValueConverter Instance { get; } = new(); + + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + + /// + protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is PackIconFlipOrientation flip) + { + return flip is PackIconFlipOrientation.Horizontal or PackIconFlipOrientation.Both + ? -1 + : 1; + } + + return 1; + } + + /// + protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return BindingNotification.UnsetValue; + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Converter/FlipToScaleYValueConverter.cs b/src/IconPacks.Avalonia.Core/Converter/FlipToScaleYValueConverter.cs new file mode 100644 index 0000000..710e167 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Converter/FlipToScaleYValueConverter.cs @@ -0,0 +1,48 @@ +using Avalonia.Data; +using System; +using System.Globalization; + +namespace IconPacks.Avalonia.Converter +{ + /// + /// ValueConverter which converts the PackIconFlipOrientation enumeration value to ScaleY value of a ScaleTransformation. + /// + public class FlipToScaleYValueConverter : MarkupConverter + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static FlipToScaleYValueConverter() + { + } + + private FlipToScaleYValueConverter() + { + } + + public static FlipToScaleYValueConverter Instance { get; } = new(); + + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + + /// + protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is PackIconFlipOrientation flip) + { + var scaleY = flip == PackIconFlipOrientation.Vertical || flip == PackIconFlipOrientation.Both ? -1 : 1; + return scaleY; + } + + return 1; + } + + /// + protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return BindingNotification.UnsetValue; + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Converter/MarkupConverter.cs b/src/IconPacks.Avalonia.Core/Converter/MarkupConverter.cs new file mode 100644 index 0000000..c2b7f12 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Converter/MarkupConverter.cs @@ -0,0 +1,76 @@ +using System; +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System.Globalization; +using Avalonia.Data; + +namespace IconPacks.Avalonia.Converter +{ + /// + /// MarkupConverter is a MarkupExtension which can be used for IValueConverter. + /// + public abstract class MarkupConverter : MarkupExtension, IValueConverter + { + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + return this; + } + + /// + /// Converts a value. + /// + /// The value to convert. + /// The type of the target. + /// A user-defined parameter. + /// The culture to use. + /// The converted value. + /// + /// This method should not throw exceptions. If the value is not convertible, return + /// a in an error state. Any exceptions thrown will be + /// treated as an application exception. + /// + protected abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture); + + /// + /// Converts a value. + /// + /// The value to convert. + /// The type of the target. + /// A user-defined parameter. + /// The culture to use. + /// The converted value. + /// + /// This method should not throw exceptions. If the value is not convertible, return + /// a in an error state. Any exceptions thrown will be + /// treated as an application exception. + /// + protected abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture); + + /// + object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + return Convert(value, targetType, parameter, culture); + } + catch + { + return BindingNotification.UnsetValue; + } + } + + /// + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + try + { + return ConvertBack(value, targetType, parameter, culture); + } + catch + { + return BindingNotification.UnsetValue; + } + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Converter/NullToUnsetValueConverter.cs b/src/IconPacks.Avalonia.Core/Converter/NullToUnsetValueConverter.cs new file mode 100644 index 0000000..ae7d30e --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Converter/NullToUnsetValueConverter.cs @@ -0,0 +1,39 @@ +using Avalonia.Data; +using System; +using System.Globalization; + +namespace IconPacks.Avalonia.Converter +{ + public class NullToUnsetValueConverter : MarkupConverter + { + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static NullToUnsetValueConverter() + { + } + + private NullToUnsetValueConverter() + { + } + + public static NullToUnsetValueConverter Instance { get; } = new(); + + /// + public override object ProvideValue(IServiceProvider serviceProvider) + { + return Instance; + } + + /// + protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value ?? BindingNotification.UnsetValue; + } + + /// + protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return BindingNotification.UnsetValue; + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/IconPacks.Avalonia.Core.csproj b/src/IconPacks.Avalonia.Core/IconPacks.Avalonia.Core.csproj new file mode 100644 index 0000000..22834c3 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/IconPacks.Avalonia.Core.csproj @@ -0,0 +1,27 @@ + + + + net8.0;net6.0;netstandard2.0 + $(DefineConstants);AVALONIA + latest + disable + true + + + + IconPacks.Avalonia.Core + IconPacks.Avalonia.Core + IconPacks.Avalonia + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/PackIconBase.cs b/src/IconPacks.Avalonia.Core/PackIconBase.cs new file mode 100644 index 0000000..526b5ec --- /dev/null +++ b/src/IconPacks.Avalonia.Core/PackIconBase.cs @@ -0,0 +1,10 @@ +using Avalonia.Controls; + +namespace IconPacks.Avalonia +{ + public abstract class PackIconBase : PathIcon + { + protected internal abstract void SetKind(TKind iconKind); + protected abstract void UpdateData(); + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/PackIconControlBase.cs b/src/IconPacks.Avalonia.Core/PackIconControlBase.cs new file mode 100644 index 0000000..e58fb0f --- /dev/null +++ b/src/IconPacks.Avalonia.Core/PackIconControlBase.cs @@ -0,0 +1,285 @@ +using System; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using Avalonia; +using Avalonia.Animation; +using Avalonia.Animation.Easings; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data; +using Avalonia.Media; +using Avalonia.Styling; + +namespace IconPacks.Avalonia +{ + /// + /// Class PackIconControlBase which is the base class for any PackIcon control. + /// + public abstract class PackIconControlBase : PackIconBase + { + public PackIconControlBase() + { + AffectsRender(SpinProperty, SpinDurationProperty, OpacityProperty, SpinEasingFunctionProperty, FlipProperty, RotationAngleProperty); + + Observable.CombineLatest( + this.GetObservable(SpinProperty).Select(_ => Unit.Default), + this.GetObservable(IsVisibleProperty).Select(_ => Unit.Default), + this.GetObservable(SpinDurationProperty).Select(_ => Unit.Default), + this.GetObservable(OpacityProperty).Select(_ => Unit.Default), + this.GetObservable(SpinEasingFunctionProperty).Select(_ => Unit.Default)) + .Select(_ => this.CanSpin()) + .Subscribe(spin => + { + this.StopSpinAnimation(); + if (spin) + { + this.BeginSpinAnimation(); + } + }); + } + + private bool CanSpin() + { + return this.Spin + && this.IsVisible + && this.SpinDuration > 0 + && this.Opacity > 0 + && this.SpinEasingFunction != null; + } + + private Grid innerGrid; + private ScaleTransform scaleTransform; + private RotateTransform rotateTransform; + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + this.innerGrid = e.NameScope.Find("PART_InnerGrid"); + + if (this.innerGrid != null) + { + var transformGroup = new TransformGroup(); + this.scaleTransform = new ScaleTransform(); + this.rotateTransform = new RotateTransform(); + transformGroup.Children.Add(scaleTransform); + transformGroup.Children.Add(rotateTransform); + this.innerGrid.RenderTransform = transformGroup; + } + + this.UpdateScaleTransformation(this.Flip); + this.UpdateRotateTransformation(this.RotationAngle); + this.UpdateData(); + + var spin = CanSpin(); + if (spin) + { + this.StopSpinAnimation(); + this.BeginSpinAnimation(); + } + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == FlipProperty) + { + if (change.NewValue != null && change.NewValue != change.OldValue) + { + this.UpdateScaleTransformation(change.GetNewValue()); + } + } + else if (change.Property == RotationAngleProperty) + { + if (change.NewValue != null && change.NewValue != change.OldValue) + { + this.UpdateRotateTransformation(change.GetNewValue()); + } + } + } + + private void UpdateScaleTransformation(PackIconFlipOrientation flipOrientation) + { + if (this.scaleTransform != null) + { + var scaleX = flipOrientation is PackIconFlipOrientation.Horizontal or PackIconFlipOrientation.Both + ? -1 + : 1; + var scaleY = flipOrientation is PackIconFlipOrientation.Vertical or PackIconFlipOrientation.Both + ? -1 + : 1; + this.scaleTransform.ScaleX = scaleX; + this.scaleTransform.ScaleY = scaleY; + } + } + + private void UpdateRotateTransformation(double angle) + { + if (this.rotateTransform != null) + { + this.rotateTransform.Angle = angle; + } + } + + /// + /// Identifies the Flip dependency property. + /// + public static readonly StyledProperty FlipProperty + = AvaloniaProperty.Register(nameof(Flip)); + + /// + /// Gets or sets the flip orientation. + /// + public PackIconFlipOrientation Flip + { + get { return this.GetValue(FlipProperty); } + set { this.SetValue(FlipProperty, value); } + } + + /// + /// Identifies the RotationAngle dependency property. + /// + public static readonly StyledProperty RotationAngleProperty + = AvaloniaProperty.Register( + nameof(RotationAngle), + 0d, + false, + BindingMode.OneWay, + null, + (packIcon, value) => + { + if (value < 0) + { + return 0d; + } + + return value > 360 ? 360d : value; + }); + + /// + /// Gets or sets the rotation (angle). + /// + /// The rotation. + public double RotationAngle + { + get { return this.GetValue(RotationAngleProperty); } + set { this.SetValue(RotationAngleProperty, value); } + } + + /// + /// Identifies the Spin dependency property. + /// + public static readonly StyledProperty SpinProperty + = AvaloniaProperty.Register(nameof(Spin)); + + /// + /// Gets or sets a value indicating whether the inner icon is spinning. + /// + /// true if spin; otherwise, false. + public bool Spin + { + get { return this.GetValue(SpinProperty); } + set { this.SetValue(SpinProperty, value); } + } + + private Animation spinAnimation = null; + private Task spinAnimationTask = null; + + private void BeginSpinAnimation() + { + if (this.innerGrid is null) + { + return; + } + + var animation = spinAnimation ?? new Animation + { + Children = + { + new KeyFrame() + { + Cue = new Cue(0), + Setters = { new Setter(RotateTransform.AngleProperty, 0d) } + }, + new KeyFrame() + { + Cue = new Cue(1), + Setters = { new Setter(RotateTransform.AngleProperty, 360d) } + } + } + }; + + animation.Duration = TimeSpan.FromSeconds(this.SpinDuration); + animation.Easing = this.SpinEasingFunction; + animation.IterationCount = IterationCount.Infinite; + this.spinAnimation = animation; + this.spinAnimationTask = animation.RunAsync(this.innerGrid); + } + + private void StopSpinAnimation() + { + if (this.spinAnimation != null) + { + this.spinAnimation.IterationCount = new IterationCount(0); + this.spinAnimationTask?.Dispose(); + this.spinAnimationTask = null; + } + } + + /// + /// Identifies the SpinDuration dependency property. + /// + public static readonly StyledProperty SpinDurationProperty + = AvaloniaProperty.Register( + nameof(SpinDuration), + 1d, + false, + BindingMode.OneWay, + null, + (iconPack, value) => value < 0 ? 0d : value); + + /// + /// Gets or sets the duration of the spinning animation (in seconds). This will also restart the spin animation. + /// + /// The duration of the spin in seconds. + public double SpinDuration + { + get { return this.GetValue(SpinDurationProperty); } + set { this.SetValue(SpinDurationProperty, value); } + } + + /// + /// Identifies the SpinEasingFunction dependency property. + /// + public static readonly StyledProperty SpinEasingFunctionProperty + = AvaloniaProperty.Register( + nameof(SpinEasingFunction), + new LinearEasing()); + + /// + /// Gets or sets the EasingFunction of the spinning animation. This will also restart the spin animation. + /// + /// The spin easing function. + public Easing SpinEasingFunction + { + get { return this.GetValue(SpinEasingFunctionProperty); } + set { this.SetValue(SpinEasingFunctionProperty, value); } + } + + public static readonly StyledProperty SpinAutoReverseProperty + = AvaloniaProperty.Register(nameof(SpinAutoReverse)); + + /// + /// Gets or sets the AutoReverse of the spinning animation. This will also restart the spin animation. + /// + /// true if [spin automatic reverse]; otherwise, false. + public bool SpinAutoReverse + { + get { return this.GetValue(SpinAutoReverseProperty); } + set { this.SetValue(SpinAutoReverseProperty, value); } + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/PackIconDataFactory.cs b/src/IconPacks.Avalonia.Core/PackIconDataFactory.cs new file mode 100644 index 0000000..8c70d1c --- /dev/null +++ b/src/IconPacks.Avalonia.Core/PackIconDataFactory.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using IconPacks.Avalonia.Utils; + +namespace IconPacks.Avalonia +{ + public static class PackIconDataFactory where TEnum : struct, Enum + { + public static Lazy> DataIndex { get; } + + static PackIconDataFactory() + { + DataIndex = new Lazy>(Create); + } + + public static IDictionary Create() + { + var json = System.Reflection.Assembly.GetAssembly(typeof(TEnum))?.ReadFile("Resources.Icons.json"); + return string.IsNullOrEmpty(json) + ? new Dictionary() + : System.Text.Json.JsonSerializer.Deserialize>(json); + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/PackIconExtension.cs b/src/IconPacks.Avalonia.Core/PackIconExtension.cs new file mode 100644 index 0000000..aded4cd --- /dev/null +++ b/src/IconPacks.Avalonia.Core/PackIconExtension.cs @@ -0,0 +1,240 @@ +using Avalonia.Animation.Easings; +using System; +using Avalonia.Markup.Xaml; + +namespace IconPacks.Avalonia +{ + public interface IPackIconExtension + { + double Width { get; set; } + double Height { get; set; } + PackIconFlipOrientation Flip { get; set; } + double RotationAngle { get; set; } + bool Spin { get; set; } + bool SpinAutoReverse { get; set; } + Easing SpinEasingFunction { get; set; } + double SpinDuration { get; set; } + } + + public static class PackIconExtensionHelper + { + public static PackIconControlBase GetPackIcon(this IPackIconExtension packIconExtension, TKind kind) where TPack : PackIconControlBase, new() + { + var packIcon = new TPack(); + packIcon.SetKind(kind); + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.Width)) + { + packIcon.Width = packIconExtension.Width; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.Height)) + { + packIcon.Height = packIconExtension.Height; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.Flip)) + { + packIcon.Flip = packIconExtension.Flip; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.RotationAngle)) + { + packIcon.RotationAngle = packIconExtension.RotationAngle; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.Spin)) + { + packIcon.Spin = packIconExtension.Spin; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.SpinAutoReverse)) + { + packIcon.SpinAutoReverse = packIconExtension.SpinAutoReverse; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.SpinEasingFunction)) + { + packIcon.SpinEasingFunction = packIconExtension.SpinEasingFunction; + } + + if (((BasePackIconExtension)packIconExtension).IsFieldChanged(BasePackIconExtension.ChangedFieldFlags.SpinDuration)) + { + packIcon.SpinDuration = packIconExtension.SpinDuration; + } + + return packIcon; + } + } + + public abstract class BasePackIconExtension : MarkupExtension, IPackIconExtension + { + private double _width = 20d; + + public double Width + { + get => _width; + set + { + if (Equals(_width, value)) + { + return; + } + + _width = value; + WriteFieldChangedFlag(ChangedFieldFlags.Width, true); + } + } + + private double _height = 20d; + + public double Height + { + get => _height; + set + { + if (Equals(_height, value)) + { + return; + } + + _height = value; + WriteFieldChangedFlag(ChangedFieldFlags.Height, true); + } + } + + private PackIconFlipOrientation _flip = PackIconFlipOrientation.Normal; + + public PackIconFlipOrientation Flip + { + get => _flip; + set + { + if (Equals(_flip, value)) + { + return; + } + + _flip = value; + WriteFieldChangedFlag(ChangedFieldFlags.Flip, true); + } + } + + private double _rotationAngle = 0d; + + public double RotationAngle + { + get => _rotationAngle; + set + { + if (Equals(_rotationAngle, value)) + { + return; + } + + _rotationAngle = value; + WriteFieldChangedFlag(ChangedFieldFlags.RotationAngle, true); + } + } + + private bool _spin; + + public bool Spin + { + get => _spin; + set + { + if (Equals(_spin, value)) + { + return; + } + + _spin = value; + WriteFieldChangedFlag(ChangedFieldFlags.Spin, true); + } + } + + private bool _spinAutoReverse; + + public bool SpinAutoReverse + { + get => _spinAutoReverse; + set + { + if (Equals(_spinAutoReverse, value)) + { + return; + } + + _spinAutoReverse = value; + WriteFieldChangedFlag(ChangedFieldFlags.SpinAutoReverse, true); + } + } + + private Easing _spinEasingFunction = null; + + public Easing SpinEasingFunction + { + get => _spinEasingFunction; + set + { + if (Equals(_spinEasingFunction, value)) + { + return; + } + + _spinEasingFunction = value; + WriteFieldChangedFlag(ChangedFieldFlags.SpinEasingFunction, true); + } + } + + private double _spinDuration = 1d; + + public double SpinDuration + { + get => _spinDuration; + set + { + if (Equals(_spinDuration, value)) + { + return; + } + + _spinDuration = value; + WriteFieldChangedFlag(ChangedFieldFlags.SpinDuration, true); + } + } + + internal ChangedFieldFlags changedField; // Cache changed field bits + + internal bool IsFieldChanged(ChangedFieldFlags reqFlag) + { + return (changedField & reqFlag) != 0; + } + + internal void WriteFieldChangedFlag(ChangedFieldFlags reqFlag, bool set) + { + if (set) + { + changedField |= reqFlag; + } + else + { + changedField &= (~reqFlag); + } + } + + [Flags] + internal enum ChangedFieldFlags : ushort + { + Width = 0x0001, + Height = 0x0002, + Flip = 0x0004, + RotationAngle = 0x0008, + Spin = 0x0010, + SpinAutoReverse = 0x0020, + SpinEasingFunction = 0x0040, + SpinDuration = 0x0080 + } + } +} \ No newline at end of file diff --git a/src/AvaloniaIconPacks/Core/PackIconFlipOrientation.cs b/src/IconPacks.Avalonia.Core/PackIconFlipOrientation.cs similarity index 90% rename from src/AvaloniaIconPacks/Core/PackIconFlipOrientation.cs rename to src/IconPacks.Avalonia.Core/PackIconFlipOrientation.cs index cae5cde..aa217db 100644 --- a/src/AvaloniaIconPacks/Core/PackIconFlipOrientation.cs +++ b/src/IconPacks.Avalonia.Core/PackIconFlipOrientation.cs @@ -1,28 +1,28 @@ -namespace MahApps.Metro.IconPacks -{ - /// - /// Enum PackIconFlipOrientation for the Flip property of any PackIcon control. - /// - public enum PackIconFlipOrientation - { - /// - /// No flip - /// - Normal, - - /// - /// Flip the icon horizontal - /// - Horizontal, - - /// - /// Flip the icon vertical - /// - Vertical, - - /// - /// Flip the icon vertical and horizontal - /// - Both - } +namespace IconPacks.Avalonia +{ + /// + /// Enum PackIconFlipOrientation for the Flip property of any PackIcon control. + /// + public enum PackIconFlipOrientation + { + /// + /// No flip + /// + Normal, + + /// + /// Flip the icon horizontal + /// + Horizontal, + + /// + /// Flip the icon vertical + /// + Vertical, + + /// + /// Flip the icon vertical and horizontal + /// + Both + } } \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/PackIconImageExtension.cs b/src/IconPacks.Avalonia.Core/PackIconImageExtension.cs new file mode 100644 index 0000000..20e199a --- /dev/null +++ b/src/IconPacks.Avalonia.Core/PackIconImageExtension.cs @@ -0,0 +1,90 @@ +using Avalonia.Markup.Xaml; +using Avalonia.Media; + +namespace IconPacks.Avalonia +{ + public abstract class BasePackIconImageExtension : MarkupExtension + { + /// + /// Gets or sets the brush to draw the icon. + /// + public IBrush Brush { get; set; } = Brushes.Black; + + /// + /// Gets or sets the flip orientation for the icon. + /// + public PackIconFlipOrientation Flip { get; set; } = PackIconFlipOrientation.Normal; + + /// + /// Gets or sets the rotation (angle) for the icon. + /// + public double RotationAngle { get; set; } = 0d; + + /// + /// Gets the path data for the given kind. + /// + protected abstract string GetPathData(object iconKind); + + /// + /// Gets the ScaleTransform for the given kind. + /// + /// The icon kind to draw. + protected virtual ScaleTransform GetScaleTransform(object iconKind) + { + return new ScaleTransform(1, 1); + } + + /// + /// Gets the for the . + /// + /// The icon kind to draw. + protected Transform GetTransformGroup(object iconKind) + { + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(this.GetScaleTransform(iconKind)); // scale + transformGroup.Children.Add(new ScaleTransform( + this.Flip is PackIconFlipOrientation.Horizontal or PackIconFlipOrientation.Both ? -1 : 1, + this.Flip is PackIconFlipOrientation.Vertical or PackIconFlipOrientation.Both ? -1 : 1 + )); // flip + transformGroup.Children.Add(new RotateTransform(this.RotationAngle)); // rotate + + return transformGroup; + } + + /// + /// Gets the object that will be used for the . + /// + protected virtual DrawingGroup GetDrawingGroup(object iconKind, Brush foregroundBrush, string path) + { + var geometryDrawing = new GeometryDrawing + { + Geometry = Geometry.Parse(path), + Brush = foregroundBrush + }; + + var drawingGroup = new DrawingGroup + { + Children = { geometryDrawing }, + Transform = this.GetTransformGroup(iconKind) + }; + + return drawingGroup; + } + + /// + /// Gets the ImageSource for the given kind. + /// + protected IImage CreateImageSource(object iconKind, Brush foregroundBrush) + { + var path = this.GetPathData(iconKind); + + if (string.IsNullOrEmpty(path)) + { + return null; + } + + var drawingImage = new DrawingImage(GetDrawingGroup(iconKind, foregroundBrush, path)); + return drawingImage; + } + } +} \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Properties/AssemblyInfo.cs b/src/IconPacks.Avalonia.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fb305e5 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; +using Avalonia.Metadata; + +[assembly: XmlnsPrefix("urn:iconpacks-avalonia", "iconPacks")] +[assembly: XmlnsDefinition("urn:iconpacks-avalonia", "IconPacks")] +[assembly: XmlnsDefinition("urn:iconpacks-avalonia", "IconPacks.Avalonia")] +[assembly: XmlnsDefinition("urn:iconpacks-avalonia", "IconPacks.Avalonia.Attributes")] + +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/src/IconPacks.Avalonia.Core/Utils/AssemblyTextFileReader.cs b/src/IconPacks.Avalonia.Core/Utils/AssemblyTextFileReader.cs new file mode 100644 index 0000000..e3a2691 --- /dev/null +++ b/src/IconPacks.Avalonia.Core/Utils/AssemblyTextFileReader.cs @@ -0,0 +1,62 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using JetBrains.Annotations; + +namespace IconPacks.Avalonia.Utils +{ + public static class AssemblyTextFileReader + { + private static string GetManifestResourceName(this Assembly assembly, string fileName) + { + var name = assembly + .GetManifestResourceNames() + .SingleOrDefault(n => n.EndsWith(fileName, StringComparison.InvariantCultureIgnoreCase)); + + if (string.IsNullOrEmpty(name)) + { + throw new FileNotFoundException($"Embedded file '{fileName}' could not be found in assembly '{assembly.FullName}'.", fileName); + } + + return name; + } + + public static async Task ReadFileAsync([NotNull] this Assembly assembly, string fileName) + { + if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + + var resourceName = assembly.GetManifestResourceName(fileName); + +#if NET6_0_OR_GREATER + await using var stream = assembly.GetManifestResourceStream(resourceName); +#else + using var stream = assembly.GetManifestResourceStream(resourceName); +#endif + if (stream != null) + { + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync().ConfigureAwait(false); + } + + return null; + } + + public static string ReadFile([NotNull] this Assembly assembly, string fileName) + { + if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + + var resourceName = assembly.GetManifestResourceName(fileName); + + using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream != null) + { + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + return null; + } + } +} \ No newline at end of file