Commits

Trammell Hudson committed e3c2686

Started on solar/lunar clock

  • Participants
  • Parent commits a9865e1

Comments (0)

Files changed (190)

+SRCS += main.c
+SRCS += lunar.c
+SRCS += draw.c
+SRCS += sin_table.c
+
+include ../Makefile.rules
+
+
+# Convert a lunar.gif image to a "compressed" png with the
+# three luma samples converted into rgb format
+# Images are from tycho.usno.navy.mil
+resources/%.png: images/%.gif
+	convert \
+		-crop 81x81+10+10 \
+		$< \
+		gray:- \
+	| convert \
+		-depth 8 \
+		-size 27x81 \
+		rgb:- \
+		$@
+
+images: \
+	resources/m000.png \
+	resources/m012.png \
+	resources/m024.png \
+	resources/m038.png \
+	resources/m050.png \
+	resources/m064.png \
+	resources/m076.png \
+	resources/m090.png \
+	resources/m102.png \
+	resources/m114.png \
+	resources/m128.png \
+	resources/m140.png \
+	resources/m154.png \
+	resources/m166.png \
+	resources/m180.png \
+	resources/m192.png \
+	resources/m204.png \
+	resources/m218.png \
+	resources/m230.png \
+	resources/m244.png \
+	resources/m256.png \
+	resources/m270.png \
+	resources/m282.png \
+	resources/m294.png \
+	resources/m308.png \
+	resources/m320.png \
+	resources/m334.png \
+	resources/m346.png \
+
+/** \file
+ * Drawing functions.
+ */
+#include "draw.h"
+
+
+void
+draw_pixel(
+	color24_t c,
+	int32_t x,
+	int32_t y
+)
+{
+	//printf("pix %d %d @ %d %d %d\n", x, y, c.red, c.blue, c.green);
+	if (x >= VSCREEN_WIDTH || y >= VSCREEN_HEIGHT)
+		return;
+	if (x < 0 || y < 0)
+		return;
+
+	x >>= VSCREEN_SHIFT;
+	y >>= VSCREEN_SHIFT;
+	pulse_set_draw_window(x, y, x, y);
+	pulse_draw_point24(c);
+}
+
+
+
+static inline int32_t
+vscreen_ipart(
+	int32_t x
+)
+{
+	return x & ~(VSCREEN_PIXEL - 1);
+}
+
+
+static inline int32_t
+vscreen_fpart(
+	int32_t x
+)
+{
+	return x & (VSCREEN_PIXEL - 1);
+}
+
+
+static inline int32_t
+vscreen_rfpart(
+	int32_t x
+)
+{
+	return VSCREEN_PIXEL - vscreen_fpart(x);
+}
+
+
+static inline int32_t
+vscreen_round(
+	int32_t x
+)
+{
+	return vscreen_ipart(x + (VSCREEN_PIXEL / 2));
+}
+
+
+static void
+draw_pixel_aa(
+	color24_t c,
+	int32_t x,
+	int32_t y,
+	uint32_t frac
+)
+{
+	if (c.red)
+		c.red = (c.red * frac) >> VSCREEN_SHIFT;
+	if (c.blue)
+		c.blue = (c.blue * frac) >> VSCREEN_SHIFT;
+	if (c.green)
+		c.green = (c.green * frac) >> VSCREEN_SHIFT;
+
+	draw_pixel(c, x, y);
+}
+
+
+/** Draw using Xiaolin Wu's line algorithm */
+#define GRAD_SCALE 256
+
+void
+draw_line_horiz(
+	color24_t c,
+	int32_t x1,
+	int32_t y1,
+	int32_t x2,
+	int32_t y2
+)
+{
+	const int32_t dx = x2 - x1;
+	const int32_t dy = y2 - y1;
+
+	if (x2 < x1)
+	{
+		int32_t temp = x1;
+		x1 = x2;
+		x2 = temp;
+		temp = y1;
+		y1 = y2;
+		y2 = temp;
+	}
+
+	// The gradient is scaled by another shift factor since
+	// otherwise we won't hit the end points correctly.
+	const int32_t grad = (dy * GRAD_SCALE * VSCREEN_PIXEL) / dx;
+	const int32_t half_pixel = VSCREEN_PIXEL / 2;
+
+	int32_t xend, yend, xgap;
+
+	// Handle first endpoint
+	xend = vscreen_round(x1);
+	yend = y1 + (grad * (xend - x1)) / GRAD_SCALE / VSCREEN_PIXEL;
+	xgap = vscreen_rfpart(x1 + half_pixel);
+	const int32_t xpxl1 = xend;
+	const int32_t ypxl1 = vscreen_ipart(yend);
+	draw_pixel_aa(c, xpxl1, ypxl1, (vscreen_rfpart(yend) * xgap) / VSCREEN_PIXEL);
+	draw_pixel_aa(c, xpxl1, ypxl1 + VSCREEN_PIXEL, (vscreen_fpart(yend) * xgap) / VSCREEN_PIXEL);
+	int32_t intery = yend * GRAD_SCALE + grad; // first y-intersection
+
+	//printf("%d %d %d => %d %d grad %d\n", x1, xend, y1, x2, y2, grad);
+
+
+	// Handle second endpoint
+	xend = vscreen_round(x2);
+	yend = y2 + (grad * (xend - x2)) / GRAD_SCALE / VSCREEN_PIXEL;
+	xgap = vscreen_fpart(x2 + half_pixel);
+	const int32_t xpxl2 = xend;
+	const int32_t ypxl2 = vscreen_ipart(yend);
+	draw_pixel_aa(c, xpxl2, ypxl2, (vscreen_rfpart(yend) * xgap) / VSCREEN_PIXEL);
+	draw_pixel_aa(c, xpxl2, ypxl2 + VSCREEN_PIXEL, (vscreen_fpart(yend) * xgap) / VSCREEN_PIXEL);
+
+	//printf("x: %d %d\n", xpxl1 + 1, xpxl2);
+
+	// Loop (this could make a single window draw)
+	for (int32_t x = xpxl1 + VSCREEN_PIXEL ; x < xpxl2 ; x += VSCREEN_PIXEL)
+	{
+		int32_t y = intery / GRAD_SCALE;
+		intery += grad;
+		draw_pixel_aa(c, x, vscreen_ipart(y), vscreen_rfpart(y));
+		draw_pixel_aa(c, x, vscreen_ipart(y)+VSCREEN_PIXEL, vscreen_fpart(y));
+	}
+}
+
+
+static void
+draw_line_vert(
+	color24_t c,
+	int32_t x1,
+	int32_t y1,
+	int32_t x2,
+	int32_t y2
+)
+{
+	const int32_t dx = x2 - x1;
+	const int32_t dy = y2 - y1;
+
+	if (y2 < y1)
+	{
+		int32_t temp = x1;
+		x1 = x2;
+		x2 = temp;
+		temp = y1;
+		y1 = y2;
+		y2 = temp;
+	}
+
+	const int32_t grad = (dx * VSCREEN_PIXEL * GRAD_SCALE) / dy;
+	const int32_t half_pixel = VSCREEN_PIXEL / 2;
+
+	int32_t xend, yend, ygap;
+
+	// Handle first endpoint
+	yend = vscreen_round(y1);
+	xend = x1 + (grad * (yend - y1)) / GRAD_SCALE / VSCREEN_PIXEL;
+	ygap = vscreen_rfpart(y1 + half_pixel);
+	const int32_t ypxl1 = yend;
+	const int32_t xpxl1 = vscreen_ipart(xend);
+	draw_pixel_aa(c, xpxl1, ypxl1, (vscreen_rfpart(xend) * ygap) / VSCREEN_PIXEL);
+	draw_pixel_aa(c, xpxl1 + VSCREEN_PIXEL, ypxl1, (vscreen_fpart(xend) * ygap) / VSCREEN_PIXEL);
+	int32_t interx = xend * GRAD_SCALE + grad; // first x-intersection
+
+
+	// Handle second endpoint
+	yend = vscreen_round(y2);
+	xend = x2 + (grad * (yend - y2)) / GRAD_SCALE / VSCREEN_PIXEL;
+	ygap = vscreen_fpart(y2 + half_pixel);
+	const int32_t ypxl2 = yend;
+	const int32_t xpxl2 = vscreen_ipart(xend);
+	draw_pixel_aa(c, xpxl2, ypxl2, (vscreen_rfpart(xend) * ygap) / VSCREEN_PIXEL);
+	draw_pixel_aa(c, xpxl2 + VSCREEN_PIXEL, ypxl2, (vscreen_fpart(xend) * ygap) / VSCREEN_PIXEL);
+
+	//printf("x: %d %d\n", xpxl1 + 1, xpxl2);
+
+	// Loop (this could make a single window draw)
+	for (int32_t y = ypxl1 + VSCREEN_PIXEL ; y < ypxl2 ; y += VSCREEN_PIXEL)
+	{
+		int32_t x = interx / GRAD_SCALE;
+		interx += grad;
+
+		draw_pixel_aa(c, vscreen_ipart(x), y, vscreen_rfpart(x));
+		draw_pixel_aa(c, vscreen_ipart(x)+VSCREEN_PIXEL, y, vscreen_fpart(x));
+	}
+}
+
+
+void
+draw_line(
+	color24_t c,
+	int32_t x0,
+	int32_t y0,
+	int32_t x1,
+	int32_t y1
+)
+{
+	const int32_t dx = x1 > x0 ? x1 - x0 : x0 - x1;
+	const int32_t dy = y1 > y0 ? y1 - y0 : y0 - y1;
+	if (dx == 0 && dy == 0)
+		draw_pixel(c, x0, y0);
+	else
+	if (dx > dy)
+		draw_line_horiz(c, x0, y0, x1, y1);
+	else
+		draw_line_vert(c, x0, y0, x1, y1);
+
+/*
+	// Mark where we should start and end
+	draw_pixel(COLOR_WHITE, x0, y0);
+	draw_pixel(COLOR_BLUE, x1, y1);
+*/
+}
+
+
+
+
+static char buf[2];
+struct PWidgetTextDynamic font;
+
+void
+draw_monostring(
+	int32_t x,
+	int32_t y,
+	color24_t color,
+	PulseResource font_name,
+	int width,
+	const char * s
+)
+{
+	pulse_init_dynamic_text_widget(
+		&font,
+		buf,
+		font_name,
+		color,
+		PWTS_TRUNCATE
+	);
+
+	uint32_t orig_x = x >> VSCREEN_SHIFT;
+
+	struct PWTextBox box = {
+		.left		= orig_x,
+		.top		= y >> VSCREEN_SHIFT,
+		.right		= SCREEN_WIDTH - 1,
+		.bottom		= SCREEN_HEIGHT - 1,
+	};
+
+	buf[1] = '\0';
+
+	while ((buf[0] = *s++))
+	{
+		pulse_render_text(&box, &font);
+		box.left += width;
+	}
+}
+/** \file
+ * Line and pixel drawing routines.
+ * Using a virtual screen buffer that is twice the resolution
+ * along each axis so that anti-aliasing can be done later.
+ */
+#ifndef _draw_h_
+
+#include <pulse_os.h>
+#include <pulse_types.h>
+#include "app_resources.h"
+
+#define VSCREEN_SHIFT	0
+#define VSCREEN_PIXEL	(1 << VSCREEN_SHIFT)
+#define VSCREEN_MASK	(VSCREEN_PIXEL - 1)
+#define VSCREEN_WIDTH	(((int32_t) SCREEN_WIDTH) << VSCREEN_SHIFT)
+#define VSCREEN_HEIGHT	(((int32_t) SCREEN_HEIGHT) << VSCREEN_SHIFT)
+
+/** Predefined colors that we use */
+#define COLOR_GRAY		((color24_t) { 0x18, 0x18, 0x18, 0 } )
+#define COLOR_RED		((color24_t) { 0xFF, 0x00, 0x00, 0 } )
+#define COLOR_GREEN		((color24_t) { 0x00, 0xFF, 0x00, 0 } )
+#define COLOR_DARKGREEN		((color24_t) { 0x00, 0x10, 0x00, 0 } )
+#define COLOR_YELLOW		((color24_t) { 0xF0, 0xF0, 0x00, 0 } )
+#define COLOR_MIDNIGHTBLUE	((color24_t) { 0x19, 0x19, 0x70, 0 } )
+#define COLOR_BLUE		((color24_t) { 0x00, 0x00, 0xC0, 0 } )
+#define COLOR_WHITE		((color24_t) { 0xFF, 0xFF, 0xFF, 0 } )
+#define COLOR_BLACK		((color24_t) { 0x00, 0x00, 0x00, 0 } )
+
+
+/* Draw a pixel in vscreen coordinates */
+void
+draw_pixel(
+	color24_t c,
+	int32_t x,
+	int32_t y
+);
+
+
+void
+fill(
+	color24_t color,
+	int32_t x0,
+	int32_t y0,
+	int32_t x1,
+	int32_t y1
+);
+
+
+/* Draw a non-antialised line using Bresenham's_line_algorithm
+ * in vscreen coordinates */
+void
+draw_line(
+	color24_t c,
+	int32_t x0,
+	int32_t y0,
+	int32_t x1,
+	int32_t y1
+);
+
+
+void
+draw_monostring(
+	int32_t x,
+	int32_t y,
+	color24_t color,
+	PulseResource font,
+	int width,
+	const char * s
+);
+
+#endif

