Compare commits
5 Commits
0de1017593
...
4ef4da7781
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef4da7781 | ||
|
|
66d6b237d1 | ||
|
|
3d05aa48be | ||
|
|
06900606c8 | ||
|
|
d7cdcbe411 |
193
Wpf_AiSportsMicrospace/Common/SportOperate.cs
Normal file
@ -0,0 +1,193 @@
|
||||
using HandyControl.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Wpf_AiSportsMicrospace.Enum;
|
||||
using Yztob.AiSports.Inferences.Abstractions;
|
||||
using Yztob.AiSports.Inferences.Things;
|
||||
using Yztob.AiSports.Postures;
|
||||
using Yztob.AiSports.Postures.Abstractions;
|
||||
using Yztob.AiSports.Postures.Sports;
|
||||
using Yztob.AiSports.Postures.Things;
|
||||
using Yztob.AiSports.Sensors.Abstractions;
|
||||
using Yztob.AiSports.Sensors.Things;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// 运动检测操作类
|
||||
/// </summary>
|
||||
public class SportOperate
|
||||
{
|
||||
IPointTracker _leftTracker;
|
||||
IPointTracker _rightTracker;
|
||||
IPointTracker _leftElbow;
|
||||
IPointTracker _rightElbow;
|
||||
WebcamClient _webcamClient;
|
||||
|
||||
private DateTime _lastActionTime = DateTime.MinValue;
|
||||
|
||||
private Point? _lastLeftWrist = null;
|
||||
private Point? _lastRightWrist = null;
|
||||
private DateTime? _raiseStartTime = null;
|
||||
private DateTime? _wristStartTime = null;
|
||||
|
||||
|
||||
public SportOperate()
|
||||
{
|
||||
_leftTracker = PostureCalculate.CreatePointTracker("left_wrist", 0);
|
||||
_rightTracker = PostureCalculate.CreatePointTracker("right_wrist", 0);
|
||||
_leftElbow = PostureCalculate.CreatePointTracker("left_elbow", 0);
|
||||
_rightElbow = PostureCalculate.CreatePointTracker("right_elbow", 0);
|
||||
|
||||
//_leftTracker.Amplitude = 0.05f;
|
||||
//_rightTracker.Amplitude = 0.05f;
|
||||
//_leftElbow.Amplitude = 0.05f;
|
||||
//_rightElbow.Amplitude = 0.05f;
|
||||
}
|
||||
|
||||
public WebcamClient CreateRTSP()
|
||||
{
|
||||
_webcamClient = WebcamClient.CreateRTSP("192.168.3.64", "admin", "yd708090", 554u);
|
||||
return _webcamClient;
|
||||
}
|
||||
|
||||
public int VerifyWavingAction(Human human)
|
||||
{
|
||||
var leftWrist = human.Keypoints.FirstOrDefault(x => x.Name == "left_wrist");
|
||||
var leftElbow = human.Keypoints.FirstOrDefault(x => x.Name == "left_elbow");
|
||||
var rightWrist = human.Keypoints.FirstOrDefault(x => x.Name == "right_wrist");
|
||||
var rightElbow = human.Keypoints.FirstOrDefault(x => x.Name == "right_elbow");
|
||||
|
||||
// 左手逻辑
|
||||
if (leftWrist != null && leftElbow != null)
|
||||
{
|
||||
var result = RecognizeLeftHandGesture(
|
||||
new Point(leftWrist.X, leftWrist.Y),
|
||||
new Point(leftElbow.X, leftElbow.Y));
|
||||
if (result != 0) return result;
|
||||
}
|
||||
|
||||
// 右手逻辑
|
||||
if (rightWrist != null && rightElbow != null)
|
||||
{
|
||||
var result = RecognizeRightHandGesture(
|
||||
new Point(rightWrist.X, rightWrist.Y),
|
||||
new Point(rightElbow.X, rightElbow.Y));
|
||||
if (result != 0) return result;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一的水平挥手检测
|
||||
/// </summary>
|
||||
private int DetectHorizontalWave(Point wrist, Point elbow, Point? lastWrist, bool isLeft)
|
||||
{
|
||||
if (lastWrist != null)
|
||||
{
|
||||
double dx = wrist.X - lastWrist.Value.X;
|
||||
double dy = Math.Abs(wrist.Y - lastWrist.Value.Y);
|
||||
|
||||
// 挥手:水平位移明显,垂直位移小,且接近肘部水平
|
||||
if (Math.Abs(dx) > 30 && dy < 40 && Math.Abs(wrist.Y - elbow.Y) < 100)
|
||||
{
|
||||
if (CheckCooldown())
|
||||
{
|
||||
if (isLeft && dx > 0)
|
||||
return (int)WavingAction.LeftWave; // 左手往右挥
|
||||
if (!isLeft && dx < 0)
|
||||
return (int)WavingAction.RightWave; // 右手往左挥
|
||||
}
|
||||
}
|
||||
}
|
||||
return (int)WavingAction.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 识别左手动作
|
||||
/// </summary>
|
||||
public int RecognizeLeftHandGesture(Point wrist, Point elbow)
|
||||
{
|
||||
int result = DetectHorizontalWave(wrist, elbow, _lastLeftWrist, true);
|
||||
_lastLeftWrist = wrist; // 更新记录
|
||||
return result;
|
||||
}
|
||||
private bool _firstHandTriggered = false;
|
||||
|
||||
/// <summary>
|
||||
/// 识别右手动作
|
||||
/// </summary>
|
||||
/// <param name="wrist"></param>
|
||||
/// <param name="elbow"></param>
|
||||
/// <returns></returns>
|
||||
public int RecognizeRightHandGesture(Point wrist, Point elbow)
|
||||
{
|
||||
// --- 先判断水平挥手 ---
|
||||
int waveResult = DetectHorizontalWave(wrist, elbow, _lastRightWrist, false);
|
||||
_lastRightWrist = wrist; // 更新记录
|
||||
if (waveResult != (int)WavingAction.None)
|
||||
return waveResult;
|
||||
|
||||
// --- 举手逻辑 ---
|
||||
double verticalRise = elbow.Y - wrist.Y; // 手腕在肘上方 → 正值
|
||||
if (verticalRise > 100) // 举手阈值
|
||||
{
|
||||
// 初始化计时
|
||||
if (_raiseStartTime == null)
|
||||
_raiseStartTime = DateTime.Now;
|
||||
|
||||
if (_wristStartTime == null)
|
||||
_wristStartTime = DateTime.Now;
|
||||
|
||||
var wristDuration = DateTime.Now - _wristStartTime.Value;
|
||||
|
||||
// 保持 >1 秒才触发一次 FirstHand
|
||||
if (!_firstHandTriggered && wristDuration.TotalSeconds >= 1)
|
||||
{
|
||||
_firstHandTriggered = true;
|
||||
return (int)WavingAction.FirstHand; // 举手开始,只触发一次
|
||||
}
|
||||
|
||||
// 判断是否完成3秒举手
|
||||
var duration = DateTime.Now - _raiseStartTime.Value;
|
||||
if (duration.TotalSeconds >= 4)
|
||||
{
|
||||
_raiseStartTime = null;
|
||||
_wristStartTime = null;
|
||||
_firstHandTriggered = false; // 重置状态
|
||||
return (int)WavingAction.RaiseHand; // 举手完成
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)WavingAction.Raising; // 举手中
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 手放下,重置计时和状态
|
||||
_raiseStartTime = null;
|
||||
_wristStartTime = null;
|
||||
_firstHandTriggered = false;
|
||||
}
|
||||
|
||||
return (int)WavingAction.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冷却防抖(避免重复触发)
|
||||
/// </summary>
|
||||
private bool CheckCooldown(int cooldownMs = 1000)
|
||||
{
|
||||
if ((DateTime.Now - _lastActionTime).TotalMilliseconds < cooldownMs)
|
||||
return false;
|
||||
|
||||
_lastActionTime = DateTime.Now;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Wpf_AiSportsMicrospace/Enum/WavingAction.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.Enum
|
||||
{
|
||||
public enum WavingAction
|
||||
{
|
||||
None = 0,
|
||||
LeftWave = 1,
|
||||
RightWave = 2,
|
||||
FirstHand = 3, // 举手开始
|
||||
Raising = 4, // 举手中
|
||||
RaiseHand = 5 // 举手完成
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,8 +2,30 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"
|
||||
Title="Home" Height="600" Width="800" Loaded="Window_Loaded">
|
||||
<Grid>
|
||||
<local:CoverFlowControl1 x:Name="coverFlow" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
Title="Home" Height="1080" Width="1920" Loaded="Window_Loaded">
|
||||
<Grid Height="1065" VerticalAlignment="Bottom">
|
||||
<Grid.Background>
|
||||
<ImageBrush ImageSource="/Resources/Img/Album/home_bg.png" Stretch="UniformToFill"/>
|
||||
</Grid.Background>
|
||||
|
||||
<!-- 顶部图片 -->
|
||||
<Image
|
||||
Source="/Resources/Img/Album/title.png"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="615"
|
||||
Margin="0,100,0,0"
|
||||
/>
|
||||
|
||||
<!-- CoverFlowControl,距离图片80 -->
|
||||
<local:CoverFlowControl
|
||||
x:Name="coverFlow"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Height="900"
|
||||
Width="1920"
|
||||
Margin="0,250,0,0"
|
||||
Padding="0,100"
|
||||
/>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
||||
@ -4,6 +4,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -13,7 +14,9 @@ using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using Wpf_AiSportsMicrospace.Common;
|
||||
using Wpf_AiSportsMicrospace.Enum;
|
||||
using Wpf_AiSportsMicrospace.MyUserControl;
|
||||
using Wpf_AiSportsMicrospace.Views;
|
||||
using Yztob.AiSports.Inferences.Abstractions;
|
||||
using Yztob.AiSports.Postures;
|
||||
using Yztob.AiSports.Postures.Abstractions;
|
||||
@ -30,19 +33,10 @@ namespace Wpf_AiSportsMicrospace
|
||||
public partial class Home : Window
|
||||
{
|
||||
private IHumanPredictor _humanPredictor;
|
||||
private IObjectDetector _objectDetector;
|
||||
private HumanGraphicsRenderer _humanGraphicsRenderer;
|
||||
private readonly List<SportDescriptor> _sports;
|
||||
private SportBase _sport;
|
||||
private readonly SportDetectionQueue _detectQueue;
|
||||
private IPointTracker _leftTracker;
|
||||
private IPointTracker _rightTracker;
|
||||
private IPointTracker _leftElbow;
|
||||
private IPointTracker _rightElbow;
|
||||
private WebcamClient _webcamClient;
|
||||
private ConcurrentQueue<VideoFrame> _frameQueue = new();
|
||||
private CancellationTokenSource _cts = new();
|
||||
private DateTime _lastSlideTime = DateTime.MinValue;
|
||||
private SportOperate _sportOperate;
|
||||
|
||||
public Home()
|
||||
{
|
||||
@ -59,49 +53,73 @@ namespace Wpf_AiSportsMicrospace
|
||||
string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
|
||||
|
||||
// 转换为 Uri
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "1.jpg")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "2.jpg")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "3.jpg")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_play.png")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_test.png")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_history.png")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "4.jpg")));
|
||||
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "5.jpg")));
|
||||
|
||||
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "1.jpg")) });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "2.jpg")) });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "3.jpg")) });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "4.jpg")) });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "5.jpg")) });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_test.png")) , ProgressColor1 = "#215bc7" , ProgressColor2 = "#fc640e" });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_play.png")), ProgressColor1 = "#e73d42", ProgressColor2 = "#fd8212" });
|
||||
coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_history.png")), ProgressColor1 = "#e73d42", ProgressColor2 = "#215bc7" });
|
||||
|
||||
// 默认选中第3张
|
||||
coverFlow.SelectedIndex = 2;
|
||||
coverFlow.SelectedIndex = 0;
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_leftTracker = PostureCalculate.CreatePointTracker("left_wrist", 0);
|
||||
_rightTracker = PostureCalculate.CreatePointTracker("right_wrist", 0);
|
||||
_leftElbow = PostureCalculate.CreatePointTracker("left_elbow", 0);
|
||||
_rightElbow = PostureCalculate.CreatePointTracker("right_elbow", 0);
|
||||
_sportOperate = new SportOperate();
|
||||
_webcamClient = _sportOperate.CreateRTSP();
|
||||
|
||||
_leftTracker.Amplitude = 0.05f;
|
||||
_rightTracker.Amplitude = 0.05f;
|
||||
|
||||
_leftElbow.Amplitude = 0.05f;
|
||||
_rightElbow.Amplitude = 0.05f;
|
||||
|
||||
StartRTSP();
|
||||
StartFrameProcessing();
|
||||
}
|
||||
|
||||
private void StartRTSP()
|
||||
{
|
||||
_webcamClient = WebcamClient.CreateRTSP("192.168.3.64", "admin", "yd708090", 554u);
|
||||
_webcamClient.OnExtractFrame += frame =>
|
||||
{
|
||||
if (frame != null)
|
||||
_frameQueue.Enqueue(frame);
|
||||
};
|
||||
_webcamClient.StartExtract();
|
||||
|
||||
StartFrameProcessing();
|
||||
coverFlow.ProgressCompleted += CoverFlow_ProgressCompleted;
|
||||
}
|
||||
|
||||
private void CoverFlow_ProgressCompleted(CoverFlowItem item)
|
||||
{
|
||||
// 停止抽帧线程/释放资源
|
||||
try
|
||||
{
|
||||
_cts.Cancel(); // 停止后台处理线程
|
||||
_webcamClient?.StopExtract(); // 停止摄像头抽帧
|
||||
_webcamClient = null;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"停止抽帧异常: {ex.Message}");
|
||||
}
|
||||
|
||||
// 解绑事件,防止重复触发
|
||||
//coverFlow.ProgressCompleted -= CoverFlow_ProgressCompleted;
|
||||
|
||||
// 根据图片跳转新窗口
|
||||
string uri = item.ImageUri.ToString();
|
||||
Window newWindow = null;
|
||||
|
||||
if (uri.EndsWith("1.jpg"))
|
||||
newWindow = new GroupJumpRope();
|
||||
else if (uri.EndsWith("2.jpg"))
|
||||
newWindow = new GroupJumpRope();
|
||||
else if (uri.EndsWith("3.jpg"))
|
||||
newWindow = new GroupJumpRope();
|
||||
|
||||
if (newWindow != null)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
newWindow.Show(); // 先显示新窗口
|
||||
this.Close(); // 再关闭当前窗口
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void StartFrameProcessing()
|
||||
@ -116,6 +134,15 @@ namespace Wpf_AiSportsMicrospace
|
||||
}
|
||||
else
|
||||
{
|
||||
//_webcamClient.OnExtractFrame += frame =>
|
||||
//{
|
||||
// if (frame != null)
|
||||
// _frameQueue.Enqueue(frame);
|
||||
//};
|
||||
//_webcamClient.StartExtract();
|
||||
|
||||
|
||||
_webcamClient.StartExtract();
|
||||
Thread.Sleep(5);
|
||||
}
|
||||
}
|
||||
@ -128,55 +155,60 @@ namespace Wpf_AiSportsMicrospace
|
||||
{
|
||||
var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
|
||||
var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
|
||||
|
||||
var humans = humanResult?.Humans?.ToList();
|
||||
if (humans == null || humans.Count == 0) return;
|
||||
if (humans == null || humans.Count == 0)
|
||||
return;
|
||||
|
||||
//var human = humans
|
||||
// .Where(h =>
|
||||
// h.Keypoints.Any(kp => kp.Name == "left_ankle" && kp.X < 1020 && kp.Y > 900 && kp.Y < 1020) &&
|
||||
// h.Keypoints.Any(kp => kp.Name == "right_ankle" && kp.X > 750 && kp.Y > 900 && kp.Y < 1020)
|
||||
// )
|
||||
// .FirstOrDefault();
|
||||
|
||||
var human = humans.FirstOrDefault();
|
||||
|
||||
if (human == null) return;
|
||||
|
||||
var leftResult = _leftTracker.Tracking(human);
|
||||
var rightResult = _rightTracker.Tracking(human);
|
||||
var leftElbowResult = _leftElbow.Tracking(human);
|
||||
var rightElbowResult = _rightElbow.Tracking(human);
|
||||
//检测挥手动作
|
||||
var wavingaction = _sportOperate.VerifyWavingAction(human);
|
||||
|
||||
if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return;
|
||||
_lastSlideTime = DateTime.Now;
|
||||
// 把低 8 位作为动作类型,高 8 位作为进度
|
||||
int actionType = wavingaction & 0xFF;
|
||||
int progress = (wavingaction >> 8) & 0xFF;
|
||||
|
||||
if (leftResult != 0 && leftElbowResult != 1)
|
||||
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
||||
if (rightResult != 0 && rightElbowResult != 1)
|
||||
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
||||
switch (actionType)
|
||||
{
|
||||
case (int)WavingAction.LeftWave: // 左手挥动
|
||||
Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
|
||||
break;
|
||||
|
||||
case (int)WavingAction.RightWave: // 右手挥动
|
||||
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
|
||||
break;
|
||||
|
||||
case (int)WavingAction.FirstHand: // 举手开始
|
||||
Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
|
||||
break;
|
||||
|
||||
case (int)WavingAction.Raising: // 举手中,实时更新进度
|
||||
break;
|
||||
|
||||
case (int)WavingAction.RaiseHand: // 举手完成
|
||||
_cts.Cancel();
|
||||
break;
|
||||
|
||||
default: // 没有动作 → 取消进度
|
||||
Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("ProcessFrame error: " + ex.Message);
|
||||
Console.WriteLine("OnFrameExtracted error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
_cts.Cancel();
|
||||
_webcamClient?.StopExtract();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 更新 UI(线程安全)
|
||||
/// </summary>
|
||||
private void SlideCoverFlow(Action slideAction)
|
||||
{
|
||||
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
slideAction?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("UI update error: " + ex.Message);
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,8 +4,12 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"
|
||||
Height="300" Width="600">
|
||||
<Grid ClipToBounds="True" Background="Transparent">
|
||||
Width="1080">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<local:ProgressToRectangleGeometryConverter x:Key="ProgressToRectangleGeometryConverter"/>
|
||||
</UserControl.Resources>
|
||||
<Grid ClipToBounds="False" Background="Transparent">
|
||||
<ItemsControl x:Name="ItemsHost" ItemsSource="{Binding Images, RelativeSource={RelativeSource AncestorType=UserControl}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
@ -15,15 +19,36 @@
|
||||
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border x:Name="ImageContainer" RenderTransformOrigin="0.5,0.5"
|
||||
MouseLeftButtonDown="Image_MouseLeftButtonDown">
|
||||
<Border x:Name="ImageContainer" RenderTransformOrigin="0.5,0.5" CornerRadius="28"
|
||||
MouseLeftButtonDown="Image_MouseLeftButtonDown" Background="#fff">
|
||||
<Border.RenderTransform>
|
||||
<TransformGroup>
|
||||
<ScaleTransform x:Name="scale" ScaleX="1" ScaleY="1"/>
|
||||
<TranslateTransform x:Name="translate" X="0" Y="0"/>
|
||||
</TransformGroup>
|
||||
</Border.RenderTransform>
|
||||
<Image Source="{Binding}" Stretch="UniformToFill" Width="150" Height="200"/>
|
||||
<Grid ClipToBounds="False">
|
||||
|
||||
<Grid Width="372" Height="456" Background="Transparent" >
|
||||
<Image Source="{Binding ImageUri}" Stretch="UniformToFill" Width="346" Height="430">
|
||||
<Image.RenderTransform>
|
||||
<TranslateTransform X="0" Y="0"/>
|
||||
</Image.RenderTransform>
|
||||
</Image>
|
||||
</Grid>
|
||||
|
||||
<!-- 矩形进度条 Stroke="{Binding ProgressColor}" -->
|
||||
|
||||
<Path StrokeThickness="13" Visibility="{Binding IsSelected, Converter={StaticResource BoolToVisibilityConverter}}" StrokeStartLineCap="Round" StrokeEndLineCap="Round"
|
||||
Data="{Binding Progress, Converter={StaticResource ProgressToRectangleGeometryConverter}, ConverterParameter='372,456,22,13'}">
|
||||
<Path.Stroke>
|
||||
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
|
||||
<GradientStop Offset="0" Color="{Binding ProgressColor1}"/>
|
||||
<GradientStop Offset="0.9" Color="{Binding ProgressColor2}"/>
|
||||
</LinearGradientBrush>
|
||||
</Path.Stroke>
|
||||
</Path>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
|
||||
@ -16,6 +16,7 @@ using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
{
|
||||
@ -24,7 +25,28 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
/// </summary>
|
||||
public partial class CoverFlowControl : UserControl
|
||||
{
|
||||
public ObservableCollection<Uri> Images { get; set; } = new ObservableCollection<Uri>();
|
||||
//public ObservableCollection<Uri> Images { get; set; } = new ObservableCollection<Uri>();
|
||||
public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>();
|
||||
|
||||
// 新增事件:进度条完成
|
||||
public event Action<CoverFlowItem> ProgressCompleted;
|
||||
|
||||
// 添加附加属性帮助类
|
||||
public static class LayoutHelper
|
||||
{
|
||||
public static int GetZIndex(DependencyObject obj)
|
||||
{
|
||||
return (int)obj.GetValue(ZIndexProperty);
|
||||
}
|
||||
|
||||
public static void SetZIndex(DependencyObject obj, int value)
|
||||
{
|
||||
obj.SetValue(ZIndexProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ZIndexProperty =
|
||||
DependencyProperty.RegisterAttached("ZIndex", typeof(int), typeof(LayoutHelper), new PropertyMetadata(0));
|
||||
}
|
||||
|
||||
private int _selectedIndex = 0;
|
||||
public int SelectedIndex
|
||||
@ -34,23 +56,112 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
{
|
||||
if (Images.Count == 0) return;
|
||||
|
||||
// 循环处理
|
||||
if (value < 0)
|
||||
_selectedIndex = Images.Count - 1;
|
||||
else if (value >= Images.Count)
|
||||
_selectedIndex = 0;
|
||||
else
|
||||
_selectedIndex = value;
|
||||
if (value < 0) _selectedIndex = Images.Count - 1;
|
||||
else if (value >= Images.Count) _selectedIndex = 0;
|
||||
else _selectedIndex = value;
|
||||
|
||||
|
||||
for (int i = 0; i < Images.Count; i++)
|
||||
{
|
||||
Images[i].IsSelected = i == _selectedIndex;
|
||||
//if (SelectedIndex == 0)
|
||||
//{
|
||||
// Images[i].ProgressColor = (Brush)new BrushConverter().ConvertFromString("#e73d42")!;
|
||||
//}
|
||||
//else if (SelectedIndex == Images.Count - 1)
|
||||
//{
|
||||
// Images[i].ProgressColor = (Brush)new BrushConverter().ConvertFromString("#215bc7")!;
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// Images[i].ProgressColor = (Brush)new BrushConverter().ConvertFromString("#fc640e")!;
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
UpdateLayoutWithAnimation();
|
||||
|
||||
// 启动进度条动画
|
||||
//var current = Images[_selectedIndex];
|
||||
//StartProgress(current);
|
||||
}
|
||||
}
|
||||
|
||||
private EventHandler _renderHandler;
|
||||
|
||||
private CoverFlowItem _currentItem;
|
||||
|
||||
private void StartProgress(CoverFlowItem item)
|
||||
{
|
||||
StopProgress();
|
||||
|
||||
_currentItem = item;
|
||||
_currentItem.Progress = 0;
|
||||
|
||||
_renderHandler = (s, e) =>
|
||||
{
|
||||
if (_currentItem.Progress < 1)
|
||||
{
|
||||
_currentItem.Progress += 1.0 / (3 * 60.0); // 3秒完成一圈,假设60帧/s
|
||||
if (_currentItem.Progress > 1) _currentItem.Progress = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
StopProgress();
|
||||
}
|
||||
};
|
||||
|
||||
CompositionTarget.Rendering += _renderHandler;
|
||||
}
|
||||
|
||||
private void StopProgress()
|
||||
{
|
||||
if (_renderHandler != null)
|
||||
{
|
||||
CompositionTarget.Rendering -= _renderHandler;
|
||||
_renderHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
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 CoverFlowControl()
|
||||
{
|
||||
|
||||
InitializeComponent();
|
||||
DataContext = this;
|
||||
Loaded += (s, e) => UpdateLayoutWithAnimation(true);
|
||||
|
||||
//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 void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
@ -69,9 +180,9 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
private void UpdateLayoutWithAnimation(bool instant = false)
|
||||
{
|
||||
double centerX = ActualWidth / 2;
|
||||
double spacing = 180;
|
||||
double sideScale = 0.8;
|
||||
double centerScale = 1.2;
|
||||
double spacing = 500;
|
||||
double sideScale = 1;
|
||||
double centerScale = 1.33;
|
||||
|
||||
for (int i = 0; i < ItemsHost.Items.Count; i++)
|
||||
{
|
||||
@ -82,7 +193,7 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
if (border == null) continue;
|
||||
|
||||
var transformGroup = border.RenderTransform as TransformGroup;
|
||||
var scale = transformGroup.Children[0] as ScaleTransform;
|
||||
var scale = transformGroup!.Children[0] as ScaleTransform;
|
||||
var translate = transformGroup.Children[1] as TranslateTransform;
|
||||
|
||||
double targetX;
|
||||
@ -91,48 +202,62 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
|
||||
if (i == SelectedIndex)
|
||||
{
|
||||
targetX = centerX - 75;
|
||||
targetX = centerX - 190;
|
||||
targetScale = centerScale;
|
||||
targetOpacity = 1.0;
|
||||
}
|
||||
else if (i == SelectedIndex - 1 || (SelectedIndex == 0 && i == Images.Count - 1))
|
||||
{
|
||||
// 左边图片,循环处理
|
||||
targetX = centerX - spacing - 75;
|
||||
targetX = centerX - spacing - 190;
|
||||
targetScale = sideScale;
|
||||
targetOpacity = 1.0;
|
||||
}
|
||||
else if (i == SelectedIndex + 1 || (SelectedIndex == Images.Count - 1 && i == 0))
|
||||
{
|
||||
// 右边图片,循环处理
|
||||
targetX = centerX + spacing - 75;
|
||||
targetX = centerX + spacing - 190;
|
||||
targetScale = sideScale;
|
||||
targetOpacity = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetX = centerX - 75;
|
||||
targetX = centerX - 190;
|
||||
targetScale = sideScale;
|
||||
targetOpacity = 0.0;
|
||||
}
|
||||
|
||||
if (instant)
|
||||
{
|
||||
translate.X = targetX;
|
||||
scale.ScaleX = scale.ScaleY = targetScale;
|
||||
translate!.X = targetX;
|
||||
scale!.ScaleX = scale.ScaleY = targetScale;
|
||||
border.Opacity = targetOpacity;
|
||||
}
|
||||
else
|
||||
{
|
||||
translate.BeginAnimation(TranslateTransform.XProperty,
|
||||
translate!.BeginAnimation(TranslateTransform.XProperty,
|
||||
new DoubleAnimation(targetX, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() });
|
||||
scale.BeginAnimation(ScaleTransform.ScaleXProperty,
|
||||
scale!.BeginAnimation(ScaleTransform.ScaleXProperty,
|
||||
new DoubleAnimation(targetScale, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() });
|
||||
scale.BeginAnimation(ScaleTransform.ScaleYProperty,
|
||||
new DoubleAnimation(targetScale, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() });
|
||||
border.BeginAnimation(Border.OpacityProperty,
|
||||
new DoubleAnimation(targetOpacity, TimeSpan.FromMilliseconds(400)));
|
||||
}
|
||||
|
||||
var item = Images[i];
|
||||
|
||||
if (i == SelectedIndex)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
StartProgress(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.IsSelected = false;
|
||||
item.Progress = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -30,23 +30,49 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>();
|
||||
private int _selectedIndex = 0;
|
||||
|
||||
private EventHandler _renderHandler;
|
||||
|
||||
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;
|
||||
|
||||
for (int i = 0; i < Images.Count; i++)
|
||||
{
|
||||
Images[i].IsSelected = (i == _selectedIndex);
|
||||
if (i == _selectedIndex) Images[i].Progress = 0;
|
||||
}
|
||||
Images[i].IsSelected = i == _selectedIndex;
|
||||
|
||||
UpdateLayoutWithAnimation();
|
||||
|
||||
// 启动进度条动画
|
||||
var current = Images[_selectedIndex];
|
||||
StartProgress(current);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartProgress(CoverFlowItem item)
|
||||
{
|
||||
StopProgress();
|
||||
_renderHandler = (s, e) =>
|
||||
{
|
||||
if (item.Progress < 1)
|
||||
item.Progress += 0.005; // 每帧增加一点
|
||||
else
|
||||
item.Progress = 0;
|
||||
};
|
||||
CompositionTarget.Rendering += _renderHandler;
|
||||
}
|
||||
|
||||
private void StopProgress()
|
||||
{
|
||||
if (_renderHandler != null)
|
||||
{
|
||||
CompositionTarget.Rendering -= _renderHandler;
|
||||
_renderHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,30 +5,70 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
{
|
||||
public class CoverFlowItem : INotifyPropertyChanged
|
||||
{
|
||||
private bool _isSelected;
|
||||
private double _progress; // 0~1
|
||||
|
||||
public Uri ImageUri { get; set; }
|
||||
|
||||
public bool IsSelected
|
||||
private double _progress;
|
||||
private String _progressColor1 = "#e73d42";
|
||||
|
||||
public String ProgressColor1
|
||||
{
|
||||
get => _isSelected;
|
||||
set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); }
|
||||
get => _progressColor1;
|
||||
set
|
||||
{
|
||||
if (_progressColor1 != value)
|
||||
{
|
||||
_progressColor1 = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String _progressColor2 = "#215bc7";
|
||||
|
||||
public String ProgressColor2
|
||||
{
|
||||
get => _progressColor2;
|
||||
set
|
||||
{
|
||||
if (_progressColor2 != value)
|
||||
{
|
||||
_progressColor2 = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get => _progress;
|
||||
set { _progress = value; OnPropertyChanged(nameof(Progress)); }
|
||||
set
|
||||
{
|
||||
if (_progress != value)
|
||||
{
|
||||
_progress = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Progress)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _isSelected;
|
||||
public bool IsSelected
|
||||
{
|
||||
get => _isSelected;
|
||||
set
|
||||
{
|
||||
if (_isSelected != value)
|
||||
{
|
||||
_isSelected = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsSelected)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
protected void OnPropertyChanged(string name) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,75 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.MyUserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 将进度值转换为表示矩形边框部分路径的几何图形(StreamGeometry)的转换器。
|
||||
/// 该转换器用于绘制一个带圆角的矩形边框,并根据进度值绘制其部分轮廓线,常用于进度条等UI控件中。
|
||||
/// </summary>
|
||||
public class ProgressToRectangleGeometryConverter : IValueConverter
|
||||
{
|
||||
// parameter: "width,height"
|
||||
/// <summary>
|
||||
/// 将进度值转换为表示矩形边框部分路径的几何图形(StreamGeometry)对象
|
||||
/// </summary>
|
||||
/// <param name="value">当前进度值,应为 [0.0, 1.0] 范围内的 double 类型</param>
|
||||
/// <param name="targetType">目标类型,应为 Geometry 类型</param>
|
||||
/// <param name="parameter">参数字符串,格式为 "width,height,radius",分别表示矩形的宽度、高度和圆角半径</param>
|
||||
/// <param name="culture">当前区域性信息</param>
|
||||
/// <returns>返回一个 StreamGeometry 对象,表示根据进度绘制的部分矩形边框</returns>
|
||||
// parameter: "width,height,radius"
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
double progress = (double)value;
|
||||
string[] sizes = parameter.ToString().Split(',');
|
||||
string[] sizes = parameter.ToString()!.Split(',');
|
||||
double width = double.Parse(sizes[0]);
|
||||
double height = double.Parse(sizes[1]);
|
||||
double radius = double.Parse(sizes[2]);
|
||||
double stroke = sizes.Length > 3 ? double.Parse(sizes[3]) : 0;
|
||||
|
||||
var geo = new StreamGeometry();
|
||||
using (var ctx = geo.Open())
|
||||
double inset = stroke / 2.0;
|
||||
double x = inset;
|
||||
double y = inset;
|
||||
double w = width - stroke;
|
||||
double h = height - stroke;
|
||||
// 保持原始圆角半径
|
||||
double r = radius;
|
||||
|
||||
// 计算总周长
|
||||
double perimeter = 2 * (w + h - 4 * r) + 2 * Math.PI * r;
|
||||
double length = perimeter * progress;
|
||||
|
||||
var geometry = new StreamGeometry();
|
||||
using (var ctx = geometry.Open())
|
||||
{
|
||||
ctx.BeginFigure(new Point(0, 0), false, false);
|
||||
// 从左上角圆角起点,顺时针
|
||||
ctx.BeginFigure(new Point(x + r, y), false, false);
|
||||
|
||||
double total = 2 * (width + height);
|
||||
double len = progress * total;
|
||||
// top line
|
||||
double top = Math.Min(length, w - 2 * r);
|
||||
ctx.LineTo(new Point(x + r + top, y), true, false);
|
||||
length -= top;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// 上边
|
||||
if (len <= width)
|
||||
// top-right arc
|
||||
double arc = Math.Min(length, Math.PI / 2 * r);
|
||||
if (arc > 0)
|
||||
{
|
||||
ctx.LineTo(new Point(len, 0), true, true);
|
||||
return geo;
|
||||
double angle = arc / r;
|
||||
double endX = x + w - r + Math.Sin(angle) * r;
|
||||
double endY = y + r - Math.Cos(angle) * r;
|
||||
if (arc < Math.PI / 2 * r)
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(endX, endY),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
return geometry;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(x + w, y + r),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
length -= Math.PI / 2 * r;
|
||||
}
|
||||
}
|
||||
ctx.LineTo(new Point(width, 0), true, true);
|
||||
len -= width;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// 右边
|
||||
if (len <= height)
|
||||
{
|
||||
ctx.LineTo(new Point(width, len), true, true);
|
||||
return geo;
|
||||
}
|
||||
ctx.LineTo(new Point(width, height), true, true);
|
||||
len -= height;
|
||||
// right line
|
||||
double right = Math.Min(length, h - 2 * r);
|
||||
ctx.LineTo(new Point(x + w, y + r + right), true, false);
|
||||
length -= right;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// 下边
|
||||
if (len <= width)
|
||||
// bottom-right arc
|
||||
arc = Math.Min(length, Math.PI / 2 * r);
|
||||
if (arc > 0)
|
||||
{
|
||||
ctx.LineTo(new Point(width - len, height), true, true);
|
||||
return geo;
|
||||
double angle = arc / r;
|
||||
double endX = x + w - r + Math.Cos(angle) * r;
|
||||
double endY = y + h - r + Math.Sin(angle) * r;
|
||||
if (arc < Math.PI / 2 * r)
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(endX, endY),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
return geometry;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(x + w - r, y + h),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
length -= Math.PI / 2 * r;
|
||||
}
|
||||
}
|
||||
ctx.LineTo(new Point(0, height), true, true);
|
||||
len -= width;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// 左边
|
||||
if (len <= height)
|
||||
// bottom line
|
||||
double bottom = Math.Min(length, w - 2 * r);
|
||||
ctx.LineTo(new Point(x + w - r - bottom, y + h), true, false);
|
||||
length -= bottom;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// bottom-left arc
|
||||
arc = Math.Min(length, Math.PI / 2 * r);
|
||||
if (arc > 0)
|
||||
{
|
||||
ctx.LineTo(new Point(0, height - len), true, true);
|
||||
double angle = arc / r;
|
||||
double endX = x + r - Math.Sin(angle) * r;
|
||||
double endY = y + h - r + Math.Cos(angle) * r;
|
||||
if (arc < Math.PI / 2 * r)
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(endX, endY),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
return geometry;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(x, y + h - r),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
length -= Math.PI / 2 * r;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// left line
|
||||
double left = Math.Min(length, h - 2 * r);
|
||||
ctx.LineTo(new Point(x, y + h - r - left), true, false);
|
||||
length -= left;
|
||||
if (length <= 0) return geometry;
|
||||
|
||||
// top-left arc
|
||||
arc = Math.Min(length, Math.PI / 2 * r);
|
||||
if (arc > 0)
|
||||
{
|
||||
ctx.LineTo(new Point(0, 0), true, true);
|
||||
double angle = arc / r;
|
||||
double endX = x + r - Math.Cos(angle) * r;
|
||||
double endY = y + r - Math.Sin(angle) * r;
|
||||
if (arc < Math.PI / 2 * r)
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(endX, endY),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
return geometry;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.ArcTo(
|
||||
new Point(x + r, y),
|
||||
new Size(r, r),
|
||||
0, false, SweepDirection.Clockwise, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
geo.Freeze();
|
||||
return geo;
|
||||
geometry.Freeze();
|
||||
return geometry;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/action_user.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/change_back.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/change_bg.png
Normal file
|
After Width: | Height: | Size: 793 B |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/change_left.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/change_right.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/home_bg.png
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/home_history.png
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/home_play.png
Normal file
|
After Width: | Height: | Size: 487 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/home_test.png
Normal file
|
After Width: | Height: | Size: 651 KiB |
BIN
Wpf_AiSportsMicrospace/Resources/Img/Album/title.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
12
Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml
Normal file
@ -0,0 +1,12 @@
|
||||
<Window x:Class="Wpf_AiSportsMicrospace.Views.GroupJumpRope"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="多人跳绳" Height="450" Width="800">
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
27
Wpf_AiSportsMicrospace/Views/JumpRope/GroupJumpRope.xaml.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// GroupJumpRope.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class GroupJumpRope : Window
|
||||
{
|
||||
public GroupJumpRope()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml
Normal file
@ -0,0 +1,12 @@
|
||||
<Window x:Class="Wpf_AiSportsMicrospace.Views.MusicJumpRope"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.Views"
|
||||
mc:Ignorable="d"
|
||||
Title="MusicJumpRope" Height="450" Width="800">
|
||||
<Grid>
|
||||
|
||||
</Grid>
|
||||
</Window>
|
||||
27
Wpf_AiSportsMicrospace/Views/JumpRope/MusicJumpRope.xaml.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Wpf_AiSportsMicrospace.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// MusicJumpRope.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class MusicJumpRope : Window
|
||||
{
|
||||
public MusicJumpRope()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,13 @@
|
||||
<None Remove="Resources\Img\Album\3.jpg" />
|
||||
<None Remove="Resources\Img\Album\4.jpg" />
|
||||
<None Remove="Resources\Img\Album\5.jpg" />
|
||||
<None Remove="Resources\Img\Album\action_user.png" />
|
||||
<None Remove="Resources\Img\Album\change_bg.png" />
|
||||
<None Remove="Resources\Img\Album\home_bg.png" />
|
||||
<None Remove="Resources\Img\Album\home_history.png" />
|
||||
<None Remove="Resources\Img\Album\home_play.png" />
|
||||
<None Remove="Resources\Img\Album\home_test.png" />
|
||||
<None Remove="Resources\Img\Album\title.png" />
|
||||
<None Remove="Resources\Img\Badge\1.jpg" />
|
||||
<None Remove="Resources\Img\Badge\2.jpg" />
|
||||
<None Remove="Resources\Img\Badge\3.jpg" />
|
||||
@ -87,6 +94,27 @@
|
||||
<Resource Include="Resources\Img\Album\5.jpg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\action_user.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\change_bg.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\home_bg.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\home_history.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\home_play.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\home_test.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Album\title.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\Img\Badge\1.jpg">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Resource>
|
||||
|
||||