Compare commits

...

5 Commits

Author SHA1 Message Date
ltx
4ef4da7781 update:合并 2025-09-26 16:48:35 +08:00
ltx
66d6b237d1 update:ui 2025-09-26 16:46:30 +08:00
ltx
3d05aa48be update:
大小&位置&背景
2025-09-25 09:37:53 +08:00
ltx
06900606c8 Merge branch 'dev' of http://8.153.108.90:3000/YD/Wpf_AiSportsMicrospace into dev 2025-09-23 16:05:59 +08:00
ltx
d7cdcbe411 1604 2025-09-23 16:05:56 +08:00
24 changed files with 853 additions and 154 deletions

View 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;
}
}
}

View 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 // 举手完成
}
}

View File

@ -2,8 +2,30 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl" xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"
Title="Home" Height="600" Width="800" Loaded="Window_Loaded"> Title="Home" Height="1080" Width="1920" Loaded="Window_Loaded">
<Grid> <Grid Height="1065" VerticalAlignment="Bottom">
<local:CoverFlowControl1 x:Name="coverFlow" HorizontalAlignment="Center" VerticalAlignment="Center"/> <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> </Grid>
</Window> </Window>

View File

@ -4,6 +4,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -13,7 +14,9 @@ using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
using System.Windows.Media.Animation; using System.Windows.Media.Animation;
using Wpf_AiSportsMicrospace.Common; using Wpf_AiSportsMicrospace.Common;
using Wpf_AiSportsMicrospace.Enum;
using Wpf_AiSportsMicrospace.MyUserControl; using Wpf_AiSportsMicrospace.MyUserControl;
using Wpf_AiSportsMicrospace.Views;
using Yztob.AiSports.Inferences.Abstractions; using Yztob.AiSports.Inferences.Abstractions;
using Yztob.AiSports.Postures; using Yztob.AiSports.Postures;
using Yztob.AiSports.Postures.Abstractions; using Yztob.AiSports.Postures.Abstractions;
@ -30,19 +33,10 @@ namespace Wpf_AiSportsMicrospace
public partial class Home : Window public partial class Home : Window
{ {
private IHumanPredictor _humanPredictor; 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 WebcamClient _webcamClient;
private ConcurrentQueue<VideoFrame> _frameQueue = new(); private ConcurrentQueue<VideoFrame> _frameQueue = new();
private CancellationTokenSource _cts = new(); private CancellationTokenSource _cts = new();
private DateTime _lastSlideTime = DateTime.MinValue; private SportOperate _sportOperate;
public Home() public Home()
{ {
@ -59,49 +53,73 @@ namespace Wpf_AiSportsMicrospace
string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album"); string albumPath = Path.Combine(projectRoot, "Resources", "Img", "Album");
// 转换为 Uri // 转换为 Uri
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "1.jpg"))); //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_play.png")));
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "2.jpg"))); //coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "home_test.png")));
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "3.jpg"))); //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, "4.jpg")));
//coverFlow.Images.Add(new Uri(Path.Combine(albumPath, "5.jpg"))); //coverFlow.Images.Add(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, "1.jpg")) }); 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, "2.jpg")) }); coverFlow.Images.Add(new CoverFlowItem { ImageUri = new Uri(Path.Combine(albumPath, "home_history.png")), ProgressColor1 = "#e73d42", ProgressColor2 = "#215bc7" });
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")) });
// 默认选中第3张 // 默认选中第3张
coverFlow.SelectedIndex = 2; coverFlow.SelectedIndex = 0;
} }
private void Window_Loaded(object sender, RoutedEventArgs e) private void Window_Loaded(object sender, RoutedEventArgs e)
{ {
_leftTracker = PostureCalculate.CreatePointTracker("left_wrist", 0); _sportOperate = new SportOperate();
_rightTracker = PostureCalculate.CreatePointTracker("right_wrist", 0); _webcamClient = _sportOperate.CreateRTSP();
_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;
StartRTSP();
StartFrameProcessing();
}
private void StartRTSP()
{
_webcamClient = WebcamClient.CreateRTSP("192.168.3.64", "admin", "yd708090", 554u);
_webcamClient.OnExtractFrame += frame => _webcamClient.OnExtractFrame += frame =>
{ {
if (frame != null) if (frame != null)
_frameQueue.Enqueue(frame); _frameQueue.Enqueue(frame);
}; };
_webcamClient.StartExtract(); _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() private void StartFrameProcessing()
@ -116,6 +134,15 @@ namespace Wpf_AiSportsMicrospace
} }
else else
{ {
//_webcamClient.OnExtractFrame += frame =>
//{
// if (frame != null)
// _frameQueue.Enqueue(frame);
//};
//_webcamClient.StartExtract();
_webcamClient.StartExtract();
Thread.Sleep(5); Thread.Sleep(5);
} }
} }
@ -128,55 +155,60 @@ namespace Wpf_AiSportsMicrospace
{ {
var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray(); var buffer = frame.GetImageBuffer(ImageFormat.Jpeg).ToArray();
var humanResult = _humanPredictor.Predicting(buffer, frame.Number); var humanResult = _humanPredictor.Predicting(buffer, frame.Number);
var humans = humanResult?.Humans?.ToList(); 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(); var human = humans.FirstOrDefault();
if (human == null) return; if (human == null) return;
var leftResult = _leftTracker.Tracking(human); //检测挥手动作
var rightResult = _rightTracker.Tracking(human); var wavingaction = _sportOperate.VerifyWavingAction(human);
var leftElbowResult = _leftElbow.Tracking(human);
var rightElbowResult = _rightElbow.Tracking(human);
if ((DateTime.Now - _lastSlideTime).TotalMilliseconds < 500) return; // 把低 8 位作为动作类型,高 8 位作为进度
_lastSlideTime = DateTime.Now; int actionType = wavingaction & 0xFF;
int progress = (wavingaction >> 8) & 0xFF;
if (leftResult != 0 && leftElbowResult != 1) switch (actionType)
{
case (int)WavingAction.LeftWave: // 左手挥动
Dispatcher.BeginInvoke(() => coverFlow.SlideRight()); Dispatcher.BeginInvoke(() => coverFlow.SlideRight());
if (rightResult != 0 && rightElbowResult != 1) break;
case (int)WavingAction.RightWave: // 右手挥动
Dispatcher.BeginInvoke(() => coverFlow.SlideLeft()); Dispatcher.BeginInvoke(() => coverFlow.SlideLeft());
} break;
catch (Exception ex)
{
Console.WriteLine("ProcessFrame error: " + ex.Message);
}
}
protected override void OnClosed(EventArgs e) case (int)WavingAction.FirstHand: // 举手开始
{ Dispatcher.BeginInvoke(() => coverFlow.StartSelectedProgress());
base.OnClosed(e); break;
case (int)WavingAction.Raising: // 举手中,实时更新进度
break;
case (int)WavingAction.RaiseHand: // 举手完成
_cts.Cancel(); _cts.Cancel();
_webcamClient?.StopExtract(); break;
default: // 没有动作 → 取消进度
Dispatcher.BeginInvoke(() => coverFlow.CancelSelectedProgress());
break;
} }
/// <summary>
/// 更新 UI线程安全
/// </summary>
private void SlideCoverFlow(Action slideAction)
{
Application.Current?.Dispatcher?.BeginInvoke(new Action(() =>
{
try
{
slideAction?.Invoke();
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine("UI update error: " + ex.Message); Console.WriteLine("OnFrameExtracted error: " + ex.Message);
}
}), System.Windows.Threading.DispatcherPriority.Background);
} }
} }
}
} }