lunar/images/m000.gif

Added
New image

lunar/images/m002.gif

Added
New image

lunar/images/m004.gif

Added
New image

lunar/images/m006.gif

Added
New image

lunar/images/m008.gif

Added
New image

lunar/images/m010.gif

Added
New image

lunar/images/m012.gif

Added
New image

lunar/images/m014.gif

Added
New image

lunar/images/m016.gif

Added
New image

lunar/images/m018.gif

Added
New image

lunar/images/m020.gif

Added
New image

lunar/images/m022.gif

Added
New image

lunar/images/m024.gif

Added
New image

lunar/images/m026.gif

Added
New image

lunar/images/m028.gif

Added
New image

lunar/images/m030.gif

Added
New image

lunar/images/m032.gif

Added
New image

lunar/images/m034.gif

Added
New image

lunar/images/m036.gif

Added
New image

lunar/images/m038.gif

Added
New image

lunar/images/m040.gif

Added
New image

lunar/images/m042.gif

Added
New image

lunar/images/m044.gif

Added
New image

lunar/images/m046.gif

Added
New image

lunar/images/m048.gif

Added
New image

lunar/images/m050.gif

Added
New image

lunar/images/m052.gif

Added
New image

lunar/images/m054.gif

Added
New image

lunar/images/m056.gif

