App 2.26.0: Overview styles Minimal, Terminal, Aviation, Nixie, Chronograph

Five more styles that differ in arrangement as well as look: the speed
alone with hairlines; a green text terminal with ASCII bars; a flight
display with tapes and a horizon that pitches with power; nixie tubes on
walnut and brass; a watch face with sub-dials. They share a new base,
ClusterCanvasView. Emulator only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
2026-10-24 20:47:43 +02:00
co-authored by Claude Opus 5.5
parent 1af5b0efe7
commit 7902d8f941
17 changed files with 946 additions and 7 deletions
@@ -0,0 +1,119 @@
package com.evsuite.abrp;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.SystemClock;
import java.util.Locale;
/**
* The Terminal Overview style: text only, green phosphor on black, faint scan lines, as if a
* shell printed the car's state — each value on a row with an ASCII bar, the gear as [D], and
* a prompt with a blinking cursor (it blinks because readings redraw the view; it does not
* animate on its own). A low battery turns its row amber. Unread values show "—" and an empty
* bar.
*/
final class TerminalView extends ClusterCanvasView {
private static final int BAR = 24;
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint scan = new Paint();
private final int green, dim, amber;
TerminalView(Context context) {
super(context);
green = 0xFF33FF66;
dim = 0xFF1C8A3A;
amber = 0xFFFFB000;
text.setTypeface(Typeface.MONOSPACE);
scan.setColor(0x0CFFFFFF);
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
float w = getWidth(), h = getHeight();
if (w <= 0 || h <= 0) return;
c.drawColor(0xFF020A04);
// About 14 rows at 1.32 em each plus margins; monospace is about 0.6 em wide and the
// longest row about 58 characters.
float size = Math.min(h / 19.5f, w / 38f);
text.setTextSize(size);
float line = size * 1.32f, x = 16f * dp, y = 12f * dp + line;
String gear = gearRow();
print(c, "nils@mg4:~$ watch -n 0.2 vehicle-status", x, y, dim);
y += line * 1.4f;
print(c, row("SPEED", fmt(v.speedKmh, "%.0f km/h", "—")) + bar(v.speedKmh, 0f, 180f), x, y, green);
y += line;
print(c, row("POWER", fmt(v.powerKw, "%.1f kW", "—")) + powerBar(), x, y, green);
y += line;
boolean low = v.socPercent != null && v.socPercent < 20f;
print(c, row("BATTERY", fmt(v.socPercent, "%.0f %%", "—")) + bar(v.socPercent, 0f, 100f)
+ (low ? " ! LOW" : ""), x, y, low ? amber : green);
y += line;
print(c, row("RANGE", fmt(v.rangeKm, "%.0f km", "—")), x, y, green);
y += line;
print(c, row("GEAR", gear), x, y, green);
y += line * 1.4f;
print(c, row("USAGE", fmt(v.usage5Min, "%.1f kWh/100km", "—")) + " # last 5 min", x, y, green);
y += line;
print(c, row("SINCE", fmt(v.usageTrip, "%.1f kWh/100km", "—")) + " # trip "
+ fmt(v.tripKm, "%.1f km", "—"), x, y, green);
y += line;
print(c, row("CLIMATE", fmt(v.climateCelsius, "%.0f C", "—")) + " ODO " + fmt(v.odometerKm, "%06.0f km", "—"), x, y, green);
y += line;
print(c, row("TYRES", "FL " + v.tyreFl + " FR " + v.tyreFr + " RL " + v.tyreRl + " RR " + v.tyreRr), x, y, green);
y += line * 1.4f;
boolean cursor = (SystemClock.uptimeMillis() / 500) % 2 == 0;
print(c, "[" + (v.clock.isEmpty() ? "--:--" : v.clock) + "] nils@mg4:~$ " + (cursor ? "█" : ""), x, y, green);
// Scan lines over everything.
for (float sy = 0; sy < h; sy += 3f * dp) c.drawLine(0, sy, w, sy, scan);
}
private void print(Canvas c, String s, float x, float y, int colour) {
text.setColor(colour);
c.drawText(s, x, y, text);
}
private static String row(String label, String value) {
return String.format(Locale.US, "%-9s%-16s", label, value);
}
/** [#####.......] from min to max; empty when unread. */
private static String bar(Float value, float min, float max) {
int n = value == null ? 0 : Math.round(BAR * (Math.max(min, Math.min(max, value)) - min) / (max - min));
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < BAR; i++) sb.append(i < n ? '#' : '.');
return sb.append(']').toString();
}
/** −50…150 kW with a | at zero: < for regeneration to its left, # for drawing to its right. */
private String powerBar() {
int zero = BAR / 4;
int to = v.powerKw == null ? zero : zero + Math.round(BAR * Math.max(-50f, Math.min(150f, v.powerKw)) / 200f);
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < BAR; i++) {
if (i == zero) sb.append('|');
else if (v.powerKw != null && i < zero && i >= to) sb.append('<');
else if (v.powerKw != null && i > zero && i <= to) sb.append('#');
else sb.append('.');
}
return sb.append(']').toString();
}
private String gearRow() {
String[] letters = { "P", "R", "N", "D" };
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) {
boolean on = v.gear != null && v.gear == i + 1;
sb.append(on ? "[" + letters[i] + "]" : " " + letters[i] + " ");
}
return sb.toString();
}
}