feat: refine watch UI — arc HR gauge, proper padel court, spacing
- HR shown as a top arc gauge in Garmin zone colors (grey/blue/green/ orange/red) with a marker on the current zone and the bpm number below - Redraw court to a proper padel layout: net centered, service lines at the 3m-from-back-wall proportion, center line only between service lines - Wider court with a configurable blue surface (bgColor param on CourtRenderer, ready for the future settings screen) - Serve ball marks the back zone (farthest from the net) of the server - Roomier score spacing; games column white, sets accent green; "Pts" - HR + time on a matched FONT_MEDIUM Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,26 +5,34 @@ import Toybox.Graphics;
|
||||
// in the given quadrant. Half-width HW and half-height HH keep it inside the
|
||||
// central band of the round screen.
|
||||
class CourtRenderer {
|
||||
private const HW = 60; // half court width (px)
|
||||
private const HH = 78; // half court height (px)
|
||||
private const BALL_R = 6;
|
||||
private const HW = 92; // half court width (px)
|
||||
private const HH = 95; // half court height (px)
|
||||
private const BALL_R = 7;
|
||||
|
||||
// bgColor fills the court surface (blue for now; a future settings screen
|
||||
// will drive it via RulesConfig).
|
||||
public function draw(dc as Graphics.Dc, cx as Number, cy as Number,
|
||||
quadrant as Number, showBall as Boolean) as Void {
|
||||
quadrant as Number, showBall as Boolean,
|
||||
bgColor as Number) as Void {
|
||||
var left = cx - HW;
|
||||
var top = cy - HH;
|
||||
|
||||
// Service line sits 3m from the back wall (7m from the net) on a 10m
|
||||
// half, i.e. 0.7 of the way from the net to the back boundary.
|
||||
var sl = (7 * HH) / 10;
|
||||
|
||||
dc.setColor(bgColor, Graphics.COLOR_TRANSPARENT);
|
||||
dc.fillRectangle(left, top, 2 * HW, 2 * HH); // court surface
|
||||
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
|
||||
dc.setPenWidth(2);
|
||||
dc.drawRectangle(left, top, 2 * HW, 2 * HH); // outer court
|
||||
dc.drawLine(cx, top, cx, cy + HH); // center service line
|
||||
dc.drawRectangle(left, top, 2 * HW, 2 * HH); // outer boundary
|
||||
dc.drawLine(left, cy - sl, left + 2 * HW, cy - sl); // upper service line
|
||||
dc.drawLine(left, cy + sl, left + 2 * HW, cy + sl); // lower service line
|
||||
dc.drawLine(cx, cy - sl, cx, cy + sl); // center line (between service lines only)
|
||||
|
||||
dc.setPenWidth(1);
|
||||
dc.drawLine(left, cy - HH / 2, left + 2 * HW, cy - HH / 2); // upper service line
|
||||
dc.drawLine(left, cy + HH / 2, left + 2 * HW, cy + HH / 2); // lower service line
|
||||
|
||||
dc.setPenWidth(3);
|
||||
dc.drawLine(left, cy, left + 2 * HW, cy); // net
|
||||
dc.setPenWidth(4);
|
||||
dc.drawLine(left, cy, left + 2 * HW, cy); // net (boldest)
|
||||
|
||||
if (showBall) {
|
||||
var pos = _ballPos(quadrant, cx, cy);
|
||||
@@ -35,7 +43,7 @@ class CourtRenderer {
|
||||
|
||||
private function _ballPos(quadrant as Number, cx as Number, cy as Number) as Array<Number> {
|
||||
var dx = HW / 2;
|
||||
var dy = HH / 2;
|
||||
var dy = (17 * HH) / 20; // centered in the back zone, farthest from the net
|
||||
if (quadrant == QUAD_BOTTOM_RIGHT) { return [cx + dx, cy + dy]; }
|
||||
if (quadrant == QUAD_BOTTOM_LEFT) { return [cx - dx, cy + dy]; }
|
||||
if (quadrant == QUAD_UPPER_LEFT) { return [cx - dx, cy - dy]; }
|
||||
|
||||
@@ -3,10 +3,12 @@ import Toybox.Graphics;
|
||||
import Toybox.WatchUi;
|
||||
import Toybox.System;
|
||||
import Toybox.Timer;
|
||||
import Toybox.Math;
|
||||
|
||||
class garmin_padelView extends WatchUi.View {
|
||||
private const ACCENT = 0x00E0A0; // padel green (sets/games)
|
||||
private const LABEL_COLOR = 0x808080; // muted grey (S/G/pts labels)
|
||||
private const COURT_BLUE = 0x1560BD; // court surface fill (configurable later)
|
||||
|
||||
private var _court as CourtRenderer;
|
||||
private var _timer as Timer.Timer;
|
||||
@@ -39,11 +41,11 @@ class garmin_padelView extends WatchUi.View {
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_BLACK);
|
||||
dc.clear();
|
||||
|
||||
_court.draw(dc, cx, cy, m.servingQuadrant(), !m.isMatchOver());
|
||||
_court.draw(dc, cx, cy, m.servingQuadrant(), !m.isMatchOver(), COURT_BLUE);
|
||||
_drawLeftColumn(dc, m);
|
||||
_drawRightColumn(dc, m, w);
|
||||
_drawHeartRate(dc, cx, 40);
|
||||
_drawTime(dc, cx, h - 44);
|
||||
_drawHeartRate(dc, cx, cy);
|
||||
_drawTime(dc, cx, h - 78);
|
||||
|
||||
if (m.isMatchOver()) {
|
||||
_drawWinner(dc, cx, cy, m);
|
||||
@@ -52,11 +54,11 @@ class garmin_padelView extends WatchUi.View {
|
||||
|
||||
// Left: S and G mini-columns, your team on top, opponents below.
|
||||
private function _drawLeftColumn(dc as Graphics.Dc, m as PadelMatch) as Void {
|
||||
var sX = 24;
|
||||
var gX = 60;
|
||||
var labelY = 150;
|
||||
var usY = 178;
|
||||
var themY = 206;
|
||||
var sX = 30;
|
||||
var gX = 76;
|
||||
var labelY = 148;
|
||||
var usY = 184;
|
||||
var themY = 240;
|
||||
|
||||
dc.setColor(LABEL_COLOR, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(sX, labelY, Graphics.FONT_XTINY, "S", Graphics.TEXT_JUSTIFY_CENTER);
|
||||
@@ -65,58 +67,79 @@ class garmin_padelView extends WatchUi.View {
|
||||
dc.setColor(ACCENT, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(sX, usY, Graphics.FONT_TINY, m.sets(US).toString(), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
dc.drawText(sX, themY, Graphics.FONT_TINY, m.sets(THEM).toString(), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(gX, usY, Graphics.FONT_TINY, m.games(US).toString(), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
dc.drawText(gX, themY, Graphics.FONT_TINY, m.games(THEM).toString(), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
}
|
||||
|
||||
// Right: pts column, your team on top, opponents below.
|
||||
// Right: Pts column, your team on top, opponents below.
|
||||
private function _drawRightColumn(dc as Graphics.Dc, m as PadelMatch, w as Number) as Void {
|
||||
var pX = w - 42;
|
||||
var labelY = 150;
|
||||
var usY = 178;
|
||||
var themY = 206;
|
||||
var pX = w - 46;
|
||||
var labelY = 148;
|
||||
var usY = 184;
|
||||
var themY = 240;
|
||||
|
||||
dc.setColor(LABEL_COLOR, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(pX, labelY, Graphics.FONT_XTINY, "pts", Graphics.TEXT_JUSTIFY_CENTER);
|
||||
dc.drawText(pX, labelY, Graphics.FONT_XTINY, "Pts", Graphics.TEXT_JUSTIFY_CENTER);
|
||||
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(pX, usY, Graphics.FONT_TINY, m.pointLabel(US), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
dc.drawText(pX, themY, Graphics.FONT_TINY, m.pointLabel(THEM), Graphics.TEXT_JUSTIFY_CENTER);
|
||||
}
|
||||
|
||||
private function _drawHeartRate(dc as Graphics.Dc, cx as Number, y as Number) as Void {
|
||||
// Heart rate as a top arc gauge: 5 zone-colored segments (grey/blue/green/
|
||||
// orange/red), a white marker on the current zone, and the bpm number just
|
||||
// below — mirroring the standard Garmin run-activity HR view.
|
||||
private function _drawHeartRate(dc as Graphics.Dc, cx as Number, cy as Number) as Void {
|
||||
var hr = getApp().getHeartRate();
|
||||
var bpm = hr.getHeartRate();
|
||||
var text = (bpm == null) ? "-- bpm" : bpm.toString() + " bpm";
|
||||
dc.setColor(Graphics.COLOR_RED, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(cx, y, Graphics.FONT_TINY, text, Graphics.TEXT_JUSTIFY_CENTER);
|
||||
_drawZoneBar(dc, cx, y + 26, hr.getZone());
|
||||
var zone = hr.getZone();
|
||||
var r = cx - 9; // arc radius, hugging the top bezel
|
||||
|
||||
// 5 zone segments across the top (150deg .. 30deg, clockwise)
|
||||
dc.setPenWidth(14);
|
||||
for (var k = 0; k < 5; k += 1) {
|
||||
var segStart = 150 - k * 24;
|
||||
dc.setColor(_zoneColor(k + 1), Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawArc(cx, cy, r, Graphics.ARC_CLOCKWISE, segStart - 2, segStart - 22);
|
||||
}
|
||||
dc.setPenWidth(1);
|
||||
|
||||
// marker on the current zone
|
||||
if (zone >= 1 && zone <= 5) {
|
||||
var mid = 150 - (zone - 1) * 24 - 12;
|
||||
var rad = Math.toRadians(mid);
|
||||
var mx = (cx + r * Math.cos(rad)).toNumber();
|
||||
var my = (cy - r * Math.sin(rad)).toNumber();
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
|
||||
dc.fillCircle(mx, my, 8);
|
||||
dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawCircle(mx, my, 8);
|
||||
}
|
||||
|
||||
// 5-segment bar; segments up to the current zone are filled.
|
||||
private function _drawZoneBar(dc as Graphics.Dc, cx as Number, y as Number, zone as Number) as Void {
|
||||
var segW = 12;
|
||||
var segH = 6;
|
||||
var gap = 3;
|
||||
var totalW = 5 * segW + 4 * gap;
|
||||
var x = cx - totalW / 2;
|
||||
for (var i = 1; i <= 5; i += 1) {
|
||||
if (i <= zone) {
|
||||
dc.setColor(ACCENT, Graphics.COLOR_TRANSPARENT);
|
||||
dc.fillRectangle(x, y, segW, segH);
|
||||
} else {
|
||||
dc.setColor(LABEL_COLOR, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawRectangle(x, y, segW, segH);
|
||||
}
|
||||
x += segW + gap;
|
||||
// exact rate, tucked just under the arc (dim placeholder when no reading,
|
||||
// so it never reads as a court line)
|
||||
var text = (bpm == null) ? "--" : bpm.toString();
|
||||
var col = (bpm == null) ? LABEL_COLOR : Graphics.COLOR_WHITE;
|
||||
dc.setColor(col, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(cx, cy - 178, Graphics.FONT_MEDIUM, text, Graphics.TEXT_JUSTIFY_CENTER);
|
||||
}
|
||||
|
||||
// Garmin HR zone colors: 1 grey, 2 blue, 3 green, 4 orange, 5 red.
|
||||
private function _zoneColor(z as Number) as Number {
|
||||
if (z == 1) { return Graphics.COLOR_LT_GRAY; }
|
||||
if (z == 2) { return Graphics.COLOR_BLUE; }
|
||||
if (z == 3) { return Graphics.COLOR_GREEN; }
|
||||
if (z == 4) { return Graphics.COLOR_ORANGE; }
|
||||
return Graphics.COLOR_RED;
|
||||
}
|
||||
|
||||
private function _drawTime(dc as Graphics.Dc, cx as Number, y as Number) as Void {
|
||||
var now = System.getClockTime();
|
||||
var text = now.hour.format("%02d") + ":" + now.min.format("%02d");
|
||||
dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
|
||||
dc.drawText(cx, y, Graphics.FONT_SMALL, text, Graphics.TEXT_JUSTIFY_CENTER);
|
||||
dc.drawText(cx, y, Graphics.FONT_MEDIUM, text, Graphics.TEXT_JUSTIFY_CENTER);
|
||||
}
|
||||
|
||||
private function _drawWinner(dc as Graphics.Dc, cx as Number, cy as Number, m as PadelMatch) as Void {
|
||||
|
||||
Reference in New Issue
Block a user