Added
New image

lunar/images/m058.gif

Added
New image

lunar/images/m060.gif

Added
New image

lunar/images/m062.gif

Added
New image

lunar/images/m064.gif

Added
New image

lunar/images/m066.gif

Added
New image

lunar/images/m068.gif

Added
New image

lunar/images/m070.gif

Added
New image

lunar/images/m072.gif

Added
New image

lunar/images/m074.gif

Added
New image

lunar/images/m076.gif

Added
New image

lunar/images/m078.gif

Added
New image

lunar/images/m080.gif

Added
New image

lunar/images/m082.gif

Added
New image

lunar/images/m084.gif

Added
New image

lunar/images/m086.gif

Added
New image

lunar/images/m088.gif

Added
New image

lunar/images/m090.gif

Added
New image

lunar/images/m092.gif

Added
New image

lunar/images/m094.gif

Added
New image

lunar/images/m096.gif

Added
New image

lunar/images/m098.gif

Added
New image

lunar/images/m100.gif

Added
New image

lunar/images/m102.gif

Added
New image

lunar/images/m104.gif

Added
New image

lunar/images/m106.gif

Added
New image

lunar/images/m108.gif

Added
New image

lunar/images/m110.gif

Added
New image

lunar/images/m112.gif

Added
New image

lunar/images/m114.gif

