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 { /// /// Interaction logic for ManualControlPad.xaml /// public partial class ManualControlPad : UserControl, INotifyPropertyChanged { public ObservableCollection MoveModes { get; } = new(); private HashSet _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.RoverEmFoco!.Controle.DirecionalAutomatico = !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); _ = VariaveisControleOperacao.EnviarComandoParadaUDP(emergencia: EmergencyActivated); } private void ManualControlPad_PauseClicked(object? sender, EventArgs e) { if (VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Operacao?.Iniciada ?? 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) { bool simulador = VariaveisControleOperacao.RoverEmFoco?.DadosLeitura?.Gnss?.Iniciado != true; VariaveisControleOperacao.EnviarComandoIniciarOperacao(true, simulador); } } } 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); VariaveisControleOperacao.EnviarComandoDirecionalUDP(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)); VariaveisControleOperacao.EnviarComandoMovimentacaoUDP(mov, Convert.ToInt32(SpeedControl), false); _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; } }