366 lines
17 KiB
C#
366 lines
17 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
|
|
|
|
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;
|
|
}
|
|
|
|
}
|