Added
New image

lunar/images/m116.gif

Added
New image

lunar/images/m118.gif

Added
New image

lunar/images/m120.gif

Added
New image

lunar/images/m122.gif

Added
New image

lunar/images/m124.gif

Added
New image

lunar/images/m126.gif

Added
New image

lunar/images/m128.gif

Added
New image

lunar/images/m130.gif

Added
New image

lunar/images/m132.gif

Added
New image

lunar/images/m134.gif

Added
New image

lunar/images/m136.gif

Added
New image

lunar/images/m138.gif

Added
New image

lunar/images/m140.gif

Added
New image

lunar/images/m142.gif

Added
New image

lunar/images/m144.gif

Added
New image

lunar/images/m146.gif

Added
New image

lunar/images/m148.gif

Added
New image

lunar/images/m150.gif

Added
New image

lunar/images/m152.gif

Added
New image

lunar/images/m154.gif

Added
New image

lunar/images/m156.gif

Added
New image

lunar/images/m158.gif

Added
New image

lunar/images/m160.gif

Added
New image

lunar/images/m162.gif

Added
New image

lunar/images/m164.gif

Added
New image

lunar/images/m166.gif

Added
New image

lunar/images/m168.gif

Added
New image

lunar/images/m170.gif

Added
New image

lunar/images/m172.gif

Added
New image

lunar/images/m174.gif

Added
New image

lunar/images/m176.gif

Added
New image

lunar/images/m178.gif

Added
New image

lunar/images/m180.gif

Added
New image

lunar/images/m182.gif

Added
New image

lunar/images/m184.gif

Added
New image

lunar/images/m186.gif

Added
New image

lunar/images/m188.gif

Added
New image

lunar/images/m190.gif

Added
New image

lunar/images/m192.gif

Added
New image

lunar/images/m194.gif

Added
New image

lunar/images/m196.gif

Added
New image

lunar/images/m198.gif

Added
New image

lunar/images/m200.gif

Added
New image

lunar/images/m202.gif

Added
New image

lunar/images/m204.gif

Added
New image

lunar/images/m206.gif

Added
New image

lunar/images/m208.gif

Added
New image

lunar/images/m210.gif

Added
New image

lunar/images/m212.gif

Added
New image

lunar/images/m214.gif

Added
New image

lunar/images/m216.gif

Added
New image

lunar/images/m218.gif

Added
New image

lunar/images/m220.gif

Added
New image

lunar/images/m222.gif

Added
New image

lunar/images/m224.gif

Added
New image

lunar/images/m226.gif

Added
New image

lunar/images/m228.gif

Added
New image

lunar/images/m230.gif

Added
New image

lunar/images/m232.gif

Added
New image

lunar/images/m234.gif

Added
New image

lunar/images/m236.gif

Added
New image

lunar/images/m238.gif

Added
New image

lunar/images/m240.gif

Added
New image

lunar/images/m242.gif

