namespace AntdWpf.Controls { using System; using System.ComponentModel; using System.Diagnostics; using System.Windows; using System.Windows.Documents; using System.Windows.Media; using AntdWpf.Utils; /// /// Semantic vector graphics. /// public class Icon : FrameworkElement, ISpinable { #region Fields private Geometry definingGeometry; #endregion #region Document Properties /// /// DependencyProperty for property. /// public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(Icon)); /// /// The FontSize property specifies the size of the font. /// [TypeConverter(typeof(FontSizeConverter))] [Localizability(LocalizationCategory.None)] public double FontSize { get { return (double)GetValue(FontSizeProperty); } set { SetValue(FontSizeProperty, value); } } /// /// DependencyProperty setter for property. /// /// The element to which to write the attached property. /// The property value to set public static void SetFontSize(DependencyObject element, double value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(FontSizeProperty, value); } /// /// DependencyProperty getter for property. /// /// The element from which to read the attached property. [TypeConverter(typeof(FontSizeConverter))] public static double GetFontSize(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (double)element.GetValue(FontSizeProperty); } /// /// DependencyProperty for property. /// public static readonly DependencyProperty ForegroundProperty = TextElement.ForegroundProperty.AddOwner(typeof(Icon)); /// /// The Foreground property specifies the foreground brush of an element's text content. /// public Brush Foreground { get { return (Brush)GetValue(ForegroundProperty); } set { SetValue(ForegroundProperty, value); } } /// /// DependencyProperty setter for property. /// /// The element to which to write the attached property. /// The property value to set public static void SetForeground(DependencyObject element, Brush value) { if (element == null) { throw new ArgumentNullException("element"); } element.SetValue(ForegroundProperty, value); } /// /// DependencyProperty getter for property. /// /// The element from which to read the attached property. public static Brush GetForeground(DependencyObject element) { if (element == null) { throw new ArgumentNullException("element"); } return (Brush)element.GetValue(ForegroundProperty); } public static readonly DependencyProperty BackgroundProperty = TextElement.BackgroundProperty.AddOwner( typeof(Icon), new FrameworkPropertyMetadata( Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// The Background property defines the brush used to fill the content area. /// public Brush Background { get { return (Brush)GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } } #endregion #region Properties /// /// Get the geometry that defines this icon. /// protected Geometry DefiningGeometry { get { if (definingGeometry == null) { if (!string.IsNullOrEmpty(Type)) { var key = "anticon." + Type.ToLower(); // With theme suffix. if (Theme == IconTheme.Filled) { key += ".fill"; } else if (Theme == IconTheme.Colorful) { key += ".colorful"; } definingGeometry = TryFindResource(key) as Geometry ?? Geometry.Empty; } else { definingGeometry = Geometry.Empty; } } return definingGeometry; } } public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(string), typeof(Icon), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnSpinChanged)); /// /// Gets/sets the type of the ant design icon. /// public string Type { get { return (string)GetValue(TypeProperty); } set { SetValue(TypeProperty, value); } } public static readonly DependencyProperty ThemeProperty = DependencyProperty.Register("Theme", typeof(IconTheme), typeof(Icon), new FrameworkPropertyMetadata(IconTheme.Outlined, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets/sets the theme of the ant design icon. /// public IconTheme Theme { get { return (IconTheme)GetValue(ThemeProperty); } set { SetValue(ThemeProperty, value); } } public static readonly DependencyProperty SpinProperty = DependencyProperty.Register("Spin", typeof(bool?), typeof(Icon), new PropertyMetadata(null, OnSpinChanged)); /// /// Gets/sets whether the icon has a spin animation. /// public bool? Spin { get { return (bool?)GetValue(SpinProperty); } set { SetValue(SpinProperty, value); } } private static void OnSpinChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as Icon).SetSpinAnimation(); } #endregion #region Attached Propperties /// /// Why is it not a dependency property? /// icons are introduced by way of resources. if you define them as dependency properties, you will lose more flexibility. /// For example, each icon needs to use different stretch parameters. /// public static readonly DependencyProperty ViewBoxProperty = DependencyProperty.RegisterAttached("ViewBox", typeof(Rect), typeof(Icon), new PropertyMetadata(new Rect(0, 0, 1024, 1024)), OnViewBoxValidate); private static bool OnViewBoxValidate(object value) { var viewBox = (Rect)value; return viewBox.IsEmpty || (viewBox.Width >= 0 && viewBox.Height >= 0); } /// /// Gets the rectangular area of the geometric stretch. /// [AttachedPropertyBrowsableForType(typeof(Geometry))] public static Rect GetViewBox(DependencyObject obj) { return (Rect)obj.GetValue(ViewBoxProperty); } /// /// Sets the rectangular area of the geometric stretch. /// public static void SetViewBox(DependencyObject obj, Rect value) { obj.SetValue(ViewBoxProperty, value); } /// /// When you need colorful icons, you need to be able to support custom brushes. /// public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached("Fill", typeof(Brush), typeof(Icon), new PropertyMetadata(null)); /// /// Gets the brush that fill the geometry. /// /// /// [AttachedPropertyBrowsableForType(typeof(Geometry))] public static Brush GetFill(DependencyObject obj) { return (Brush)obj.GetValue(FillProperty); } /// /// Sets the brush to fill the geometry. Valid when Theme is colorful. /// public static void SetFill(DependencyObject obj, Brush value) { obj.SetValue(FillProperty, value); } #endregion #region Constructors static Icon() { DefaultStyleKeyProperty.OverrideMetadata(typeof(Icon), new FrameworkPropertyMetadata(typeof(Icon))); } public Icon() { Loaded += (s, e) => SetSpinAnimation(); } #endregion #region Overrides /// /// Notification that a specified property has been invalidated. /// /// EventArgs that contains the property, metadata, old value, and new value for this change protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) { if (e.NewValue != e.OldValue && (e.Property == TypeProperty || e.Property == ThemeProperty)) { // Reset definition geometry. definingGeometry = null; } base.OnPropertyChanged(e); } /// /// Updates DesiredSize of the icon. Called by parent UIElement during is the first pass of layout. /// /// Constraint size is an "upper limit" that should not exceed. /// icon desired size. protected override Size MeasureOverride(Size constraint) { return GetRenderSize(constraint, FontSize); } /// /// Compute the rendered geometry. /// /// /// protected override Size ArrangeOverride(Size finalSize) { return GetRenderSize(finalSize, FontSize); } /// /// Render callback. /// protected override void OnRender(DrawingContext dc) { Geometry rendered; var geometry = DefiningGeometry; Debug.Assert(geometry != null); var foreground = Foreground; var matrix = GetStretchMatrix(geometry, FontSize); // Need to use colorful render. if (geometry is GeometryGroup) { Brush brush; int index = 0; var isSolidColor = foreground is SolidColorBrush; var children = ((GeometryGroup)geometry).Children; foreach (var child in children) { rendered = GetRenderedGeometry(child, matrix); if (rendered != Geometry.Empty) { brush = rendered.GetValue(FillProperty) as Brush; // It may need to be tinted if (brush == null) { if (!isSolidColor || index == 0 || index == 6 || index > 9) { brush = foreground; }else { brush = new SolidColorBrush(ColorPalette.Toning(((SolidColorBrush)foreground).Color, index)); } index++; } dc.DrawGeometry(brush, null, rendered); } } } else { rendered = GetRenderedGeometry(geometry, matrix); if (rendered != Geometry.Empty) { dc.DrawGeometry(foreground, null, rendered); } } // Without background, the mouse can penetrate geometry and cause event failure. var background = Background; if (background != null) { dc.DrawRectangle(background, null, new Rect(0, 0, RenderSize.Width, RenderSize.Height)); } } #endregion #region Private Methods private void SetSpinAnimation() { var spin = Spin ?? Type == "loading"; if (spin) { this.BeginSpin(1d); } else { this.StopSpin(); } } private Size GetRenderSize(Size availableSize, double fontSize) { if (IsGeometryEmpty(DefiningGeometry)) { return new Size(0d, 0d); } return new Size(Math.Min(availableSize.Width, fontSize), Math.Min(availableSize.Height, fontSize)); } private bool IsGeometryEmpty(Geometry geometry) { return geometry.IsEmpty() || geometry.Bounds.IsEmpty; } /// /// Get the rendered geometry. /// private Geometry GetRenderedGeometry(Geometry geometry, Matrix matrix) { var rendered = geometry.CloneCurrentValue(); if (ReferenceEquals(geometry, rendered)) { rendered = rendered.Clone(); } var transform = rendered.Transform; if (transform == null) { rendered.Transform = new MatrixTransform(matrix); } else { rendered.Transform = new MatrixTransform(transform.Value * matrix); } return rendered; } /// /// Get the stretch matrix of the geometry. /// private Matrix GetStretchMatrix(Geometry geometry, double size) { var matrix = Matrix.Identity; if (!IsGeometryEmpty(geometry)) { double scaleX, scaleY; var viewBox = (Rect)geometry.GetValue(ViewBoxProperty); if (viewBox.IsEmpty) { viewBox = geometry.Bounds; scaleX = size / viewBox.Right; scaleY = size / viewBox.Bottom; if (scaleX > scaleY) { scaleX = scaleY; } else { scaleY = scaleX; } } else { scaleX = size / viewBox.Width; scaleY = size / viewBox.Height; matrix.Translate(-(scaleX * viewBox.X), -(scaleY * viewBox.Y)); } matrix.Scale(scaleX, scaleY); } return matrix; } #endregion } public enum IconTheme : byte { Filled, Outlined, Colorful } }