Files
mg4/app/src/main/java/com/evsuite/abrp/TerminalView.java
T
nilsandClaude Sonnet 5.5 d80221db15 App 2.47.0: now playing in Quest, Empire, Terminal and Sci-fi
Quest: a dragon tows a scroll. Empire: an eagle flies one. Terminal: a
PLAYING row. Sci-fi: the song in black on the top bar.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
2026-11-02 22:20:11 +01:00

143 lines
6.1 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
*
* Two palettes: green phosphor, or the chosen accent colour ({@link #setAccentPalette}) — the
* text in its light shade, the command line in the accent itself, on plain black. The low
* battery stays amber in both.
*/
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 accentText, accentDim;
private final int amber = 0xFFFFB000;
private int green, dim, background;
TerminalView(Context context) {
super(context);
accentText = AccentColor.resolve(context, R.attr.evAccentLight);
accentDim = AccentColor.resolve(context, R.attr.evAccent);
setAccentPalette(false);
text.setTypeface(Typeface.MONOSPACE);
scan.setColor(0x0CFFFFFF);
}
/** The chosen accent colour instead of green phosphor. */
void setAccentPalette(boolean accent) {
green = accent ? accentText : 0xFF33FF66;
dim = accent ? accentDim : 0xFF1C8A3A;
background = accent ? 0xFF050505 : 0xFF020A04;
invalidate();
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
float w = getWidth(), h = getHeight();
if (w <= 0 || h <= 0) return;
c.drawColor(background);
// 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 x = 16f * dp;
float size = Math.min(h / 19.5f, (w - x) / 38f);
text.setTextSize(size);
float line = size * 1.32f, 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;
if (v.songTitle != null) {
// What plays, cut to the row's width.
String label = String.format(Locale.US, "%-9s", "PLAYING");
String s = v.songTitle + (v.songArtist == null ? "" : " — " + v.songArtist);
int fits = text.breakText(s, true, Math.max(0f, getWidth() - x - text.measureText(label)), null);
print(c, label + (fits < s.length() ? s.substring(0, Math.max(0, fits - 1)).trim() + "…" : s), x, y, green);
y += line;
}
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();
}
}