Added
New image

lunar/images/m244.gif

Added
New image

lunar/images/m246.gif

Added
New image

lunar/images/m248.gif

Added
New image

lunar/images/m250.gif

Added
New image

lunar/images/m252.gif

Added
New image

lunar/images/m254.gif

Added
New image

lunar/images/m256.gif

Added
New image

lunar/images/m258.gif

Added
New image

lunar/images/m260.gif

Added
New image

lunar/images/m262.gif

Added
New image

lunar/images/m264.gif

Added
New image

lunar/images/m266.gif

Added
New image

lunar/images/m268.gif

Added
New image

lunar/images/m270.gif

Added
New image

lunar/images/m272.gif

Added
New image

lunar/images/m274.gif

Added
New image

lunar/images/m276.gif

Added
New image

lunar/images/m278.gif

Added
New image

lunar/images/m280.gif

Added
New image

lunar/images/m282.gif

Added
New image

lunar/images/m284.gif

Added
New image

lunar/images/m286.gif

Added
New image

lunar/images/m288.gif

Added
New image

lunar/images/m290.gif

Added
New image

lunar/images/m292.gif

Added
New image

lunar/images/m294.gif

Added
New image

lunar/images/m296.gif

Added
New image

lunar/images/m298.gif

Added
New image

lunar/images/m300.gif

Added
New image

lunar/images/m302.gif

Added
New image

lunar/images/m304.gif

Added
New image

lunar/images/m306.gif

Added
New image

lunar/images/m308.gif

Added
New image

lunar/images/m310.gif

Added
New image

lunar/images/m312.gif

Added
New image

lunar/images/m314.gif

Added
New image

lunar/images/m316.gif

Added
New image

lunar/images/m318.gif

Added
New image

lunar/images/m320.gif

Added
New image

lunar/images/m322.gif

Added
New image

lunar/images/m324.gif

Added
New image

lunar/images/m326.gif

Added
New image

lunar/images/m328.gif

Added
New image

lunar/images/m330.gif

Added
New image

lunar/images/m332.gif

Added
New image

lunar/images/m334.gif

Added
New image

lunar/images/m336.gif

Added
New image

lunar/images/m338.gif

Added
New image

lunar/images/m340.gif

Added
New image

lunar/images/m342.gif

Added
New image

lunar/images/m344.gif

Added
New image

lunar/images/m346.gif

Added
New image

lunar/images/m348.gif

Added
New image

lunar/images/m350.gif

Added
New image

lunar/images/m352.gif

Added
New image

lunar/images/m354.gif

Added
New image

lunar/images/m356.gif

Added
New image

lunar/images/m358.gif

Added
New image

lunar/images/m360.gif

