pebble / words / src / words.c

/** \file
 * Word clock, like the qlock2
 */
#include <pebble_os.h>
#include <pebble_app.h>
#include <pebble_fonts.h>

PBL_APP_INFO(
	"Wordsquare",
	"hudson",
	1, // Version
	INVALID_RESOURCE,
	APP_INFO_WATCH_FACE
);

#define ROWS 10
#define COLS 11
#define FONT_H 16
#define FONT_W 11
#define FONT_ON		RESOURCE_ID_FONT_COURIER_BOLD_19
#define FONT_OFF	RESOURCE_ID_FONT_COURIER_19


typedef struct
{
	int row;
	int col;
	char text_on[8];
	char text_off[8];
} word_t;

#define LAYER_FIVE	13
#define LAYER_TEN	14
#define LAYER_QUARTER	15
#define LAYER_HALF	16
#define LAYER_TWENTY	17
#define LAYER_IT	 0
#define LAYER_IS	18
#define LAYER_PAST	19
#define LAYER_TO	20
#define LAYER_OCLOCK	21
#define LAYER_A		22
#define LAYER_FILLER	23

static const word_t words[] = {
	// Hours 1-12
	[1] = {  5, 0, "ONE", "one" },
	[2] = {  6, 8, "TWO", "two" },
	[3] = {  5, 6, "THREE", "three" },
	[4] = {  6, 0, "FOUR", "four" },
	[5] = {  6, 4, "FIVE", "five" }, // hour
	[6] = {  5, 3, "SIX", "six" },
	[7] = {  8, 0, "SEVEN", "seven" },
	[8] = {  7, 0, "EIGHT", "eight" },
	[9] = {  4, 7, "NINE", "nine" },
	[10] = {  9, 0, "TEN", "ten" }, // hour
	[11] = {  7, 5, "ELEVEN", "eleven" },
	[12] = {  8, 5, "TWELVE", "twelve" },

	// Minutes 13-
	[LAYER_FIVE]	= {  2, 6, "FIVE", "five" }, // minute
	[LAYER_TEN]	= {  3, 5, "TEN", "ten" }, // minute
	[LAYER_A]	= {  1, 0, "A", "a" },
	[LAYER_QUARTER]	= {  1, 2, "QUARTER", "quarter" },
	[LAYER_HALF]	= {  3, 0, "HALF", "half" },
	[LAYER_TWENTY]	= {  2, 0, "TWENTY", "twenty" },

	// Relative
	[LAYER_IT] = {  0, 0, "IT", "it" },
	[LAYER_PAST]	= {  4, 0, "PAST", "past" },
	[LAYER_TO]	= {  3, 9, "TO", "to", },
	[LAYER_OCLOCK]	= {  9, 5, "OCLOCK", "oclock" },
	[LAYER_IS]	= {  0, 3, "IS", "is" },

	// Fillers
	[LAYER_FILLER] =
	{  0, 2, "a", "a" },
	{  0, 5, "CDTIME", "pebble" },
	{  1, 1, "T", "t" },
	{  1, 9, "TH", "th" },
	{  2, 10, "X", "x" },
	{  3, 4, "B", "b" },
	{  3, 8, "F", "f" },
	{  4, 4, "ERU", "eru" },
	{  9, 3, "SE", "se" },
};

#define WORD_COUNT ((sizeof(words) / sizeof(*words)))

static Window window;
static TextLayer layers[WORD_COUNT];
static Layer minute_layer;
static int minute_num;


/** Draw a box in a corner to indicate the number of minutes past the five.
 */
static void
minute_layer_update(
        Layer * const me,
        GContext * ctx
)
{
	const int w = 5;
	GRect r = GRect(0, 0, w, w);

	if (minute_num == 0)
		return; // nothing to draw
	else
	if (minute_num == 1)
		r.origin = GPoint(0,0);
	else
	if (minute_num == 2)
		r.origin = GPoint(144-w,0);
	else
	if (minute_num == 3)
		r.origin = GPoint(144-w,168-w);
	else
	if (minute_num == 4)
		r.origin = GPoint(0,168-w);

	graphics_context_set_fill_color(ctx, GColorWhite);
	graphics_fill_rect(ctx, r, 1, GCornersAll);
}


static void
word_mark(
	int which,
	int on
)
{
	TextLayer * const layer = &layers[which];
	const word_t * const w = &words[which];

	text_layer_set_text(
		layer,
		on ? w->text_on : w->text_off
	);

	text_layer_set_font(
		layer,
		fonts_load_custom_font(resource_get_handle(on ? FONT_ON : FONT_OFF))
	);
}


