#include "web_svg.h" #include "web_dom.h" #include "web_opts.h" #include "board.h" #include #include #include #define C_BLACK "#272b2c" #define C_WHITE "#fbe3ba" #define C_RED "#f6302f" #define C_GREEN "#22912d" #define C_WIRE "#909ca0" #define C_WIRE_INNER "#d8e6ec" #define C_NUMBER "#fff" #define C_NUMBER_RING "#ddd" #define C_POINT "#33f" #define C_POINT_OUTLINE "#ff0" #define POINT_RADIUS 6 static struct dom_elem *svg_elem_init(char *name, int size_attrs) { return dom_elem_init("http://www.w3.org/2000/svg", name, size_attrs); } static struct dom_elem *gen_circle(double r, char *col) { struct dom_elem *e = svg_elem_init("circle", 2); dom_elem_add_attrd(e, "r", r); dom_elem_add_attr(e, "fill", col); return e; } // draws a ring if angles are equal static struct dom_elem *gen_arc(double a1, double a2, double r1, double r2, char *col) { struct dom_elem *e = svg_elem_init("path", 2); bool ring = (a1 == a2); if (ring) { // these values are not significant. the ring is drawn as two // consecutive arcs, so the path goes from one angle to the // other and back to the first. a1 = 0; a2 = 1; } struct ccoords cc1 = pol_to_cart((struct pcoords){ .a = a2, .r = r1 }); struct ccoords cc2 = pol_to_cart((struct pcoords){ .a = a1, .r = r1 }); struct ccoords cc3 = pol_to_cart((struct pcoords){ .a = a1, .r = r2 }); struct ccoords cc4 = pol_to_cart((struct pcoords){ .a = a2, .r = r2 }); // (arc) // M - move to inner start // A - draw arc to inner end // L - draw line to outer end // A - draw arc to outer start // Z - draw line to inner start // // (ring) // M - move to inner 1 // A - draw arc to inner 2 // A - draw arc to inner 1 // Z - draw zero-length line to close path // M - move to outer 2 // A - draw arc to outer 1 // A - draw arc to outer 2 // Z - draw zero-length line to close path // // for default `nonzero' fill-rule to work for rings, the inner and // outer arcs must go in opposite directions char buf[512]; int len = 0; len += sprintf(buf + len, "M%f %fA%f %f 0 0 0 %f %fL", cc1.x, cc1.y, r1, r1, cc2.x, cc2.y); if (ring) len += -1 + sprintf(buf + len - 1, "A%f %f 0 1 0 %f %fZM", r1, r1, cc1.x, cc1.y); len += sprintf(buf + len, "%f %fA%f %f 0 0 1 %f %fZ", cc3.x, cc3.y, r2, r2, cc4.x, cc4.y); if (ring) sprintf(buf + len - 1, "A%f %f 0 1 1 %f %fZ", r2, r2, cc3.x, cc3.y); dom_elem_add_attr(e, "d", buf); dom_elem_add_attr(e, "fill", col); return e; } static struct dom_elem *gen_ring(double r, double w, char *col) { return gen_arc(0, 0, r - (w / 2), r + (w / 2), col); } static struct dom_elem *gen_segment(double a, double r1, double r2, char *col) { return gen_arc(a - (SECTOR_WIDTH / 2), a + (SECTOR_WIDTH / 2), r1, r2, col); } static struct dom_elem *gen_line(double a, double r1, double r2, double w, char *col) { struct dom_elem *e = svg_elem_init("line", 6); struct ccoords cc1 = pol_to_cart((struct pcoords){ .a = a, .r = r1 }); struct ccoords cc2 = pol_to_cart((struct pcoords){ .a = a, .r = r2 }); dom_elem_add_attrd(e, "x1", cc1.x); dom_elem_add_attrd(e, "y1", cc1.y); dom_elem_add_attrd(e, "x2", cc2.x); dom_elem_add_attrd(e, "y2", cc2.y); dom_elem_add_attr(e, "stroke", col); dom_elem_add_attrd(e, "stroke-width", w); return e; } static void add_segments(struct dom_elem *e) { for (int i = R_DOUBLE; i > R_25; --i) { for (int j = 0; j < NUM_SECTORS; ++j) { double a = 90 - (j * SECTOR_WIDTH); if (a < 0) a += 360; dom_elem_add_child(e, gen_segment(a, OUTER_DISTS[i - 1], OUTER_DISTS[i], i % 2 ? (j % 2 ? C_GREEN : C_RED) : (j % 2 ? C_WHITE : C_BLACK))); } } dom_elem_add_child(e, gen_circle(OUTER_DISTS[R_25], C_GREEN)); dom_elem_add_child(e, gen_circle(OUTER_DISTS[R_BULL], C_RED)); } static void add_wires(struct dom_elem *e) { for (int i = R_DOUBLE; i > R_25; --i) { dom_elem_add_child(e, gen_ring(OUTER_DISTS[i], WIRE_WIDTH, C_WIRE)); dom_elem_add_child(e, gen_ring(OUTER_DISTS[i], WIRE_WIDTH / 2, C_WIRE_INNER)); } for (int i = 0; i < NUM_SECTORS; ++i) { double a = 90 - (i * SECTOR_WIDTH) - (SECTOR_WIDTH / 2); if (a < 0) a += 360; dom_elem_add_child(e, gen_line(a, OUTER_DISTS[R_25], OUTER_DISTS[R_DOUBLE] + 10, WIRE_WIDTH, C_WIRE)); dom_elem_add_child(e, gen_line(a, OUTER_DISTS[R_25], OUTER_DISTS[R_DOUBLE] + 10, WIRE_WIDTH / 2, C_WIRE_INNER)); } for (int i = R_25; i >= R_BULL; --i) { dom_elem_add_child(e, gen_ring(OUTER_DISTS[i], WIRE_WIDTH, C_WIRE)); dom_elem_add_child(e, gen_ring(OUTER_DISTS[i], WIRE_WIDTH / 2, C_WIRE_INNER)); } } static void add_numbers(struct dom_elem *e) { dom_elem_add_child(e, gen_ring((DIAMETER / 2) - (WIRE_WIDTH * 4), WIRE_WIDTH, C_NUMBER_RING)); int r = (DIAMETER / 2) - (33 / 2); for (int i = 0; i < NUM_SECTORS; ++i) { struct dom_elem *c = svg_elem_init("text", 5); asprintf(&c->content, "%d", SECTORS[i]); double a = 90 - (i * SECTOR_WIDTH); if (a < 0) a += 360; struct ccoords cc = pol_to_cart( (struct pcoords){ .a = a, .r = r }); dom_elem_add_attrf(c, "transform", "scale(1 -1) translate(%f %f) rotate(%f)", cc.x, -cc.y, a <= 180 ? 90 - a : 270 - a); dom_elem_add_attr(c, "font-size", "33"); dom_elem_add_attr(c, "text-anchor", "middle"); dom_elem_add_attr(c, "dominant-baseline", "central"); dom_elem_add_attr(c, "fill", C_NUMBER); dom_elem_add_child(e, c); } } static void add_board(struct dom_elem *e) { dom_elem_add_child(e, gen_circle(DIAMETER / 2, C_BLACK)); add_segments(e); add_wires(e); add_numbers(e); } static void draw_board() { struct dom_elem *svg = svg_elem_init("svg", 2); dom_elem_add_attr(svg, "id", "dartboard"); dom_elem_add_attrf(svg, "viewBox", "0 0 %f %f", DIAMETER, DIAMETER); for (int i = 0; i < 2; ++i) { struct dom_elem *e = svg_elem_init("g", 2); dom_elem_add_attr(e, "class", i ? "overlay" : "board"); dom_elem_add_attrf(e, "transform", "translate(%f %f) scale(1 -1)", DIAMETER / 2, DIAMETER / 2); if (!i) add_board(e); dom_elem_add_child(svg, e); } dom_append_elemv("#dartboard-container", 1, &svg); dom_elem_free(svg); } void svg_init() { draw_board(); } void svg_draw_point(double x, double y) { struct dom_elem *e = gen_circle(POINT_RADIUS, C_POINT); dom_elem_add_attrd(e, "cx", x); dom_elem_add_attrd(e, "cy", y); dom_elem_add_attr(e, "stroke", C_POINT_OUTLINE); dom_elem_add_attr(e, "stroke-width", "2"); if (delay_ms >= SVG_THROW_ANIM_MS) dom_elem_add_attrf(e, "style", "animation: throw-anim %dms;", SVG_THROW_ANIM_MS); dom_append_elemv("#dartboard .overlay", 1, &e); dom_elem_free(e); } void svg_clear_points() { dom_set_content("#dartboard .overlay", NULL); }