Files
mg4/app/src/main/java/com/evsuite/abrp/SciFiView.java
T
nilsandClaude Opus 5.5 5fb367d7cd App 2.24.0: Overview styles Sci-fi, Cartoon and Blueprint
Sci-fi: a starship console with elbow frames, pill labels and block
graphs, red alert below 20 %. Cartoon: comic panels, a battery with a
face, a springy speedometer and sound words for power. Blueprint: a
top-view drawing with the battery hatched to the charge, tyre pressures
on leader lines, power as a force arrow and a title block. Styles are
now mapped to panes in a table. Emulator only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
2026-10-24 20:08:24 +02:00

195 lines
9.8 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.Path;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.view.View;
import java.util.Locale;
/**
* The Sci-fi Overview style: a starship console. Rounded "elbow" frames in orange, lilac and
* blue, pill-shaped labels, condensed capitals and blocky bar graphs on black.
*
* Left: velocity (the speed) and the power transfer as a bar of blocks, regeneration in blue to
* the left of zero. Middle: labelled rows — reserves, range, efficiency, trip, climate. Right:
* the reserves as a stack of blocks. The gears are blocks in the left frame, the odometer and
* clock run along the bottom frame. Below 20 % charge the frames turn red ("red alert").
* Unread values show "—" and light no blocks. Redrawn only when a reading arrives.
*/
public final class SciFiView extends View {
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path path = new Path();
private final RectF rect = new RectF();
private final int orange, peach, lilac, blue, alert, off;
private ClusterValues v = new ClusterValues();
public SciFiView(Context context, AttributeSet attrs) {
super(context, attrs);
orange = context.getColor(R.color.scifi_orange);
peach = context.getColor(R.color.scifi_peach);
lilac = context.getColor(R.color.scifi_lilac);
blue = context.getColor(R.color.scifi_blue);
alert = context.getColor(R.color.scifi_alert);
off = context.getColor(R.color.scifi_off);
text.setTypeface(Typeface.create("sans-serif-condensed", Typeface.BOLD));
text.setFontFeatureSettings("tnum");
}
void setValues(ClusterValues values) {
v = values == null ? new ClusterValues() : values;
invalidate();
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
float w = getWidth(), h = getHeight();
if (w <= 0 || h <= 0) return;
float d = getResources().getDisplayMetrics().density;
boolean redAlert = v.socPercent != null && v.socPercent < 20f;
int frame = redAlert ? alert : orange, frame2 = redAlert ? alert : lilac;
// Frames: an elbow at the top left and one at the bottom left.
float side = w * 0.085f, bar = h * 0.055f, gap = 6f * d;
float split = h * 0.50f;
// Each elbow is only its corner; blocks fill the side between them.
float corner = bar + side * 0.9f + 12f * d;
elbow(c, 0, 0, w * 0.60f, corner, side, bar, true, frame);
elbow(c, 0, h - corner, w * 0.70f, h, side, bar, false, frame2);
// The top bar's end: the title, then a pill.
label(c, redAlert ? "RED ALERT · LOW RESERVES" : "PROPULSION STATUS", w * 0.61f + 10f * d,
bar * 0.86f, bar * 1.05f, redAlert ? alert : orange, Paint.Align.LEFT);
pill(c, w * 0.94f, 0, w, bar, frame2);
// The bottom bar's end: odometer and time.
String odo = v.odometerKm == null ? "—" : String.format(Locale.US, "%06.0f", v.odometerKm);
label(c, "ODO " + odo + " · " + (v.clock.isEmpty() ? "—" : v.clock),
w - 4f * d, h - bar * 0.14f, bar * 0.95f, peach, Paint.Align.RIGHT);
// The gears as blocks down the lower elbow.
String[] gears = { "P", "R", "N", "D" };
float gy = split + gap / 2f, gh = (h - corner - gap - gy - 3 * gap) / 4f;
for (int i = 0; i < 4; i++) {
boolean on = v.gear != null && v.gear == i + 1;
float top = gy + i * (gh + gap);
fill.setColor(on ? peach : off);
c.drawRect(0, top, side, top + gh, fill);
label(c, gears[i], side - 8f * d, top + gh - 6f * d, gh * 0.7f, on ? 0xFF000000 : lilac, Paint.Align.RIGHT);
}
// Upper elbow: three numbered blocks, the console's decoration.
float by = corner + gap, bh = (split - gap / 2f - by - 2 * gap) / 3f;
int[] colours = { lilac, blue, peach };
String[] codes = { "01-4471", "02-0815", "03-2026" };
for (int i = 0; i < 3; i++) {
float top = by + i * (bh + gap);
fill.setColor(redAlert ? alert : colours[i]);
c.drawRect(0, top, side, top + bh, fill);
label(c, codes[i], side - 8f * d, top + bh - 6f * d, Math.min(bh * 0.4f, 16f * d), 0xFF000000, Paint.Align.RIGHT);
}
float x0 = side + w * 0.035f;
// Velocity, large.
label(c, "VELOCITY · KM/H", x0, h * 0.17f, h * 0.045f, lilac, Paint.Align.LEFT);
label(c, v.speedKmh == null ? "—" : String.format(Locale.US, "%.0f", v.speedKmh), x0 - 4f * d,
h * 0.45f, h * 0.30f, orange, Paint.Align.LEFT);
// Power transfer: blocks from −50 to 150 kW in 10 kW steps.
label(c, "POWER TRANSFER · KW", x0, h * 0.62f, h * 0.045f, lilac, Paint.Align.LEFT);
String kw = v.powerKw == null ? "—" : String.format(Locale.US, "%.1f", v.powerKw);
label(c, kw, w * 0.42f, h * 0.62f, h * 0.06f, v.powerKw != null && v.powerKw < 0f ? blue : peach, Paint.Align.RIGHT);
int blocks = 20, zero = 5;
float bx0 = x0, bx1 = w * 0.42f, pitch = (bx1 - bx0) / blocks;
int litTo = v.powerKw == null ? zero : zero + Math.round(Math.max(-50f, Math.min(150f, v.powerKw)) / 10f);
for (int i = 0; i < blocks; i++) {
boolean on = v.powerKw != null && (litTo > zero ? i >= zero && i < litTo : i >= litTo && i < zero);
fill.setColor(on ? (i < zero ? blue : i >= 15 ? alert : orange) : off);
float x = bx0 + i * pitch;
rect.set(x, h * 0.65f, x + pitch - 4f * d, h * 0.74f);
c.drawRoundRect(rect, i == 0 || i == blocks - 1 ? 10f * d : 0f, 10f * d, fill);
}
label(c, "−50", bx0, h * 0.79f, h * 0.035f, lilac, Paint.Align.LEFT);
label(c, "0", bx0 + zero * pitch, h * 0.79f, h * 0.035f, lilac, Paint.Align.LEFT);
label(c, "150", bx1, h * 0.79f, h * 0.035f, lilac, Paint.Align.RIGHT);
// Middle: labelled rows.
String[][] rows = {
{ "RESERVES", v.socPercent == null ? "—" : String.format(Locale.US, "%.0f %%", v.socPercent) },
{ "RANGE", v.rangeKm == null ? "—" : String.format(Locale.US, "%.0f KM", v.rangeKm) },
{ "EFFICIENCY 5 MIN", v.usage5Min == null ? "—" : String.format(Locale.US, "%.1f", v.usage5Min) },
{ "SINCE START", v.usageTrip == null ? "—" : String.format(Locale.US, "%.1f", v.usageTrip) },
{ "TRIP", v.tripKm == null ? "—" : String.format(Locale.US, "%.1f KM", v.tripKm) },
{ "CLIMATE", v.climateCelsius == null ? "—" : String.format(Locale.US, "%.0f °C", v.climateCelsius) },
};
float rx = w * 0.47f, rw = w * 0.15f, ry = h * 0.11f, rh = h * 0.085f, rg = h * 0.035f;
for (int i = 0; i < rows.length; i++) {
float top = ry + i * (rh + rg);
int colour = redAlert && i == 0 ? alert : i % 2 == 0 ? lilac : blue;
pill(c, rx, top, rx + rw, top + rh, colour);
label(c, rows[i][0], rx + rw - 14f * d, top + rh * 0.72f, rh * 0.48f, 0xFF000000, Paint.Align.RIGHT);
label(c, rows[i][1], rx + rw + 16f * d, top + rh * 0.82f, rh * 0.85f, i == 0 && redAlert ? alert : peach, Paint.Align.LEFT);
}
// Right: the reserves as ten blocks, filled from the bottom.
float sx0 = w * 0.84f, sx1 = w * 0.93f, sTop = h * 0.11f, sBottom = h * 0.80f;
label(c, "RESERVES", (sx0 + sx1) / 2f, sTop - 8f * d, h * 0.04f, lilac, Paint.Align.CENTER);
int on = v.socPercent == null ? 0 : Math.round(Math.max(0f, Math.min(100f, v.socPercent)) / 10f);
float sh = (sBottom - sTop) / 10f;
for (int i = 0; i < 10; i++) {
float bottom = sBottom - i * sh;
fill.setColor(i < on ? (redAlert ? alert : i % 2 == 0 ? blue : lilac) : off);
rect.set(sx0, bottom - sh + 3f * d, sx1, bottom);
c.drawRoundRect(rect, i == 9 || i == 0 ? sh * 0.4f : 0f, sh * 0.4f, fill);
}
}
/**
* A frame: a vertical bar down the left joined by a rounded outer corner to a horizontal
* bar along the top (or the bottom), with a smaller rounded inner corner.
*/
private void elbow(Canvas c, float l, float t, float r, float b, float side, float bar, boolean top, int colour) {
float outer = side * 0.9f, inner = bar * 1.2f;
path.reset();
if (top) {
path.moveTo(l, b);
path.lineTo(l, t + outer);
path.quadTo(l, t, l + outer, t);
path.lineTo(r, t);
path.lineTo(r, t + bar);
path.lineTo(l + side + inner, t + bar);
path.quadTo(l + side, t + bar, l + side, t + bar + inner);
path.lineTo(l + side, b);
} else {
path.moveTo(l, t);
path.lineTo(l, b - outer);
path.quadTo(l, b, l + outer, b);
path.lineTo(r, b);
path.lineTo(r, b - bar);
path.lineTo(l + side + inner, b - bar);
path.quadTo(l + side, b - bar, l + side, b - bar - inner);
path.lineTo(l + side, t);
}
path.close();
fill.setColor(colour);
c.drawPath(path, fill);
}
private void pill(Canvas c, float l, float t, float r, float b, int colour) {
fill.setColor(colour);
rect.set(l, t, r, b);
c.drawRoundRect(rect, (b - t) / 2f, (b - t) / 2f, fill);
}
private void label(Canvas c, String s, float x, float baseline, float size, int colour, Paint.Align align) {
text.setTextSize(size);
text.setColor(colour);
text.setTextAlign(align);
c.drawText(s, x, baseline, text);
}
}