using System.Windows; using System.Windows.Media.Animation; using System.Windows.Media; using UserControl = System.Windows.Controls.UserControl; using Color = System.Windows.Media.Color; namespace OperationControl.Controls { /// /// Interaction logic for TemperatureIndicator.xaml /// public partial class TemperatureIndicator : UserControl { public TemperatureIndicator() { InitializeComponent(); Loaded += (_, __) => UpdateMarker(); SizeChanged += (_, __) => UpdateMarker(); } // ===== Dependency Properties ===== public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value), typeof(double), typeof(TemperatureIndicator), new PropertyMetadata(0d, OnValueChanged)); public static readonly DependencyProperty MinProperty = DependencyProperty.Register(nameof(Min), typeof(double), typeof(TemperatureIndicator), new PropertyMetadata(0d, OnAnyRangeChanged)); public static readonly DependencyProperty MaxProperty = DependencyProperty.Register(nameof(Max), typeof(double), typeof(TemperatureIndicator), new PropertyMetadata(80d, OnAnyRangeChanged)); public static readonly DependencyProperty WarnStartProperty = DependencyProperty.Register(nameof(WarnStart), typeof(double), typeof(TemperatureIndicator), new PropertyMetadata(50d)); public static readonly DependencyProperty CritStartProperty = DependencyProperty.Register(nameof(CritStart), typeof(double), typeof(TemperatureIndicator), new PropertyMetadata(55d)); public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(nameof(Label), typeof(string), typeof(TemperatureIndicator), new PropertyMetadata("Temperatura")); public static readonly DependencyProperty UnitsProperty = DependencyProperty.Register(nameof(Units), typeof(string), typeof(TemperatureIndicator), new PropertyMetadata(" °C")); public static readonly DependencyProperty AnimateOnChangeProperty = DependencyProperty.Register(nameof(AnimateOnChange), typeof(bool), typeof(TemperatureIndicator), new PropertyMetadata(true)); public static readonly DependencyProperty BlinkOnCriticalProperty = DependencyProperty.Register(nameof(BlinkOnCritical), typeof(bool), typeof(TemperatureIndicator), new PropertyMetadata(true)); // ===== CLR wrappers ===== public double Value { get => (double)GetValue(ValueProperty); set => SetValue(ValueProperty, value); } public double Min { get => (double)GetValue(MinProperty); set => SetValue(MinProperty, value); } public double Max { get => (double)GetValue(MaxProperty); set => SetValue(MaxProperty, value); } public double WarnStart { get => (double)GetValue(WarnStartProperty); set => SetValue(WarnStartProperty, value); } public double CritStart { get => (double)GetValue(CritStartProperty); set => SetValue(CritStartProperty, value); } public string Label { get => (string)GetValue(LabelProperty); set => SetValue(LabelProperty, value); } public string Units { get => (string)GetValue(UnitsProperty); set => SetValue(UnitsProperty, value); } public bool AnimateOnChange { get => (bool)GetValue(AnimateOnChangeProperty); set => SetValue(AnimateOnChangeProperty, value); } public bool BlinkOnCritical { get => (bool)GetValue(BlinkOnCriticalProperty); set => SetValue(BlinkOnCriticalProperty, value); } // ===== Callbacks ===== static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (TemperatureIndicator)d; ctl.UpdateMarker(animated: ctl.AnimateOnChange); ctl.UpdateCriticalBlink(); } static void OnAnyRangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var ctl = (TemperatureIndicator)d; ctl.UpdateMarker(); } // ===== Marker positioning + animation ===== void UpdateMarker(bool animated = false) { if (!IsLoaded) return; var barWidth = ActualWidth - 12; // margem interna aproximada if (barWidth <= 0) return; double clamped = Math.Max(Min, Math.Min(Max, Value)); double t = (clamped - Min) / Math.Max(1e-6, (Max - Min)); double targetX = t * barWidth; if (MarkerTT == null) return; if (animated) { var anim = new DoubleAnimation { To = Math.Max(0, targetX - 5), // centraliza o triângulo de 10px Duration = TimeSpan.FromMilliseconds(140), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut } }; MarkerTT.BeginAnimation(TranslateTransform.XProperty, anim, HandoffBehavior.SnapshotAndReplace); } else { MarkerTT.BeginAnimation(TranslateTransform.XProperty, null); MarkerTT.X = Math.Max(0, targetX - 5); } } // ===== Glow crítico ===== Storyboard _blinkSb; void UpdateCriticalBlink() { if (!BlinkOnCritical || CritStart <= WarnStart) { StopBlink(); return; } if (Value >= CritStart) StartBlink(); else StopBlink(); } void StartBlink() { if (_blinkSb != null) return; var anim = new ColorAnimation { From = Color.FromArgb(0x00, 0xFF, 0x00, 0x00), To = Color.FromArgb(0x22, 0xFF, 0x00, 0x00), Duration = TimeSpan.FromMilliseconds(600), AutoReverse = true, RepeatBehavior = RepeatBehavior.Forever, EasingFunction = new SineEase { EasingMode = EasingMode.EaseInOut } }; var brush = new SolidColorBrush(Color.FromArgb(0, 255, 0, 0)); AlertGlow.Background = brush; _blinkSb = new Storyboard(); Storyboard.SetTarget(_blinkSb, brush); Storyboard.SetTargetProperty(_blinkSb, new PropertyPath(SolidColorBrush.ColorProperty)); _blinkSb.Children.Add(anim); _blinkSb.Begin(); } void StopBlink() { _blinkSb?.Stop(); _blinkSb = null; if (AlertGlow?.Background is SolidColorBrush b) b.Color = Color.FromArgb(0x00, 0xFF, 0x00, 0x00); } } }