using System.ComponentModel; using System.Windows; using System.Windows.Media; using UserControl = System.Windows.Controls.UserControl; using Color = System.Windows.Media.Color; namespace OperationControl.Controls { /// /// Interaction logic for AttitudeIndicator.xaml /// public partial class AttitudeIndicator : UserControl { public AttitudeIndicator() { InitializeComponent(); DataContext = this; UpdateChrome(); } // ===== Dependency Properties ===== public static readonly DependencyProperty RollDegProperty = DependencyProperty.Register(nameof(RollDeg), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(0.0, OnAngleChanged)); public static readonly DependencyProperty PitchDegProperty = DependencyProperty.Register(nameof(PitchDeg), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(0.0, OnAngleChanged)); public static readonly DependencyProperty PxPerDegProperty = DependencyProperty.Register(nameof(PxPerDeg), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(2.0, OnAngleChanged)); public static readonly DependencyProperty WarningLimitProperty = DependencyProperty.Register(nameof(WarningLimit), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(10.0, OnAngleChanged)); public static readonly DependencyProperty CriticalLimitProperty = DependencyProperty.Register(nameof(CriticalLimit), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(18.0, OnAngleChanged)); private static readonly DependencyPropertyKey PitchOffsetPxPropertyKey = DependencyProperty.RegisterReadOnly( nameof(PitchOffsetPx), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(0.0)); public static readonly DependencyProperty PitchOffsetPxProperty = PitchOffsetPxPropertyKey.DependencyProperty; public double PitchOffsetPx { get => (double)GetValue(PitchOffsetPxProperty); private set => SetValue(PitchOffsetPxPropertyKey, value); } public double RollDeg { get => (double)GetValue(RollDegProperty); set => SetValue(RollDegProperty, value); } public double PitchDeg { get => (double)GetValue(PitchDegProperty); set => SetValue(PitchDegProperty, value); } public double PxPerDeg { get => (double)GetValue(PxPerDegProperty); set => SetValue(PxPerDegProperty, value); } public double WarningLimit { get => (double)GetValue(WarningLimitProperty); set => SetValue(WarningLimitProperty, value); } public double CriticalLimit { get => (double)GetValue(CriticalLimitProperty); set => SetValue(CriticalLimitProperty, value); } // === LATERAL ERROR === public static readonly DependencyProperty LateralErrorProperty = DependencyProperty.Register( nameof(LateralError), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(0.0, OnLateralChanged)); public static readonly DependencyProperty LateralMaxAbsProperty = DependencyProperty.Register( nameof(LateralMaxAbs), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(50.0, OnLateralChanged)); // 0,5 m por padrão private static readonly DependencyPropertyKey LateralNormalizedPropertyKey = DependencyProperty.RegisterReadOnly( nameof(LateralNormalized), typeof(double), typeof(AttitudeIndicator), new PropertyMetadata(0.0)); public static readonly DependencyProperty LateralNormalizedProperty = LateralNormalizedPropertyKey.DependencyProperty; public double LateralError { get => (double)GetValue(LateralErrorProperty); set => SetValue(LateralErrorProperty, value); } public double LateralMaxAbs { get => (double)GetValue(LateralMaxAbsProperty); set => SetValue(LateralMaxAbsProperty, value); } /// /// Erro lateral normalizado em [-1, 1] /// -1 = limite à esquerda, 0 = centro, +1 = limite à direita /// public double LateralNormalized { get => (double)GetValue(LateralNormalizedProperty); private set => SetValue(LateralNormalizedPropertyKey, value); } // Derivados public double MaxAbsAngle => Math.Max(Math.Abs(RollDeg), Math.Abs(PitchDeg)); public event PropertyChangedEventHandler PropertyChanged; void Raise(string n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(n)); static void OnAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var c = (AttitudeIndicator)d; // agora compila pois herda de UserControl c.PitchOffsetPx = -c.PitchDeg * c.PxPerDeg; c.Raise(nameof(PitchOffsetPx)); c.Raise(nameof(MaxAbsAngle)); c.UpdateChrome(); } void UpdateChrome() { if (Chrome == null) return; var a = MaxAbsAngle; if (a >= CriticalLimit) Chrome.BorderBrush = new SolidColorBrush(Color.FromRgb(0xE7, 0x4C, 0x3C)); // vermelho else if (a >= WarningLimit) Chrome.BorderBrush = new SolidColorBrush(Color.FromRgb(0xF1, 0xC4, 0x0F)); // amarelo else Chrome.BorderBrush = new SolidColorBrush(Color.FromRgb(0x2E, 0xCC, 0x71)); // verde } static void OnLateralChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var c = (AttitudeIndicator)d; c.UpdateLateral(); c.Raise(nameof(LateralNormalized)); } void UpdateLateral() { var max = Math.Max(0.0001, LateralMaxAbs); // normaliza para [-1, 1] e faz clamp var n = LateralError / max; if (n > 1) n = 1; if (n < -1) n = -1; LateralNormalized = n; } } }