From be03bdf34e682d191aab0786a0235a55cd3f9fc5 Mon Sep 17 00:00:00 2001 From: QI Mingxuan Date: Wed, 22 Apr 2026 20:48:39 +0800 Subject: [PATCH] =?UTF-8?q?Common=E7=B1=BB=E5=BA=93=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=91=87=E6=9D=86=E6=8E=A7=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- XP.Common/Controls/JoystickCalculator.cs | 87 ++++ XP.Common/Controls/JoystickMode.cs | 18 + XP.Common/Controls/MouseButtonType.cs | 23 + XP.Common/Controls/Themes/Generic.xaml | 219 ++++++++ XP.Common/Controls/VirtualJoystick.cs | 629 +++++++++++++++++++++++ 5 files changed, 976 insertions(+) create mode 100644 XP.Common/Controls/JoystickCalculator.cs create mode 100644 XP.Common/Controls/JoystickMode.cs create mode 100644 XP.Common/Controls/MouseButtonType.cs create mode 100644 XP.Common/Controls/Themes/Generic.xaml create mode 100644 XP.Common/Controls/VirtualJoystick.cs 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 + } +}