2026-05-12 15:36:08 +08:00

461 lines
15 KiB
C#

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace Wpf_AiSportsMicrospace.MyUserControl
{
/// <summary>
/// CoverFlowControl.xaml 的交互逻辑
/// </summary>
public partial class CoverFlowControl : UserControl
{// 新增事件:进度条完成
public event Action<CoverFlowItem>? ProgressCompleted;
public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>();
private int _selectedIndex = 0;
private bool _isAnimating;
private int _pendingSlideSteps;
private const double CardWidth = 368;
private const double CardHeight = 534;
private const double CardGap = 30; // 卡片之间水平间距
private const double SlideDurationMs = 300; // 动画时长
private const double ProgressDurationMs = 2600;
public int SelectedIndex
{
get => _selectedIndex;
set
{
if (Images.Count == 0) return;
if (value < 0) _selectedIndex = Images.Count - 1;
else if (value >= Images.Count) _selectedIndex = 0;
else _selectedIndex = value;
UpdateSelectionFlags();
RefreshCards(instant: true);
// 启动进度条动画
//var current = Images[_selectedIndex];
//StartProgress(current);
}
}
public CoverFlowControl()
{
InitializeComponent();
DataContext = this;
Loaded += CoverFlowControl_Loaded;
//var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(5) };
//timer.Tick += (s, e) =>
//{
// if (Images.Count == 0) return;
// var current = Images[_selectedIndex];
// if (current.Progress < 1)
// current.Progress += 0.01; // 调整速度
//};
//timer.Start();
}
private EventHandler? _renderHandler;
private CoverFlowItem? _currentItem;
private DateTime _progressStartTime;
private void CoverFlowControl_Loaded(object sender, RoutedEventArgs e)
{
if (Images.Count > 0)
{
UpdateSelectionFlags();
RefreshCards(instant: true);
}
CarouselCanvas.Focus();
Keyboard.Focus(CarouselCanvas);
}
/// <summary>
/// 针对当前选中项启动进度条动画
/// </summary>
public void StartSelectedProgress()
{
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
{
var current = Images[SelectedIndex];
if (current.Progress >= 1) return; // 已完成,直接返回,不再启动
StartProgress(current);
}
}
public void CancelSelectedProgress()
{
if (SelectedIndex >= 0 && SelectedIndex < Images.Count)
{
var current = Images[SelectedIndex];
// 停止动画/计时
StopProgress(resetProgress: true);
// 清零当前的进度值
current.Progress = 0;
}
}
public void StartProgress(CoverFlowItem item)
{
StopProgress(resetProgress: true);
_currentItem = item;
item.Progress = 0;
_progressStartTime = DateTime.UtcNow;
_renderHandler = (s, e) =>
{
if (_currentItem == null)
return;
var elapsedMs = (DateTime.UtcNow - _progressStartTime).TotalMilliseconds;
_currentItem.Progress = Math.Min(1, elapsedMs / ProgressDurationMs);
if (_currentItem.Progress >= 1)
{
_currentItem.Progress = 1;
ProgressCompleted?.Invoke(_currentItem);
// 停止动画
StopProgress(resetProgress: false);
}
};
CompositionTarget.Rendering += _renderHandler;
}
private void StopProgress(bool resetProgress = true)
{
if (_renderHandler != null)
{
CompositionTarget.Rendering -= _renderHandler;
_renderHandler = null;
}
if (_currentItem != null)
{
if (resetProgress)
_currentItem.Progress = 0;
_currentItem = null;
}
}
private void UpdateSelectionFlags()
{
for (int i = 0; i < Images.Count; i++)
Images[i].IsSelected = i == _selectedIndex;
}
private int WrapIndex(int index)
{
if (Images.Count == 0) return 0;
var m = index % Images.Count;
if (m < 0) m += Images.Count;
return m;
}
private double SlotDistance => CardWidth + CardGap;
private double XFarLeft => 0;
private double XLeft => SlotDistance;
private double XCenter => SlotDistance * 2;
private double XRight => SlotDistance * 3;
private double XOffLeft => -SlotDistance;
private double XOffRight => SlotDistance * 4;
private void SetCard(ContentPresenter card, CoverFlowItem? item, double left, double opacity = 1)
{
card.Content = item;
Canvas.SetLeft(card, left);
Canvas.SetTop(card, 0);
card.Opacity = opacity;
card.CacheMode ??= new BitmapCache();
EnsureTranslateTransform(card).X = 0;
}
private static TranslateTransform EnsureTranslateTransform(FrameworkElement element)
{
if (element.RenderTransform is TranslateTransform transform)
return transform;
transform = new TranslateTransform();
element.RenderTransform = transform;
return transform;
}
private void RefreshCards(bool instant)
{
if (Images.Count == 0) return;
var farLeft = Images[WrapIndex(_selectedIndex - 2)];
var left = Images[WrapIndex(_selectedIndex - 1)];
var center = Images[WrapIndex(_selectedIndex)];
var right = Images[WrapIndex(_selectedIndex + 1)];
SetCard(CardFarLeft, farLeft, XFarLeft, 1);
SetCard(CardLeft, left, XLeft, 1);
SetCard(CardCenter, center, XCenter, 1);
SetCard(CardRight, right, XRight, 1);
// spare stays hidden offscreen
SetCard(CardSpare, null, XOffRight, 0);
if (!instant)
return;
}
private void Carousel_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if (e.Delta < 0) SlideLeft();
else if (e.Delta > 0) SlideRight();
e.Handled = true;
}
private void Carousel_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Right || e.Key == Key.Down)
{
SlideLeft();
e.Handled = true;
}
else if (e.Key == Key.Left || e.Key == Key.Up)
{
SlideRight();
e.Handled = true;
}
}
private void CardLeft_Click(object sender, MouseButtonEventArgs e)
{
RequestSlide(-1);
}
private void CardRight_Click(object sender, MouseButtonEventArgs e)
{
RequestSlide(+1);
}
private void CardFarLeft_Click(object sender, MouseButtonEventArgs e)
{
RequestSlide(-2);
}
private void CardCenter_Click(object sender, MouseButtonEventArgs e)
{
// center click: treat as selecting current (already selected)
}
private void RequestSlide(int steps)
{
if (Images.Count == 0) return;
if (steps == 0) return;
_pendingSlideSteps += steps;
TryRunNextSlide();
}
private void TryRunNextSlide()
{
if (_isAnimating) return;
if (_pendingSlideSteps == 0) return;
if (_pendingSlideSteps > 0)
{
_pendingSlideSteps--;
AnimateLeftOne();
}
else
{
_pendingSlideSteps++;
AnimateRightOne();
}
}
private void AnimateLeftOne()
{
if (_isAnimating) return;
if (Images.Count == 0) return;
_isAnimating = true;
StopProgress();
var newSelected = WrapIndex(_selectedIndex + 1);
var incomingRight = Images[WrapIndex(newSelected + 1)];
// Prepare spare entering from the right (to right slot)
SetCard(CardSpare, incomingRight, XOffRight, 1);
var sb = new Storyboard { FillBehavior = FillBehavior.Stop };
var ease = new CubicEase { EasingMode = EasingMode.EaseOut };
var dur = TimeSpan.FromMilliseconds(SlideDurationMs);
void AddMove(FrameworkElement fe, double toX)
{
var fromX = Canvas.GetLeft(fe);
EnsureTranslateTransform(fe);
var anim = new DoubleAnimation
{
From = 0,
To = toX - fromX,
Duration = new Duration(dur),
EasingFunction = ease
};
Storyboard.SetTarget(anim, fe);
Storyboard.SetTargetProperty(anim, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
sb.Children.Add(anim);
}
void AddFade(FrameworkElement fe, double toOpacity)
{
var anim = new DoubleAnimation
{
To = toOpacity,
Duration = new Duration(dur),
EasingFunction = ease
};
Storyboard.SetTarget(anim, fe);
Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity"));
sb.Children.Add(anim);
}
// Far-left card slides out to left and fades
AddMove(CardFarLeft, XOffLeft);
AddFade(CardFarLeft, 0);
// Shift others left by one slot, spare enters to right
AddMove(CardLeft, XFarLeft);
AddMove(CardCenter, XLeft);
AddMove(CardRight, XCenter);
AddMove(CardSpare, XRight);
sb.Completed += (_, _) =>
{
_selectedIndex = newSelected;
UpdateSelectionFlags();
var oldFarLeftContent = (CoverFlowItem?)CardFarLeft.Content;
var oldLeftContent = (CoverFlowItem?)CardLeft.Content;
var oldCenterContent = (CoverFlowItem?)CardCenter.Content;
var oldRightContent = (CoverFlowItem?)CardRight.Content;
var oldSpareContent = (CoverFlowItem?)CardSpare.Content;
SetCard(CardFarLeft, oldLeftContent, XFarLeft, 1);
SetCard(CardLeft, oldCenterContent, XLeft, 1);
SetCard(CardCenter, oldRightContent, XCenter, 1);
SetCard(CardRight, oldSpareContent, XRight, 1);
SetCard(CardSpare, null, XOffRight, 0);
_isAnimating = false;
TryRunNextSlide();
};
sb.Begin(this, true);
}
private void AnimateRightOne()
{
if (_isAnimating) return;
if (Images.Count == 0) return;
_isAnimating = true;
StopProgress();
var newSelected = WrapIndex(_selectedIndex - 1);
var incomingFarLeft = Images[WrapIndex(newSelected - 2)];
// Prepare spare entering from the left (to far-left slot)
SetCard(CardSpare, incomingFarLeft, XOffLeft, 1);
var sb = new Storyboard { FillBehavior = FillBehavior.Stop };
var ease = new CubicEase { EasingMode = EasingMode.EaseOut };
var dur = TimeSpan.FromMilliseconds(SlideDurationMs);
void AddMove(FrameworkElement fe, double toX)
{
var fromX = Canvas.GetLeft(fe);
EnsureTranslateTransform(fe);
var anim = new DoubleAnimation
{
From = 0,
To = toX - fromX,
Duration = new Duration(dur),
EasingFunction = ease
};
Storyboard.SetTarget(anim, fe);
Storyboard.SetTargetProperty(anim, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
sb.Children.Add(anim);
}
void AddFade(FrameworkElement fe, double toOpacity)
{
var anim = new DoubleAnimation
{
To = toOpacity,
Duration = new Duration(dur),
EasingFunction = ease
};
Storyboard.SetTarget(anim, fe);
Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity"));
sb.Children.Add(anim);
}
// Right card slides out to right and fades
AddMove(CardRight, XOffRight);
AddFade(CardRight, 0);
// Shift others right by one slot, spare enters to far-left
AddMove(CardCenter, XRight);
AddMove(CardLeft, XCenter);
AddMove(CardFarLeft, XLeft);
AddMove(CardSpare, XFarLeft);
sb.Completed += (_, _) =>
{
_selectedIndex = newSelected;
UpdateSelectionFlags();
var oldFarLeftContent = (CoverFlowItem?)CardFarLeft.Content;
var oldLeftContent = (CoverFlowItem?)CardLeft.Content;
var oldCenterContent = (CoverFlowItem?)CardCenter.Content;
var oldRightContent = (CoverFlowItem?)CardRight.Content;
var oldSpareContent = (CoverFlowItem?)CardSpare.Content;
SetCard(CardRight, oldCenterContent, XRight, 1);
SetCard(CardCenter, oldLeftContent, XCenter, 1);
SetCard(CardLeft, oldFarLeftContent, XLeft, 1);
SetCard(CardFarLeft, oldSpareContent, XFarLeft, 1);
SetCard(CardSpare, null, XOffRight, 0);
_isAnimating = false;
TryRunNextSlide();
};
sb.Begin(this, true);
}
public void SlideLeft()
{
RequestSlide(+1);
}
public void SlideRight()
{
RequestSlide(-1);
}
}
}