agrobot_base/AgroBase/OperationControl/Controls/ManualControlPad.xaml.cs

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;
}
}