import 'package:flutter/material.dart'; import '../models/angle_unit.dart'; class TacticalCrosshair extends StatelessWidget { final double headingDeg; final double trueAzimuthDeg; final AngleUnit angleUnit; final bool isAimLocked; final String? targetName; const TacticalCrosshair({ super.key, required this.headingDeg, required this.trueAzimuthDeg, this.angleUnit = AngleUnit.dual, this.isAimLocked = false, this.targetName, }); @override Widget build(BuildContext context) { return IgnorePointer( child: Stack( children: [ // Reticle Canvas CustomPaint( size: Size.infinite, painter: _CrosshairPainter( isLocked: isAimLocked, ), ), // Top Azimuth & Mils Telemetry Strip Positioned( top: 16, left: 20, right: 20, child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: Colors.black.withAlpha(204), borderRadius: BorderRadius.circular(10), border: Border.all( color: isAimLocked ? const Color(0xFF22C55E) : const Color(0xFF00F0FF), width: 1.5, ), boxShadow: [ BoxShadow( color: isAimLocked ? const Color(0xFF22C55E).withAlpha(102) : const Color(0xFF00F0FF).withAlpha(102), blurRadius: 12, ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( Icons.explore, size: 16, color: isAimLocked ? const Color(0xFF4ADE80) : const Color(0xFF00F0FF), ), const SizedBox(width: 8), Text( 'الاتجاه: ${AngleFormatter.format(trueAzimuthDeg, angleUnit, includeLabel: false)}', style: TextStyle( color: isAimLocked ? const Color(0xFF4ADE80) : const Color(0xFF00F0FF), fontSize: 12.5, fontWeight: FontWeight.w900, ), ), const SizedBox(width: 8), Container(width: 1, height: 14, color: Colors.white24), const SizedBox(width: 8), Text( 'مغناطيسي: ${headingDeg.toStringAsFixed(0).padLeft(3, '0')}°', style: const TextStyle( color: Color(0xFF94A3B8), fontSize: 10.5, ), ), ], ), ), ), ), // Target Aiming Name Label under reticle if (targetName != null) Positioned( bottom: 90, left: 20, right: 20, child: Center( child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: const Color(0xF00F172A), borderRadius: BorderRadius.circular(10), border: Border.all( color: isAimLocked ? const Color(0xFF22C55E) : const Color(0xFF38BDF8), ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isAimLocked ? Icons.lock : Icons.my_location, size: 14, color: isAimLocked ? const Color(0xFF4ADE80) : const Color(0xFF38BDF8), ), const SizedBox(width: 8), Flexible( child: Text( 'المعلم المصوب عليه: $targetName', style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ), ), ], ), ); } } class _CrosshairPainter extends CustomPainter { final bool isLocked; _CrosshairPainter({required this.isLocked}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final primaryColor = isLocked ? const Color(0xFF22C55E) : const Color(0xFF00F0FF); final linePaint = Paint() ..color = primaryColor ..strokeWidth = 1.5 ..style = PaintingStyle.stroke; final glowPaint = Paint() ..color = primaryColor.withAlpha(51) ..strokeWidth = 4.0 ..style = PaintingStyle.stroke; final centerDotPaint = Paint() ..color = isLocked ? const Color(0xFF22C55E) : const Color(0xFFEF4444) ..style = PaintingStyle.fill; // Center Crosshair Circle (Outer) const outerRadius = 46.0; canvas.drawCircle(center, outerRadius, glowPaint); canvas.drawCircle(center, outerRadius, linePaint); // Inner Aiming Circle const innerRadius = 18.0; canvas.drawCircle(center, innerRadius, linePaint); // Red Center Pip canvas.drawCircle(center, 3.5, centerDotPaint); // Crosshair Lines with Mil Marks const lineLen = 70.0; const gap = 24.0; // Top ray canvas.drawLine(Offset(center.dx, center.dy - gap), Offset(center.dx, center.dy - gap - lineLen), linePaint); // Bottom ray canvas.drawLine(Offset(center.dx, center.dy + gap), Offset(center.dx, center.dy + gap + lineLen), linePaint); // Left ray canvas.drawLine(Offset(center.dx - gap, center.dy), Offset(center.dx - gap - lineLen, center.dy), linePaint); // Right ray canvas.drawLine(Offset(center.dx + gap, center.dy), Offset(center.dx + gap + lineLen, center.dy), linePaint); // Stadia / Mil tick marks for (int i = 1; i <= 3; i++) { final d = gap + i * 20.0; final tickLen = (i % 2 == 0) ? 8.0 : 4.0; // Top tick canvas.drawLine(Offset(center.dx - tickLen, center.dy - d), Offset(center.dx + tickLen, center.dy - d), linePaint); // Bottom tick canvas.drawLine(Offset(center.dx - tickLen, center.dy + d), Offset(center.dx + tickLen, center.dy + d), linePaint); // Left tick canvas.drawLine(Offset(center.dx - d, center.dy - tickLen), Offset(center.dx - d, center.dy + tickLen), linePaint); // Right tick canvas.drawLine(Offset(center.dx + d, center.dy - tickLen), Offset(center.dx + d, center.dy + tickLen), linePaint); } // Corner HUD Bracket Accents final bracketPaint = Paint() ..color = primaryColor.withAlpha(153) ..strokeWidth = 2.0 ..style = PaintingStyle.stroke; const bSize = 25.0; const pad = 35.0; // Top-Left canvas.drawPath( Path() ..moveTo(pad, pad + bSize) ..lineTo(pad, pad) ..lineTo(pad + bSize, pad), bracketPaint, ); // Top-Right canvas.drawPath( Path() ..moveTo(size.width - pad - bSize, pad) ..lineTo(size.width - pad, pad) ..lineTo(size.width - pad, pad + bSize), bracketPaint, ); // Bottom-Left canvas.drawPath( Path() ..moveTo(pad, size.height - pad - bSize) ..lineTo(pad, size.height - pad) ..lineTo(pad + bSize, size.height - pad), bracketPaint, ); // Bottom-Right canvas.drawPath( Path() ..moveTo(size.width - pad - bSize, size.height - pad) ..lineTo(size.width - pad, size.height - pad) ..lineTo(size.width - pad, size.height - pad - bSize), bracketPaint, ); } @override bool shouldRepaint(covariant _CrosshairPainter oldDelegate) { return oldDelegate.isLocked != isLocked; } }