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