One settings table in CameraBridge watches every camera value into the telemetry tick and backs a generic setCameraValue, so the tabbed camera menu (Exposure / Photo / Video / Other) is built from row specs and the camera's own capability ranges. Writes show at once and fall back to what the camera reports. Adds the quick exposure panel (Auto/A/S/M, ISO slider, shutter and aperture wheels, EV strip, metered EV in M), a new camera column with record timer and interval/pano progress, a zoom column for zoom cameras, tap-to-focus / tap-to-meter, and a live histogram. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
127 lines
4.2 KiB
Dart
127 lines
4.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../../../fly/fly_prefs.dart';
|
|
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.
|
|
class VisualOverlays extends StatelessWidget {
|
|
const VisualOverlays({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return IgnorePointer(
|
|
child: Stack(fit: StackFit.expand, children: <Widget>[
|
|
ValueListenableBuilder<GridStyle>(
|
|
valueListenable: FlyPrefs.grid,
|
|
builder: (BuildContext context, GridStyle g, _) =>
|
|
g == GridStyle.off ? const SizedBox.shrink() : CustomPaint(painter: GridPainter(diagonals: g == GridStyle.diagonals)),
|
|
),
|
|
ValueListenableBuilder<bool>(
|
|
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>[Color(0xFF2A4E86), Color(0xFF3E6199), Color(0xFF1C2A1E), Color(0xFF0E1710)],
|
|
stops: <double>[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>[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;
|
|
}
|