Status bar: part lamps (CAN, OBD, IMU, CAM), drawn signal and battery icons
WiFi and LTE show signal in four steps with Android's traffic arrows; the battery is filled to its level in good (charging), caution (under 40 %) or alarm (under 20 %). OBD, IMU and CAM are lamps by CAN's rule; CAM reads orin-status' new cam_up and vision_up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
// The status bar's drawn icons, Android-like: a battery whose fill shows level and
|
||||
// colour, WiFi and cellular signal in four steps, and the traffic arrow pair. Drawn
|
||||
// rather than taken from the icon font, which has no four-step cellular icon and no
|
||||
// separately coloured battery fill.
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A horizontal battery: [outline] for the case, the inside filled to [level]
|
||||
/// (0–1, null unknown) in [fill].
|
||||
class BatteryIcon extends StatelessWidget {
|
||||
const BatteryIcon({
|
||||
super.key,
|
||||
required this.level,
|
||||
required this.fill,
|
||||
required this.outline,
|
||||
});
|
||||
final double? level;
|
||||
final Color fill;
|
||||
final Color outline;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => CustomPaint(
|
||||
size: const Size(24, 12),
|
||||
painter: _Battery(level, fill, outline),
|
||||
);
|
||||
}
|
||||
|
||||
class _Battery extends CustomPainter {
|
||||
_Battery(this.level, this.fill, this.outline);
|
||||
final double? level;
|
||||
final Color fill;
|
||||
final Color outline;
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
const nub = 2.0;
|
||||
final body = Rect.fromLTWH(0.75, 0.75, s.width - nub - 1.5, s.height - 1.5);
|
||||
c.drawRRect(
|
||||
RRect.fromRectAndRadius(body, const Radius.circular(2.5)),
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5
|
||||
..color = outline,
|
||||
);
|
||||
c.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(s.width - nub, s.height * 0.3, nub, s.height * 0.4),
|
||||
const Radius.circular(1),
|
||||
),
|
||||
Paint()..color = outline,
|
||||
);
|
||||
final l = level;
|
||||
if (l == null) return;
|
||||
final inner = body.deflate(2);
|
||||
c.drawRRect(
|
||||
RRect.fromRectAndRadius(
|
||||
Rect.fromLTWH(
|
||||
inner.left,
|
||||
inner.top,
|
||||
inner.width * l.clamp(0, 1),
|
||||
inner.height,
|
||||
),
|
||||
const Radius.circular(1),
|
||||
),
|
||||
Paint()..color = fill,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_Battery o) =>
|
||||
o.level != level || o.fill != fill || o.outline != outline;
|
||||
}
|
||||
|
||||
/// Signal in [bars] of four (null: off, drawn crossed), WiFi's fan or cellular's
|
||||
/// triangle; the unlit part faint.
|
||||
class SignalIcon extends StatelessWidget {
|
||||
const SignalIcon.wifi({super.key, required this.bars, required this.color})
|
||||
: wifi = true;
|
||||
const SignalIcon.cellular({
|
||||
super.key,
|
||||
required this.bars,
|
||||
required this.color,
|
||||
}) : wifi = false;
|
||||
final int? bars;
|
||||
final Color color;
|
||||
final bool wifi;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => CustomPaint(
|
||||
size: const Size(18, 16),
|
||||
painter: _Signal(bars, color, wifi),
|
||||
);
|
||||
}
|
||||
|
||||
class _Signal extends CustomPainter {
|
||||
_Signal(this.bars, this.color, this.wifi);
|
||||
final int? bars;
|
||||
final Color color;
|
||||
final bool wifi;
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
final faint = Paint()..color = color.withValues(alpha: 0.3);
|
||||
final lit = Paint()..color = color;
|
||||
final n = bars ?? 0;
|
||||
if (wifi) {
|
||||
// A quarter-circle fan standing on its point; lit from the point outwards.
|
||||
final apex = Offset(s.width / 2, s.height);
|
||||
Path fan(double r) => Path()
|
||||
..moveTo(apex.dx, apex.dy)
|
||||
..arcTo(
|
||||
Rect.fromCircle(center: apex, radius: r),
|
||||
-3 * math.pi / 4,
|
||||
math.pi / 2,
|
||||
false,
|
||||
)
|
||||
..close();
|
||||
final full = s.height;
|
||||
c.drawPath(fan(full), faint);
|
||||
if (n > 0) c.drawPath(fan(full * (0.25 + 0.75 * n / 4)), lit);
|
||||
} else {
|
||||
// A right triangle rising to the right; lit from the left.
|
||||
final tri = Path()
|
||||
..moveTo(0, s.height)
|
||||
..lineTo(s.width, s.height)
|
||||
..lineTo(s.width, 0)
|
||||
..close();
|
||||
c.drawPath(tri, faint);
|
||||
if (n > 0) {
|
||||
c.save();
|
||||
c.clipRect(
|
||||
Rect.fromLTWH(0, 0, s.width * (0.2 + 0.8 * n / 4), s.height),
|
||||
);
|
||||
c.drawPath(tri, lit);
|
||||
c.restore();
|
||||
}
|
||||
}
|
||||
if (bars == null) {
|
||||
// Off: a stroke through it.
|
||||
c.drawLine(
|
||||
Offset(s.width * 0.15, s.height * 0.15),
|
||||
Offset(s.width * 0.85, s.height * 0.95),
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = 1.5,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_Signal o) =>
|
||||
o.bars != bars || o.color != color || o.wifi != wifi;
|
||||
}
|
||||
|
||||
/// Android's traffic pair beside a signal icon: up lit while sending, down while
|
||||
/// receiving.
|
||||
class TrafficArrows extends StatelessWidget {
|
||||
const TrafficArrows({
|
||||
super.key,
|
||||
required this.up,
|
||||
required this.down,
|
||||
required this.color,
|
||||
});
|
||||
final bool up;
|
||||
final bool down;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
CustomPaint(size: const Size(6, 14), painter: _Arrows(up, down, color));
|
||||
}
|
||||
|
||||
class _Arrows extends CustomPainter {
|
||||
_Arrows(this.up, this.down, this.color);
|
||||
final bool up;
|
||||
final bool down;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
void paint(Canvas c, Size s) {
|
||||
Paint p(bool on) =>
|
||||
Paint()..color = on ? color : color.withValues(alpha: 0.25);
|
||||
final h = s.height / 2 - 1;
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(0, h)
|
||||
..lineTo(s.width, h)
|
||||
..lineTo(s.width / 2, 0)
|
||||
..close(),
|
||||
p(up),
|
||||
);
|
||||
c.drawPath(
|
||||
Path()
|
||||
..moveTo(0, s.height - h)
|
||||
..lineTo(s.width, s.height - h)
|
||||
..lineTo(s.width / 2, s.height)
|
||||
..close(),
|
||||
p(down),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(_Arrows o) =>
|
||||
o.up != up || o.down != down || o.color != color;
|
||||
}
|
||||
Reference in New Issue
Block a user