217 lines
10 KiB
C#
217 lines
10 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Data;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media.Animation;
|
|
|
|
namespace OperationControl.Controls
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for ManualControlPad.xaml
|
|
/// </summary>
|
|
public partial class ManualControlPad : UserControl, INotifyPropertyChanged
|
|
{
|
|
public ObservableCollection<string> MoveModes { get; } = new();
|
|
private string _selectedMoveMode;
|
|
public string SelectedMoveMode
|
|
{
|
|
get => _selectedMoveMode;
|
|
set { _selectedMoveMode = value; /* Raise se precisar */ }
|
|
}
|
|
|
|
public ManualControlPad()
|
|
{
|
|
InitializeComponent();
|
|
|
|
MoveModes.Clear();
|
|
MoveModes.Add("RodasDianteiras");
|
|
MoveModes.Add("RodasTraseiras");
|
|
MoveModes.Add("RotacionarNoEixo");
|
|
MoveModes.Add("MovimentoArco");
|
|
MoveModes.Add("MovimentoLateral");
|
|
MoveModes.Add("MovimentoDiagonal");
|
|
MoveModes.Add("Diagnostico");
|
|
|
|
SelectedMoveMode = MoveModes[0];
|
|
|
|
Loaded += (_, __) => UpdateThumb();
|
|
SizeChanged += (_, __) => { if (ManualContainer.Height > 0) AnimateManual(true, instant: true); };
|
|
}
|
|
|
|
// ======= Header displays (bind do app) =======
|
|
public double SpeedSpKmh { get => (double)GetValue(SpeedSpKmhProperty); set => SetValue(SpeedSpKmhProperty, value); }
|
|
public static readonly DependencyProperty SpeedSpKmhProperty = DependencyProperty.Register(nameof(SpeedSpKmh), typeof(double), typeof(ManualControlPad), new PropertyMetadata(0d));
|
|
public double SpeedCurKmh { get => (double)GetValue(SpeedCurKmhProperty); set => SetValue(SpeedCurKmhProperty, value); }
|
|
public static readonly DependencyProperty SpeedCurKmhProperty = DependencyProperty.Register(nameof(SpeedCurKmh), typeof(double), typeof(ManualControlPad), new PropertyMetadata(0d));
|
|
|
|
public double AngleSpDeg { get => (double)GetValue(AngleSpDegProperty); set => SetValue(AngleSpDegProperty, value); }
|
|
public static readonly DependencyProperty AngleSpDegProperty = DependencyProperty.Register(nameof(AngleSpDeg), typeof(double), typeof(ManualControlPad), new PropertyMetadata(0d));
|
|
public double AngleCurDeg { get => (double)GetValue(AngleCurDegProperty); set => SetValue(AngleCurDegProperty, value); }
|
|
public static readonly DependencyProperty AngleCurDegProperty = DependencyProperty.Register(nameof(AngleCurDeg), typeof(double), typeof(ManualControlPad), new PropertyMetadata(0d));
|
|
|
|
public string MoveType { get => (string)GetValue(MoveTypeProperty); set => SetValue(MoveTypeProperty, value); }
|
|
public static readonly DependencyProperty MoveTypeProperty = DependencyProperty.Register(nameof(MoveType), typeof(string), typeof(ManualControlPad), new PropertyMetadata("—"));
|
|
|
|
// ======= Ajustes (painel manual) =======
|
|
public double SpeedPercent { get => (double)GetValue(SpeedPercentProperty); set => SetValue(SpeedPercentProperty, value); }
|
|
public static readonly DependencyProperty SpeedPercentProperty = DependencyProperty.Register(nameof(SpeedPercent), typeof(double), typeof(ManualControlPad), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
|
|
|
public double MaxSteerDeg { get => (double)GetValue(MaxSteerDegProperty); set => SetValue(MaxSteerDegProperty, value); }
|
|
public static readonly DependencyProperty MaxSteerDegProperty = DependencyProperty.Register(nameof(MaxSteerDeg), typeof(double), typeof(ManualControlPad), new FrameworkPropertyMetadata(30d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
|
|
|
|
// Joystick cmds (-1..+1)
|
|
double _steerCmd, _throttleCmd;
|
|
public double SteerCmd { get => _steerCmd; private set { _steerCmd = Math.Clamp(value, -1, 1); Raise(nameof(SteerCmd)); CommandChanged?.Invoke(this, EventArgs.Empty); UpdateThumb(); } }
|
|
public double ThrottleCmd { get => _throttleCmd; private set { _throttleCmd = Math.Clamp(value, -1, 1); Raise(nameof(ThrottleCmd)); CommandChanged?.Invoke(this, EventArgs.Empty); UpdateThumb(); } }
|
|
|
|
// ===== Eventos públicos =====
|
|
public event PropertyChangedEventHandler PropertyChanged; void Raise(string n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n));
|
|
public event EventHandler EmergencyClicked;
|
|
public event EventHandler RunToggled; // BtnRun.IsChecked
|
|
public event EventHandler ReferenceClicked;
|
|
public event EventHandler ModeChanged;
|
|
public event EventHandler CommandChanged;
|
|
|
|
// Botões
|
|
void OnEmergencyClick(object s, RoutedEventArgs e) => EmergencyClicked?.Invoke(this, EventArgs.Empty);
|
|
void OnRefClick(object s, RoutedEventArgs e) => ReferenceClicked?.Invoke(this, EventArgs.Empty);
|
|
void OnRunToggle(object s, RoutedEventArgs e) => RunToggled?.Invoke(this, EventArgs.Empty);
|
|
|
|
// Modo Manual → anima painel
|
|
void OnManualToggle(object s, RoutedEventArgs e)
|
|
{
|
|
bool on = TgManual.IsChecked == true;
|
|
AnimateManual(on);
|
|
if (!on) { SteerCmd = 0; ThrottleCmd = 0; }
|
|
Focus(); // captura teclado se precisar
|
|
}
|
|
|
|
void AnimateManual(bool open, bool instant = false)
|
|
{
|
|
// mede a altura real do conteúdo
|
|
ManualInner.Measure(new Size(ActualWidth, double.PositiveInfinity));
|
|
double target = open ? ManualInner.DesiredSize.Height : 0.0;
|
|
|
|
// garante visibilidade antes de abrir
|
|
if (open && ManualContainer.Visibility != Visibility.Visible)
|
|
ManualContainer.Visibility = Visibility.Visible;
|
|
|
|
if (instant)
|
|
{
|
|
ManualContainer.Height = target;
|
|
if (!open) ManualContainer.Visibility = Visibility.Collapsed;
|
|
return;
|
|
}
|
|
|
|
var hAnim = new DoubleAnimation
|
|
{
|
|
To = target,
|
|
Duration = TimeSpan.FromMilliseconds(180),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
|
|
};
|
|
|
|
if (!open)
|
|
{
|
|
// ao finalizar o fechamento, colapsa para não ocupar espaço
|
|
hAnim.Completed += (_, __) =>
|
|
{
|
|
ManualContainer.Visibility = Visibility.Collapsed;
|
|
};
|
|
}
|
|
|
|
ManualContainer.BeginAnimation(FrameworkElement.HeightProperty, hAnim);
|
|
|
|
// se você estava ajustando um fundo externo, mantenha simétrico:
|
|
cnvFundo.Height = 64 + target; // remova se não for necessário
|
|
/*var cAnim = new DoubleAnimation
|
|
{
|
|
To = 64 + target,
|
|
Duration = TimeSpan.FromMilliseconds(180),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
|
|
};
|
|
cnvFundo.BeginAnimation(FrameworkElement.HeightProperty, cAnim);*/
|
|
}
|
|
|
|
// ====== Teclado (quando manual ON) ======
|
|
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
|
{
|
|
base.OnPreviewKeyDown(e);
|
|
if (TgManual.IsChecked != true) return;
|
|
switch (e.Key)
|
|
{
|
|
case Key.W:
|
|
case Key.Up: ThrottleCmd = 1; e.Handled = true; break;
|
|
case Key.S:
|
|
case Key.Down: ThrottleCmd = -1; e.Handled = true; break;
|
|
case Key.D:
|
|
case Key.Right: SteerCmd = 1; e.Handled = true; break;
|
|
case Key.A:
|
|
case Key.Left: SteerCmd = -1; e.Handled = true; break;
|
|
case Key.Space: SteerCmd = 0; ThrottleCmd = 0; e.Handled = true; break;
|
|
case Key.Escape: TgManual.IsChecked = false; OnManualToggle(null, null); e.Handled = true; break;
|
|
}
|
|
}
|
|
protected override void OnPreviewKeyUp(KeyEventArgs e)
|
|
{
|
|
base.OnPreviewKeyUp(e);
|
|
if (TgManual.IsChecked != true) return;
|
|
switch (e.Key)
|
|
{
|
|
case Key.W:
|
|
case Key.Up:
|
|
case Key.S:
|
|
case Key.Down: if (ThrottleCmd != 0) ThrottleCmd = 0; break;
|
|
case Key.D:
|
|
case Key.Right:
|
|
case Key.A:
|
|
case Key.Left: if (SteerCmd != 0) SteerCmd = 0; break;
|
|
}
|
|
}
|
|
|
|
// ====== Mouse joystick ======
|
|
bool _drag;
|
|
void Pad_MouseDown(object s, System.Windows.Input.MouseButtonEventArgs e)
|
|
{ if (TgManual.IsChecked != true) return; _drag = true; CaptureMouse(); UpdateFromMouse(e.GetPosition(Pad)); }
|
|
void Pad_MouseMove(object s, System.Windows.Input.MouseEventArgs e)
|
|
{ if (_drag && TgManual.IsChecked == true) UpdateFromMouse(e.GetPosition(Pad)); }
|
|
void Pad_MouseUp(object s, System.Windows.Input.MouseButtonEventArgs e)
|
|
{ _drag = false; ReleaseMouseCapture(); SteerCmd = 0; ThrottleCmd = 0; }
|
|
|
|
void UpdateFromMouse(Point p)
|
|
{
|
|
double w = Pad.ActualWidth, h = Pad.ActualHeight;
|
|
var cx = w * 0.5; var cy = h * 0.5;
|
|
double dx = p.X - cx, dy = p.Y - cy;
|
|
double r = Math.Min(w, h) * 0.5 - 10;
|
|
double mag = Math.Sqrt(dx * dx + dy * dy);
|
|
if (mag > r) { dx *= r / mag; dy *= r / mag; }
|
|
SteerCmd = dx / r;
|
|
ThrottleCmd = -dy / r; // cima +
|
|
}
|
|
void UpdateThumb()
|
|
{
|
|
if (Thumb == null || ThumbTT == null || Pad == null) return;
|
|
|
|
double w = Pad.ActualWidth, h = Pad.ActualHeight;
|
|
double r = Math.Min(w, h) * 0.5 - 10;
|
|
|
|
ThumbTT.X = SteerCmd * r; // direita +
|
|
ThumbTT.Y = -ThrottleCmd * r; // cima +
|
|
}
|
|
|
|
// Helpers
|
|
public bool IsRunning => BtnRun.IsChecked == true;
|
|
}
|
|
|
|
// ===== visuais
|
|
public class BoolToOpacityConverter : System.Windows.Data.IValueConverter
|
|
{
|
|
public double EnabledOpacity { get; set; } = 1.0;
|
|
public double DisabledOpacity { get; set; } = 0.35;
|
|
public object Convert(object v, Type t, object p, System.Globalization.CultureInfo c) => (v is bool b && b) ? EnabledOpacity : DisabledOpacity;
|
|
public object ConvertBack(object v, Type t, object p, System.Globalization.CultureInfo c) => Binding.DoNothing;
|
|
}
|
|
}
|