已合并 PR 34: 合并AxisControl运动控制控件
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,361 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace XP.Common.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 虚拟摇杆 UserControl,提供圆形区域内的鼠标拖拽操控能力 | Virtual joystick UserControl 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>
|
||||
public partial class VirtualJoystick : UserControl
|
||||
{
|
||||
#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</summary>
|
||||
private readonly TranslateTransform _thumbTransform = new TranslateTransform();
|
||||
|
||||
/// <summary>操控点元素引用 | Thumb element reference</summary>
|
||||
private Ellipse? _thumbElement;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构造函数 | Constructor
|
||||
|
||||
public VirtualJoystick()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// 控件加载完成后绑定操控点的 TranslateTransform | Bind thumb TranslateTransform after control loaded
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
_thumbElement = FindName("PART_Thumb") as Ellipse;
|
||||
if (_thumbElement != null)
|
||||
_thumbElement.RenderTransform = _thumbTransform;
|
||||
|
||||
// 初始化时更新背景和图标可见性 | Update background and icon visibility on init
|
||||
UpdateIconVisibility();
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region JoystickMode 依赖属性 | JoystickMode Dependency Property
|
||||
|
||||
public static readonly DependencyProperty JoystickModeProperty =
|
||||
DependencyProperty.Register(nameof(JoystickMode), typeof(JoystickMode), typeof(VirtualJoystick),
|
||||
new PropertyMetadata(JoystickMode.DualAxis, OnJoystickModeChanged));
|
||||
|
||||
/// <summary>获取或设置摇杆轴模式 | Gets or sets the joystick axis mode</summary>
|
||||
public JoystickMode JoystickMode
|
||||
{
|
||||
get => (JoystickMode)GetValue(JoystickModeProperty);
|
||||
set => SetValue(JoystickModeProperty, value);
|
||||
}
|
||||
|
||||
private static void OnJoystickModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is VirtualJoystick joystick)
|
||||
joystick.UpdateIconVisibility();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OutputX/OutputY 只读依赖属性 | OutputX/OutputY Read-Only Dependency Properties
|
||||
|
||||
private static readonly DependencyPropertyKey OutputXPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(OutputX), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
|
||||
public static readonly DependencyProperty OutputXProperty = OutputXPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>X 轴归一化输出值 [-1.0, 1.0] | X-axis normalized output</summary>
|
||||
public double OutputX
|
||||
{
|
||||
get => (double)GetValue(OutputXProperty);
|
||||
internal set => SetValue(OutputXPropertyKey, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey OutputYPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(OutputY), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.0));
|
||||
public static readonly DependencyProperty OutputYProperty = OutputYPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>Y 轴归一化输出值 [-1.0, 1.0] | Y-axis normalized output</summary>
|
||||
public double OutputY
|
||||
{
|
||||
get => (double)GetValue(OutputYProperty);
|
||||
internal set => SetValue(OutputYPropertyKey, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DeadZone 依赖属性 | DeadZone Dependency Property
|
||||
|
||||
public static readonly DependencyProperty DeadZoneProperty =
|
||||
DependencyProperty.Register(nameof(DeadZone), typeof(double), typeof(VirtualJoystick), new PropertyMetadata(0.05));
|
||||
|
||||
/// <summary>死区比例 [0.0, 1.0],默认 0.05 | Dead zone ratio, default 0.05</summary>
|
||||
public double DeadZone
|
||||
{
|
||||
get => (double)GetValue(DeadZoneProperty);
|
||||
set => SetValue(DeadZoneProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ActiveMouseButton 只读依赖属性 | ActiveMouseButton Read-Only Dependency Property
|
||||
|
||||
private static readonly DependencyPropertyKey ActiveMouseButtonPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(nameof(ActiveMouseButton), typeof(MouseButtonType), typeof(VirtualJoystick),
|
||||
new PropertyMetadata(MouseButtonType.None, OnActiveMouseButtonChanged));
|
||||
public static readonly DependencyProperty ActiveMouseButtonProperty = ActiveMouseButtonPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>当前激活的鼠标按键 | Currently active mouse button</summary>
|
||||
public MouseButtonType ActiveMouseButton
|
||||
{
|
||||
get => (MouseButtonType)GetValue(ActiveMouseButtonProperty);
|
||||
internal set => SetValue(ActiveMouseButtonPropertyKey, value);
|
||||
}
|
||||
|
||||
private static void OnActiveMouseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is VirtualJoystick joystick)
|
||||
joystick.UpdateIconVisibility();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 四向图标依赖属性 | Directional Icon Dependency Properties
|
||||
|
||||
// 左键图标 | Left button icons
|
||||
public static readonly DependencyProperty LeftButtonTopIconProperty = DependencyProperty.Register(nameof(LeftButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonTopIcon { get => GetValue(LeftButtonTopIconProperty); set => SetValue(LeftButtonTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonBottomIconProperty = DependencyProperty.Register(nameof(LeftButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonBottomIcon { get => GetValue(LeftButtonBottomIconProperty); set => SetValue(LeftButtonBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonLeftIconProperty = DependencyProperty.Register(nameof(LeftButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonLeftIcon { get => GetValue(LeftButtonLeftIconProperty); set => SetValue(LeftButtonLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty LeftButtonRightIconProperty = DependencyProperty.Register(nameof(LeftButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? LeftButtonRightIcon { get => GetValue(LeftButtonRightIconProperty); set => SetValue(LeftButtonRightIconProperty, value); }
|
||||
|
||||
// 右键图标 | Right button icons
|
||||
public static readonly DependencyProperty RightButtonTopIconProperty = DependencyProperty.Register(nameof(RightButtonTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonTopIcon { get => GetValue(RightButtonTopIconProperty); set => SetValue(RightButtonTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonBottomIconProperty = DependencyProperty.Register(nameof(RightButtonBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonBottomIcon { get => GetValue(RightButtonBottomIconProperty); set => SetValue(RightButtonBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonLeftIconProperty = DependencyProperty.Register(nameof(RightButtonLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonLeftIcon { get => GetValue(RightButtonLeftIconProperty); set => SetValue(RightButtonLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty RightButtonRightIconProperty = DependencyProperty.Register(nameof(RightButtonRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? RightButtonRightIcon { get => GetValue(RightButtonRightIconProperty); set => SetValue(RightButtonRightIconProperty, value); }
|
||||
|
||||
// 默认图标 | Default icons
|
||||
public static readonly DependencyProperty DefaultTopIconProperty = DependencyProperty.Register(nameof(DefaultTopIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultTopIcon { get => GetValue(DefaultTopIconProperty); set => SetValue(DefaultTopIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultBottomIconProperty = DependencyProperty.Register(nameof(DefaultBottomIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultBottomIcon { get => GetValue(DefaultBottomIconProperty); set => SetValue(DefaultBottomIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultLeftIconProperty = DependencyProperty.Register(nameof(DefaultLeftIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultLeftIcon { get => GetValue(DefaultLeftIconProperty); set => SetValue(DefaultLeftIconProperty, value); }
|
||||
|
||||
public static readonly DependencyProperty DefaultRightIconProperty = DependencyProperty.Register(nameof(DefaultRightIcon), typeof(object), typeof(VirtualJoystick), new PropertyMetadata(null));
|
||||
public object? DefaultRightIcon { get => GetValue(DefaultRightIconProperty); set => SetValue(DefaultRightIconProperty, value); }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 鼠标交互事件处理 | Mouse Interaction Event Handlers
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
if (_isDragging || !IsEnabled) return;
|
||||
StartDrag(MouseButtonType.Left);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseRightButtonDown(e);
|
||||
if (_isDragging || !IsEnabled) return;
|
||||
StartDrag(MouseButtonType.Right);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
if (!_isDragging) return;
|
||||
|
||||
try
|
||||
{
|
||||
var mousePosition = e.GetPosition(this);
|
||||
var dx = mousePosition.X - _centerPoint.X;
|
||||
var dy = mousePosition.Y - _centerPoint.Y;
|
||||
|
||||
// SingleAxisY 模式下强制水平位移为零,操控点只能上下移动 | Force dx=0 in SingleAxisY mode, thumb moves vertically only
|
||||
if (JoystickMode == JoystickMode.SingleAxisY)
|
||||
dx = 0;
|
||||
|
||||
var radius = GetRadius();
|
||||
if (radius <= 0) return;
|
||||
|
||||
var (clampedDx, clampedDy) = JoystickCalculator.ClampToRadius(dx, dy, radius);
|
||||
var (outputX, outputY) = JoystickCalculator.CalculateOutput(dx, dy, radius, DeadZone, JoystickMode);
|
||||
|
||||
OutputX = outputX;
|
||||
OutputY = outputY;
|
||||
_thumbTransform.X = clampedDx;
|
||||
_thumbTransform.Y = clampedDy;
|
||||
}
|
||||
catch
|
||||
{
|
||||
ResetToCenter();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
if (!_isDragging || ActiveMouseButton != MouseButtonType.Left) return;
|
||||
EndDrag();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseRightButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseRightButtonUp(e);
|
||||
if (!_isDragging || ActiveMouseButton != MouseButtonType.Right) return;
|
||||
EndDrag();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 拖拽辅助方法 | Drag Helper Methods
|
||||
|
||||
private void StartDrag(MouseButtonType buttonType)
|
||||
{
|
||||
CaptureMouse();
|
||||
ActiveMouseButton = buttonType;
|
||||
_centerPoint = new Point(ActualWidth / 2.0, ActualHeight / 2.0);
|
||||
_isDragging = true;
|
||||
}
|
||||
|
||||
private void EndDrag()
|
||||
{
|
||||
ResetToCenter();
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
/// <summary>重置操控点到中心位置 | Reset thumb to center</summary>
|
||||
internal void ResetToCenter()
|
||||
{
|
||||
OutputX = 0.0;
|
||||
OutputY = 0.0;
|
||||
ActiveMouseButton = MouseButtonType.None;
|
||||
_isDragging = false;
|
||||
_thumbTransform.X = 0.0;
|
||||
_thumbTransform.Y = 0.0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可用半径(限制为控件尺寸的 80%)| Get usable radius (limited to 80% of control size)
|
||||
/// 双轴取宽高最小值的一半,单轴取高度的一半 | DualAxis uses min(W,H)/2, SingleAxisY uses H/2
|
||||
/// </summary>
|
||||
private double GetRadius()
|
||||
{
|
||||
var raw = JoystickMode == JoystickMode.SingleAxisY
|
||||
? ActualHeight / 2.0
|
||||
: Math.Min(ActualWidth, ActualHeight) / 2.0;
|
||||
return raw * 0.8;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 图标可见性切换 | Icon Visibility Switching
|
||||
|
||||
/// <summary>
|
||||
/// 根据 ActiveMouseButton 和 JoystickMode 更新图标可见性 | Update icon visibility based on ActiveMouseButton and JoystickMode
|
||||
/// </summary>
|
||||
private void UpdateIconVisibility()
|
||||
{
|
||||
// 查找命名元素 | Find named elements
|
||||
var defaultTop = FindName("DefaultTopPresenter") as UIElement;
|
||||
var defaultBottom = FindName("DefaultBottomPresenter") as UIElement;
|
||||
var defaultLeft = FindName("DefaultLeftPresenter") as UIElement;
|
||||
var defaultRight = FindName("DefaultRightPresenter") as UIElement;
|
||||
var leftTop = FindName("LeftTopPresenter") as UIElement;
|
||||
var leftBottom = FindName("LeftBottomPresenter") as UIElement;
|
||||
var leftLeft = FindName("LeftLeftPresenter") as UIElement;
|
||||
var leftRight = FindName("LeftRightPresenter") as UIElement;
|
||||
var rightTop = FindName("RightTopPresenter") as UIElement;
|
||||
var rightBottom = FindName("RightBottomPresenter") as UIElement;
|
||||
var rightLeft = FindName("RightLeftPresenter") as UIElement;
|
||||
var rightRight = FindName("RightRightPresenter") as UIElement;
|
||||
var dualBg = FindName("DualAxisBackground") as UIElement;
|
||||
var singleBg = FindName("SingleAxisBackground") as UIElement;
|
||||
var hLine = FindName("HorizontalLine") as UIElement;
|
||||
|
||||
if (defaultTop == null) return; // 控件尚未加载 | Control not yet loaded
|
||||
|
||||
// 切换背景形状:双轴=圆形,单轴=腰圆 | Switch background: DualAxis=circle, SingleAxisY=capsule
|
||||
var isSingleAxis = JoystickMode == JoystickMode.SingleAxisY;
|
||||
if (dualBg != null) dualBg.Visibility = isSingleAxis ? Visibility.Collapsed : Visibility.Visible;
|
||||
if (singleBg != null) singleBg.Visibility = isSingleAxis ? Visibility.Visible : Visibility.Collapsed;
|
||||
if (hLine != null) hLine.Visibility = isSingleAxis ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
// 先全部隐藏 | Hide all first
|
||||
SetVisibility(Visibility.Collapsed, leftTop, leftBottom, leftLeft, leftRight, rightTop, rightBottom, rightLeft, rightRight);
|
||||
SetVisibility(Visibility.Visible, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
|
||||
// 根据 ActiveMouseButton 切换 | Switch based on ActiveMouseButton
|
||||
switch (ActiveMouseButton)
|
||||
{
|
||||
case MouseButtonType.Left:
|
||||
SetVisibility(Visibility.Collapsed, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
SetVisibility(Visibility.Visible, leftTop, leftBottom, leftLeft, leftRight);
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x3A, 0x7B, 0xC8));
|
||||
break;
|
||||
case MouseButtonType.Right:
|
||||
SetVisibility(Visibility.Collapsed, defaultTop, defaultBottom, defaultLeft, defaultRight);
|
||||
SetVisibility(Visibility.Visible, rightTop, rightBottom, rightLeft, rightRight);
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x5B, 0xA8, 0x5B));
|
||||
break;
|
||||
default:
|
||||
if (_thumbElement != null) _thumbElement.Fill = new SolidColorBrush(Color.FromRgb(0x4A, 0x90, 0xD9));
|
||||
break;
|
||||
}
|
||||
|
||||
// SingleAxisY 模式下隐藏左右图标 | Hide left/right icons in SingleAxisY mode
|
||||
if (isSingleAxis)
|
||||
{
|
||||
SetVisibility(Visibility.Collapsed, defaultLeft, defaultRight, leftLeft, leftRight, rightLeft, rightRight);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetVisibility(Visibility visibility, params UIElement?[] elements)
|
||||
{
|
||||
foreach (var element in elements)
|
||||
if (element != null) element.Visibility = visibility;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<UserControl x:Class="XP.Common.Controls.VirtualJoystick"
|
||||
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:XP.Common.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignWidth="200" d:DesignHeight="200"
|
||||
Cursor="Hand">
|
||||
|
||||
<Grid x:Name="PART_Root">
|
||||
|
||||
<!-- ========== 双轴模式背景:圆形 | DualAxis background: Circle ========== -->
|
||||
<Ellipse x:Name="DualAxisBackground"
|
||||
Fill="#FFF5F5F5" Stroke="#FFCCCCCC" StrokeThickness="1.5" />
|
||||
|
||||
<!-- ========== 单轴模式背景:腰圆 | SingleAxisY background: Capsule ========== -->
|
||||
<Border x:Name="SingleAxisBackground"
|
||||
Background="#FFF5F5F5" BorderBrush="#FFCCCCCC" BorderThickness="1.5"
|
||||
CornerRadius="20" Visibility="Collapsed" />
|
||||
|
||||
<!-- 十字形背景虚线(双轴显示水平+垂直,单轴只显示垂直)| Cross-shaped dashed lines -->
|
||||
<Line x:Name="HorizontalLine" X1="0" Y1="0" X2="1" Y2="0" Stretch="Fill"
|
||||
Stroke="#FFD0D0D0" StrokeThickness="1" StrokeDashArray="4 2"
|
||||
VerticalAlignment="Center" IsHitTestVisible="False" />
|
||||
<Line X1="0" Y1="0" X2="0" Y2="1" Stretch="Fill"
|
||||
Stroke="#FFD0D0D0" StrokeThickness="1" StrokeDashArray="4 2"
|
||||
HorizontalAlignment="Center" IsHitTestVisible="False" />
|
||||
|
||||
<!-- 四向图标区域 | Four-directional icon area -->
|
||||
<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 -->
|
||||
<ContentPresenter x:Name="DefaultTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding DefaultTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" />
|
||||
<ContentPresenter x:Name="LeftTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding LeftButtonTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightTopPresenter" Grid.Row="0" Grid.Column="1"
|
||||
Content="{Binding RightButtonTopIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Top" Visibility="Collapsed" />
|
||||
|
||||
<!-- 下方图标 | Bottom icons -->
|
||||
<ContentPresenter x:Name="DefaultBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding DefaultBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" />
|
||||
<ContentPresenter x:Name="LeftBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding LeftButtonBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightBottomPresenter" Grid.Row="2" Grid.Column="1"
|
||||
Content="{Binding RightButtonBottomIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Bottom" Visibility="Collapsed" />
|
||||
|
||||
<!-- 左方图标 | Left icons -->
|
||||
<ContentPresenter x:Name="DefaultLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding DefaultLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" />
|
||||
<ContentPresenter x:Name="LeftLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding LeftButtonLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightLeftPresenter" Grid.Row="1" Grid.Column="0"
|
||||
Content="{Binding RightButtonLeftIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
|
||||
<!-- 右方图标 | Right icons -->
|
||||
<ContentPresenter x:Name="DefaultRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding DefaultRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" />
|
||||
<ContentPresenter x:Name="LeftRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding LeftButtonRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
<ContentPresenter x:Name="RightRightPresenter" Grid.Row="1" Grid.Column="2"
|
||||
Content="{Binding RightButtonRightIcon, RelativeSource={RelativeSource AncestorType=local:VirtualJoystick}}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Visibility="Collapsed" />
|
||||
</Grid>
|
||||
|
||||
<!-- 中心操控点 | Center thumb -->
|
||||
<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>
|
||||
</UserControl>
|
||||
@@ -31,8 +31,7 @@
|
||||
<PackageReference Include="Telerik.UI.for.Wpf.NetCore.Xaml" Version="2024.1.408" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Controls\ViewModels\" />
|
||||
<Folder Include="Controls\Views\" />
|
||||
|
||||
<Folder Include="Extensions\" />
|
||||
<Folder Include="Serialization\" />
|
||||
<Folder Include="Converters\" />
|
||||
|
||||
Reference in New Issue
Block a user