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

369 lines
18 KiB
C#

using OperationControl.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using UserControl = System.Windows.Controls.UserControl;
using KeyEventArgs = System.Windows.Input.KeyEventArgs;
using Binding = System.Windows.Data.Binding;
using Application = System.Windows.Application;
using Point = System.Windows.Point;
using Size = System.Windows.Size;
namespace OperationControl.Controls
{
/// <summary>
/// Interaction logic for ManualControlPad.xaml
/// </summary>
public partial class ManualControlPad : UserControl, INotifyPropertyChanged
{
public ObservableCollection<string> MoveModes { get; } = new();
private HashSet<Key> _pressedKeys = new();
private AgroBase.Models.Enums.Direcao _lastMovKey = AgroBase.Models.Enums.Direcao.Parado;
private AgroBase.Models.Enums.Direcao _lastDirKey = AgroBase.Models.Enums.Direcao.Parado;
public ManualControlPad()
{
InitializeComponent();
MoveModes.Clear();
foreach (var t in Enum.GetNames(typeof(AgroBase.Models.Enums.TipoMovimentoDirecional)))
{
MoveModes.Add(t);
}
MoveTypeControl = MoveType;
Loaded += (_, __) => UpdateThumb();
SizeChanged += (_, __) => { if (ManualContainer.Height > 0) AnimateManual(true, instant: true); };
EmergencyClicked += ManualControlPad_EmergencyClicked;
PauseClicked += ManualControlPad_PauseClicked;
ReferenceClicked += ManualControlPad_ReferenceClicked;
CommandChanged += ManualControlPad_CommandChanged;
}
// ======= Header displays (bind do app) =======
public bool EmergencyActivated { get => (bool)GetValue(EmergencyActivatedProperty); set => SetValue(EmergencyActivatedProperty, value); }
public static readonly DependencyProperty EmergencyActivatedProperty = DependencyProperty.Register(nameof(EmergencyActivated), typeof(bool), typeof(ManualControlPad), new PropertyMetadata(false));
public bool PauseActivated { get => (bool)GetValue(PauseActivatedProperty); set => SetValue(PauseActivatedProperty, value); }
public static readonly DependencyProperty PauseActivatedProperty = DependencyProperty.Register(nameof(PauseActivated), typeof(bool), typeof(ManualControlPad), new PropertyMetadata(false));
public bool RefActivated { get => (bool)GetValue(RefActivatedProperty); set => SetValue(RefActivatedProperty, value); }
public static readonly DependencyProperty RefActivatedProperty = DependencyProperty.Register(nameof(RefActivated), typeof(bool), typeof(ManualControlPad), new PropertyMetadata(false));
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 AgroBase.Models.Enums.TipoMovimentoDirecional MoveType { get => (AgroBase.Models.Enums.TipoMovimentoDirecional)GetValue(MoveTypeProperty); set => SetValue(MoveTypeProperty, value); }
public static readonly DependencyProperty MoveTypeProperty = DependencyProperty.Register(nameof(MoveType), typeof(AgroBase.Models.Enums.TipoMovimentoDirecional), typeof(ManualControlPad), new PropertyMetadata(AgroBase.Models.Enums.TipoMovimentoDirecional.RodasDianteiras));
// ======= Ajustes (painel manual) =======
public double SpeedControl { get => (double)GetValue(SpeedControlProperty); set => SetValue(SpeedControlProperty, value); }
public static readonly DependencyProperty SpeedControlProperty = DependencyProperty.Register(nameof(SpeedControl), typeof(double), typeof(ManualControlPad), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public double AngleControl { get => (double)GetValue(AngleControlProperty); set => SetValue(AngleControlProperty, value); }
public static readonly DependencyProperty AngleControlProperty = DependencyProperty.Register(nameof(AngleControl), typeof(double), typeof(ManualControlPad), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public AgroBase.Models.Enums.TipoMovimentoDirecional MoveTypeControl { get => (AgroBase.Models.Enums.TipoMovimentoDirecional)GetValue(MoveTypeControlProperty); set => SetValue(MoveTypeControlProperty, value); }
public static readonly DependencyProperty MoveTypeControlProperty = DependencyProperty.Register(nameof(MoveTypeControl), typeof(AgroBase.Models.Enums.TipoMovimentoDirecional), typeof(ManualControlPad), new PropertyMetadata(AgroBase.Models.Enums.TipoMovimentoDirecional.RodasDianteiras));
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 PauseClicked;
public event EventHandler ReferenceClicked;
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 OnPauseClick(object s, RoutedEventArgs e) => PauseClicked?.Invoke(this, EventArgs.Empty);
// Modo Manual → anima painel
void OnManualToggle(object s, RoutedEventArgs e)
{
bool manual_on = TgManual.IsChecked == true;
AnimateManual(manual_on);
if (!manual_on) { SteerCmd = 0; ThrottleCmd = 0; }
Focus(); // captura teclado se precisar
if (VariaveisControleOperacao.RoverEmFoco != null)
{
VariaveisControleOperacao.RoverEmFoco!.Controle.MovimentoAutomatico = !manual_on;
VariaveisControleOperacao.EnviarParametrosOperacao(new AgroBase.Models.Operacoes.OperacaoParametrosModel()
{
Controle = VariaveisControleOperacao.RoverEmFoco?.Controle
});
}
}
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;
// Se já está pressionada, IGNORA (é evento repetido)
if (_pressedKeys.Contains(e.Key))
{
e.Handled = true;
return;
}
// Marca como pressionada (primeira vez)
_pressedKeys.Add(e.Key);
switch (e.Key)
{
case Key.W:
case Key.Up:
ThrottleCmd = 1;
break;
case Key.S:
case Key.Down:
ThrottleCmd = -1;
break;
case Key.D:
case Key.Right:
SteerCmd = 1;
break;
case Key.A:
case Key.Left:
SteerCmd = -1;
break;
case Key.Space:
SteerCmd = 0;
ThrottleCmd = 0;
break;
case Key.Escape:
TgManual.IsChecked = false;
OnManualToggle(null, null);
break;
}
e.Handled = true;
}
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (TgManual.IsChecked != true) return;
// Remove do set (agora pode receber keydown de novo)
_pressedKeys.Remove(e.Key);
switch (e.Key)
{
case Key.W:
case Key.Up:
case Key.S:
case Key.Down:
ThrottleCmd = 0;
break;
case Key.D:
case Key.Right:
case Key.A:
case Key.Left:
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 +
}
public void AtualizarDadosControle(double? angMax = null, double? angSp = null, double? angCur = null, AgroBase.Models.Enums.TipoMovimentoDirecional? tipoMov = null, double? velSp = null, double? velKmh = null, bool? emg = null, bool? pause = null, bool? refing = null)
{
if (angMax != null) MaxSteerDeg = (double)angMax;
if (angSp != null) AngleSpDeg = (double)angSp;
if (angCur != null) AngleCurDeg = (double)angCur;
if (tipoMov != null) MoveType = (AgroBase.Models.Enums.TipoMovimentoDirecional)tipoMov;
if (velSp != null) SpeedSpKmh = (double)velSp;
if (velKmh != null) SpeedCurKmh = (double)velKmh;
if (emg != null) EmergencyActivated = (bool)emg;
if (pause != null) PauseActivated = (bool)pause;
if (refing != null) RefActivated = (bool)refing;
}
private void ManualControlPad_EmergencyClicked(object? sender, EventArgs e)
{
EmergencyActivated = !EmergencyActivated;
VariaveisControleOperacao.EnviarComandoParada(emergencia: EmergencyActivated);
}
private void ManualControlPad_PauseClicked(object? sender, EventArgs e)
{
if (VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.OperacaoIniciada ?? false)
{
//PauseActivated = !PauseActivated;
//VariaveisControleOperacao.EnviarComandoParada(pausa: PauseActivated);
if (System.Windows.MessageBox.Show("Deseja finalizar a operação em andamento?", "Finalizar Operação", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
VariaveisControleOperacao.EnviarComandoIniciarOperacao(false);
}
}
else
{
if (!(TgManual.IsChecked ?? false) && !(((App)Application.Current).Shell.Main?.MAP?.RuasMapaCarregado?.Any(x => x.Selected) ?? false))
{
System.Windows.MessageBox.Show("Para iniciar a operação automática, é necessário carregar um mapa e selecionar as ruas onde o equipamento irá operar!", "Parametrização", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
if (System.Windows.MessageBox.Show("Deseja iniciar a operação com os parâmetros atuais?", "Iniciar Operação", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
VariaveisControleOperacao.EnviarComandoIniciarOperacao(true, true);
}
}
}
private void ManualControlPad_ReferenceClicked(object? sender, EventArgs e)
{
RefActivated = !RefActivated;
VariaveisControleOperacao.EnviarComandoReferenciamento();
}
private void ManualControlPad_CommandChanged(object? sender, EventArgs e)
{
AgroBase.Models.Enums.Direcao dir =
_steerCmd < 0 ? AgroBase.Models.Enums.Direcao.Esquerda :
_steerCmd > 0 ? AgroBase.Models.Enums.Direcao.Direita :
AgroBase.Models.Enums.Direcao.Parado;
if (_lastDirKey != dir)
{
VariaveisControleOperacao.EnviarComandoDirecional(dir, Convert.ToInt32(AngleControl), MoveTypeControl);
_lastDirKey = dir;
}
AgroBase.Models.Enums.Direcao mov =
_throttleCmd < 0 ? AgroBase.Models.Enums.Direcao.Baixo :
_throttleCmd > 0 ? AgroBase.Models.Enums.Direcao.Cima :
AgroBase.Models.Enums.Direcao.Parado;
if (_lastMovKey != mov)
{
VariaveisControleOperacao.EnviarComandoMovimentacao(mov, Convert.ToInt32(SpeedControl));
_lastMovKey = mov;
}
}
}
// ===== 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;
}
}