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(); } }