课表组件、天气组件全面升级。
This commit is contained in:
lincube
2026-03-03 15:09:49 +08:00
parent 2d09c1aca2
commit 478ed115a1
47 changed files with 4876 additions and 771 deletions

View File

@@ -0,0 +1,127 @@
using System;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Threading;
namespace LanMontainDesktop.Behaviors;
public class PanelIntroAnimationBehavior
{
public static readonly AttachedProperty<bool> IsEnabledProperty =
AvaloniaProperty.RegisterAttached<PanelIntroAnimationBehavior, Panel, bool>("IsEnabled");
public static readonly AttachedProperty<bool> IsAnimationPlayedProperty =
AvaloniaProperty.RegisterAttached<PanelIntroAnimationBehavior, Control, bool>("IsAnimationPlayed");
public static readonly AttachedProperty<bool> CanPlayAnimationProperty =
AvaloniaProperty.RegisterAttached<PanelIntroAnimationBehavior, Control, bool>("CanPlayAnimation");
private static readonly AttachedProperty<bool> IsAnimationStartedProperty =
AvaloniaProperty.RegisterAttached<PanelIntroAnimationBehavior, Panel, bool>("IsAnimationStarted");
static PanelIntroAnimationBehavior()
{
IsEnabledProperty.Changed.AddClassHandler<Panel>(OnIsEnabledChanged);
}
public static void SetIsEnabled(Panel panel, bool value)
{
panel.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(Panel panel)
{
return panel.GetValue(IsEnabledProperty);
}
public static void SetIsAnimationPlayed(Control control, bool value)
{
control.SetValue(IsAnimationPlayedProperty, value);
}
public static bool GetIsAnimationPlayed(Control control)
{
return control.GetValue(IsAnimationPlayedProperty);
}
public static void SetCanPlayAnimation(Control control, bool value)
{
control.SetValue(CanPlayAnimationProperty, value);
}
public static bool GetCanPlayAnimation(Control control)
{
return control.GetValue(CanPlayAnimationProperty);
}
private static bool GetIsAnimationStarted(Panel panel)
{
return panel.GetValue(IsAnimationStartedProperty);
}
private static void SetIsAnimationStarted(Panel panel, bool value)
{
panel.SetValue(IsAnimationStartedProperty, value);
}
private static void OnIsEnabledChanged(Panel panel, AvaloniaPropertyChangedEventArgs args)
{
if (args.NewValue is not bool isEnabled || !isEnabled || GetIsAnimationStarted(panel))
{
return;
}
panel.AttachedToVisualTree += OnPanelAttachedToVisualTree;
StartStaggerAnimation(panel);
}
private static void OnPanelAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is not Panel panel)
{
return;
}
StartStaggerAnimation(panel);
}
private static void StartStaggerAnimation(Panel panel)
{
if (!GetIsEnabled(panel) || GetIsAnimationStarted(panel))
{
return;
}
var targets = panel.Children
.OfType<Control>()
.Where(control => control.IsVisible)
.ToList();
foreach (var target in targets)
{
SetCanPlayAnimation(target, true);
SetIsAnimationPlayed(target, false);
}
SetIsAnimationStarted(panel, true);
var index = 0;
var timer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromMilliseconds(24)
};
timer.Tick += (_, _) =>
{
if (index >= targets.Count)
{
timer.Stop();
return;
}
SetIsAnimationPlayed(targets[index], true);
index++;
};
timer.Start();
}
}

View File

