Zum Inhalt

analog_clock.tc

Analog clock with watchface background and dirty-rect hand erase

Source on GitHub

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