using System; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; namespace AntdWpf.Controls; /// /// To trigger an operation. /// [TemplatePart(Name = PART_Border, Type = typeof(FrameworkElement))] [TemplateVisualState(Name = "Loaded", GroupName = "LoadStates")] [TemplateVisualState(Name = "Unloaded", GroupName = "LoadStates")] public class AntButton : Button { #region Fields private const string PART_Border = "PART_Border"; private FrameworkElement border; private VisualState mouseOverState; private VisualState pressedState; private VisualState focusedState; #endregion #region Properties public static readonly DependencyProperty GhostProperty = DependencyProperty.Register("Ghost", typeof(bool), typeof(AntButton), new PropertyMetadata(false, OnEffectBrushChanged)); /// /// Gets/sets whether to make the background transparent and invert text and border colors. /// public bool Ghost { get { return (bool)GetValue(GhostProperty); } set { SetValue(GhostProperty, value); } } public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(string), typeof(AntButton), new PropertyMetadata(null)); /// /// Gets/sets the icon type of the button. /// public string Icon { get { return (string)GetValue(IconProperty); } set { SetValue(IconProperty, value); } } public static readonly DependencyProperty LoadingProperty = DependencyProperty.Register("Loading", typeof(bool), typeof(AntButton), new PropertyMetadata(false, OnLoadingChanged)); /// /// Gets/sets the loading state of the button. /// public bool Loading { get { return (bool)GetValue(LoadingProperty); } set { SetValue(LoadingProperty, value); } } private static void OnLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as AntButton).SetLoadVisualState(); } private void SetLoadVisualState() { VisualStateManager.GoToState(this, (Loading ? "Loaded" : "Unloaded"), true); } public static readonly DependencyProperty ShapeProperty = DependencyProperty.Register("Shape", typeof(Shapes), typeof(AntButton), new PropertyMetadata(Shapes.Square)); /// /// Gets/sets the shape of button. /// public Shapes Shape { get { return (Shapes)GetValue(ShapeProperty); } set { SetValue(ShapeProperty, value); } } public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Sizes?), typeof(AntButton), new PropertyMetadata(null)); /// /// Gets/sets the size of the button. /// public Sizes? Size { get { return (Sizes?)GetValue(SizeProperty); } set { SetValue(SizeProperty, value); } } public static readonly DependencyProperty TypeProperty = DependencyProperty.Register("Type", typeof(ButtonType?), typeof(AntButton), new PropertyMetadata(null)); /// /// Gets/sets the type of the button. /// public ButtonType? Type { get { return (ButtonType?)GetValue(TypeProperty); } set { SetValue(TypeProperty, value); } } public static readonly DependencyProperty EffectBrushProperty = DependencyProperty.Register( "EffectBrush", typeof(Brush), typeof(AntButton), new FrameworkPropertyMetadata( Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.Inherits, OnEffectBrushChanged)); /// /// Gets/sets the border effect brush of the button. /// public Brush EffectBrush { get { return (Brush)GetValue(EffectBrushProperty); } set { SetValue(EffectBrushProperty, value); } } private static void OnEffectBrushChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as AntButton).SetVisualStateAnimation(); } /// /// Force background transparent in Ghost state. /// private static object OnBackgroundCoerceValue(DependencyObject d, object baseValue) { var button = d as AntButton; if (button.Ghost) { return Brushes.Transparent; } return baseValue; } #endregion #region Constructors static AntButton() { DefaultStyleKeyProperty.OverrideMetadata(typeof(AntButton), new FrameworkPropertyMetadata(typeof(AntButton))); BackgroundProperty.OverrideMetadata(typeof(AntButton), new FrameworkPropertyMetadata { CoerceValueCallback = OnBackgroundCoerceValue }); } #endregion #region Overrides public override void OnApplyTemplate() { base.OnApplyTemplate(); border = GetTemplateChild(PART_Border) as FrameworkElement; mouseOverState = GetTemplateChild("MouseOver") as VisualState; focusedState = GetTemplateChild("Focused") as VisualState; pressedState = GetTemplateChild("Pressed") as VisualState; SetVisualStateAnimation(); SetLoadVisualState(); } #endregion #region Private Methods private void SetVisualStateAnimation() { // No initialization or no need for me to handle. if (border == null || mouseOverState == null && focusedState == null && pressedState == null) return; // Unable to extract color. if (EffectBrush is SolidColorBrush brush) { var isShape = border is Shape; Func func; if (!Type.HasValue || Type.Value == ButtonType.Dashed) { func = CreateDefaultStoryboard; } else if (Type.Value == ButtonType.Primary) { func = CreatePrimaryStoryboard; } else { // Danger func = CreateDangerStoryboard; } if (mouseOverState != null) { mouseOverState.Storyboard = func(this, brush.Color, 5, isShape, false, null); } if (focusedState != null) { focusedState.Storyboard = func(this, brush.Color, 5, isShape, true, null); } if (pressedState != null) { pressedState.Storyboard = func(this, brush.Color, 7, isShape, false, TimeSpan.FromSeconds(0)); } } } private static Storyboard CreateDefaultStoryboard(AntButton button, Color color, int index, bool IsShape, bool focused, Duration? duration = null) { var storyboard = new Storyboard(); var children = storyboard.Children; color = ColorPalette.Toning(color, index); children.Add(CreateForegroundAnimation(button, color, duration)); children.Add(CreateBorderAnimation(PART_Border, color, IsShape, duration)); return storyboard; } private static Storyboard CreatePrimaryStoryboard(AntButton button, Color color, int index, bool IsShape, bool focused, Duration? duration = null) { var storyboard = new Storyboard(); var children = storyboard.Children; color = ColorPalette.Toning(color, index); if (button.Ghost) { children.Add(CreateForegroundAnimation(button, color, duration)); } else { children.Add(CreateBackgroundAnimation(PART_Border, color, IsShape, duration)); } children.Add(CreateBorderAnimation(PART_Border, color, IsShape, duration)); return storyboard; } private static Storyboard CreateDangerStoryboard(AntButton button, Color color, int index, bool IsShape, bool focused, Duration? duration = null) { var storyboard = new Storyboard(); var children = storyboard.Children; Color foreground; color = ColorPalette.Toning(color, index); if (button.Ghost) { foreground = color; } else { Color background; if (focused) { foreground = color; background = Colors.White; } else { foreground = Colors.White; background = color; } children.Add(CreateBackgroundAnimation(PART_Border, background, IsShape, duration)); } children.Add(CreateForegroundAnimation(button, foreground, duration)); children.Add(CreateBorderAnimation(PART_Border, color, IsShape, duration)); return storyboard; } private static Timeline CreateForegroundAnimation(DependencyObject target, Color color, Duration? duration = null) { return CreateColorAnimation(target, "(Control.Foreground).(SolidColorBrush.Color)", color, duration); } private static Timeline CreateBackgroundAnimation(string target, Color color, bool IsShape, Duration? duration = null) { return CreateColorAnimation(target, (IsShape ? "Fill" : "Background") + ".Color", color, duration); } private static Timeline CreateBorderAnimation(string target, Color color, bool IsShape, Duration? duration = null) { return CreateColorAnimation(target, (IsShape ? "Stroke" : "BorderBrush") + ".Color", color, duration); } private static Timeline CreateColorAnimation(object target, string path, Color color, Duration? duration) { var animation = new ColorAnimation() { To = color }; if (duration.HasValue) { animation.Duration = duration.Value; } if (target is DependencyObject) { Storyboard.SetTarget(animation, (DependencyObject)target); } else { Storyboard.SetTargetName(animation, (string)target); } Storyboard.SetTargetProperty(animation, new PropertyPath(path)); return animation; } #endregion } public enum ButtonType : byte { Primary, Dashed, Danger }