Files
mg4/app/src/main/java/com/evsuite/abrp/MascotView.java
T
nilsandClaude Opus 5.5 7df1930edd App 2.29.0: a chibi Mascot, chosen in Settings beside the tyre unit
ChibiFigure draws a small, full-figure co-pilot in a hoodie whose whole
body acts the mood (waving, arms up and hopping, fist, hands on cheeks,
nose bubble, pointing back, leaning into the wind). Settings → Mascot
(Classic / Chibi, MascotCharacter) shows only for the Mascot style; the
chibi has lines of her own.

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

533 lines
22 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.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.SystemClock;
/**
* The Mascot Overview style: an anime co-pilot beside the values, who reacts to the drive.
*
* On the left, head and shoulders: a young woman with dark plum hair, a headset with a boom mic
* and a racing jacket in the accent colour with an octagon badge. Her face follows
* {@link MascotMood} (held for a moment so it does not flicker): content, enjoying the speed
* with wind lines, cheering with sparkles while regenerating, determined under hard
* acceleration, worried with a sweat drop on a low battery, asleep with drifting Zs in P,
* glancing back in R, and patient while nothing is read. She blinks now and then and says a
* line for her mood in a speech bubble.
*
* On the right: speed large, the gear in a ring, battery as a bar with percent and range, then
* power, 5-minute usage and the trip; the clock top right, left of the full-screen button.
* Unread values show "—".
*
* Two characters ({@link MascotCharacter}): this classic co-pilot, or the chibi drawn by
* {@link ChibiFigure}, each with her own lines.
*
* Drawn on a 100 × 100 grid scaled to the pane. Nothing animates on its own: blinking, the Zs,
* sparkles and wind move because readings redraw the view every 0.2 s.
*/
final class MascotView extends ClusterCanvasView {
/** Her lines per {@link MascotCharacter}, then per {@link MascotMood}, in their orders. */
private static final int[][] LINES = {
{ R.array.mascot_waiting, R.array.mascot_reverse, R.array.mascot_worried, R.array.mascot_sleeping,
R.array.mascot_cheering, R.array.mascot_determined, R.array.mascot_fast, R.array.mascot_content },
{ R.array.chibi_waiting, R.array.chibi_reverse, R.array.chibi_worried, R.array.chibi_sleeping,
R.array.chibi_cheering, R.array.chibi_determined, R.array.chibi_fast, R.array.chibi_content } };
private final Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint stroke = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint text = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path p = new Path();
private final RectF r = new RectF();
private final MascotMood.Holder holder = new MascotMood.Holder();
private final String[][][] lines = new String[LINES.length][][];
private final ChibiFigure chibi;
private MascotCharacter character = MascotCharacter.CLASSIC;
private final int accent, accentDark, accentLight, regen, warn, white, grey, track;
private final int hair = 0xFF2E2440, hairLight = 0xFF5A4880, skin = 0xFFFBE2D0, skinShade = 0xFFE9BFA9,
iris = 0xFF2F8F9D, pupil = 0xFF123238, lash = 0xFF221A2E, lips = 0xFFC2606A, mouth = 0xFF8E3A48,
shirt = 0xFFF4F4F6, headset = 0xFF2A2D33, cup = 0xFF3C414A;
MascotView(Context context) {
super(context);
accent = AccentColor.resolve(context, R.attr.evAccent);
accentLight = AccentColor.resolve(context, R.attr.evAccentLight);
accentDark = Color.rgb(Math.round(Color.red(accent) * 0.6f), Math.round(Color.green(accent) * 0.6f),
Math.round(Color.blue(accent) * 0.6f));
regen = context.getColor(R.color.ev_ok);
warn = context.getColor(R.color.ev_error);
white = context.getColor(R.color.ev_text_primary);
grey = context.getColor(R.color.ev_text_secondary);
track = context.getColor(R.color.ev_outline);
for (int i = 0; i < LINES.length; i++) {
lines[i] = new String[LINES[i].length][];
for (int j = 0; j < LINES[i].length; j++) lines[i][j] = context.getResources().getStringArray(LINES[i][j]);
}
chibi = new ChibiFigure(accent, accentLight, regen);
stroke.setStyle(Paint.Style.STROKE);
stroke.setStrokeCap(Paint.Cap.ROUND);
stroke.setStrokeJoin(Paint.Join.ROUND);
text.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL));
text.setFontFeatureSettings("tnum");
}
/** Who sits beside the values (Settings → Mascot, shown for this style only). */
void setCharacter(MascotCharacter character) {
this.character = character == null ? MascotCharacter.CLASSIC : character;
invalidate();
}
@Override
protected void onDraw(Canvas c) {
super.onDraw(c);
float w = getWidth(), h = getHeight();
if (w <= 0 || h <= 0) return;
long t = SystemClock.uptimeMillis();
MascotMood mood = holder.update(MascotMood.of(v), t);
// A soft halo behind her.
float k = h * 0.98f / 100f, ox = w * 0.02f, oy = h - 100f * k;
fill.setColor(AccentColor.withAlpha(accent, 0x30));
c.drawCircle(ox + 50f * k, oy + 46f * k, 46f * k, fill);
c.save();
c.translate(ox, oy);
c.scale(k, k);
boolean chibiShown = character == MascotCharacter.CHIBI;
if (chibiShown) chibi.draw(c, mood, t, t % 4200 < 260);
else drawCharacter(c, mood, t);
c.restore();
float x0 = ox + 78f * k;
float tipX = chibiShown ? ChibiFigure.TIP_X : 70f, tipY = chibiShown ? ChibiFigure.TIP_Y : 46f;
drawBubble(c, mood, t, x0, h, ox + tipX * k, oy + tipY * k, w);
drawValues(c, x0, ox + 93f * k, w, h);
}
// ---------- The co-pilot, on the 100 × 100 grid ----------
private void drawCharacter(Canvas c, MascotMood mood, long t) {
float tilt = mood == MascotMood.SLEEPING ? 8f : mood == MascotMood.REVERSE ? -6f
: mood == MascotMood.CHEERING ? -4f : 0f;
// Hair behind the head, falling behind the shoulders.
c.save();
c.rotate(tilt, 50f, 62f);
fill.setColor(hair);
c.drawOval(oval(27f, 9f, 73f, 57f), fill);
p.reset();
p.moveTo(28.5f, 28f);
p.cubicTo(26f, 46f, 25f, 64f, 22f, 84f);
p.lineTo(78f, 84f);
p.cubicTo(75f, 64f, 74f, 46f, 71.5f, 28f);
p.close();
c.drawPath(p, fill);
c.restore();
// Neck, jacket, shirt collar, badge.
fill.setColor(skin);
c.drawRect(44.5f, 48f, 55.5f, 70f, fill);
fill.setColor(skinShade);
c.drawOval(oval(44f, 50f, 56f, 57f), fill);
p.reset();
p.moveTo(10f, 100f);
p.lineTo(14f, 80f);
p.cubicTo(16f, 71f, 26f, 68f, 40f, 66f);
p.lineTo(60f, 66f);
p.cubicTo(74f, 68f, 84f, 71f, 86f, 80f);
p.lineTo(90f, 100f);
p.close();
fill.setColor(accent);
c.drawPath(p, fill);
triangle(c, 40.5f, 66f, 59.5f, 66f, 50f, 85f, shirt);
triangle(c, 45f, 66f, 55f, 66f, 50f, 74f, skin);
stroke.setColor(accentDark);
stroke.setStrokeWidth(2.2f);
c.drawLine(40.5f, 66.5f, 49f, 90f, stroke);
c.drawLine(59.5f, 66.5f, 51f, 90f, stroke);
stroke.setColor(shirt);
stroke.setStrokeWidth(1.6f);
c.drawLine(16f, 79f, 38f, 70f, stroke);
c.drawLine(84f, 79f, 62f, 70f, stroke);
badge(c, 68f, 86f, 4f);
// The head.
c.save();
c.rotate(tilt, 50f, 62f);
p.reset();
p.moveTo(35f, 28f);
p.cubicTo(35f, 44f, 42f, 55f, 50f, 57f);
p.cubicTo(58f, 55f, 65f, 44f, 65f, 28f);
p.cubicTo(65f, 14f, 35f, 14f, 35f, 28f);
p.close();
fill.setColor(skin);
c.drawPath(p, fill);
boolean blink = t % 4200 < 260;
boolean blush = mood == MascotMood.CHEERING || mood == MascotMood.FAST || mood == MascotMood.SLEEPING;
if (blush) {
fill.setColor(0x55FF7A8A);
c.drawOval(oval(37.5f, 43.2f, 43.5f, 46f), fill);
c.drawOval(oval(56.5f, 43.2f, 62.5f, 46f), fill);
}
eye(c, 43f, 38f, mood, blink, true);
eye(c, 57f, 38f, mood, blink, false);
stroke.setColor(skinShade);
stroke.setStrokeWidth(0.8f);
c.drawLine(50f, 43.5f, 49.3f, 45.2f, stroke);
mouth(c, mood);
drawFrontHair(c);
brows(c, mood);
drawHeadset(c);
if (mood == MascotMood.WORRIED) sweatDrop(c);
c.restore();
if (mood == MascotMood.SLEEPING) sleepZs(c, t);
if (mood == MascotMood.CHEERING) sparkles(c, t);
if (mood == MascotMood.FAST) wind(c, t);
}
private void drawFrontHair(Canvas c) {
fill.setColor(hair);
p.reset();
p.moveTo(33f, 36f);
p.lineTo(33.5f, 24f);
p.cubicTo(36f, 10f, 64f, 10f, 66.5f, 24f);
p.lineTo(67f, 36f);
p.lineTo(64f, 28f);
p.lineTo(61.5f, 33f);
p.lineTo(58.5f, 26.5f);
p.lineTo(55f, 32f);
p.lineTo(51.5f, 25.5f);
p.lineTo(48f, 31.5f);
p.lineTo(44.5f, 26f);
p.lineTo(41.5f, 32.5f);
p.lineTo(38.5f, 27f);
p.lineTo(36f, 34f);
p.close();
c.drawPath(p, fill);
// Side locks framing the face, the right one mirrored.
p.reset();
p.moveTo(33.5f, 26f);
p.cubicTo(31f, 38f, 31f, 50f, 33f, 61f);
p.lineTo(36.2f, 52f);
p.cubicTo(35.5f, 44f, 36f, 36f, 37.5f, 30f);
p.close();
c.drawPath(p, fill);
c.save();
c.scale(-1f, 1f, 50f, 0f);
c.drawPath(p, fill);
c.restore();
stroke.setColor(hairLight);
stroke.setStrokeWidth(1.3f);
c.drawArc(oval(38f, 13.5f, 62f, 30f), 205f, 55f, false, stroke);
}
private void drawHeadset(Canvas c) {
stroke.setColor(headset);
stroke.setStrokeWidth(2.2f);
c.drawArc(oval(31f, 9.5f, 69f, 48f), 180f, 180f, false, stroke);
fill.setColor(cup);
c.drawRoundRect(oval(28.5f, 29f, 36f, 42f), 2.5f, 2.5f, fill);
c.drawRoundRect(oval(64f, 29f, 71.5f, 42f), 2.5f, 2.5f, fill);
fill.setColor(accent);
c.drawRect(29.5f, 33.5f, 30.6f, 37.5f, fill);
c.drawRect(69.4f, 33.5f, 70.5f, 37.5f, fill);
stroke.setStrokeWidth(1f);
p.reset();
p.moveTo(67.5f, 41f);
p.quadTo(66f, 50f, 58.5f, 50.5f);
c.drawPath(p, stroke);
fill.setColor(headset);
c.drawCircle(57.6f, 50.5f, 1.3f, fill);
}
private void eye(Canvas c, float cx, float cy, MascotMood mood, boolean blink, boolean left) {
stroke.setColor(lash);
if (mood == MascotMood.SLEEPING || (blink && mood != MascotMood.CHEERING)) {
// Closed, calm: a lid line curving down.
stroke.setStrokeWidth(1.2f);
c.drawArc(oval(cx - 4f, cy - 3.2f, cx + 4f, cy + 1.6f), 20f, 140f, false, stroke);
return;
}
if (mood == MascotMood.CHEERING) {
// Happy, squeezed shut: ^ ^.
stroke.setStrokeWidth(1.4f);
c.drawArc(oval(cx - 4f, cy - 1.5f, cx + 4f, cy + 4.5f), 200f, 140f, false, stroke);
return;
}
float look = mood == MascotMood.REVERSE ? -1.6f : 0f;
float irisW = mood == MascotMood.WORRIED ? 1.7f : 2.3f;
fill.setColor(Color.WHITE);
oval(cx - 4f, cy - 2.6f, cx + 4f, cy + 2.8f);
c.drawOval(r, fill);
c.save();
p.reset();
p.addOval(r, Path.Direction.CW);
c.clipPath(p);
fill.setColor(iris);
c.drawOval(oval(cx - irisW + look, cy - 2.8f, cx + irisW + look, cy + 2.8f), fill);
fill.setColor(pupil);
c.drawOval(oval(cx - irisW * 0.5f + look, cy - 1.5f, cx + irisW * 0.5f + look, cy + 1.6f), fill);
fill.setColor(Color.WHITE);
c.drawCircle(cx - 0.8f + look, cy - 1.2f, 0.75f, fill);
c.drawCircle(cx + 0.8f + look, cy + 1.2f, 0.35f, fill);
if (mood == MascotMood.DETERMINED) {
// Narrowed: the lid comes down over the top of the eye.
fill.setColor(skin);
c.drawRect(cx - 4.5f, cy - 3.2f, cx + 4.5f, cy - 1.0f, fill);
}
c.restore();
stroke.setStrokeWidth(1.25f);
if (mood == MascotMood.DETERMINED) {
c.drawLine(cx - 4.3f, cy - 0.9f, cx + 4.3f, cy - 1.1f, stroke);
} else {
c.drawArc(oval(cx - 4.4f, cy - 3f, cx + 4.4f, cy + 3.4f), 195f, 150f, false, stroke);
}
// The flick at the outer corner.
float outer = left ? cx - 4.2f : cx + 4.2f, dir = left ? -1f : 1f;
c.drawLine(outer, cy - 0.6f, outer + dir * 1.3f, cy - 1.6f, stroke);
}
private void brows(Canvas c, MascotMood mood) {
stroke.setColor(hair);
stroke.setStrokeWidth(1.1f);
switch (mood) {
case WORRIED:
c.drawLine(39f, 31.5f, 46.5f, 29.3f, stroke);
c.drawLine(53.5f, 29.3f, 61f, 31.5f, stroke);
break;
case DETERMINED:
c.drawLine(39f, 29.6f, 46.8f, 32f, stroke);
c.drawLine(53.2f, 32f, 61f, 29.6f, stroke);
break;
default:
float lift = mood == MascotMood.CHEERING || mood == MascotMood.FAST ? -0.8f : 0f;
c.drawArc(oval(39f, 30f + lift, 47f, 33.5f + lift), 200f, 120f, false, stroke);
c.drawArc(oval(53f, 30f + lift, 61f, 33.5f + lift), 220f, 120f, false, stroke);
}
}
private void mouth(Canvas c, MascotMood mood) {
stroke.setColor(lips);
stroke.setStrokeWidth(1f);
fill.setColor(mouth);
switch (mood) {
case CHEERING:
case FAST:
p.reset();
p.moveTo(45.5f, 48.8f);
p.quadTo(50f, 56f, 54.5f, 48.8f);
p.close();
c.drawPath(p, fill);
fill.setColor(0xFFE88A95);
c.drawOval(oval(47.8f, 51.2f, 52.2f, 52.8f), fill);
break;
case SLEEPING:
case REVERSE:
c.drawOval(oval(48.8f, 49.3f, 51.2f, 51.6f), fill);
break;
case WORRIED:
p.reset();
p.moveTo(46.5f, 51f);
p.quadTo(47.8f, 49.6f, 49.2f, 51f);
p.quadTo(50.6f, 52.4f, 52f, 51f);
p.quadTo(53.2f, 49.8f, 54f, 50.8f);
c.drawPath(p, stroke);
break;
case DETERMINED:
p.reset();
p.moveTo(46f, 50.6f);
p.quadTo(51f, 52.6f, 55f, 49f);
c.drawPath(p, stroke);
break;
default:
c.drawArc(oval(46f, 46.5f, 54f, 51.2f), 25f, 130f, false, stroke);
}
}
private void sweatDrop(Canvas c) {
fill.setColor(0xFF8FD3FF);
p.reset();
p.moveTo(63f, 21f);
p.quadTo(66.5f, 26.5f, 63f, 28f);
p.quadTo(59.5f, 26.5f, 63f, 21f);
c.drawPath(p, fill);
}
/** Three Zs drifting up and to the left, over three seconds. */
private void sleepZs(Canvas c, long t) {
float phase = (t % 3000) / 3000f;
text.setTextAlign(Paint.Align.CENTER);
float[][] zs = { { 24f, 22f, 5f }, { 17f, 14f, 6.5f }, { 9f, 5.5f, 8f } };
for (float[] z : zs) {
text.setTextSize(z[2]);
text.setColor(AccentColor.withAlpha(accentLight, Math.round(255 * (1f - phase * 0.5f))));
c.drawText("Z", z[0] - phase * 2f, z[1] + z[2] - phase * 3f, text);
}
}
/** Four-pointed sparkles around her head, twinkling. */
private void sparkles(Canvas c, long t) {
float[][] at = { { 22f, 20f }, { 78f, 14f }, { 18f, 46f }, { 84f, 40f }, { 50f, 3f } };
for (int i = 0; i < at.length; i++) {
float s = 2.2f + 1.6f * (float) Math.abs(Math.sin(t / 260.0 + i * 1.3));
fill.setColor(i % 2 == 0 ? regen : Color.WHITE);
star(c, at[i][0], at[i][1], s);
}
}
/** Streaks rushing past on her left. */
private void wind(Canvas c, long t) {
stroke.setStrokeWidth(0.9f);
stroke.setColor(0x88FFFFFF);
for (int i = 0; i < 4; i++) {
float x = 26f - ((t / 6f + i * 37f) % 24f);
float y = 16f + i * 10f;
c.drawLine(x - 9f, y, x, y, stroke);
}
}
private void star(Canvas c, float x, float y, float s) {
p.reset();
p.moveTo(x, y - s);
p.quadTo(x, y, x + s, y);
p.quadTo(x, y, x, y + s);
p.quadTo(x, y, x - s, y);
p.quadTo(x, y, x, y - s);
p.close();
c.drawPath(p, fill);
}
/** A small silver octagon on the jacket. */
private void badge(Canvas c, float x, float y, float s) {
p.reset();
for (int i = 0; i < 8; i++) {
double a = Math.toRadians(22.5 + i * 45);
float px = x + s * (float) Math.cos(a), py = y + s * (float) Math.sin(a);
if (i == 0) p.moveTo(px, py);
else p.lineTo(px, py);
}
p.close();
fill.setColor(0xFFD7DBE0);
c.drawPath(p, fill);
text.setTextAlign(Paint.Align.CENTER);
text.setTextSize(3.4f);
text.setColor(accentDark);
c.drawText("MG", x, y + 1.2f, text);
}
private void triangle(Canvas c, float x1, float y1, float x2, float y2, float x3, float y3, int colour) {
p.reset();
p.moveTo(x1, y1);
p.lineTo(x2, y2);
p.lineTo(x3, y3);
p.close();
fill.setColor(colour);
c.drawPath(p, fill);
}
private RectF oval(float left, float top, float right, float bottom) {
r.set(left, top, right, bottom);
return r;
}
// ---------- Speech bubble and values, in pixels ----------
private void drawBubble(Canvas c, MascotMood mood, long t, float x0, float h, float tipX, float tipY, float w) {
String[] options = lines[character.ordinal()][mood.ordinal()];
String line = options.length == 0 ? "" : options[(int) ((t / 30000 + mood.ordinal()) % options.length)];
text.setTextAlign(Paint.Align.LEFT);
float pad = h * 0.045f, room = w - 140f * dp - x0 - 2f * pad;
// Shrink a long line to fit the room left of the clock.
text.setTextSize(h * 0.07f);
float width = text.measureText(line);
if (width > room && room > 0f) {
text.setTextSize(h * 0.07f * room / width);
width = room;
}
float right = x0 + width + 2f * pad;
float top = h * 0.05f, bottom = top + h * 0.07f + 2f * pad;
fill.setColor(0xFFF7F7FA);
c.drawRoundRect(oval(x0, top, right, bottom), h * 0.05f, h * 0.05f, fill);
triangle(c, x0 + h * 0.03f, bottom - 2f, x0 + h * 0.11f, bottom - 2f, tipX, tipY, 0xFFF7F7FA);
text.setColor(0xFF1C1A24);
c.drawText(line, x0 + pad, bottom - pad - h * 0.012f, text);
}
/** Speed and gear from x0; the battery and the bottom row from xLow, clear of her shoulder. */
private void drawValues(Canvas c, float x0, float xLow, float w, float h) {
float right = w - 16f * dp;
// The clock, left of the full-screen button.
text.setTextAlign(Paint.Align.RIGHT);
text.setTextSize(20f * dp);
text.setColor(grey);
c.drawText(v.clock.isEmpty() ? "—" : v.clock, w - 72f * dp, 30f * dp, text);
// Speed, large, and the gear in a ring at the right.
text.setTextAlign(Paint.Align.LEFT);
text.setTextSize(h * 0.34f);
text.setColor(white);
String speed = fmt(v.speedKmh, "%.0f", "—");
float baseline = h * 0.64f;
c.drawText(speed, x0, baseline, text);
float after = x0 + text.measureText(speed) + 14f * dp;
text.setTextSize(h * 0.065f);
text.setColor(grey);
c.drawText("km/h", after, baseline, text);
float gr = h * 0.11f, gx = right - gr, gy = h * 0.50f;
stroke.setColor(accent);
stroke.setStrokeWidth(4f * dp);
c.drawCircle(gx, gy, gr, stroke);
String gear = Gear.label(v.gear);
text.setTextAlign(Paint.Align.CENTER);
text.setTextSize(gr * 1.1f);
text.setColor(white);
c.drawText(gear.isEmpty() ? " " : gear, gx, gy + gr * 0.38f, text);
// Battery: percent and range over a bar.
float barY = h * 0.77f, barH = 12f * dp;
boolean low = v.socPercent != null && v.socPercent < MascotMood.LOW_SOC;
text.setTextSize(h * 0.055f);
text.setTextAlign(Paint.Align.LEFT);
text.setColor(low ? warn : white);
c.drawText(fmt(v.socPercent, "%.0f %%", "— %"), xLow, barY - 10f * dp, text);
text.setTextAlign(Paint.Align.RIGHT);
text.setColor(white);
c.drawText(fmt(v.rangeKm, "%.0f km", "— km"), right, barY - 10f * dp, text);
fill.setColor(track);
c.drawRoundRect(oval(xLow, barY, right, barY + barH), barH / 2f, barH / 2f, fill);
if (v.socPercent != null) {
float soc = Math.max(0f, Math.min(100f, v.socPercent)) / 100f;
fill.setColor(low ? warn : accent);
c.drawRoundRect(oval(xLow, barY, xLow + Math.max(barH, (right - xLow) * soc), barY + barH), barH / 2f, barH / 2f, fill);
}
// Power, usage, trip.
float col = (right - xLow) / 3f, labelY = h * 0.885f, valueY = h * 0.965f;
boolean regenerating = v.powerKw != null && v.powerKw < 0f;
cell(c, xLow, labelY, valueY, "POWER", fmt(v.powerKw, "%.1f kW", "— kW"), regenerating ? regen : white);
cell(c, xLow + col, labelY, valueY, "USAGE · 5 MIN", fmt(v.usage5Min, "%.1f kWh/100 km", "—"), white);
cell(c, xLow + 2f * col, labelY, valueY, "TRIP", fmt(v.tripKm, "%.1f km", "— km"), white);
}
private void cell(Canvas c, float x, float labelY, float valueY, String label, String value, int colour) {
text.setTextAlign(Paint.Align.LEFT);
text.setTextSize(13f * dp);
text.setLetterSpacing(0.12f);
text.setColor(grey);
c.drawText(label, x, labelY, text);
text.setLetterSpacing(0f);
text.setTextSize(getHeight() * 0.055f);
text.setColor(colour);
c.drawText(value, x, valueY, text);
}
}