2025-11-24 16:05:50 +00:00
using OperationControl.Models ;
using System.Collections.ObjectModel ;
2025-11-12 11:15:07 +00:00
using System.ComponentModel ;
using System.Windows ;
using System.Windows.Input ;
using System.Windows.Media.Animation ;
2025-11-24 16:05:50 +00:00
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 ;
2025-11-12 11:15:07 +00:00
namespace OperationControl.Controls
{
/// <summary>
/// Interaction logic for ManualControlPad.xaml
/// </summary>
public partial class ManualControlPad : UserControl , INotifyPropertyChanged
{
public ObservableCollection < string > MoveModes { get ; } = new ( ) ;
2025-11-26 20:00:16 +00:00
private HashSet < Key > _pressedKeys = new ( ) ;
2026-03-24 00:47:53 +00:00
private AgroBase . Models . Enums . BotoesJoystick _lastMovKey = AgroBase . Models . Enums . BotoesJoystick . Vazio ;
private AgroBase . Models . Enums . BotoesJoystick _lastDirKey = AgroBase . Models . Enums . BotoesJoystick . Vazio ;
2025-11-12 11:15:07 +00:00
public ManualControlPad ( )
{
InitializeComponent ( ) ;
MoveModes . Clear ( ) ;
2025-11-24 16:05:50 +00:00
foreach ( var t in Enum . GetNames ( typeof ( AgroBase . Models . Enums . TipoMovimentoDirecional ) ) )
{
MoveModes . Add ( t ) ;
}
2025-11-26 20:00:16 +00:00
MoveTypeControl = MoveType ;
2025-11-12 11:15:07 +00:00
Loaded + = ( _ , __ ) = > UpdateThumb ( ) ;
SizeChanged + = ( _ , __ ) = > { if ( ManualContainer . Height > 0 ) AnimateManual ( true , instant : true ) ; } ;
2025-11-24 16:05:50 +00:00
EmergencyClicked + = ManualControlPad_EmergencyClicked ;
PauseClicked + = ManualControlPad_PauseClicked ;
ReferenceClicked + = ManualControlPad_ReferenceClicked ;
CommandChanged + = ManualControlPad_CommandChanged ;
2025-11-12 11:15:07 +00:00
}
2025-11-24 16:05:50 +00:00
2025-11-12 11:15:07 +00:00
// ======= Header displays (bind do app) =======
2025-11-24 16:05:50 +00:00
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 ) ) ;
2025-11-12 11:15:07 +00:00
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 ) ) ;
2025-11-24 16:05:50 +00:00
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 ) ) ;
2025-11-12 11:15:07 +00:00
// ======= Ajustes (painel manual) =======
2025-11-26 20:00:16 +00:00
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 ) ) ;
2025-11-12 11:15:07 +00:00
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 ;
2025-11-24 16:05:50 +00:00
public event EventHandler PauseClicked ;
2025-11-12 11:15:07 +00:00
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 ) ;
2025-11-24 16:05:50 +00:00
void OnPauseClick ( object s , RoutedEventArgs e ) = > PauseClicked ? . Invoke ( this , EventArgs . Empty ) ;
2025-11-12 11:15:07 +00:00
// Modo Manual → anima painel
void OnManualToggle ( object s , RoutedEventArgs e )
{
2025-11-26 20:00:16 +00:00
bool manual_on = TgManual . IsChecked = = true ;
AnimateManual ( manual_on ) ;
if ( ! manual_on ) { SteerCmd = 0 ; ThrottleCmd = 0 ; }
2025-11-12 11:15:07 +00:00
Focus ( ) ; // captura teclado se precisar
2025-11-26 20:00:16 +00:00
2025-11-28 10:19:39 +00:00
if ( VariaveisControleOperacao . RoverEmFoco ! = null )
2025-11-26 20:00:16 +00:00
{
2025-11-28 10:19:39 +00:00
VariaveisControleOperacao . RoverEmFoco ! . Controle . MovimentoAutomatico = ! manual_on ;
2026-03-04 18:55:52 +00:00
VariaveisControleOperacao . RoverEmFoco ! . Controle . DirecionalAutomatico = ! manual_on ;
2025-11-28 10:19:39 +00:00
VariaveisControleOperacao . EnviarParametrosOperacao ( new AgroBase . Models . Operacoes . OperacaoParametrosModel ( )
{
Controle = VariaveisControleOperacao . RoverEmFoco ? . Controle
} ) ;
}
2025-11-12 11:15:07 +00:00
}
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 ;
2025-11-26 20:00:16 +00:00
// 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 ) ;
2025-11-12 11:15:07 +00:00
switch ( e . Key )
{
case Key . W :
2025-11-26 20:00:16 +00:00
case Key . Up :
ThrottleCmd = 1 ;
break ;
2025-11-12 11:15:07 +00:00
case Key . S :
2025-11-26 20:00:16 +00:00
case Key . Down :
ThrottleCmd = - 1 ;
break ;
2025-11-12 11:15:07 +00:00
case Key . D :
2025-11-26 20:00:16 +00:00
case Key . Right :
SteerCmd = 1 ;
break ;
2025-11-12 11:15:07 +00:00
case Key . A :
2025-11-26 20:00:16 +00:00
case Key . Left :
SteerCmd = - 1 ;
break ;
case Key . Space :
SteerCmd = 0 ;
ThrottleCmd = 0 ;
break ;
case Key . Escape :
TgManual . IsChecked = false ;
OnManualToggle ( null , null ) ;
break ;
2025-11-12 11:15:07 +00:00
}
2025-11-26 20:00:16 +00:00
e . Handled = true ;
2025-11-12 11:15:07 +00:00
}
2025-11-26 20:00:16 +00:00
2025-11-12 11:15:07 +00:00
protected override void OnPreviewKeyUp ( KeyEventArgs e )
{
base . OnPreviewKeyUp ( e ) ;
if ( TgManual . IsChecked ! = true ) return ;
2025-11-26 20:00:16 +00:00
// Remove do set (agora pode receber keydown de novo)
_pressedKeys . Remove ( e . Key ) ;
2025-11-12 11:15:07 +00:00
switch ( e . Key )
{
case Key . W :
case Key . Up :
case Key . S :
2025-11-26 20:00:16 +00:00
case Key . Down :
ThrottleCmd = 0 ;
break ;
2025-11-12 11:15:07 +00:00
case Key . D :
case Key . Right :
case Key . A :
2025-11-26 20:00:16 +00:00
case Key . Left :
SteerCmd = 0 ;
break ;
2025-11-12 11:15:07 +00:00
}
}
// ====== 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 +
}
2025-11-24 16:05:50 +00:00
2025-11-26 20:00:16 +00:00
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 )
2025-11-24 16:05:50 +00:00
{
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 ;
2026-03-05 13:50:01 +00:00
//VariaveisControleOperacao.EnviarComandoParada(emergencia: EmergencyActivated);
2026-03-05 15:40:10 +00:00
_ = VariaveisControleOperacao . EnviarComandoParadaUDP ( emergencia : EmergencyActivated ) ;
2025-11-24 16:05:50 +00:00
}
private void ManualControlPad_PauseClicked ( object? sender , EventArgs e )
{
2025-12-17 19:27:06 +00:00
if ( VariaveisControleOperacao . RoverEmFoco ? . DadosLeitura ? . Operacao ? . Iniciada ? ? false )
2025-11-24 16:05:50 +00:00
{
//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 )
{
2026-03-06 10:42:21 +00:00
bool simulador = VariaveisControleOperacao . RoverEmFoco ? . DadosLeitura ? . Gnss ? . Iniciado ! = true ;
VariaveisControleOperacao . EnviarComandoIniciarOperacao ( true , simulador ) ;
2025-11-24 16:05:50 +00:00
}
}
}
private void ManualControlPad_ReferenceClicked ( object? sender , EventArgs e )
{
RefActivated = ! RefActivated ;
VariaveisControleOperacao . EnviarComandoReferenciamento ( ) ;
}
private void ManualControlPad_CommandChanged ( object? sender , EventArgs e )
{
2026-03-24 00:47:53 +00:00
AgroBase . Models . Enums . BotoesJoystick dir =
_steerCmd < 0 ? AgroBase . Models . Enums . BotoesJoystick . SEsquerda :
_steerCmd > 0 ? AgroBase . Models . Enums . BotoesJoystick . SDireita :
AgroBase . Models . Enums . BotoesJoystick . Vazio ;
2025-11-26 20:00:16 +00:00
if ( _lastDirKey ! = dir )
{
2026-03-05 13:50:01 +00:00
//VariaveisControleOperacao.EnviarComandoDirecional(dir, Convert.ToInt32(AngleControl), MoveTypeControl);
2026-03-24 00:47:53 +00:00
VariaveisControleOperacao . EnviarComandoDirecionalUDP ( dir , dir = = AgroBase . Models . Enums . BotoesJoystick . Vazio , Convert . ToInt32 ( AngleControl ) , MoveTypeControl ) ;
2025-11-26 20:00:16 +00:00
_lastDirKey = dir ;
}
2025-11-24 16:05:50 +00:00
2026-03-24 00:47:53 +00:00
AgroBase . Models . Enums . BotoesJoystick mov =
_throttleCmd < 0 ? AgroBase . Models . Enums . BotoesJoystick . Quadrado :
_throttleCmd > 0 ? AgroBase . Models . Enums . BotoesJoystick . Xis :
AgroBase . Models . Enums . BotoesJoystick . Vazio ;
2025-11-26 20:00:16 +00:00
if ( _lastMovKey ! = mov )
{
2026-03-05 13:50:01 +00:00
//VariaveisControleOperacao.EnviarComandoMovimentacao(mov, Convert.ToInt32(SpeedControl));
2026-03-24 00:47:53 +00:00
VariaveisControleOperacao . EnviarComandoMovimentacaoUDP ( mov , mov = = AgroBase . Models . Enums . BotoesJoystick . Vazio , Convert . ToInt32 ( SpeedControl ) , false ) ;
2025-11-26 20:00:16 +00:00
_lastMovKey = mov ;
}
2025-11-24 16:05:50 +00:00
}
2025-11-12 11:15:07 +00:00
}
// ===== 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 ;
}
2025-11-24 16:05:50 +00:00
2025-11-12 11:15:07 +00:00
}