import 'package:flutter/foundation.dart' show ValueListenable; import 'package:flutter/material.dart'; import '../../../fly/fly_prefs.dart'; import '../../../fly/modes/vision_modes.dart' show VisionGeometry; import '../../dji_video_view.dart'; import '../../pv_icons.dart'; /// The live feed — or, with nothing connected, a painted stand-in so the HUD /// can still be laid out and reviewed. class VideoLayer extends StatelessWidget { const VideoLayer({super.key, required this.connected}); final bool connected; @override Widget build(BuildContext context) => connected ? const DjiVideoView() : const CustomPaint(painter: FeedPlaceholderPainter()); } /// The visual overlays drawn straight on the video: the grid and the centre /// point, as set in the camera menu (`FlyPrefs`). Never takes touches. /// /// The grid divides the picture, not the letterbox bars around it: [video] is /// the feed's size, as the native view fits it. class VisualOverlays extends StatelessWidget { const VisualOverlays({super.key, this.video}); final ValueListenable? video; @override Widget build(BuildContext context) { final Widget grid = ValueListenableBuilder( valueListenable: FlyPrefs.grid, builder: (BuildContext context, GridStyle g, _) => g == GridStyle.off ? const SizedBox.shrink() : CustomPaint(painter: GridPainter(diagonals: g == GridStyle.diagonals)), ); return IgnorePointer( child: Stack(fit: StackFit.expand, children: [ if (video == null) grid else LayoutBuilder( builder: (BuildContext context, BoxConstraints c) => ValueListenableBuilder( valueListenable: video!, builder: (BuildContext context, Size? v, Widget? child) => Stack(children: [Positioned.fromRect(rect: VisionGeometry.contentRect(c.biggest, v), child: child!)]), child: grid, ), ), ValueListenableBuilder( valueListenable: FlyPrefs.centerPoint, builder: (BuildContext context, bool on, _) => on ? const Center(child: PVIcon('crosshair', size: 30, stroke: 1.1, color: Color(0xB3FFFFFF))) : const SizedBox.shrink(), ), ]), ); } } /// Rule-of-thirds grid, optionally with the two diagonals. class GridPainter extends CustomPainter { const GridPainter({this.diagonals = false}); final bool diagonals; @override void paint(Canvas canvas, Size size) { final Paint p = Paint() ..color = const Color(0x33FFFFFF) ..strokeWidth = 1; for (int i = 1; i < 3; i++) { final double x = size.width * i / 3, y = size.height * i / 3; canvas.drawLine(Offset(x, 0), Offset(x, size.height), p); canvas.drawLine(Offset(0, y), Offset(size.width, y), p); } if (diagonals) { canvas.drawLine(Offset.zero, Offset(size.width, size.height), p); canvas.drawLine(Offset(size.width, 0), Offset(0, size.height), p); } } @override bool shouldRepaint(covariant GridPainter oldDelegate) => oldDelegate.diagonals != diagonals; } /// Painted placeholder feed: graded sky → ground, haze, a perspective grid and /// a few distant buildings. class FeedPlaceholderPainter extends CustomPainter { const FeedPlaceholderPainter(); @override void paint(Canvas canvas, Size size) { final double w = size.width, h = size.height; final double horizon = h * 0.52; final Rect full = Offset.zero & size; canvas.drawRect( full, Paint() ..shader = const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF2A4E86), Color(0xFF3E6199), Color(0xFF1C2A1E), Color(0xFF0E1710)], stops: [0.0, 0.51, 0.54, 1.0], ).createShader(full), ); final Rect hazeRect = Rect.fromLTWH(0, horizon - 30, w, 60); canvas.drawRect( hazeRect, Paint() ..shader = const LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0x808FB4D8), Color(0x008FB4D8)], ).createShader(hazeRect), ); final Paint bld = Paint()..color = const Color(0xE612201A); void building(double x, double y, double bw, double bh) => canvas.drawRect(Rect.fromLTWH(x * w, horizon + y, bw, bh), bld); building(0.10, -40, 46, 40); building(0.17, -52, 30, 52); building(0.74, -46, 54, 46); building(0.83, -34, 34, 34); final Paint grid = Paint() ..color = const Color(0x297FE0B0) ..strokeWidth = 1; for (int i = 1; i <= 5; i++) { final double t = i / 5.0; final double y = horizon + (h - horizon) * t * t; canvas.drawLine(Offset(0, y), Offset(w, y), grid); } final double vx = w / 2; for (int k = -6; k <= 6; k += 2) { canvas.drawLine(Offset(vx + k * 10, horizon), Offset(vx + k * (w * 0.16), h), grid); } } @override bool shouldRepaint(covariant FeedPlaceholderPainter oldDelegate) => false; }