434 lines
14 KiB
C#
434 lines
14 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 = 380; // 动画时长
|
|
|
|
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 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();
|
|
|
|
// 清零当前的进度值
|
|
current.Progress = 0;
|
|
}
|
|
}
|
|
public void StartProgress(CoverFlowItem item)
|
|
{
|
|
StopProgress();
|
|
|
|
_currentItem = item;
|
|
item.Progress = 0;
|
|
|
|
_renderHandler = (s, e) =>
|
|
{
|
|
if (_currentItem.Progress < 1)
|
|
{
|
|
_currentItem.Progress += 0.007; // 控制速度
|
|
}
|
|
else
|
|
{
|
|
ProgressCompleted?.Invoke(_currentItem);
|
|
// 停止动画
|
|
StopProgress();
|
|
}
|
|
};
|
|
|
|
CompositionTarget.Rendering += _renderHandler;
|
|
}
|
|
|
|
private void StopProgress()
|
|
|
|
{
|
|
if (_renderHandler != null)
|
|
{
|
|
CompositionTarget.Rendering -= _renderHandler;
|
|
_renderHandler = null;
|
|
}
|
|
|
|
if (_currentItem != null)
|
|
{
|
|
_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;
|
|
}
|
|
|
|
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 anim = new DoubleAnimation
|
|
{
|
|
To = toX,
|
|
Duration = new Duration(dur),
|
|
EasingFunction = ease
|
|
};
|
|
Storyboard.SetTarget(anim, fe);
|
|
Storyboard.SetTargetProperty(anim, new PropertyPath("(Canvas.Left)"));
|
|
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 anim = new DoubleAnimation
|
|
{
|
|
To = toX,
|
|
Duration = new Duration(dur),
|
|
EasingFunction = ease
|
|
};
|
|
Storyboard.SetTarget(anim, fe);
|
|
Storyboard.SetTargetProperty(anim, new PropertyPath("(Canvas.Left)"));
|
|
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);
|
|
}
|
|
|
|
}
|
|
} |