455 lines
15 KiB
C#
455 lines
15 KiB
C#
using System.Windows;
|
||||
|
|
using System.Windows.Controls;
|
|||
|
|
using System.Windows.Input;
|
|||
|
|
using System.Windows.Media;
|
|||
|
|
using System.Windows.Shapes;
|
|||
|
|
|
|||
|
|
namespace PCL;
|
|||
|
|
|
|||
|
|
public partial class MyIconButton
|
|||
|
|
{
|
|||
|
|
public delegate void ClickEventHandler(object sender, EventArgs e);
|
|||
|
|
|
|||
|
|
public enum Themes
|
|||
|
|
{
|
|||
|
|
Color,
|
|||
|
|
White,
|
|||
|
|
Black,
|
|||
|
|
Red,
|
|||
|
|
Custom
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 务必放在 IsMouseDown 更新之后
|
|||
|
|
private const int animationColorIn = 120;
|
|||
|
|
private const int animationColorOut = 150;
|
|||
|
|
|
|||
|
|
//鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
|
|||
|
|
private bool isMouseDown;
|
|||
|
|
|
|||
|
|
// 自定义属性
|
|||
|
|
|
|||
|
|
public int Uuid = ModBase.GetUuid();
|
|||
|
|
|
|||
|
|
protected override Size MeasureOverride(Size constraint)
|
|||
|
|
{
|
|||
|
|
var measured = base.MeasureOverride(constraint);
|
|||
|
|
if (double.IsNaN(Width) && !double.IsNaN(Height) && Height > 0D && !double.IsInfinity(Height))
|
|||
|
|
return new Size(Height, Height);
|
|||
|
|
return measured;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public MyIconButton()
|
|||
|
|
{
|
|||
|
|
InitializeComponent();
|
|||
|
|
|
|||
|
|
MouseLeftButtonUp += Button_MouseUp;
|
|||
|
|
MouseLeftButtonDown += Button_MouseDown;
|
|||
|
|
MouseLeftButtonUp += (_, _) => Button_MouseUp();
|
|||
|
|
MouseLeave += (_, _) => Button_MouseLeave();
|
|||
|
|
MouseEnter += (_, _) => RefreshAnim();
|
|||
|
|
MouseLeave += (_, _) => RefreshAnim();
|
|||
|
|
Loaded += (_, _) => RefreshAnim();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string Logo
|
|||
|
|
{
|
|||
|
|
get => Path.Data?.ToString() ?? string.Empty;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
if (Path is null) return;
|
|||
|
|
Path.Data = (Geometry)new GeometryConverter().ConvertFromString(value);
|
|||
|
|
SvgIconControlHelper.ApplyVisibility(Path, ShapeSvgIcon, IsUsingSvgIcon);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string SvgIcon
|
|||
|
|
{
|
|||
|
|
get;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
value ??= string.Empty;
|
|||
|
|
if (value == field)
|
|||
|
|
return;
|
|||
|
|
field = value;
|
|||
|
|
if (Path is null || ShapeSvgIcon is null)
|
|||
|
|
return;
|
|||
|
|
SvgIconControlHelper.ApplyIcon(Path, ShapeSvgIcon, field);
|
|||
|
|
ApplyLogoScale();
|
|||
|
|
RefreshAnim();
|
|||
|
|
}
|
|||
|
|
} = string.Empty;
|
|||
|
|
|
|||
|
|
private bool IsUsingSvgIcon => SvgIconControlHelper.HasSvgIcon(SvgIcon);
|
|||
|
|
|
|||
|
|
private double EffectiveLogoScale => IsUsingSvgIcon ? 1D : LogoScale;
|
|||
|
|
|
|||
|
|
private void ApplyLogoScale()
|
|||
|
|
{
|
|||
|
|
IconHost?.RenderTransform = new ScaleTransform { ScaleX = EffectiveLogoScale, ScaleY = EffectiveLogoScale };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public double LogoScale
|
|||
|
|
{
|
|||
|
|
get;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
field = value;
|
|||
|
|
ApplyLogoScale();
|
|||
|
|
}
|
|||
|
|
} = 1d;
|
|||
|
|
|
|||
|
|
public Themes Theme { get; set; } = Themes.Color;
|
|||
|
|
|
|||
|
|
public SolidColorBrush Foreground
|
|||
|
|
{
|
|||
|
|
get;
|
|||
|
|
set
|
|||
|
|
{
|
|||
|
|
field = value;
|
|||
|
|
ModAnimation.AniControlEnabled += 1;
|
|||
|
|
RefreshAnim();
|
|||
|
|
ModAnimation.AniControlEnabled -= 1;
|
|||
|
|
}
|
|||
|
|
} = new(Color.FromRgb(128, 128, 128));
|
|||
|
|
|
|||
|
|
private string ColorAnimationKey => "MyIconButton Color " + Uuid;
|
|||
|
|
|
|||
|
|
// 自定义事件
|
|||
|
|
public event ClickEventHandler? Click;
|
|||
|
|
|
|||
|
|
private static ModBase.MyColor GetTransparentBackground()
|
|||
|
|
{
|
|||
|
|
return new ModBase.MyColor(0d, 255d, 255d, 255d);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ModBase.MyColor? GetBaseFillColor()
|
|||
|
|
{
|
|||
|
|
return Theme switch
|
|||
|
|
{
|
|||
|
|
Themes.Red => new ModBase.MyColor(160d, 255d, 76d, 76d),
|
|||
|
|
Themes.Black => ThemeManager.IsDarkMode
|
|||
|
|
? new ModBase.MyColor(160d, 255d, 255d, 255d)
|
|||
|
|
: new ModBase.MyColor(160d, 0d, 0d, 0d),
|
|||
|
|
Themes.Custom => new ModBase.MyColor(160d, Foreground),
|
|||
|
|
_ => null
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void EnsureBaseBrushes()
|
|||
|
|
{
|
|||
|
|
PanBack.Background ??= GetTransparentBackground();
|
|||
|
|
var baseFill = GetBaseFillColor();
|
|||
|
|
if (baseFill is not null && !IsUsingSvgIcon)
|
|||
|
|
Path.Fill ??= baseFill;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AnimateActiveSvgIconBrush(string resourceKey, int duration)
|
|||
|
|
{
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
SvgIconControlHelper.AnimateSvgIconBrushTo(ShapeSvgIcon, resourceKey, duration, ColorAnimationKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void AnimateActiveSvgIconBrush(ModBase.MyColor color, int duration)
|
|||
|
|
{
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
SvgIconControlHelper.AnimateSvgIconBrushTo(ShapeSvgIcon, color, duration, ColorAnimationKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SetActiveIconResource(string resourceKey)
|
|||
|
|
{
|
|||
|
|
SvgIconControlHelper.SetIconResource(Path, ShapeSvgIcon, IsUsingSvgIcon, resourceKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void SetActiveIconBrush(Brush brush)
|
|||
|
|
{
|
|||
|
|
SvgIconControlHelper.SetIconBrush(Path, ShapeSvgIcon, IsUsingSvgIcon, brush);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private List<ModAnimation.AniData> GetHoverAnimations()
|
|||
|
|
{
|
|||
|
|
var animations = new List<ModAnimation.AniData>();
|
|||
|
|
switch (Theme)
|
|||
|
|
{
|
|||
|
|
case Themes.Color:
|
|||
|
|
{
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush("ColorBrush2", animationColorIn);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
"ColorBrush2",
|
|||
|
|
animationColorIn));
|
|||
|
|
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.White:
|
|||
|
|
{
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
PanBack,
|
|||
|
|
BackgroundProperty,
|
|||
|
|
new ModBase.MyColor(50d, 255d, 255d, 255d) - PanBack.Background,
|
|||
|
|
animationColorIn));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Red:
|
|||
|
|
{
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(
|
|||
|
|
new ModBase.MyColor(255d, 76d, 76d),
|
|||
|
|
animationColorIn);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
new ModBase.MyColor(255d, 76d, 76d) - Path.Fill,
|
|||
|
|
animationColorIn));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Black:
|
|||
|
|
{
|
|||
|
|
var blackHoverColor = ThemeManager.IsDarkMode
|
|||
|
|
? new ModBase.MyColor(230d, 255d, 255d, 255d)
|
|||
|
|
: new ModBase.MyColor(230d, 0d, 0d, 0d);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(blackHoverColor, animationColorIn);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
blackHoverColor - Path.Fill,
|
|||
|
|
animationColorIn));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Custom:
|
|||
|
|
{
|
|||
|
|
var customHoverColor = new ModBase.MyColor(255d, Foreground);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(customHoverColor, animationColorIn);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
customHoverColor - Path.Fill,
|
|||
|
|
animationColorIn));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return animations;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private List<ModAnimation.AniData> GetNormalAnimations()
|
|||
|
|
{
|
|||
|
|
var animations = new List<ModAnimation.AniData>();
|
|||
|
|
switch (Theme)
|
|||
|
|
{
|
|||
|
|
case Themes.Color:
|
|||
|
|
{
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush("ColorBrush4", animationColorOut);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
"ColorBrush4",
|
|||
|
|
animationColorOut));
|
|||
|
|
|
|||
|
|
PanBack.Background = GetTransparentBackground();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.White:
|
|||
|
|
{
|
|||
|
|
var whiteNormalColor = new ModBase.MyColor(234d, 242d, 254d);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(whiteNormalColor, animationColorOut);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
whiteNormalColor,
|
|||
|
|
animationColorOut));
|
|||
|
|
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
PanBack,
|
|||
|
|
BackgroundProperty,
|
|||
|
|
GetTransparentBackground() - PanBack.Background,
|
|||
|
|
animationColorOut));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Red:
|
|||
|
|
{
|
|||
|
|
var redNormalColor = new ModBase.MyColor(160d, 255d, 76d, 76d);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(redNormalColor, animationColorOut);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
redNormalColor - Path.Fill,
|
|||
|
|
animationColorOut));
|
|||
|
|
|
|||
|
|
PanBack.Background = GetTransparentBackground();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Black:
|
|||
|
|
{
|
|||
|
|
var blackNormalColor = ThemeManager.IsDarkMode
|
|||
|
|
? new ModBase.MyColor(160d, 255d, 255d, 255d)
|
|||
|
|
: new ModBase.MyColor(160d, 0d, 0d, 0d);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(blackNormalColor, animationColorOut);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
blackNormalColor - Path.Fill,
|
|||
|
|
animationColorOut));
|
|||
|
|
|
|||
|
|
PanBack.Background = GetTransparentBackground();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case Themes.Custom:
|
|||
|
|
{
|
|||
|
|
var customNormalColor = new ModBase.MyColor(160d, Foreground);
|
|||
|
|
if (IsUsingSvgIcon)
|
|||
|
|
AnimateActiveSvgIconBrush(customNormalColor, animationColorOut);
|
|||
|
|
else
|
|||
|
|
animations.Add(ModAnimation.AaColor(
|
|||
|
|
Path,
|
|||
|
|
Shape.FillProperty,
|
|||
|
|
customNormalColor - Path.Fill,
|
|||
|
|
animationColorOut));
|
|||
|
|
|
|||
|
|
PanBack.Background = GetTransparentBackground();
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return animations;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void ApplyNonAnimatedTheme()
|
|||
|
|
{
|
|||
|
|
switch (Theme)
|
|||
|
|
{
|
|||
|
|
case Themes.Color:
|
|||
|
|
SetActiveIconResource("ColorBrush4");
|
|||
|
|
break;
|
|||
|
|
case Themes.White:
|
|||
|
|
SetActiveIconBrush(new ModBase.MyColor(234d, 242d, 254d));
|
|||
|
|
break;
|
|||
|
|
case Themes.Red:
|
|||
|
|
SetActiveIconBrush(new ModBase.MyColor(160d, 255d, 76d, 76d));
|
|||
|
|
break;
|
|||
|
|
case Themes.Black:
|
|||
|
|
SetActiveIconBrush(ThemeManager.IsDarkMode
|
|||
|
|
? new ModBase.MyColor(160d, 255d, 255d, 255d)
|
|||
|
|
: new ModBase.MyColor(160d, 0d, 0d, 0d));
|
|||
|
|
break;
|
|||
|
|
case Themes.Custom:
|
|||
|
|
SetActiveIconBrush(new ModBase.MyColor(160d, Foreground));
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PanBack.Background = GetTransparentBackground();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Button_MouseUp(object sender, MouseButtonEventArgs e)
|
|||
|
|
{
|
|||
|
|
if (!isMouseDown)
|
|||
|
|
return;
|
|||
|
|
ModBase.Log("[Control] 按下图标按钮" + (string.IsNullOrEmpty(Name) ? "" : ":" + Name));
|
|||
|
|
Click?.Invoke(sender, e);
|
|||
|
|
e.Handled = true;
|
|||
|
|
Button_MouseUp();
|
|||
|
|
ModMain.RaiseCustomEvent(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Button_MouseDown(object sender, MouseButtonEventArgs e)
|
|||
|
|
{
|
|||
|
|
isMouseDown = true;
|
|||
|
|
Focus();
|
|||
|
|
// 指向
|
|||
|
|
ModAnimation.AniStart(
|
|||
|
|
ModAnimation.AaScaleTransform(PanBack, 0.8d - ((ScaleTransform)PanBack.RenderTransform).ScaleX,
|
|||
|
|
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Strong)),
|
|||
|
|
"MyIconButton Scale " + Uuid);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Button_MouseUp()
|
|||
|
|
{
|
|||
|
|
if (isMouseDown)
|
|||
|
|
{
|
|||
|
|
isMouseDown = false;
|
|||
|
|
ModAnimation.AniStart(
|
|||
|
|
new[]
|
|||
|
|
{
|
|||
|
|
ModAnimation.AaScaleTransform(PanBack, 1.05d - ((ScaleTransform)PanBack.RenderTransform).ScaleX,
|
|||
|
|
250, ease: new ModAnimation.AniEaseOutBack(ModAnimation.AniEasePower.Weak)),
|
|||
|
|
ModAnimation.AaScaleTransform(PanBack, -0.05d, 250,
|
|||
|
|
ease: new ModAnimation.AniEaseOutFluent(ModAnimation.AniEasePower.Strong))
|
|||
|
|
}, "MyIconButton Scale " + Uuid);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RefreshAnim(); // 直接刷新颜色以判断是否已触发 MouseLeave
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void Button_MouseLeave()
|
|||
|
|
{
|
|||
|
|
isMouseDown = false;
|
|||
|
|
ModAnimation.AniStart(
|
|||
|
|
new[]
|
|||
|
|
{
|
|||
|
|
ModAnimation.AaScaleTransform(PanBack, 1d - ((ScaleTransform)PanBack.RenderTransform).ScaleX, 250,
|
|||
|
|
ease: new ModAnimation.AniEaseOutFluent())
|
|||
|
|
}, "MyIconButton Scale " + Uuid);
|
|||
|
|
RefreshAnim(); // 直接刷新颜色以判断是否已触发 MouseLeave
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void RefreshAnim()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
if (ControlVisualHelpers.ShouldAnimate(this)) // 防止默认属性变更触发动画
|
|||
|
|
{
|
|||
|
|
EnsureBaseBrushes();
|
|||
|
|
ModAnimation.AniStart(IsMouseOver ? GetHoverAnimations() : GetNormalAnimations(), ColorAnimationKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
ModAnimation.AniStop(ColorAnimationKey);
|
|||
|
|
ApplyNonAnimatedTheme();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
ModBase.Log(ex, "刷新图标按钮动画状态出错");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static partial class ModAnimation
|
|||
|
|
{
|
|||
|
|
public static void AniDispose(MyIconButton control, bool removeFromChildren,
|
|||
|
|
ParameterizedThreadStart callBack = null)
|
|||
|
|
{
|
|||
|
|
if (!control.IsHitTestVisible)
|
|||
|
|
return;
|
|||
|
|
control.IsHitTestVisible = false;
|
|||
|
|
AniStart(new[]
|
|||
|
|
{
|
|||
|
|
AaScaleTransform(control, -1.5d, 200, ease: new AniEaseInFluent()),
|
|||
|
|
AaCode(() =>
|
|||
|
|
{
|
|||
|
|
if (removeFromChildren)
|
|||
|
|
((Panel)control.Parent).Children.Remove(control);
|
|||
|
|
else
|
|||
|
|
control.Visibility = Visibility.Collapsed;
|
|||
|
|
if (callBack is not null)
|
|||
|
|
callBack(control);
|
|||
|
|
}, after: true)
|
|||
|
|
}, "MyIconButton Dispose " + control.Uuid);
|
|||
|
|
}
|
|||
|
|
}
|