import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../theme/autopilot_theme.dart'; import '../../theme/theme_provider.dart'; /// Rudder angle indicator — horizontal bar from –35° (port) to +35° (starboard). /// /// [rudderDeg] is the actual rudder angle in degrees. /// Negative = port, positive = starboard. /// [limitDeg] is the hard stop (default 35°). class RudderIndicator extends StatelessWidget { const RudderIndicator({ super.key, required this.rudderDeg, this.limitDeg = 35.0, this.height = 48, }); final double rudderDeg; final double limitDeg; final double height; @override Widget build(BuildContext context) { final theme = context.watch().current; return SizedBox( height: height, child: CustomPaint( painter: _RudderPainter( theme: theme, rudderDeg: rudderDeg, limitDeg: limitDeg, ), ), ); } } class _RudderPainter extends CustomPainter { const _RudderPainter({ required this.theme, required this.rudderDeg, required this.limitDeg, }); final AutopilotTheme theme; final double rudderDeg; final double limitDeg; @override void paint(Canvas canvas, Size size) { final t = theme; final cx = size.width / 2; final cy = size.height / 2; final halfW = size.width / 2 - 12; // Track canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCenter(center: Offset(cx, cy), width: size.width - 24, height: 6), const Radius.circular(3), ), Paint()..color = t.panelBorder, ); // Centre line canvas.drawLine( Offset(cx, cy - 10), Offset(cx, cy + 10), Paint() ..color = t.textMuted ..strokeWidth = 1, ); // Rudder fill final fraction = (rudderDeg / limitDeg).clamp(-1.0, 1.0); final fillWidth = (halfW * fraction.abs()).clamp(0.0, halfW); if (fillWidth > 1) { final left = fraction < 0 ? cx - fillWidth : cx; canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromLTWH(left, cy - 3, fillWidth, 6), const Radius.circular(2), ), Paint()..color = t.accentMid, ); } // Indicator knob final kx = cx + halfW * fraction; canvas.drawCircle( Offset(kx, cy), 9, Paint()..color = t.accentLight, ); if (t.accentGlowRadius > 0) { canvas.drawCircle( Offset(kx, cy), 9, Paint() ..color = t.accentGlowColor ..maskFilter = MaskFilter.blur(BlurStyle.normal, t.accentGlowRadius / 2), ); } // Labels final labelStyle = TextStyle(color: t.textMuted, fontSize: 10); _drawLabel(canvas, 'P', Offset(12, cy), labelStyle); _drawLabel(canvas, 'S', Offset(size.width - 12, cy), labelStyle); } void _drawLabel(Canvas canvas, String text, Offset center, TextStyle style) { final span = TextSpan(text: text, style: style); final tp = TextPainter( text: span, textDirection: TextDirection.ltr, )..layout(); tp.paint(canvas, center - Offset(tp.width / 2, tp.height / 2)); } @override bool shouldRepaint(_RudderPainter old) => old.rudderDeg != rudderDeg || old.theme != theme; }