View File

@ -4,8 +4,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl" xmlns:local="clr-namespace:Wpf_AiSportsMicrospace.MyUserControl"
Height="300" Width="600"> Width="1080">
<Grid ClipToBounds="True" Background="Transparent"> <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 x:Name="ItemsHost" ItemsSource="{Binding Images, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
@ -15,15 +19,36 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Border x:Name="ImageContainer" RenderTransformOrigin="0.5,0.5" <Border x:Name="ImageContainer" RenderTransformOrigin="0.5,0.5" CornerRadius="28"
MouseLeftButtonDown="Image_MouseLeftButtonDown"> MouseLeftButtonDown="Image_MouseLeftButtonDown" Background="#fff">
<Border.RenderTransform> <Border.RenderTransform>
<TransformGroup> <TransformGroup>
<ScaleTransform x:Name="scale" ScaleX="1" ScaleY="1"/> <ScaleTransform x:Name="scale" ScaleX="1" ScaleY="1"/>
<TranslateTransform x:Name="translate" X="0" Y="0"/> <TranslateTransform x:Name="translate" X="0" Y="0"/>
</TransformGroup> </TransformGroup>
</Border.RenderTransform> </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> </Border>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@ -16,6 +16,7 @@ using System.Windows.Media.Animation;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Windows.Navigation; using System.Windows.Navigation;
using System.Windows.Shapes; using System.Windows.Shapes;
using System.Windows.Threading;
namespace Wpf_AiSportsMicrospace.MyUserControl namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
@ -24,7 +25,28 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
/// </summary> /// </summary>
public partial class CoverFlowControl : UserControl 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; private int _selectedIndex = 0;
public int SelectedIndex public int SelectedIndex
@ -34,23 +56,112 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
if (Images.Count == 0) return; if (Images.Count == 0) return;
// 循环处理 if (value < 0) _selectedIndex = Images.Count - 1;
if (value < 0) else if (value >= Images.Count) _selectedIndex = 0;
_selectedIndex = Images.Count - 1; else _selectedIndex = value;
else if (value >= Images.Count)
_selectedIndex = 0;
else for (int i = 0; i < Images.Count; i++)
_selectedIndex = value; {
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(); 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() public CoverFlowControl()
{ {
InitializeComponent(); InitializeComponent();
DataContext = this; DataContext = this;
Loaded += (s, e) => UpdateLayoutWithAnimation(true); 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) private void Image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@ -69,9 +180,9 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
private void UpdateLayoutWithAnimation(bool instant = false) private void UpdateLayoutWithAnimation(bool instant = false)
{ {
double centerX = ActualWidth / 2; double centerX = ActualWidth / 2;
double spacing = 180; double spacing = 500;
double sideScale = 0.8; double sideScale = 1;
double centerScale = 1.2; double centerScale = 1.33;
for (int i = 0; i < ItemsHost.Items.Count; i++) for (int i = 0; i < ItemsHost.Items.Count; i++)
{ {
@ -82,7 +193,7 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
if (border == null) continue; if (border == null) continue;
var transformGroup = border.RenderTransform as TransformGroup; 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; var translate = transformGroup.Children[1] as TranslateTransform;
double targetX; double targetX;
@ -91,48 +202,62 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
if (i == SelectedIndex) if (i == SelectedIndex)
{ {
targetX = centerX - 75; targetX = centerX - 190;
targetScale = centerScale; targetScale = centerScale;
targetOpacity = 1.0; targetOpacity = 1.0;
} }
else if (i == SelectedIndex - 1 || (SelectedIndex == 0 && i == Images.Count - 1)) else if (i == SelectedIndex - 1 || (SelectedIndex == 0 && i == Images.Count - 1))
{ {
// 左边图片,循环处理 // 左边图片,循环处理
targetX = centerX - spacing - 75; targetX = centerX - spacing - 190;
targetScale = sideScale; targetScale = sideScale;
targetOpacity = 1.0; targetOpacity = 1.0;
} }
else if (i == SelectedIndex + 1 || (SelectedIndex == Images.Count - 1 && i == 0)) else if (i == SelectedIndex + 1 || (SelectedIndex == Images.Count - 1 && i == 0))
{ {
// 右边图片,循环处理 // 右边图片,循环处理
targetX = centerX + spacing - 75; targetX = centerX + spacing - 190;
targetScale = sideScale; targetScale = sideScale;
targetOpacity = 1.0; targetOpacity = 1.0;
} }
else else
{ {
targetX = centerX - 75; targetX = centerX - 190;
targetScale = sideScale; targetScale = sideScale;
targetOpacity = 0.0; targetOpacity = 0.0;
} }
if (instant) if (instant)
{ {
translate.X = targetX; translate!.X = targetX;
scale.ScaleX = scale.ScaleY = targetScale; scale!.ScaleX = scale.ScaleY = targetScale;
border.Opacity = targetOpacity; border.Opacity = targetOpacity;
} }
else else
{ {
translate.BeginAnimation(TranslateTransform.XProperty, translate!.BeginAnimation(TranslateTransform.XProperty,
new DoubleAnimation(targetX, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() }); 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() }); new DoubleAnimation(targetScale, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() });
scale.BeginAnimation(ScaleTransform.ScaleYProperty, scale.BeginAnimation(ScaleTransform.ScaleYProperty,
new DoubleAnimation(targetScale, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() }); new DoubleAnimation(targetScale, TimeSpan.FromMilliseconds(400)) { EasingFunction = new QuadraticEase() });
border.BeginAnimation(Border.OpacityProperty, border.BeginAnimation(Border.OpacityProperty,
new DoubleAnimation(targetOpacity, TimeSpan.FromMilliseconds(400))); 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;
}
} }
} }

View File

@ -30,23 +30,49 @@ namespace Wpf_AiSportsMicrospace.MyUserControl
public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>(); public ObservableCollection<CoverFlowItem> Images { get; set; } = new ObservableCollection<CoverFlowItem>();
private int _selectedIndex = 0; private int _selectedIndex = 0;
private EventHandler _renderHandler;
public int SelectedIndex public int SelectedIndex
{ {
get => _selectedIndex; get => _selectedIndex;
set set
{ {
if (Images.Count == 0) return; if (Images.Count == 0) return;
if (value < 0) _selectedIndex = Images.Count - 1; if (value < 0) _selectedIndex = Images.Count - 1;
else if (value >= Images.Count) _selectedIndex = 0; else if (value >= Images.Count) _selectedIndex = 0;
else _selectedIndex = value; else _selectedIndex = value;
for (int i = 0; i < Images.Count; i++) for (int i = 0; i < Images.Count; i++)
{ Images[i].IsSelected = i == _selectedIndex;
Images[i].IsSelected = (i == _selectedIndex);
if (i == _selectedIndex) Images[i].Progress = 0;
}
UpdateLayoutWithAnimation(); 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;
} }
} }

View File

@ -5,30 +5,70 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Media;
namespace Wpf_AiSportsMicrospace.MyUserControl namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
public class CoverFlowItem : INotifyPropertyChanged public class CoverFlowItem : INotifyPropertyChanged
{ {
private bool _isSelected;
private double _progress; // 0~1
public Uri ImageUri { get; set; } public Uri ImageUri { get; set; }
public bool IsSelected private double _progress;
private String _progressColor1 = "#e73d42";
public String ProgressColor1
{ {
get => _isSelected; get => _progressColor1;
set { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); } set
{
if (_progressColor1 != value)
{
_progressColor1 = value;
}
}
}
private String _progressColor2 = "#215bc7";
public String ProgressColor2
{
get => _progressColor2;
set
{
if (_progressColor2 != value)
{
_progressColor2 = value;
}
}
} }
public double Progress public double Progress
{ {
get => _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; public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
} }
} }

View File

@ -1,75 +1,186 @@
using System; using System;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
using System.Windows.Ink;
using System.Windows.Media; using System.Windows.Media;
namespace Wpf_AiSportsMicrospace.MyUserControl namespace Wpf_AiSportsMicrospace.MyUserControl
{ {
/// <summary>
/// 将进度值转换为表示矩形边框部分路径的几何图形StreamGeometry的转换器。
/// 该转换器用于绘制一个带圆角的矩形边框并根据进度值绘制其部分轮廓线常用于进度条等UI控件中。
/// </summary>
public class ProgressToRectangleGeometryConverter : IValueConverter 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) public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{ {
double progress = (double)value; double progress = (double)value;
string[] sizes = parameter.ToString().Split(','); string[] sizes = parameter.ToString()!.Split(',');
double width = double.Parse(sizes[0]); double width = double.Parse(sizes[0]);
double height = double.Parse(sizes[1]); 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(); double inset = stroke / 2.0;
using (var ctx = geo.Open()) 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); // top line
double len = progress * total; 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;
// 上边 // top-right arc
if (len <= width) double arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{ {
ctx.LineTo(new Point(len, 0), true, true); double angle = arc / r;
return geo; double endX = x + w - r + Math.Sin(angle) * r;
} double endY = y + r - Math.Cos(angle) * r;
ctx.LineTo(new Point(width, 0), true, true); if (arc < Math.PI / 2 * r)
len -= width;
// 右边
if (len <= height)
{ {
ctx.LineTo(new Point(width, len), true, true); ctx.ArcTo(
return geo; new Point(endX, endY),
} new Size(r, r),
ctx.LineTo(new Point(width, height), true, true); 0, false, SweepDirection.Clockwise, true, false);
len -= height; return geometry;
// 下边
if (len <= width)
{
ctx.LineTo(new Point(width - len, height), true, true);
return geo;
}
ctx.LineTo(new Point(0, height), true, true);
len -= width;
// 左边
if (len <= height)
{
ctx.LineTo(new Point(0, height - len), true, true);
} }
else else
{ {
ctx.LineTo(new Point(0, 0), true, true); ctx.ArcTo(
new Point(x + w, y + r),
new Size(r, r),
0, false, SweepDirection.Clockwise, true, false);
length -= Math.PI / 2 * r;
} }
} }
geo.Freeze(); if (length <= 0) return geometry;
return geo;
// 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;
// bottom-right arc
arc = Math.Min(length, Math.PI / 2 * r);
if (arc > 0)
{
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;
}
}
if (length <= 0) return geometry;
// 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)
{
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;
}
}
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)
{
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);
}
}
}
geometry.Freeze();
return geometry;
} }
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException(); => throw new NotImplementedException();
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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>

View 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();
}
}
}

View 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>

View 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();
}
}
}

View File

@ -14,6 +14,13 @@
<None Remove="Resources\Img\Album\3.jpg" /> <None Remove="Resources\Img\Album\3.jpg" />
<None Remove="Resources\Img\Album\4.jpg" /> <None Remove="Resources\Img\Album\4.jpg" />
<None Remove="Resources\Img\Album\5.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\1.jpg" />
<None Remove="Resources\Img\Badge\2.jpg" /> <None Remove="Resources\Img\Badge\2.jpg" />
<None Remove="Resources\Img\Badge\3.jpg" /> <None Remove="Resources\Img\Badge\3.jpg" />
@ -87,6 +94,27 @@
<Resource Include="Resources\Img\Album\5.jpg"> <Resource Include="Resources\Img\Album\5.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource> </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"> <Resource Include="Resources\Img\Badge\1.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource> </Resource>