/** Called once per minute.
 *
0-4 "IT IS X OCLOCK"
5-9 "IT IS FIVE PAST X"
10-14 "IT IS TEN PAST X"
15-19 "IT IS A QUARTER PAST X"
20-24 "IT IS TWENTY PAST X"
25-29 "IT IS TWENTY FIVE PAST X"
30-34 "IT IS HALF PAST X"
35-39 "IT IS TWENTY FIVE TO X+1"
40-44 "IT IS TWENTY TO X+1"
45-49 "IT IS A QUARTER TO X+1"
50-54 "IT IS TEN TO X+1"
55-59 "IT IS FIVE TO X+1"
 */
static void
handle_tick(
	AppContextRef ctx,
	PebbleTickEvent * const event
)
{
	(void) ctx;
	const PblTm * const ptm = event->tick_time;

	int hour = ptm->tm_hour;
	int min = ptm->tm_min;

	// mark all of the minutes as off,
	// and then turn on the ones that count
	word_mark(LAYER_OCLOCK, 0);
	word_mark(LAYER_FIVE, 0);
	word_mark(LAYER_TEN, 0);
	word_mark(LAYER_A, 0);
	word_mark(LAYER_QUARTER, 0);
	word_mark(LAYER_TWENTY, 0);
	word_mark(LAYER_HALF, 0);
	word_mark(LAYER_PAST, 0);
	word_mark(LAYER_TO, 0);

	if (min < 5)
	{
		word_mark(LAYER_OCLOCK, 1);
	} else
	if (min < 10)
	{
		word_mark(LAYER_FIVE, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 15)
	{
		word_mark(LAYER_TEN, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 20)
	{
		word_mark(LAYER_A, 1);
		word_mark(LAYER_QUARTER, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 25)
	{
		word_mark(LAYER_TWENTY, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 30)
	{
		word_mark(LAYER_TWENTY, 1);
		word_mark(LAYER_FIVE, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 35)
	{
		word_mark(LAYER_HALF, 1);
		word_mark(LAYER_PAST, 1);
	} else
	if (min < 40)
	{
		word_mark(LAYER_TWENTY, 1);
		word_mark(LAYER_FIVE, 1);
		word_mark(LAYER_TO, 1);
		hour++;
	} else
	if (min < 45)
	{
		word_mark(LAYER_TWENTY, 1);
		word_mark(LAYER_TO, 1);
		hour++;
	} else
	if (min < 50)
	{
		word_mark(LAYER_A, 1);
		word_mark(LAYER_QUARTER, 1);
		word_mark(LAYER_TO, 1);
		hour++;
	} else
	if (min < 55)
	{
		word_mark(LAYER_TEN, 1);
		word_mark(LAYER_TO, 1);
		hour++;
	} else {
		word_mark(LAYER_FIVE, 1);
		word_mark(LAYER_TO, 1);
		hour++;
	}

	// update the minute box
	minute_num = min % 5;
	layer_mark_dirty(&minute_layer);

	// Convert from 24-hour to 12-hour time
	if (hour == 0)
		hour = 12;
	else
	if (hour > 12)
		hour -= 12;

	// light up the one hour marker
	for (int i = 1 ; i <= 12 ; i++)
		word_mark(i, i == hour ? 1 : 0);
}


static void
word_layer_init(
	int which
)
{
	TextLayer * const layer = &layers[which];
	const word_t * const w = &words[which];

	GRect frame = GRect(
		w->col*FONT_W + 12,
		w->row*FONT_H,
		strlen(w->text_on)*(FONT_W+4),
		FONT_H+5
	);
	text_layer_init(layer, frame);
	text_layer_set_text_color(layer, GColorWhite);
	text_layer_set_background_color(layer, GColorClear);
	word_mark(which, 0); // all are "off" initially

	layer_add_child(&window.layer, &layer->layer);
}


static void
handle_init(
	AppContextRef ctx
)
{
	(void) ctx;

	window_init(&window, "Main");
	window_stack_push(&window, true);
	window_set_background_color(&window, GColorBlack);

	resource_init_current_app(&RESOURCES);

	for (unsigned i = 0 ; i < WORD_COUNT ; i++)
		word_layer_init(i);

	// Flag the ones that are always on
	word_mark(LAYER_IT, 1);
	word_mark(LAYER_IS, 1);

	// Create a graphics layer for the entire background
	layer_init(&minute_layer, GRect(0, 0, 144, 168));
	minute_layer.update_proc = minute_layer_update;
	layer_add_child(&window.layer, &minute_layer);
}


void
pbl_main(
	void * const params
)
{
	PebbleAppHandlers handlers = {
		.init_handler	= &handle_init,
		.tick_info	= {
			.tick_handler = &handle_tick,
			.tick_units = MINUTE_UNIT,
		},
	};

	app_event_loop(params, &handlers);
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.