Added
New image
+/** \file
+ * Draw a lunar face clock as well as the sun-up, sun-down times.
+ * None of that is ready yet; this is a first import with copy.
+ */
+#include <string.h>
+#include "main.h"
+#include "draw.h"
+#include "sin_table.h"
+
+
+void
+init(void)
+{
+}
+
+
+void
+button_down(void)
+{
+	init();
+}
+
+
+void
+button_up(
+	uint32_t ms
+)
+{
+	// Nothing to do
+	(void) ms;
+}
+
+
+
+static void
+circle_line(
+	color24_t color,
+	int32_t x0,
+	int32_t y0,
+	int32_t radius
+)
+{
+	int32_t f = 1 - radius;
+	int ddF_x = 1;
+	int ddF_y = -2 * radius;
+	int x = 0;
+	int y = radius;
+
+	draw_pixel(color, x0, y0 + radius);
+	draw_pixel(color, x0, y0 - radius);
+	draw_pixel(color, x0 + radius, y0);
+	draw_pixel(color, x0 - radius, y0);
+
+	while (x < y)
+	{
+		if (f >= 0)
+		{
+			y--;
+			ddF_y += 2;
+			f += ddF_y;
+		}
+		x++;
+		ddF_x += 2;
+		f += ddF_x;
+
+		draw_pixel(color, x0 + x, y0 + y);
+		draw_pixel(color, x0 + x, y0 - y);
+		draw_pixel(color, x0 - x, y0 + y);
+		draw_pixel(color, x0 - x, y0 - y);
+
+		draw_pixel(color, x0 + y, y0 + x);
+		draw_pixel(color, x0 + y, y0 - x);
+		draw_pixel(color, x0 - y, y0 + x);
+		draw_pixel(color, x0 - y, y0 - x);
+	}
+}
+
+
+void
+fill(
+	color24_t color,
+	int32_t x0,
+	int32_t y0,
+	int32_t x1,
+	int32_t y1
+)
+{
+	int32_t temp;
+	if (x0 > x1)
+	{
+		temp = x0;
+		x0 = x1;
+		x1 = temp;
+	}
+	if (y0 > y1)
+	{
+		temp = y0;
+		y0 = y1;
+		y1 = temp;
+	}
+
+	if (x1 < 0 || y1 < 0)
+		return;
+	if (x0 >= SCREEN_WIDTH || y1 >= SCREEN_HEIGHT)
+		return;
+	if (x0 < 0)
+		x0 = 0;
+	if (y0 < 0)
+		y0 = 0;
+	if (x1 >= SCREEN_WIDTH)
+		x1 = SCREEN_WIDTH - 1;
+	if (y1 >= SCREEN_HEIGHT)
+		y1 = SCREEN_HEIGHT - 1;
+
+	int32_t h = y1 - y0 + 1;
+	int32_t w = x1 - x0 + 1;
+	uint16_t num = w * h;
+	if (num == 0)
+		return;
+	
+	pulse_set_draw_window(x0, y0, x1, y1);
+
+	while (num--)
+		pulse_draw_point24(color);
+}
+
+
+static void
+circle_fill(
+	color24_t color,
+	int32_t x0,
+	int32_t y0,
+	int32_t radius
+)
+{
+	int32_t f = 1 - radius;
+	int ddF_x = 1;
+	int ddF_y = -2 * radius;
+	int x = 0;
+	int y = radius;
+
+	fill(color, x0 - radius, y0, x0 + radius, y0);
+
+	while (x < y)
+	{
+		if (f >= 0)
+		{
+			y--;
+			ddF_y += 2;
+			f += ddF_y;
+		}
+		x++;
+		ddF_x += 2;
+		f += ddF_x;
+
+		fill(color, x0 - x, y0 + y, x0 + x, y0 + y);
+		fill(color, x0 - x, y0 - y, x0 + x, y0 - y);
+		fill(color, x0 - y, y0 + x, x0 + y, y0 + x);
+		fill(color, x0 - y, y0 - x, x0 + y, y0 - x);
+	}
+}
+
+
+#if 0
+static void
+draw_gradient_arc(
+	int32_t radius,
+	int32_t dot_radius,
+	uint16_t initial_angle
+)
+{
+	const uint16_t arc_len = 0xB000;
+	const uint16_t step = 128;
+
+	for (uint32_t t = 0 ; t < arc_len  ; t += step)
+	{
+		const uint16_t angle = initial_angle + t - arc_len;
+		const int16_t sx = (radius * sin_lookup(angle)) / 32768;
+		const int16_t sy = (radius * cos_lookup(angle)) / 32768;
+
+		color24_t color = {
+			(t * dot_color.red) / arc_len,
+			(t * dot_color.green) / arc_len,
+			(t * dot_color.blue) / arc_len,
+		};
+
+		circle_fill(
+			color,
+			SCREEN_WIDTH/2 + sx,
+			SCREEN_HEIGHT/2 - sy,
+			dot_radius
+		);
+	}
+}
+#endif
+
+
+void
+draw(
+	uint32_t cur_ms
+)
+{
+	uint32_t ms = cur_ms % 1000; cur_ms /= 1000;
+	const uint32_t new_sec = cur_ms % 60; cur_ms /= 60;
+	const uint8_t new_min = cur_ms % 60; cur_ms /= 60;
+	const uint8_t new_hour = cur_ms % 12; // cur_ms %= 24;
+
+#if 0
+	const uint16_t sec_angle = ((new_sec * 6 + (ms*6) / 1000) * 32768) / 180;
+	const uint16_t min_angle = ((new_min * 6 + (new_sec*6) / 60) * 32768) / 180;
+	const uint16_t hour_angle = ((new_hour * 30 + (new_min*30) / 60) * 32768) / 180;
+
+	const int32_t sec_radius = SCREEN_WIDTH/2;
+	const int32_t sec_dot_radius = 3;
+	if (sec_angle != last_sec_angle || first_time)
+	{
+		draw_gradient_arc(
+			sec_radius,
+			sec_dot_radius,
+			sec_angle
+		);
+		last_sec_angle = sec_angle;
+	}
+
+	const int32_t min_dot_radius =  8;
+	const int32_t min_radius = sec_radius - sec_dot_radius - min_dot_radius - 1;
+	if (min_angle != last_min_angle || first_time)
+	{
+		draw_gradient_arc(
+			min_radius,
+			min_dot_radius,
+			min_angle
+		);
+		last_min_angle = min_angle;
+	}
+
+	const int32_t hour_dot_radius = 12;
+	const int32_t hour_radius = min_radius - min_dot_radius - hour_dot_radius - 1;
+	if (hour_angle != last_hour_angle || first_time)
+	{
+		draw_gradient_arc(
+			hour_radius,
+			hour_dot_radius,
+			hour_angle
+		);
+		last_hour_angle = hour_angle;
+	}
+
+	first_time = 0;
+#endif
+}
+/** \file
+ * Main program for all of the 30-days watches.
+ *
+ * The mundane bits involved with power management, setup, etc are
+ * handled here.  The bootloader mode is also done here.
+ *
+ * The watch program only needs to implement:
+ *
+ * init (called by wakeup)
+ * draw
+ *
+ */
+#include "main.h"
+#include "draw.h"
+
+
+static uint32_t not_charging;
+static uint8_t shutdown_scheduled;
+
+
+/** At wake up, sample the boot time in ms and use it to scale
+ * the ms timestamp to a time since midnight
+ */
+static uint32_t ms_since_midnight;
+static uint32_t epoch_ms;
+
+/* Track how long the button has been held down for */
+static uint8_t do_load;
+static uint32_t button_down_ms;
+
+
+
+
+static void
+do_empty_loop(void)
+{
+	static uint8_t last_sec;
+	struct pulse_time_tm now;
+	pulse_get_time_date(&now);
+	if (now.tm_sec == last_sec)
+		return;
+	last_sec = now.tm_sec;
+
+	printf("%02d:%02d:%02d\n", now.tm_hour, now.tm_min, now.tm_sec);
+}
+
+
+
+void
+main_app_loop(void)
+{
+	// Limit the update rate to 30 fps == 33 ms/frame
+	const uint64_t millis = pulse_get_millis();
+	if (button_down_ms && millis - button_down_ms > 1000)
+	{
+		do_load = !do_load;
+		button_down_ms = 0;
+		return;
+	}
+	if (do_load)
+	{
+		do_empty_loop();
+		return;
+	}
+
+	// Draw the charging indicator
+	color24_t charge_light = not_charging
+		? (color24_t){10,10,10}
+		: (color24_t){0x80,0,0};
+
+	fill(
+		charge_light,
+		0,
+		8,
+		2,
+		12
+	);
+
+        // If the not-charging state has not been toggled in the last
+        // 30 seconds, schedule a power down soon.
+        if (not_charging && millis - not_charging > 60000)
+	{
+		if (!shutdown_scheduled)
+			pulse_update_power_down_timer(1);
+		shutdown_scheduled = 1;
+		return;
+	}
+
+	// Compute the ms since midnight
+	uint32_t cur_ms = ms_since_midnight + (millis - epoch_ms);
+	draw(cur_ms);
+}
+
+
+static void
+wakeup(void *arg)
+{
+	pulse_oled_set_brightness(100);
+	pulse_blank_canvas();
+	not_charging = pulse_get_millis();
+	shutdown_scheduled = 0;
+
+	// Compute the number of mseconds since midnight
+	struct pulse_time_tm now;
+	pulse_get_time_date(&now);
+	uint32_t sec_since_midnight = 0
+		+ now.tm_sec
+		+ now.tm_min * 60
+		+ now.tm_hour * 3600;
+	ms_since_midnight = sec_since_midnight * 1000;
+	epoch_ms = pulse_get_millis();
+	do_load = 0;
+	button_down_ms = 0;
+
+	init();
+}
+
+
+void
+main_app_init(void)
+{
+	// Register a callback for the woke_from_button event
+	pulse_register_callback(
+		ACTION_WOKE_FROM_BUTTON,
+		&wakeup
+	);
+
+	wakeup(0);
+}
+
+
+void
+main_app_handle_button_down(void)
+{
+	// Track when the button was pressed and draw a small
+	// marker to indicate it since my button is twitchy.
+	button_down_ms = pulse_get_millis();
+
+	// Reset the shutdown timer when they hit the button
+	if (not_charging)
+		not_charging = pulse_get_millis();
+
+	fill(
+		COLOR_BLUE,
+		VSCREEN_WIDTH - (2 << VSCREEN_SHIFT),
+		VSCREEN_HEIGHT/2 - (2 << VSCREEN_SHIFT),
+		VSCREEN_WIDTH - 1,
+		VSCREEN_HEIGHT/2 + (2 << VSCREEN_SHIFT)
+	);
+
+	button_down();
+}
+
+
+void
+main_app_handle_button_up(void)
+{
+	const uint32_t now = pulse_get_millis();
+
+	// Stop the power down timer
+	if (not_charging)
+		not_charging = now;
+
+	button_up(now - button_down_ms);
+	button_down_ms = 0;
+
+	fill(
+		COLOR_BLACK,
+		VSCREEN_WIDTH - (2 << VSCREEN_SHIFT),
+		VSCREEN_HEIGHT/2 - (2 << VSCREEN_SHIFT),
+		VSCREEN_WIDTH - 1,
+		VSCREEN_HEIGHT/2 + (2 << VSCREEN_SHIFT)
+	);
+
+	
+}
+
+
+void
+main_app_handle_hardware_update(
+	enum PulseHardwareEvent event
+)
+{
+	switch (event)
+	{
+	case BATTERY_CHARGING:
+		// Erase the last non-charging time to avoid flakey power
+		not_charging = 0;
+		break;
+
+	case BATTERY_NOT_CHARGING:
+		// Record the time that the state was toggled.
+		not_charging = pulse_get_millis();
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+void
+main_app_handle_doz(void)
+{
+	// Fade out
+	for (int i = 100; i >= 0; i -= 6)
+	{
+		pulse_oled_set_brightness(i);
+		pulse_mdelay(60);
+	}
+}
+/** \file
+ * Simplified watch interface.
+ *
+ * The watch program must implement these functions.
+ */
+#ifndef _main_h_
+#define _main_h_
+
+#include <pulse_os.h>
+#include <pulse_types.h>
+#include <app_resources.h>
+#include <stdio.h>
+#include <stdint.h>
+
+
+/** init() is called when the watch wakes up.
+ * The screen will be blank and the OLED will be at 100%.
+ */
+void init(void);
+
+/** draw() is called everytime the app mainloop is invoked.
+ * \todo Frame rate limiting?
+ */
+void draw(uint32_t ms_since_midnight);
+
+/** button_down() is called as soon as the button is pressed */
+void button_down(void);
+
+/** button_up() is called as soon as the button is released
+ * with the length of time in ms that the button was held.
+ */
+void button_up(uint32_t ms_held);
+
+#endif

lunar/resources/fonts.txt

+# This file describes font rendering options
+# 
+# <filename>, <friendly_name>, <px_size>, <clock_font>, <pixel_font>
+#
+# <filename>      - filename of the ttf font to render 
+#                   ('#' is an illegal character in the name and avoid spaces in filenames)
+# <friendly_name> - name that will be used in the header file generated
+#                   ('#' and spaces are illegal characters in the name)
+# <px_size>       - size in px to render the font
+# <clock_font>    - (y/n) only render characters 0 - 9, :, a, p, A, P (for use as a clock)
+# <pixel_font>    - (y/n) don't render grayscale (for pixel fonts)
+#
+# Example:
+# good_times.ttf, good_times_10, 10, n, n
+#Clockopia.ttf, clockopia_32, 32, y, n
+#Clockopia.ttf, clockopia_22, 22, y, n

lunar/sin_table.c

+#include "sin_table.h"
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+static const uint8_t sin_table[] = {
+	0,
+	25,
+	50,
+	74,
+	98,
+	121,
+	142,
+	162,
+	181,
+	198,
+	213,
+	226,
+	237,
+	245,
+	251,
+	255,
+	255 // duplicate to avoid extra code for handling last case
+};
+
+
+int16_t
+sin_lookup(
+	uint16_t theta_in
+)
+{
+	int sign = 1;
+	uint32_t theta = theta_in;
+
+	if (theta < 64ul*256)
+	{
+		// q1, upward slope, normal theta, positive sign
+	} else
+	if (theta < 128ul*256)
+	{
+		theta = 128ul*256 - theta; // downward slope, still positive
+	} else
+	if (theta < 192ul*256)
+	{
+		// q3, downward slope, negative side
+		theta = theta - 128ul*256;
+		sign = -1;
+	} else
+	{
+		// q4, upward slope, negative side
+		theta = 256ul*256 - theta;
+		sign = -1;
+	}
+
+	int32_t s1 = ((uint32_t) sin_table[(theta >> 10) + 0]) << 8;
+	int32_t s2 = ((uint32_t) sin_table[(theta >> 10) + 1]) << 8;
+	int16_t result = (s1 + ((s2 - s1) * (theta & 1023)) / 1024) / 2;
+
+	if (sign == -1)
+		return -result;
+	else
+		return result;
+}
+
+
+#if 0
+int main(void)
+{
+	unsigned theta;
+
+	for (theta = 0 ; theta < 0x100 ; theta++)
+	{
+		double sx = sin_lookup(theta) / 128.0;
+		double sf = sin(theta * 2 * M_PI / 256.0);
+		double cx = cos_lookup(theta) / 128.0;
+		double cf = cos(theta * 2 * M_PI / 256.0);
+
+		printf("%.4f %.4f %.4f\n",
+			cx,
+			cf,
+			cf - cx
+		);
+	}
+}
+#endif

lunar/sin_table.h

+/** \file
+ * Approximate sin function.
+ * theta goes from 0 == 0 Pi to 65536 == 2 Pi
+ */
+#ifndef _sin_table_h_
+#define _sin_table_h_
+
+#include <stdint.h>
+
+extern int16_t sin_lookup(uint16_t theta);
+
+static inline int16_t
+cos_lookup(
+	uint16_t theta
+)
+{
+	return sin_lookup(theta + 16384);
+}
+
+
+#endif