Dash: DIN 1451 opt-in per widget (hudFont), GNSS compass, rei by default; widget catalogue
From smart-serow: the DIN 1451 face (Alte DIN 1451 Mittelschrift, via gps-speedo) and GpsCompass. A cosmetic font is opt-in: widgets name hudFont in their own TextStyle, the rest keeps the basic font (CJK). Boxes and telltales shrink long labels instead of wrapping. Telltale and Reading get their own files; lib/README.md catalogues folders and widgets like smart-serow's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
+16
-84
@@ -6,35 +6,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'boundary.dart';
|
||||
import 'reading.dart';
|
||||
import 'theme/palette.dart';
|
||||
import 'widgets/accel_graph.dart';
|
||||
import 'widgets/gps_compass.dart';
|
||||
import 'widgets/stat_box.dart';
|
||||
import 'widgets/telltale.dart';
|
||||
import 'widgets/whiskey_mark.dart';
|
||||
|
||||
/// One signal's current state, as the dash needs it.
|
||||
class Reading {
|
||||
const Reading(this.value, {this.dim = false});
|
||||
|
||||
/// The value, or null when missing or known-without-value.
|
||||
final Object? value;
|
||||
|
||||
/// Missing or stale: drawn in subdued.
|
||||
final bool dim;
|
||||
|
||||
static Reading of(Boundary b, String name) {
|
||||
final e = b.signals[name];
|
||||
if (e is! Map) return const Reading(null, dim: true);
|
||||
final q = e['quality'] as String? ?? 'ok';
|
||||
return Reading(e['value'], dim: q != 'ok' || e['value'] == null);
|
||||
}
|
||||
|
||||
double? get number => value is num ? (value as num).toDouble() : null;
|
||||
bool get on => value == true;
|
||||
|
||||
String fixed([int decimals = 0]) =>
|
||||
number == null ? '—' : number!.toStringAsFixed(decimals);
|
||||
}
|
||||
|
||||
class DashPage extends StatelessWidget {
|
||||
const DashPage({super.key, required this.boundary, required this.themes});
|
||||
final Boundary boundary;
|
||||
@@ -54,6 +33,13 @@ class DashPage extends StatelessWidget {
|
||||
final coolant = r('obd_coolant_temp_c');
|
||||
final lat = r('imu_accel_lat_g');
|
||||
final lon = r('imu_accel_lon_g');
|
||||
// The compass's states, from gnss_fix: none → acquiring, 2d/3d → fix, missing → none known.
|
||||
final fix = r('gnss_fix');
|
||||
final gpsState = fix.value == null
|
||||
? null
|
||||
: fix.value == 'none'
|
||||
? 'acquiring'
|
||||
: 'fix';
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: p.background,
|
||||
@@ -139,6 +125,12 @@ class DashPage extends StatelessWidget {
|
||||
dim: coolant.dim,
|
||||
isWarning: () => (coolant.number ?? 0) > 105,
|
||||
),
|
||||
Expanded(
|
||||
child: GpsCompass(
|
||||
heading: r('gnss_heading_deg').number,
|
||||
gpsState: gpsState,
|
||||
),
|
||||
),
|
||||
Telltale(
|
||||
label: 'LEFT',
|
||||
icon: Icons.arrow_left,
|
||||
@@ -183,66 +175,6 @@ class DashPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// An on/off lamp in the boxed pattern: the label is the lamp. Lit in foreground
|
||||
/// (highlight when [warn]), dark in subdued; missing shows as a dash.
|
||||
class Telltale extends StatelessWidget {
|
||||
const Telltale({
|
||||
super.key,
|
||||
required this.label,
|
||||
required this.reading,
|
||||
this.icon,
|
||||
this.warn = false,
|
||||
this.flex = 1,
|
||||
});
|
||||
|
||||
final String label;
|
||||
|
||||
/// Drawn instead of the label when set. Glyphs like ◀ ▶ come out as emoji on Android.
|
||||
final IconData? icon;
|
||||
final Reading reading;
|
||||
final bool warn;
|
||||
final int flex;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = AppTheme.of(context);
|
||||
final missing = reading.value == null;
|
||||
final lit = reading.on && !reading.dim;
|
||||
final color = lit
|
||||
? (warn ? p.highlight : p.foreground)
|
||||
: p.subdued.withValues(alpha: missing ? 0.35 : 0.6);
|
||||
return Expanded(
|
||||
flex: flex,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, c) {
|
||||
final size = c.maxHeight * 0.3;
|
||||
if (icon != null && !missing) {
|
||||
return Center(
|
||||
child: Icon(
|
||||
icon,
|
||||
size: c.maxHeight * 0.7,
|
||||
color: color,
|
||||
semanticLabel: label,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Center(
|
||||
child: Text(
|
||||
missing ? '—' : label,
|
||||
style: TextStyle(
|
||||
fontSize: size,
|
||||
fontWeight: lit ? FontWeight.w700 : FontWeight.w400,
|
||||
color: color,
|
||||
letterSpacing: 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The dash's own thin top line: link state, back, and the dark/bright switch.
|
||||
class _DashBar extends StatelessWidget {
|
||||
const _DashBar({required this.boundary, required this.themes});
|
||||
|
||||
Reference in New Issue
Block a user