diff --git a/XP.Common/Controls/JoystickCalculator.cs b/XP.Common/Controls/JoystickCalculator.cs
new file mode 100644
index 0000000..f1bb028
--- /dev/null
+++ b/XP.Common/Controls/JoystickCalculator.cs
@@ -0,0 +1,87 @@
+using System;
+
+namespace XP.Common.Controls
+{
+ ///
+ /// 虚拟摇杆核心计算逻辑(纯函数,无副作用)| Virtual joystick core calculation logic (pure functions, no side effects)
+ ///
+ public static class JoystickCalculator
+ {
+ ///
+ /// 将位移限制在圆形区域内 | Clamp displacement within circular radius
+ ///
+ /// X 轴偏移量 | X axis displacement
+ /// Y 轴偏移量 | Y axis displacement
+ /// 最大半径(必须大于 0)| Maximum radius (must be greater than 0)
+ /// 限制后的 (clampedDx, clampedDy) 元组 | Clamped (clampedDx, clampedDy) tuple
+ 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);
+ }
+
+ ///
+ /// 计算归一化输出,包含死区映射 | Calculate normalized output with dead zone mapping
+ ///
+ /// X 轴偏移量 | X axis displacement
+ /// Y 轴偏移量 | Y axis displacement
+ /// 最大半径(必须大于 0)| Maximum radius (must be greater than 0)
+ /// 死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)
+ /// 摇杆模式 | Joystick mode
+ /// 归一化输出 (outputX, outputY) 元组,范围 [-1.0, 1.0] | Normalized output tuple, range [-1.0, 1.0]
+ 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);
+ }
+
+ ///
+ /// 对单轴比例值应用死区映射 | Apply dead zone mapping to a single axis ratio
+ ///
+ /// 轴比例值,范围 [-1.0, 1.0] | Axis ratio, range [-1.0, 1.0]
+ /// 死区比例,范围 [0.0, 1.0) | Dead zone ratio, range [0.0, 1.0)
+ /// 死区映射后的输出值 | Output value after dead zone mapping
+ 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);
+ }
+ }
+}
diff --git a/XP.Common/Controls/JoystickMode.cs b/XP.Common/Controls/JoystickMode.cs
new file mode 100644
index 0000000..0c44ede
--- /dev/null
+++ b/XP.Common/Controls/JoystickMode.cs
@@ -0,0 +1,18 @@
+namespace XP.Common.Controls
+{
+ ///
+ /// 虚拟摇杆轴模式枚举 | Virtual joystick axis mode enumeration
+ ///
+ public enum JoystickMode
+ {
+ ///
+ /// 双轴模式:X + Y 自由移动 | Dual axis mode: free movement in X and Y directions
+ ///
+ DualAxis,
+
+ ///
+ /// 单轴 Y 模式:仅上下移动 | Single axis Y mode: vertical movement only
+ ///
+ SingleAxisY
+ }
+}
diff --git a/XP.Common/Controls/MouseButtonType.cs b/XP.Common/Controls/MouseButtonType.cs
new file mode 100644
index 0000000..9cd0753
--- /dev/null
+++ b/XP.Common/Controls/MouseButtonType.cs
@@ -0,0 +1,23 @@
+namespace XP.Common.Controls
+{
+ ///
+ /// 鼠标按键类型枚举 | Mouse button type enumeration
+ ///
+ public enum MouseButtonType
+ {
+ ///
+ /// 未按下 | No button pressed
+ ///
+ None,
+
+ ///
+ /// 鼠标左键 | Left mouse button
+ ///
+ Left,
+
+ ///
+ /// 鼠标右键 | Right mouse button
+ ///
+ Right
+ }
+}
diff --git a/XP.Common/Controls/Themes/Generic.xaml b/XP.Common/Controls/Themes/Generic.xaml
new file mode 100644
index 0000000..c48a889
--- /dev/null
+++ b/XP.Common/Controls/Themes/Generic.xaml
@@ -0,0 +1,219 @@
+
+
+
+
+
diff --git a/XP.Common/Controls/VirtualJoystick.cs b/XP.Common/Controls/VirtualJoystick.cs
new file mode 100644
index 0000000..50d8994
--- /dev/null
+++ b/XP.Common/Controls/VirtualJoystick.cs
@@ -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
+{
+ ///
+ /// 虚拟摇杆自定义控件,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick custom control providing mouse drag interaction within a circular area
+ ///
+ ///
+ /// 支持双轴/单轴 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.
+ ///
+ [TemplatePart(Name = PART_Thumb, Type = typeof(UIElement))]
+ public class VirtualJoystick : Control
+ {
+ ///
+ /// 操控点模板部件名称 | Thumb template part name
+ ///
+ private const string PART_Thumb = "PART_Thumb";
+
+ #region 私有字段 | Private Fields
+
+ ///
+ /// 是否正在拖拽 | Whether dragging is in progress
+ ///
+ private bool _isDragging;
+
+ ///
+ /// 控件中心点坐标 | Control center point coordinates
+ ///
+ private Point _centerPoint;
+
+ ///
+ /// 操控点的平移变换,用于跟随鼠标移动 | Translate transform for thumb element to follow mouse movement
+ ///
+ 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
+
+ ///
+ /// 摇杆轴模式依赖属性 | Joystick axis mode dependency property
+ ///
+ public static readonly DependencyProperty JoystickModeProperty =
+ DependencyProperty.Register(
+ nameof(JoystickMode),
+ typeof(JoystickMode),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(JoystickMode.DualAxis));
+
+ ///
+ /// 获取或设置摇杆轴模式(双轴或单轴 Y)| Gets or sets the joystick axis mode (DualAxis or SingleAxisY)
+ ///
+ public JoystickMode JoystickMode
+ {
+ get => (JoystickMode)GetValue(JoystickModeProperty);
+ set => SetValue(JoystickModeProperty, value);
+ }
+
+ #endregion
+
+ #region OutputX 只读依赖属性 | OutputX Read-Only Dependency Property
+
+ ///
+ /// OutputX 只读依赖属性键 | OutputX read-only dependency property key
+ ///
+ private static readonly DependencyPropertyKey OutputXPropertyKey =
+ DependencyProperty.RegisterReadOnly(
+ nameof(OutputX),
+ typeof(double),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(0.0));
+
+ ///
+ /// X 轴归一化输出依赖属性 | X-axis normalized output dependency property
+ ///
+ public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty;
+
+ ///
+ /// 获取 X 轴归一化输出值,范围 [-1.0, 1.0] | Gets the X-axis normalized output value, range [-1.0, 1.0]
+ ///
+ public double OutputX
+ {
+ get => (double)GetValue(OutputXProperty);
+ internal set => SetValue(OutputXPropertyKey, value);
+ }
+
+ #endregion
+
+ #region OutputY 只读依赖属性 | OutputY Read-Only Dependency Property
+
+ ///
+ /// OutputY 只读依赖属性键 | OutputY read-only dependency property key
+ ///
+ private static readonly DependencyPropertyKey OutputYPropertyKey =
+ DependencyProperty.RegisterReadOnly(
+ nameof(OutputY),
+ typeof(double),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(0.0));
+
+ ///
+ /// Y 轴归一化输出依赖属性 | Y-axis normalized output dependency property
+ ///
+ public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty;
+
+ ///
+ /// 获取 Y 轴归一化输出值,范围 [-1.0, 1.0] | Gets the Y-axis normalized output value, range [-1.0, 1.0]
+ ///
+ public double OutputY
+ {
+ get => (double)GetValue(OutputYProperty);
+ internal set => SetValue(OutputYPropertyKey, value);
+ }
+
+ #endregion
+
+ #region DeadZone 依赖属性 | DeadZone Dependency Property
+
+ ///
+ /// 死区比例依赖属性 | Dead zone ratio dependency property
+ ///
+ public static readonly DependencyProperty DeadZoneProperty =
+ DependencyProperty.Register(
+ nameof(DeadZone),
+ typeof(double),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(0.05));
+
+ ///
+ /// 获取或设置死区比例,范围 [0.0, 1.0],默认值 0.05 | Gets or sets the dead zone ratio, range [0.0, 1.0], default 0.05
+ ///
+ public double DeadZone
+ {
+ get => (double)GetValue(DeadZoneProperty);
+ set => SetValue(DeadZoneProperty, value);
+ }
+
+ #endregion
+
+ #region ActiveMouseButton 只读依赖属性 | ActiveMouseButton Read-Only Dependency Property
+
+ ///
+ /// ActiveMouseButton 只读依赖属性键 | ActiveMouseButton read-only dependency property key
+ ///
+ private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey =
+ DependencyProperty.RegisterReadOnly(
+ nameof(ActiveMouseButton),
+ typeof(MouseButtonType),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(MouseButtonType.None));
+
+ ///
+ /// 当前激活的鼠标按键依赖属性 | Active mouse button dependency property
+ ///
+ public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty;
+
+ ///
+ /// 获取当前激活的鼠标按键(None、Left、Right)| Gets the currently active mouse button (None, Left, Right)
+ ///
+ public MouseButtonType ActiveMouseButton
+ {
+ get => (MouseButtonType)GetValue(ActiveMouseButtonProperty);
+ internal set => SetValue(ActiveMouseButtonPropertyKey, value);
+ }
+
+ #endregion
+
+ #region 左键四向图标依赖属性 | Left Button Directional Icon Dependency Properties
+
+ ///
+ /// 左键拖动时上方图标内容依赖属性 | Left button top icon content dependency property
+ ///
+ public static readonly DependencyProperty LeftButtonTopIconProperty =
+ DependencyProperty.Register(
+ nameof(LeftButtonTopIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置左键拖动时上方图标内容 | Gets or sets the top icon content when left button is dragging
+ ///
+ public object? LeftButtonTopIcon
+ {
+ get => GetValue(LeftButtonTopIconProperty);
+ set => SetValue(LeftButtonTopIconProperty, value);
+ }
+
+ ///
+ /// 左键拖动时下方图标内容依赖属性 | Left button bottom icon content dependency property
+ ///
+ public static readonly DependencyProperty LeftButtonBottomIconProperty =
+ DependencyProperty.Register(
+ nameof(LeftButtonBottomIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置左键拖动时下方图标内容 | Gets or sets the bottom icon content when left button is dragging
+ ///
+ public object? LeftButtonBottomIcon
+ {
+ get => GetValue(LeftButtonBottomIconProperty);
+ set => SetValue(LeftButtonBottomIconProperty, value);
+ }
+
+ ///
+ /// 左键拖动时左方图标内容依赖属性 | Left button left icon content dependency property
+ ///
+ public static readonly DependencyProperty LeftButtonLeftIconProperty =
+ DependencyProperty.Register(
+ nameof(LeftButtonLeftIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置左键拖动时左方图标内容 | Gets or sets the left icon content when left button is dragging
+ ///
+ public object? LeftButtonLeftIcon
+ {
+ get => GetValue(LeftButtonLeftIconProperty);
+ set => SetValue(LeftButtonLeftIconProperty, value);
+ }
+
+ ///
+ /// 左键拖动时右方图标内容依赖属性 | Left button right icon content dependency property
+ ///
+ public static readonly DependencyProperty LeftButtonRightIconProperty =
+ DependencyProperty.Register(
+ nameof(LeftButtonRightIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置左键拖动时右方图标内容 | Gets or sets the right icon content when left button is dragging
+ ///
+ public object? LeftButtonRightIcon
+ {
+ get => GetValue(LeftButtonRightIconProperty);
+ set => SetValue(LeftButtonRightIconProperty, value);
+ }
+
+ #endregion
+
+ #region 右键四向图标依赖属性 | Right Button Directional Icon Dependency Properties
+
+ ///
+ /// 右键拖动时上方图标内容依赖属性 | Right button top icon content dependency property
+ ///
+ public static readonly DependencyProperty RightButtonTopIconProperty =
+ DependencyProperty.Register(
+ nameof(RightButtonTopIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置右键拖动时上方图标内容 | Gets or sets the top icon content when right button is dragging
+ ///
+ public object? RightButtonTopIcon
+ {
+ get => GetValue(RightButtonTopIconProperty);
+ set => SetValue(RightButtonTopIconProperty, value);
+ }
+
+ ///
+ /// 右键拖动时下方图标内容依赖属性 | Right button bottom icon content dependency property
+ ///
+ public static readonly DependencyProperty RightButtonBottomIconProperty =
+ DependencyProperty.Register(
+ nameof(RightButtonBottomIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置右键拖动时下方图标内容 | Gets or sets the bottom icon content when right button is dragging
+ ///
+ public object? RightButtonBottomIcon
+ {
+ get => GetValue(RightButtonBottomIconProperty);
+ set => SetValue(RightButtonBottomIconProperty, value);
+ }
+
+ ///
+ /// 右键拖动时左方图标内容依赖属性 | Right button left icon content dependency property
+ ///
+ public static readonly DependencyProperty RightButtonLeftIconProperty =
+ DependencyProperty.Register(
+ nameof(RightButtonLeftIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置右键拖动时左方图标内容 | Gets or sets the left icon content when right button is dragging
+ ///
+ public object? RightButtonLeftIcon
+ {
+ get => GetValue(RightButtonLeftIconProperty);
+ set => SetValue(RightButtonLeftIconProperty, value);
+ }
+
+ ///
+ /// 右键拖动时右方图标内容依赖属性 | Right button right icon content dependency property
+ ///
+ public static readonly DependencyProperty RightButtonRightIconProperty =
+ DependencyProperty.Register(
+ nameof(RightButtonRightIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置右键拖动时右方图标内容 | Gets or sets the right icon content when right button is dragging
+ ///
+ public object? RightButtonRightIcon
+ {
+ get => GetValue(RightButtonRightIconProperty);
+ set => SetValue(RightButtonRightIconProperty, value);
+ }
+
+ #endregion
+
+ #region ThumbElement 内部属性 | ThumbElement Internal Property
+
+ ///
+ /// 操控点 UI 元素引用(从模板获取)| Thumb UI element reference (obtained from template)
+ ///
+ internal UIElement? ThumbElement { get; private set; }
+
+ #endregion
+
+ #region 默认四向图标依赖属性 | Default Directional Icon Dependency Properties
+
+ ///
+ /// 默认状态上方图标内容依赖属性 | Default top icon content dependency property
+ ///
+ public static readonly DependencyProperty DefaultTopIconProperty =
+ DependencyProperty.Register(
+ nameof(DefaultTopIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置默认状态上方图标内容 | Gets or sets the top icon content in default state
+ ///
+ public object? DefaultTopIcon
+ {
+ get => GetValue(DefaultTopIconProperty);
+ set => SetValue(DefaultTopIconProperty, value);
+ }
+
+ ///
+ /// 默认状态下方图标内容依赖属性 | Default bottom icon content dependency property
+ ///
+ public static readonly DependencyProperty DefaultBottomIconProperty =
+ DependencyProperty.Register(
+ nameof(DefaultBottomIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置默认状态下方图标内容 | Gets or sets the bottom icon content in default state
+ ///
+ public object? DefaultBottomIcon
+ {
+ get => GetValue(DefaultBottomIconProperty);
+ set => SetValue(DefaultBottomIconProperty, value);
+ }
+
+ ///
+ /// 默认状态左方图标内容依赖属性 | Default left icon content dependency property
+ ///
+ public static readonly DependencyProperty DefaultLeftIconProperty =
+ DependencyProperty.Register(
+ nameof(DefaultLeftIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置默认状态左方图标内容 | Gets or sets the left icon content in default state
+ ///
+ public object? DefaultLeftIcon
+ {
+ get => GetValue(DefaultLeftIconProperty);
+ set => SetValue(DefaultLeftIconProperty, value);
+ }
+
+ ///
+ /// 默认状态右方图标内容依赖属性 | Default right icon content dependency property
+ ///
+ public static readonly DependencyProperty DefaultRightIconProperty =
+ DependencyProperty.Register(
+ nameof(DefaultRightIcon),
+ typeof(object),
+ typeof(VirtualJoystick),
+ new PropertyMetadata(null));
+
+ ///
+ /// 获取或设置默认状态右方图标内容 | Gets or sets the right icon content in default state
+ ///
+ public object? DefaultRightIcon
+ {
+ get => GetValue(DefaultRightIconProperty);
+ set => SetValue(DefaultRightIconProperty, value);
+ }
+
+ #endregion
+
+ #region 模板应用 | Template Application
+
+ ///
+ /// 应用控件模板时查找操控点元素并设置平移变换 | Find thumb element and set up translate transform when template is applied
+ ///
+ 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
+
+ ///
+ /// 鼠标左键按下时开始拖拽 | Start dragging when left mouse button is pressed
+ ///
+ 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;
+ }
+
+ ///
+ /// 鼠标右键按下时开始拖拽 | Start dragging when right mouse button is pressed
+ ///
+ 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;
+ }
+
+ ///
+ /// 鼠标移动时更新操控点位置和输出值 | Update thumb position and output values when mouse moves
+ ///
+ 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;
+ }
+
+ ///
+ /// 鼠标左键释放时结束拖拽 | End dragging when left mouse button is released
+ ///
+ 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;
+ }
+
+ ///
+ /// 鼠标右键释放时结束拖拽 | End dragging when right mouse button is released
+ ///
+ 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
+
+ ///
+ /// 开始拖拽操作 | Start drag operation
+ ///
+ /// 触发拖拽的鼠标按键类型 | Mouse button type that initiated the drag
+ 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;
+ }
+
+ ///
+ /// 结束拖拽操作 | End drag operation
+ ///
+ private void EndDrag()
+ {
+ // 重置到中心位置并归零输出 | Reset to center and zero output
+ ResetToCenter();
+
+ // 释放鼠标捕获 | Release mouse capture
+ ReleaseMouseCapture();
+ }
+
+ ///
+ /// 重置操控点到中心位置,输出归零,清除拖拽状态 | Reset thumb to center, zero output, clear drag state
+ ///
+ 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;
+ }
+
+ ///
+ /// 获取可用半径(取宽高较小值的一半)| Get available radius (half of the smaller dimension)
+ ///
+ /// 可用半径 | Available radius
+ private double GetRadius()
+ {
+ return Math.Min(ActualWidth, ActualHeight) / 2.0;
+ }
+
+ #endregion
+ }
+}