Common类库新增摇杆控件。
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace XP.Common.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 虚拟摇杆核心计算逻辑(纯函数,无副作用)| Virtual joystick core calculation logic (pure functions, no side effects)
|
||||||
|
/// </summary>
|
||||||
|
public static class JoystickCalculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 将位移限制在圆形区域内 | Clamp displacement within circular radius
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
|
||||||
|
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
|
||||||
|
/// <param name="radius">最大半径(必须大于 0)| Maximum radius (must be greater than 0)</param>
|
||||||
|
/// <returns>限制后的 (clampedDx, clampedDy) 元组 | Clamped (clampedDx, clampedDy) tuple</returns>
|
||||||
|
public static (double clampedDx, double clampedDy) ClampToRadius(double dx, double dy, double radius)
|
||||||
|
{
|
||||||
|
if (radius <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
|
||||||
|
|
||||||
|
var distance = Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// 如果距离在半径内,直接返回原值 | If within radius, return original values
|
||||||
|
if (distance <= radius)
|
||||||
|
return (dx, dy);
|
||||||
|
|
||||||
|
// 归一化到半径上 | Normalize to radius boundary
|
||||||
|
var scale = radius / distance;
|
||||||
|
return (dx * scale, dy * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算归一化输出,包含死区映射 | Calculate normalized output with dead zone mapping
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dx">X 轴偏移量 | X axis displacement</param>
|
||||||
|
/// <param name="dy">Y 轴偏移量 | Y axis displacement</param>
|
||||||
|
/// <param name="radius">最大半径(必须大于 0)| Maximum radius (must be greater than 0)</param>
|
||||||
|
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
|
||||||
|
/// <param name="mode">摇杆模式 | Joystick mode</param>
|
||||||
|
/// <returns>归一化输出 (outputX, outputY) 元组,范围 [-1.0, 1.0] | Normalized output tuple, range [-1.0, 1.0]</returns>
|
||||||
|
public static (double outputX, double outputY) CalculateOutput(
|
||||||
|
double dx, double dy, double radius, double deadZone, JoystickMode mode)
|
||||||
|
{
|
||||||
|
if (radius <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(radius), "半径必须大于 0 | Radius must be greater than 0");
|
||||||
|
|
||||||
|
if (deadZone < 0.0 || deadZone >= 1.0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(deadZone), "死区比例必须在 [0.0, 1.0) 范围内 | Dead zone must be in range [0.0, 1.0)");
|
||||||
|
|
||||||
|
// 步骤 1-2:将位移限制在半径内 | Step 1-2: Clamp displacement within radius
|
||||||
|
var (clampedDx, clampedDy) = ClampToRadius(dx, dy, radius);
|
||||||
|
|
||||||
|
// 步骤 3:计算比例值 | Step 3: Calculate ratio
|
||||||
|
var ratioX = clampedDx / radius;
|
||||||
|
var ratioY = clampedDy / radius;
|
||||||
|
|
||||||
|
// 步骤 4:对每个轴分别应用死区 | Step 4: Apply dead zone to each axis independently
|
||||||
|
var outputX = ApplyDeadZone(ratioX, deadZone);
|
||||||
|
var outputY = ApplyDeadZone(ratioY, deadZone);
|
||||||
|
|
||||||
|
// 步骤 5:SingleAxisY 模式下强制 OutputX = 0 | Step 5: Force OutputX = 0 in SingleAxisY mode
|
||||||
|
if (mode == JoystickMode.SingleAxisY)
|
||||||
|
outputX = 0.0;
|
||||||
|
|
||||||
|
return (outputX, outputY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 对单轴比例值应用死区映射 | Apply dead zone mapping to a single axis ratio
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ratio">轴比例值,范围 [-1.0, 1.0] | Axis ratio, range [-1.0, 1.0]</param>
|
||||||
|
/// <param name="deadZone">死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)</param>
|
||||||
|
/// <returns>死区映射后的输出值 | Output value after dead zone mapping</returns>
|
||||||
|
internal static double ApplyDeadZone(double ratio, double deadZone)
|
||||||
|
{
|
||||||
|
var absRatio = Math.Abs(ratio);
|
||||||
|
|
||||||
|
// 在死区内,输出为 0 | Within dead zone, output is 0
|
||||||
|
if (absRatio < deadZone)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
// 死区外线性映射:sign(ratio) × (absRatio - D) / (1 - D) | Linear mapping outside dead zone
|
||||||
|
return Math.Sign(ratio) * (absRatio - deadZone) / (1.0 - deadZone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
namespace XP.Common.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 虚拟摇杆轴模式枚举 | Virtual joystick axis mode enumeration
|
||||||
|
/// </summary>
|
||||||
|
public enum JoystickMode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 双轴模式:X + Y 自由移动 | Dual axis mode: free movement in X and Y directions
|
||||||
|
/// </summary>
|
||||||
|
DualAxis,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单轴 Y 模式:仅上下移动 | Single axis Y mode: vertical movement only
|
||||||
|
/// </summary>
|
||||||
|
SingleAxisY
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace XP.Common.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标按键类型枚举 | Mouse button type enumeration
|
||||||
|
/// </summary>
|
||||||
|
public enum MouseButtonType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 未按下 | No button pressed
|
||||||
|
/// </summary>
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标左键 | Left mouse button
|
||||||
|
/// </summary>
|
||||||
|
Left,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标右键 | Right mouse button
|
||||||
|
/// </summary>
|
||||||
|
Right
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="clr-namespace:XP.Common.Controls">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
VirtualJoystick 默认样式和控件模板 | VirtualJoystick default style and control template
|
||||||
|
视觉结构:圆形裁剪区域 + 十字形背景线 + 四向功能图标 + 中心操控点
|
||||||
|
Visual structure: Circular clip + cross background + four-directional icons + center thumb
|
||||||
|
-->
|
||||||
|
<Style TargetType="{x:Type controls:VirtualJoystick}">
|
||||||
|
<Setter Property="Width" Value="200" />
|
||||||
|
<Setter Property="Height" Value="200" />
|
||||||
|
<Setter Property="Background" Value="#FFF5F5F5" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type controls:VirtualJoystick}">
|
||||||
|
<Grid x:Name="PART_Root">
|
||||||
|
<!-- 圆形背景和边框 | Circular background and border -->
|
||||||
|
<Ellipse Fill="{TemplateBinding Background}"
|
||||||
|
Stroke="#FFCCCCCC"
|
||||||
|
StrokeThickness="1.5" />
|
||||||
|
|
||||||
|
<!-- 十字形背景虚线 | Cross-shaped dashed background lines -->
|
||||||
|
<!-- 水平线 | Horizontal line -->
|
||||||
|
<Line X1="0"
|
||||||
|
Y1="0"
|
||||||
|
X2="1"
|
||||||
|
Y2="0"
|
||||||
|
Stretch="Fill"
|
||||||
|
Stroke="#FFD0D0D0"
|
||||||
|
StrokeThickness="1"
|
||||||
|
StrokeDashArray="4 2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
IsHitTestVisible="False" />
|
||||||
|
<!-- 垂直线 | Vertical line -->
|
||||||
|
<Line X1="0"
|
||||||
|
Y1="0"
|
||||||
|
X2="0"
|
||||||
|
Y2="1"
|
||||||
|
Stretch="Fill"
|
||||||
|
Stroke="#FFD0D0D0"
|
||||||
|
StrokeThickness="1"
|
||||||
|
StrokeDashArray="4 2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
IsHitTestVisible="False" />
|
||||||
|
|
||||||
|
<!-- 四向图标区域(3x3 Grid 布局)| Four-directional icon area (3x3 Grid) -->
|
||||||
|
<Grid IsHitTestVisible="False" Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- ===== 上方图标(三组切换)| Top icons (3 switchable sets) ===== -->
|
||||||
|
<ContentPresenter x:Name="DefaultTopPresenter"
|
||||||
|
Grid.Row="0" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding DefaultTopIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top" />
|
||||||
|
<ContentPresenter x:Name="LeftTopPresenter"
|
||||||
|
Grid.Row="0" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding LeftButtonTopIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<ContentPresenter x:Name="RightTopPresenter"
|
||||||
|
Grid.Row="0" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding RightButtonTopIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
|
||||||
|
<!-- ===== 下方图标 | Bottom icons ===== -->
|
||||||
|
<ContentPresenter x:Name="DefaultBottomPresenter"
|
||||||
|
Grid.Row="2" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding DefaultBottomIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Bottom" />
|
||||||
|
<ContentPresenter x:Name="LeftBottomPresenter"
|
||||||
|
Grid.Row="2" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding LeftButtonBottomIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<ContentPresenter x:Name="RightBottomPresenter"
|
||||||
|
Grid.Row="2" Grid.Column="1"
|
||||||
|
Content="{TemplateBinding RightButtonBottomIcon}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
|
||||||
|
<!-- ===== 左方图标 | Left icons ===== -->
|
||||||
|
<ContentPresenter x:Name="DefaultLeftPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="0"
|
||||||
|
Content="{TemplateBinding DefaultLeftIcon}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ContentPresenter x:Name="LeftLeftPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="0"
|
||||||
|
Content="{TemplateBinding LeftButtonLeftIcon}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<ContentPresenter x:Name="RightLeftPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="0"
|
||||||
|
Content="{TemplateBinding RightButtonLeftIcon}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
|
||||||
|
<!-- ===== 右方图标 | Right icons ===== -->
|
||||||
|
<ContentPresenter x:Name="DefaultRightPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="2"
|
||||||
|
Content="{TemplateBinding DefaultRightIcon}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<ContentPresenter x:Name="LeftRightPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="2"
|
||||||
|
Content="{TemplateBinding LeftButtonRightIcon}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
<ContentPresenter x:Name="RightRightPresenter"
|
||||||
|
Grid.Row="1" Grid.Column="2"
|
||||||
|
Content="{TemplateBinding RightButtonRightIcon}"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 中心操控点 PART_Thumb(通过 TranslateTransform 跟随拖拽)-->
|
||||||
|
<!-- Center thumb (follows drag via TranslateTransform) -->
|
||||||
|
<Ellipse x:Name="PART_Thumb"
|
||||||
|
Width="28"
|
||||||
|
Height="28"
|
||||||
|
Fill="#FF4A90D9"
|
||||||
|
Stroke="#FF2A6AB0"
|
||||||
|
StrokeThickness="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Cursor="SizeAll"
|
||||||
|
IsHitTestVisible="False">
|
||||||
|
<Ellipse.Effect>
|
||||||
|
<DropShadowEffect Color="#40000000"
|
||||||
|
BlurRadius="4"
|
||||||
|
ShadowDepth="1"
|
||||||
|
Opacity="0.5" />
|
||||||
|
</Ellipse.Effect>
|
||||||
|
</Ellipse>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
|
||||||
|
<!-- ===== ActiveMouseButton = Left 时切换图标 | Switch icons when Left button active ===== -->
|
||||||
|
<DataTrigger Binding="{Binding ActiveMouseButton, RelativeSource={RelativeSource Self}}"
|
||||||
|
Value="{x:Static controls:MouseButtonType.Left}">
|
||||||
|
<!-- 隐藏默认图标 | Hide default icons -->
|
||||||
|
<Setter TargetName="DefaultTopPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultBottomPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<!-- 显示左键图标 | Show left button icons -->
|
||||||
|
<Setter TargetName="LeftTopPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="LeftBottomPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="LeftLeftPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="LeftRightPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<!-- 操控点拖拽中高亮 | Thumb highlight during drag -->
|
||||||
|
<Setter TargetName="PART_Thumb" Property="Fill" Value="#FF3A7BC8" />
|
||||||
|
</DataTrigger>
|
||||||
|
|
||||||
|
<!-- ===== ActiveMouseButton = Right 时切换图标 | Switch icons when Right button active ===== -->
|
||||||
|
<DataTrigger Binding="{Binding ActiveMouseButton, RelativeSource={RelativeSource Self}}"
|
||||||
|
Value="{x:Static controls:MouseButtonType.Right}">
|
||||||
|
<!-- 隐藏默认图标 | Hide default icons -->
|
||||||
|
<Setter TargetName="DefaultTopPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultBottomPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<!-- 显示右键图标 | Show right button icons -->
|
||||||
|
<Setter TargetName="RightTopPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="RightBottomPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="RightLeftPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<Setter TargetName="RightRightPresenter" Property="Visibility" Value="Visible" />
|
||||||
|
<!-- 操控点拖拽中高亮(右键使用不同颜色)| Thumb highlight (different color for right button) -->
|
||||||
|
<Setter TargetName="PART_Thumb" Property="Fill" Value="#FF5BA85B" />
|
||||||
|
</DataTrigger>
|
||||||
|
|
||||||
|
<!-- ===== SingleAxisY 模式下隐藏左右图标 | Hide left/right icons in SingleAxisY mode ===== -->
|
||||||
|
<DataTrigger Binding="{Binding JoystickMode, RelativeSource={RelativeSource Self}}"
|
||||||
|
Value="{x:Static controls:JoystickMode.SingleAxisY}">
|
||||||
|
<!-- 隐藏所有左方图标 | Hide all left icons -->
|
||||||
|
<Setter TargetName="DefaultLeftPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="LeftLeftPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="RightLeftPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<!-- 隐藏所有右方图标 | Hide all right icons -->
|
||||||
|
<Setter TargetName="DefaultRightPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="LeftRightPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
<Setter TargetName="RightRightPresenter" Property="Visibility" Value="Collapsed" />
|
||||||
|
</DataTrigger>
|
||||||
|
|
||||||
|
<!-- 控件禁用时降低透明度 | Reduce opacity when control is disabled -->
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.4" />
|
||||||
|
<Setter Property="Cursor" Value="Arrow" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,629 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace XP.Common.Controls
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 虚拟摇杆自定义控件,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick custom control providing mouse drag interaction within a circular area
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// 支持双轴/单轴 Y 模式、死区配置、归一化比例输出(-1.0 ~ 1.0)、鼠标左右键区分及四向功能图标切换。
|
||||||
|
/// Supports dual-axis/single-axis Y mode, dead zone configuration, normalized proportional output (-1.0 ~ 1.0),
|
||||||
|
/// left/right mouse button differentiation, and four-directional icon switching.
|
||||||
|
/// </remarks>
|
||||||
|
[TemplatePart(Name = PART_Thumb, Type = typeof(UIElement))]
|
||||||
|
public class VirtualJoystick : Control
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 操控点模板部件名称 | Thumb template part name
|
||||||
|
/// </summary>
|
||||||
|
private const string PART_Thumb = "PART_Thumb";
|
||||||
|
|
||||||
|
#region 私有字段 | Private Fields
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否正在拖拽 | Whether dragging is in progress
|
||||||
|
/// </summary>
|
||||||
|
private bool _isDragging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 控件中心点坐标 | Control center point coordinates
|
||||||
|
/// </summary>
|
||||||
|
private Point _centerPoint;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操控点的平移变换,用于跟随鼠标移动 | Translate transform for thumb element to follow mouse movement
|
||||||
|
/// </summary>
|
||||||
|
private TranslateTransform _thumbTransform = new TranslateTransform();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
#region 静态构造函数 | Static Constructor
|
||||||
|
|
||||||
|
static VirtualJoystick()
|
||||||
|
{
|
||||||
|
DefaultStyleKeyProperty.OverrideMetadata(
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new FrameworkPropertyMetadata(typeof(VirtualJoystick)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region JoystickMode 依赖属性 | JoystickMode Dependency Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 摇杆轴模式依赖属性 | Joystick axis mode dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty JoystickModeProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(JoystickMode),
|
||||||
|
typeof(JoystickMode),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(JoystickMode.DualAxis));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置摇杆轴模式(双轴或单轴 Y)| Gets or sets the joystick axis mode (DualAxis or SingleAxisY)
|
||||||
|
/// </summary>
|
||||||
|
public JoystickMode JoystickMode
|
||||||
|
{
|
||||||
|
get => (JoystickMode)GetValue(JoystickModeProperty);
|
||||||
|
set => SetValue(JoystickModeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region OutputX 只读依赖属性 | OutputX Read-Only Dependency Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OutputX 只读依赖属性键 | OutputX read-only dependency property key
|
||||||
|
/// </summary>
|
||||||
|
private static readonly DependencyPropertyKey OutputXPropertyKey =
|
||||||
|
DependencyProperty.RegisterReadOnly(
|
||||||
|
nameof(OutputX),
|
||||||
|
typeof(double),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(0.0));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// X 轴归一化输出依赖属性 | X-axis normalized output dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 X 轴归一化输出值,范围 [-1.0, 1.0] | Gets the X-axis normalized output value, range [-1.0, 1.0]
|
||||||
|
/// </summary>
|
||||||
|
public double OutputX
|
||||||
|
{
|
||||||
|
get => (double)GetValue(OutputXProperty);
|
||||||
|
internal set => SetValue(OutputXPropertyKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region OutputY 只读依赖属性 | OutputY Read-Only Dependency Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// OutputY 只读依赖属性键 | OutputY read-only dependency property key
|
||||||
|
/// </summary>
|
||||||
|
private static readonly DependencyPropertyKey OutputYPropertyKey =
|
||||||
|
DependencyProperty.RegisterReadOnly(
|
||||||
|
nameof(OutputY),
|
||||||
|
typeof(double),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(0.0));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Y 轴归一化输出依赖属性 | Y-axis normalized output dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 Y 轴归一化输出值,范围 [-1.0, 1.0] | Gets the Y-axis normalized output value, range [-1.0, 1.0]
|
||||||
|
/// </summary>
|
||||||
|
public double OutputY
|
||||||
|
{
|
||||||
|
get => (double)GetValue(OutputYProperty);
|
||||||
|
internal set => SetValue(OutputYPropertyKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region DeadZone 依赖属性 | DeadZone Dependency Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 死区比例依赖属性 | Dead zone ratio dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty DeadZoneProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(DeadZone),
|
||||||
|
typeof(double),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(0.05));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置死区比例,范围 [0.0, 1.0],默认值 0.05 | Gets or sets the dead zone ratio, range [0.0, 1.0], default 0.05
|
||||||
|
/// </summary>
|
||||||
|
public double DeadZone
|
||||||
|
{
|
||||||
|
get => (double)GetValue(DeadZoneProperty);
|
||||||
|
set => SetValue(DeadZoneProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ActiveMouseButton 只读依赖属性 | ActiveMouseButton Read-Only Dependency Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ActiveMouseButton 只读依赖属性键 | ActiveMouseButton read-only dependency property key
|
||||||
|
/// </summary>
|
||||||
|
private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey =
|
||||||
|
DependencyProperty.RegisterReadOnly(
|
||||||
|
nameof(ActiveMouseButton),
|
||||||
|
typeof(MouseButtonType),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(MouseButtonType.None));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前激活的鼠标按键依赖属性 | Active mouse button dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取当前激活的鼠标按键(None、Left、Right)| Gets the currently active mouse button (None, Left, Right)
|
||||||
|
/// </summary>
|
||||||
|
public MouseButtonType ActiveMouseButton
|
||||||
|
{
|
||||||
|
get => (MouseButtonType)GetValue(ActiveMouseButtonProperty);
|
||||||
|
internal set => SetValue(ActiveMouseButtonPropertyKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 左键四向图标依赖属性 | Left Button Directional Icon Dependency Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 左键拖动时上方图标内容依赖属性 | Left button top icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty LeftButtonTopIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(LeftButtonTopIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置左键拖动时上方图标内容 | Gets or sets the top icon content when left button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? LeftButtonTopIcon
|
||||||
|
{
|
||||||
|
get => GetValue(LeftButtonTopIconProperty);
|
||||||
|
set => SetValue(LeftButtonTopIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 左键拖动时下方图标内容依赖属性 | Left button bottom icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty LeftButtonBottomIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(LeftButtonBottomIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置左键拖动时下方图标内容 | Gets or sets the bottom icon content when left button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? LeftButtonBottomIcon
|
||||||
|
{
|
||||||
|
get => GetValue(LeftButtonBottomIconProperty);
|
||||||
|
set => SetValue(LeftButtonBottomIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 左键拖动时左方图标内容依赖属性 | Left button left icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty LeftButtonLeftIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(LeftButtonLeftIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置左键拖动时左方图标内容 | Gets or sets the left icon content when left button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? LeftButtonLeftIcon
|
||||||
|
{
|
||||||
|
get => GetValue(LeftButtonLeftIconProperty);
|
||||||
|
set => SetValue(LeftButtonLeftIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 左键拖动时右方图标内容依赖属性 | Left button right icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty LeftButtonRightIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(LeftButtonRightIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置左键拖动时右方图标内容 | Gets or sets the right icon content when left button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? LeftButtonRightIcon
|
||||||
|
{
|
||||||
|
get => GetValue(LeftButtonRightIconProperty);
|
||||||
|
set => SetValue(LeftButtonRightIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 右键四向图标依赖属性 | Right Button Directional Icon Dependency Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 右键拖动时上方图标内容依赖属性 | Right button top icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty RightButtonTopIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(RightButtonTopIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置右键拖动时上方图标内容 | Gets or sets the top icon content when right button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? RightButtonTopIcon
|
||||||
|
{
|
||||||
|
get => GetValue(RightButtonTopIconProperty);
|
||||||
|
set => SetValue(RightButtonTopIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 右键拖动时下方图标内容依赖属性 | Right button bottom icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty RightButtonBottomIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(RightButtonBottomIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置右键拖动时下方图标内容 | Gets or sets the bottom icon content when right button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? RightButtonBottomIcon
|
||||||
|
{
|
||||||
|
get => GetValue(RightButtonBottomIconProperty);
|
||||||
|
set => SetValue(RightButtonBottomIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 右键拖动时左方图标内容依赖属性 | Right button left icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty RightButtonLeftIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(RightButtonLeftIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置右键拖动时左方图标内容 | Gets or sets the left icon content when right button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? RightButtonLeftIcon
|
||||||
|
{
|
||||||
|
get => GetValue(RightButtonLeftIconProperty);
|
||||||
|
set => SetValue(RightButtonLeftIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 右键拖动时右方图标内容依赖属性 | Right button right icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty RightButtonRightIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(RightButtonRightIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置右键拖动时右方图标内容 | Gets or sets the right icon content when right button is dragging
|
||||||
|
/// </summary>
|
||||||
|
public object? RightButtonRightIcon
|
||||||
|
{
|
||||||
|
get => GetValue(RightButtonRightIconProperty);
|
||||||
|
set => SetValue(RightButtonRightIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ThumbElement 内部属性 | ThumbElement Internal Property
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 操控点 UI 元素引用(从模板获取)| Thumb UI element reference (obtained from template)
|
||||||
|
/// </summary>
|
||||||
|
internal UIElement? ThumbElement { get; private set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 默认四向图标依赖属性 | Default Directional Icon Dependency Properties
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认状态上方图标内容依赖属性 | Default top icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty DefaultTopIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(DefaultTopIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置默认状态上方图标内容 | Gets or sets the top icon content in default state
|
||||||
|
/// </summary>
|
||||||
|
public object? DefaultTopIcon
|
||||||
|
{
|
||||||
|
get => GetValue(DefaultTopIconProperty);
|
||||||
|
set => SetValue(DefaultTopIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认状态下方图标内容依赖属性 | Default bottom icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty DefaultBottomIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(DefaultBottomIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置默认状态下方图标内容 | Gets or sets the bottom icon content in default state
|
||||||
|
/// </summary>
|
||||||
|
public object? DefaultBottomIcon
|
||||||
|
{
|
||||||
|
get => GetValue(DefaultBottomIconProperty);
|
||||||
|
set => SetValue(DefaultBottomIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认状态左方图标内容依赖属性 | Default left icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty DefaultLeftIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(DefaultLeftIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置默认状态左方图标内容 | Gets or sets the left icon content in default state
|
||||||
|
/// </summary>
|
||||||
|
public object? DefaultLeftIcon
|
||||||
|
{
|
||||||
|
get => GetValue(DefaultLeftIconProperty);
|
||||||
|
set => SetValue(DefaultLeftIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 默认状态右方图标内容依赖属性 | Default right icon content dependency property
|
||||||
|
/// </summary>
|
||||||
|
public static readonly DependencyProperty DefaultRightIconProperty =
|
||||||
|
DependencyProperty.Register(
|
||||||
|
nameof(DefaultRightIcon),
|
||||||
|
typeof(object),
|
||||||
|
typeof(VirtualJoystick),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取或设置默认状态右方图标内容 | Gets or sets the right icon content in default state
|
||||||
|
/// </summary>
|
||||||
|
public object? DefaultRightIcon
|
||||||
|
{
|
||||||
|
get => GetValue(DefaultRightIconProperty);
|
||||||
|
set => SetValue(DefaultRightIconProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 模板应用 | Template Application
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 应用控件模板时查找操控点元素并设置平移变换 | Find thumb element and set up translate transform when template is applied
|
||||||
|
/// </summary>
|
||||||
|
public override void OnApplyTemplate()
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate();
|
||||||
|
|
||||||
|
// 查找模板中的操控点元素 | Find the thumb element in the template
|
||||||
|
ThumbElement = GetTemplateChild(PART_Thumb) as UIElement;
|
||||||
|
|
||||||
|
if (ThumbElement != null)
|
||||||
|
{
|
||||||
|
// 设置平移变换用于拖拽跟随 | Set translate transform for drag following
|
||||||
|
ThumbElement.RenderTransform = _thumbTransform;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 鼠标交互事件处理 | Mouse Interaction Event Handlers
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标左键按下时开始拖拽 | Start dragging when left mouse button is pressed
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseLeftButtonDown(e);
|
||||||
|
|
||||||
|
// 如果已在拖拽中或控件未启用,忽略 | Ignore if already dragging or control is disabled
|
||||||
|
if (_isDragging || !IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StartDrag(MouseButtonType.Left);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标右键按下时开始拖拽 | Start dragging when right mouse button is pressed
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseRightButtonDown(e);
|
||||||
|
|
||||||
|
// 如果已在拖拽中或控件未启用,忽略 | Ignore if already dragging or control is disabled
|
||||||
|
if (_isDragging || !IsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StartDrag(MouseButtonType.Right);
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标移动时更新操控点位置和输出值 | Update thumb position and output values when mouse moves
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnMouseMove(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseMove(e);
|
||||||
|
|
||||||
|
if (!_isDragging)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 获取鼠标相对于控件的位置 | Get mouse position relative to the control
|
||||||
|
var mousePosition = e.GetPosition(this);
|
||||||
|
|
||||||
|
// 计算相对于中心的偏移量 | Calculate displacement from center
|
||||||
|
var dx = mousePosition.X - _centerPoint.X;
|
||||||
|
var dy = mousePosition.Y - _centerPoint.Y;
|
||||||
|
|
||||||
|
// 计算可用半径(取宽高较小值的一半)| Calculate available radius (half of the smaller dimension)
|
||||||
|
var radius = GetRadius();
|
||||||
|
if (radius <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 将位移限制在圆形区域内 | Clamp displacement within circular area
|
||||||
|
var (clampedDx, clampedDy) = JoystickCalculator.ClampToRadius(dx, dy, radius);
|
||||||
|
|
||||||
|
// 计算归一化输出(含死区映射)| Calculate normalized output (with dead zone mapping)
|
||||||
|
var (outputX, outputY) = JoystickCalculator.CalculateOutput(dx, dy, radius, DeadZone, JoystickMode);
|
||||||
|
|
||||||
|
// 更新输出值 | Update output values
|
||||||
|
OutputX = outputX;
|
||||||
|
OutputY = outputY;
|
||||||
|
|
||||||
|
// 更新操控点的平移变换位置(使用限制后的位移)| Update thumb translate transform (using clamped displacement)
|
||||||
|
_thumbTransform.X = clampedDx;
|
||||||
|
_thumbTransform.Y = clampedDy;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 异常时重置到中心位置并输出归零 | Reset to center and zero output on exception
|
||||||
|
ResetToCenter();
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标左键释放时结束拖拽 | End dragging when left mouse button is released
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseLeftButtonUp(e);
|
||||||
|
|
||||||
|
// 仅当正在拖拽且激活按键为左键时处理 | Only handle when dragging with left button active
|
||||||
|
if (!_isDragging || ActiveMouseButton != MouseButtonType.Left)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EndDrag();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 鼠标右键释放时结束拖拽 | End dragging when right mouse button is released
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnMouseRightButtonUp(e);
|
||||||
|
|
||||||
|
// 仅当正在拖拽且激活按键为右键时处理 | Only handle when dragging with right button active
|
||||||
|
if (!_isDragging || ActiveMouseButton != MouseButtonType.Right)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EndDrag();
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 拖拽辅助方法 | Drag Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始拖拽操作 | Start drag operation
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="buttonType">触发拖拽的鼠标按键类型 | Mouse button type that initiated the drag</param>
|
||||||
|
private void StartDrag(MouseButtonType buttonType)
|
||||||
|
{
|
||||||
|
// 捕获鼠标以接收控件外的鼠标事件 | Capture mouse to receive events outside the control
|
||||||
|
CaptureMouse();
|
||||||
|
|
||||||
|
// 设置激活的鼠标按键 | Set the active mouse button
|
||||||
|
ActiveMouseButton = buttonType;
|
||||||
|
|
||||||
|
// 计算控件中心点 | Calculate control center point
|
||||||
|
_centerPoint = new Point(ActualWidth / 2.0, ActualHeight / 2.0);
|
||||||
|
|
||||||
|
// 标记拖拽状态 | Mark dragging state
|
||||||
|
_isDragging = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 结束拖拽操作 | End drag operation
|
||||||
|
/// </summary>
|
||||||
|
private void EndDrag()
|
||||||
|
{
|
||||||
|
// 重置到中心位置并归零输出 | Reset to center and zero output
|
||||||
|
ResetToCenter();
|
||||||
|
|
||||||
|
// 释放鼠标捕获 | Release mouse capture
|
||||||
|
ReleaseMouseCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 重置操控点到中心位置,输出归零,清除拖拽状态 | Reset thumb to center, zero output, clear drag state
|
||||||
|
/// </summary>
|
||||||
|
internal void ResetToCenter()
|
||||||
|
{
|
||||||
|
// 输出值归零 | Zero output values
|
||||||
|
OutputX = 0.0;
|
||||||
|
OutputY = 0.0;
|
||||||
|
|
||||||
|
// 清除激活的鼠标按键 | Clear active mouse button
|
||||||
|
ActiveMouseButton = MouseButtonType.None;
|
||||||
|
|
||||||
|
// 清除拖拽状态 | Clear dragging state
|
||||||
|
_isDragging = false;
|
||||||
|
|
||||||
|
// 重置操控点平移变换到中心 | Reset thumb translate transform to center
|
||||||
|
_thumbTransform.X = 0.0;
|
||||||
|
_thumbTransform.Y = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取可用半径(取宽高较小值的一半)| Get available radius (half of the smaller dimension)
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>可用半径 | Available radius</returns>
|
||||||
|
private double GetRadius()
|
||||||
|
{
|
||||||
|
return Math.Min(ActualWidth, ActualHeight) / 2.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user