@@ -0,0 +1,136 @@
using System;
using Avalonia;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Rendering.Composition;
namespace LanMontainDesktop.Behaviors;
public class PopupIntroAnimationBehavior
{
public static readonly AttachedProperty<bool> IsEnabledProperty =
AvaloniaProperty.RegisterAttached<PopupIntroAnimationBehavior, Control, bool>("IsEnabled");
private static readonly AttachedProperty<bool> IsHookedProperty =
AvaloniaProperty.RegisterAttached<PopupIntroAnimationBehavior, Control, bool>("IsHooked");
static PopupIntroAnimationBehavior()
{
IsEnabledProperty.Changed.AddClassHandler<Control>(OnIsEnabledChanged);
}
public static void SetIsEnabled(Control control, bool value)
{
control.SetValue(IsEnabledProperty, value);
}
public static bool GetIsEnabled(Control control)
{
return control.GetValue(IsEnabledProperty);
}
private static bool GetIsHooked(Control control)
{
return control.GetValue(IsHookedProperty);
}
private static void SetIsHooked(Control control, bool value)
{
control.SetValue(IsHookedProperty, value);
}
private static void OnIsEnabledChanged(Control control, AvaloniaPropertyChangedEventArgs args)
{
if (args.NewValue is not bool isEnabled || !isEnabled || GetIsHooked(control))
{
return;
}
switch (control)
{
case PopupRoot popupRoot:
popupRoot.Opened += OnPopupOpened;
SetIsHooked(popupRoot, true);
break;
case OverlayPopupHost overlayPopupHost:
overlayPopupHost.AttachedToVisualTree += OnOverlayPopupHostAttached;
SetIsHooked(overlayPopupHost, true);
break;
}
}
private static void OnPopupOpened(object? sender, EventArgs e)
{
if (sender is Control control)
{
PlayIntroAnimation(control);
}
}
private static void OnOverlayPopupHostAttached(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is Control control)
{
PlayIntroAnimation(control);
}
}
private static void PlayIntroAnimation(Control control)
{
var compositionVisual = ElementComposition.GetElementVisual(control);
if (compositionVisual is null)
{
return;
}
var popup = control.Parent as Popup;
compositionVisual.CenterPoint = ResolveCenterPoint(
popup?.Placement ?? PlacementMode.Pointer,
control.Bounds.Size,
compositionVisual.CenterPoint);
var compositor = compositionVisual.Compositor;
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
opacityAnimation.Target = nameof(compositionVisual.Opacity);
opacityAnimation.Duration = TimeSpan.FromMilliseconds(160);
opacityAnimation.InsertKeyFrame(0f, 0f);
opacityAnimation.InsertKeyFrame(1f, 1f, Easing.Parse("0.22, 1, 0.36, 1"));
compositionVisual.StartAnimation(nameof(compositionVisual.Opacity), opacityAnimation);
var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
scaleAnimation.Target = nameof(compositionVisual.Scale);
scaleAnimation.Duration = TimeSpan.FromMilliseconds(160);
scaleAnimation.InsertKeyFrame(0f, compositionVisual.Scale with { X = 0.94, Y = 0.94 });
scaleAnimation.InsertKeyFrame(1f, compositionVisual.Scale with { X = 1, Y = 1 }, Easing.Parse("0.22, 1, 0.36, 1"));
compositionVisual.StartAnimation(nameof(compositionVisual.Scale), scaleAnimation);
}
private static Vector3D ResolveCenterPoint(PlacementMode placement, Size size, Vector3D fallback)
{
var relative = placement switch
{
PlacementMode.Bottom => new RelativePoint(0.5, 0.0, RelativeUnit.Relative),
PlacementMode.Left => new RelativePoint(1.0, 0.5, RelativeUnit.Relative),
PlacementMode.Right => new RelativePoint(0.0, 0.5, RelativeUnit.Relative),
PlacementMode.Top => new RelativePoint(0.5, 1.0, RelativeUnit.Relative),
PlacementMode.Pointer => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
PlacementMode.BottomEdgeAlignedLeft => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
PlacementMode.BottomEdgeAlignedRight => new RelativePoint(1.0, 0.0, RelativeUnit.Relative),
PlacementMode.LeftEdgeAlignedTop => new RelativePoint(1.0, 1.0, RelativeUnit.Relative),
PlacementMode.LeftEdgeAlignedBottom => new RelativePoint(1.0, 0.0, RelativeUnit.Relative),
PlacementMode.RightEdgeAlignedTop => new RelativePoint(0.0, 1.0, RelativeUnit.Relative),
PlacementMode.RightEdgeAlignedBottom => new RelativePoint(0.0, 0.0, RelativeUnit.Relative),
PlacementMode.TopEdgeAlignedLeft => new RelativePoint(0.0, 1.0, RelativeUnit.Relative),
PlacementMode.TopEdgeAlignedRight => new RelativePoint(1.0, 1.0, RelativeUnit.Relative),
_ => new RelativePoint(0.5, 0.5, RelativeUnit.Relative)
};
return fallback with
{
X = size.Width * relative.Point.X,
Y = size.Height * relative.Point.Y
};
}
}