analog_clock.tc¶
Analog clock with watchface background and dirty-rect hand erase
// Analog clock with watchface background and dirty-rect hand erase
// Requires: /watch_ren_back_240.jpg on filesystem
// Set DRAW_X/DRAW_Y to position clock on larger displays
// Uses: dspLoadImage, dspPushImageRect, drawLine, sin, cos
//#define USE_WF200
// --- easy-to-change parameters ---
#ifdef USE_WF200
#define DRAW_X 400 // x position on display
#define DRAW_Y 280 // y position on display
#else
#define DRAW_X 0 // x position on display
#define DRAW_Y 0 // y position on display
#endif
#define HOUR_PCT 58 // hour hand length in % of radius
#define MIN_PCT 79 // minute hand length in % of radius
#define SEC_PCT 87 // second hand length in % of radius
#define HOUR_THICK 3
#define MIN_THICK 2
#define SEC_THICK 1
#define HOUR_COLOR 0xFFFF
#define MIN_COLOR 0xFFFF
#define SEC_COLOR 0xF800
#define DATE_XP 77 // date x position in % of image width
#define DATE_YP 48 // date y position in % of image height
#ifdef USE_WF200
#define WATCHFACE "/watch_ren_back_200.jpg"
#else
#define WATCHFACE "/watch_ren_back_240.jpg"
#endif
int face; // image store slot
int img_w; // image width (from JPG)
int img_h; // image height (from JPG)
int cx; // center x
int cy; // center y
// previous hand endpoints for erase
int oh_x; int oh_y; // old hour
int om_x; int om_y; // old minute
int os_x; int os_y; // old second
// hand lengths
int h_len; // hour hand
int m_len; // minute hand
int s_len; // second hand
int date_x; // computed date position
int date_y;
int prev_sec; // to detect second change
// hand endpoint output (global — TinyC can't return via array param)
int hx_out;
int hy_out;
// calculate hand endpoint from angle (0=12 o'clock, clockwise)
void handXY(float angle, int len) {
float rad;
rad = angle * 3.14159 / 180.0;
hx_out = cx + (int)(sin(rad) * (float)len);
hy_out = cy - (int)(cos(rad) * (float)len);
}
// draw a thick line — offsets in both x and y for full coverage
void thickLine(int x0, int y0, int x1, int y1, int color, int width) {
int d;
int half;
half = width / 2;
dspColor(color, 0);
d = 0 - half;
while (d <= half) {
dspPos(x0 + d, y0);
dspLine(x1 + d, y1);
dspPos(x0, y0 + d);
dspLine(x1, y1 + d);
d = d + 1;
}
}
// erase a hand by restoring the bounding rect from the watchface image
void eraseHand(int hx, int hy, int thick) {
int x0; int y0; int w; int h;
int margin;
margin = thick + 2;
// bounding rect of line from center to endpoint
if (cx < hx) { x0 = cx - margin; } else { x0 = hx - margin; }
if (cy < hy) { y0 = cy - margin; } else { y0 = hy - margin; }
w = abs(hx - cx) + margin * 2;
h = abs(hy - cy) + margin * 2;
// clamp to image bounds
if (x0 < DRAW_X) { w = w - (DRAW_X - x0); x0 = DRAW_X; }
if (y0 < DRAW_Y) { h = h - (DRAW_Y - y0); y0 = DRAW_Y; }
if (x0 + w > DRAW_X + img_w) { w = DRAW_X + img_w - x0; }
if (y0 + h > DRAW_Y + img_h) { h = DRAW_Y + img_h - y0; }
if (w > 0 && h > 0) {
dspPushImageRect(face, x0 - DRAW_X, y0 - DRAW_Y, x0, y0, w, h);
}
}
// draw center dot
void drawCenter() {
dspColor(0xFFFF, 0);
dspPos(cx, cy);
dspFillCircle(4);
dspColor(0xF800, 0);
dspPos(cx, cy);
dspFillCircle(2);
}
void drawHands() {
float h_angle;
float m_angle;
float s_angle;
int hr;
int mn;
int sc;
hr = tasm_hour;
mn = tasm_minute;
sc = tasm_second;
// angles in degrees (0 = 12 o'clock)
h_angle = (float)(hr % 12) * 30.0 + (float)mn * 0.5;
m_angle = (float)mn * 6.0;
s_angle = (float)sc * 6.0;
// hour hand
handXY(h_angle, h_len);
thickLine(cx, cy, hx_out, hy_out, HOUR_COLOR, HOUR_THICK);
oh_x = hx_out; oh_y = hy_out;
// minute hand
handXY(m_angle, m_len);
thickLine(cx, cy, hx_out, hy_out, MIN_COLOR, MIN_THICK);
om_x = hx_out; om_y = hy_out;
// second hand
handXY(s_angle, s_len);
thickLine(cx, cy, hx_out, hy_out, SEC_COLOR, SEC_THICK);
os_x = hx_out; os_y = hy_out;
drawCenter();
drawDate();
}
void drawDate() {
// draw date
char buf[30];
// font1 for 240, f3 for 200 size
sprintf(buf, "[C%dB0f1s1D2", BLACK);
sprintfAppend(buf, "x%dy%dp-2]%d", date_x, date_y, tasm_day);
dspText(buf);
dspText("[D0]");
}
void EverySecond() {
if (tasm_second == prev_sec) { return; }
prev_sec = tasm_second;
// erase old hands (order: second first, then minute, then hour)
// so the restore rects overlap correctly
eraseHand(os_x, os_y, SEC_THICK);
eraseHand(om_x, om_y, MIN_THICK + 1);
eraseHand(oh_x, oh_y, HOUR_THICK + 1);
// draw new hands
drawHands();
}
void main() {
// load watchface background into image store
face = dspLoadImage(WATCHFACE);
img_w = dspImageWidth(face);
img_h = dspImageHeight(face);
int radius;
// center and hand lengths derived from image size
cx = DRAW_X + img_w / 2;
cy = DRAW_Y + img_h / 2;
radius = img_w / 2;
if (img_h < img_w) { radius = img_h / 2; }
h_len = radius * HOUR_PCT / 100;
m_len = radius * MIN_PCT / 100;
s_len = radius * SEC_PCT / 100;
date_x = DRAW_X + img_w * DATE_XP / 100;
date_y = DRAW_Y + img_h * DATE_YP / 100;
prev_sec = -1;
oh_x = cx; oh_y = cy;
om_x = cx; om_y = cy;
os_x = cx; os_y = cy;
// draw full watchface from PSRAM image store
dspPushImageRect(face, 0, 0, DRAW_X, DRAW_Y, img_w, img_h);
// draw initial hands
drawHands();
}