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
143 lines
6.1 KiB
Java
143 lines
6.1 KiB
Java
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();
|
||
}
|
||
}
|