xutils.c

#include "xutils.h"
#include <err.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <X11/Xutil.h>

extern int verbose;

static int str_widths_by_weight[TXT_SZ_NUM][STR_NUM];
static int str_heights_by_weight[TXT_SZ_NUM];

unsigned long get_color(Display *display, unsigned int color);

int render_init(struct geom *g, struct render_ctx *ctx, struct style *s) {
	const char *color_names[] = {
		"black", "blue", "green", "red", "white", "yellow"
	};

	int i, j;
	XColor xcolor;

	/* Open connection to X server */
	ctx->d = XOpenDisplay(NULL);
	if (ctx->d == NULL)
		err(1, "Unable to open X display\n");

	/* Get screen number */
	ctx->screen_num = DefaultScreen(ctx->d);

	/* Create colormap */
	ctx->colormap = DefaultColormap(ctx->d, ctx->screen_num);

	/* Create window */
	ctx->w = XCreateSimpleWindow(ctx->d, RootWindow(ctx->d, ctx->screen_num), g->x, g->y, g->w, g->h, 1, 
			get_color(ctx->d, s->fg), get_color(ctx->d, s->bg));

	/* Select input events for the window */
	XSelectInput(ctx->d, ctx->w, ExposureMask | KeyPressMask | StructureNotifyMask);

	/* Map (display) the window */
	XMapWindow(ctx->d, ctx->w);

	/* Create a reusable GC (Graphics Context) */
	XGCValues values;
	ctx->gc = XCreateGC(ctx->d, ctx->w, 0, &values);

	/* Load fonts */
	/* Use xfontsel (1) to find available fonts */
	ctx->fonts[TXT_SZ_SM] = XLoadQueryFont(ctx->d,
	    "-*-helvetica-*-r-normal--14-*");
	ctx->fonts[TXT_SZ_MD] = XLoadQueryFont(ctx->d,
	    "-*-helvetica-*-r-normal--25-*");
	ctx->fonts[TXT_SZ_LG] = XLoadQueryFont(ctx->d,
	    "-*-helvetica-*-r-normal--34-*");

	if (!ctx->fonts[TXT_SZ_SM] || !ctx->fonts[TXT_SZ_MD] ||
	    !ctx->fonts[TXT_SZ_LG]) {
		fprintf(stderr, "Unable to load fonts\n");
		return -1;
	}

	/* Calculate string widths in all sizes */
	for (i = 0; i < TXT_SZ_NUM; ++i) {
		for (j = 0; j < STR_NUM; ++j) {
			str_widths_by_weight[i][j] = XTextWidth(ctx->fonts[i],
			    strs[j], strlen(strs[j]));
			if (verbose)
				fprintf(stderr, "Width of %s at weight %d is "
				    "%d.\n", strs[j], i,
				    str_widths_by_weight[i][j]);
		}
	}

	/* Use some guesses for text height */
	str_heights_by_weight[TXT_SZ_SM] = 9;
	str_heights_by_weight[TXT_SZ_MD] = 20;
	str_heights_by_weight[TXT_SZ_LG] = 29;

	/* Set initial dimensions of the window */
	ctx->width = g->w;
	ctx->height = g->h;

	/* Default redraw flag */
	ctx->needs_redraw = 1;

	/* Default key event handler to NULL */
	ctx->on_key_press = NULL;


	for (i = 0; i < 6; i++) {
		XParseColor(ctx->d, ctx->colormap, color_names[i], &xcolor);
		XAllocColor(ctx->d, ctx->colormap, &xcolor);
		ctx->colors[i] = xcolor.pixel;
	}

	/* Flush all pending requests to the X server */
	XFlush(ctx->d);

	return 0;
}

void render_destroy(struct render_ctx *ctx) {
	int i;
	/* Free fonts */
	for (i = 0; i < TXT_SZ_NUM; ++i) {
		if (ctx->fonts[i])
			XFreeFont(ctx->d, ctx->fonts[i]);
	}

	/* Free the graphics context */
	if (ctx->gc) {
		XFreeGC(ctx->d, ctx->gc);
	}

	/* Destroy the window */
	if (ctx->w) {
		XDestroyWindow(ctx->d, ctx->w);
	}

	/* Close the connection to the display */
	if (ctx->d) {
		XCloseDisplay(ctx->d);
	}
}
/* Helper function to map style color to X11 color */
unsigned long get_color(Display *display, unsigned int color) {
	Colormap colormap = DefaultColormap(display, 0);
	XColor xcolor;

	switch (color) {
		case PAL_BLK:
			XParseColor(display, colormap, "black", &xcolor);
			break;
		case PAL_BLU:
			XParseColor(display, colormap, "royal blue", &xcolor);
			break;
		case PAL_GRN:
			XParseColor(display, colormap, "green", &xcolor);
			break;
		case PAL_RED:
			XParseColor(display, colormap, "red", &xcolor);
			break;
		case PAL_WHT:
			XParseColor(display, colormap, "white", &xcolor);
			break;
		case PAL_YLW:
			XParseColor(display, colormap, "yellow", &xcolor);
			break;
		default:
			XParseColor(display, colormap, "black", &xcolor);
			break;
	}
	XAllocColor(display, colormap, &xcolor);
	return xcolor.pixel;
}

int create_window(struct geom *g, struct render_ctx *c, struct style *s)
{
	int screen_num;
	c->d = XOpenDisplay(NULL);

	if (c->d == NULL) {
		fprintf(stderr, "Unable to open X display\n");
		return -1;
	}

	screen_num = DefaultScreen(c->d);

	c->w = XCreateSimpleWindow(c->d, RootWindow(c->d, screen_num), g->x,
			g->y, g->w, g->h, 1, get_color(c->d, s->fg),
			get_color(c->d, s->bg));

	XSelectInput(c->d, c->w, ExposureMask | KeyPressMask);
	XMapWindow(c->d, c->w);
	XFlush(c->d);

	/* Create the GC once */
	XGCValues values;
	c->gc = XCreateGC(c->d, c->w, 0, &values);

	return 0;
}

int destroy_window(struct render_ctx *c) {
	if (c->d == NULL) {
		fprintf(stderr, "No active display found\n");
		return -1;
	}

	XDestroyWindow(c->d, c->w);
	XCloseDisplay(c->d);

	return 0;
}

void draw_text(struct geom *g, struct style *s, int text, struct render_ctx *c) {
	const char *t;
	int text_w, text_h;

	if (c->d == NULL) {
		fprintf(stderr, "No active display found\n");
		return;
	}
	t = strs[text];
	text_w = str_widths_by_weight[s->txt_sz][text];
	text_h = str_heights_by_weight[s->txt_sz];

	XSetForeground(c->d, c->gc, c->colors[s->fg]);
	XSetFont(c->d, c->gc, c->fonts[s->txt_sz]->fid);
	/* All text is centered */
	XDrawString(c->d, c->w, c->gc,
	    g->x - text_w / 2,
	    g->y + text_h / 2,
	    t, strlen(t));
}

/* Function to draw the timer */
void draw_timer(struct geom *g, struct style *s, struct timespec *val, struct render_ctx *c) {
	char time_str[50];  // Buffer to store the formatted time string
	int text_width, text_x, text_y;

	// Convert timespec to a string with one decimal place for seconds
	double total_seconds = val->tv_sec + (val->tv_nsec / 1e9);
	snprintf(time_str, sizeof(time_str), "%.1f", total_seconds);

	// Access the preloaded font based on the style size
	XFontStruct *font = c->fonts[s->txt_sz];

	if (!font) {
		fprintf(stderr, "Invalid font for size %d.\n", s->txt_sz);
		return;
	}

	// Set the font for the graphics context
	XSetFont(c->d, c->gc, font->fid);

	// Calculate the width of the text to center it
	text_width = XTextWidth(font, time_str, strlen(time_str));

	// Set the foreground color based on the style's foreground color
	XSetForeground(c->d, c->gc, c->colors[s->fg]);

	// Calculate the text's X and Y positions to center it within the geometry
	text_x = g->x + (g->w - text_width) / 2;
	text_y = g->y + (g->h + font->ascent - font->descent) / 2;

	// Draw the timer string
	XDrawString(c->d, c->w, c->gc, text_x, text_y, time_str, strlen(time_str));
}

/*
 * Draws a rectangle with rounded corners using the color from the limited palette.
 */
void draw_rounded_border(struct geom *g, struct style *s, int corner_radius, struct render_ctx *ctx)
{
	const int line_thickness = 2;
	int arc_diameter;

	if (ctx->d == NULL) {
		fprintf(stderr, "No active display found\n");
		return;
	}


	// Set the color for the border using the palette
	XSetForeground(ctx->d, ctx->gc, get_color(ctx->d, s->fg));

	// Set the line thickness for the border
	XSetLineAttributes(ctx->d, ctx->gc, line_thickness, LineSolid, CapButt, JoinMiter);

	/* Draw the rounded rectangle */
	arc_diameter = 2 * corner_radius;

	// Draw the four straight sides, excluding the corner arcs
	// Top horizontal line (with gaps for the rounded corners)
	XDrawLine(ctx->d, ctx->w, ctx->gc, g->x + corner_radius, g->y, g->x + g->w - corner_radius, g->y);
	// Bottom horizontal line (with gaps for the rounded corners)
	XDrawLine(ctx->d, ctx->w, ctx->gc, g->x + corner_radius, g->y + g->h, g->x + g->w - corner_radius, g->y + g->h);
	// Left vertical line
	XDrawLine(ctx->d, ctx->w, ctx->gc, g->x, g->y + corner_radius, g->x, g->y + g->h - corner_radius);
	// Right vertical line
	XDrawLine(ctx->d, ctx->w, ctx->gc, g->x + g->w, g->y + corner_radius, g->x + g->w, g->y + g->h - corner_radius);

	// Draw the four rounded corners as arcs
	// Top-left corner
	XDrawArc(ctx->d, ctx->w, ctx->gc, g->x, g->y, arc_diameter, arc_diameter, 90 * 64, 90 * 64);
	// Top-right corner
	XDrawArc(ctx->d, ctx->w, ctx->gc, g->x + g->w - arc_diameter, g->y, arc_diameter, arc_diameter, 0 * 64, 90 * 64);
	// Bottom-left corner
	XDrawArc(ctx->d, ctx->w, ctx->gc, g->x, g->y + g->h - arc_diameter, arc_diameter, arc_diameter, 180 * 64, 90 * 64);
	// Bottom-right corner
	XDrawArc(ctx->d, ctx->w, ctx->gc, g->x + g->w - arc_diameter, g->y + g->h - arc_diameter, arc_diameter, arc_diameter, 270 * 64, 90 * 64);
}

void
draw_text_with_border(struct geom *g, struct style *s, int text,
    struct render_ctx *c)
{
	struct geom border_geom;
	border_geom.x = g->x - TXT_MARGIN -
	    (str_widths_by_weight[s->txt_sz][text] / 2);
	border_geom.y = g->y - TXT_MARGIN -
	    (str_heights_by_weight[s->txt_sz] / 2);
	border_geom.w = TXT_MARGIN * 2 + str_widths_by_weight[s->txt_sz][text];
	border_geom.h = TXT_MARGIN * 2 + str_heights_by_weight[s->txt_sz];
	draw_rounded_border(&border_geom, s, 15, c);
	draw_text(g, s, text, c);
}


/* Function to draw the score */
void draw_score(struct geom *g, struct style *s, int score, struct render_ctx *c) {
	char score_str[50];  // Buffer to store the formatted score string
	int text_width, text_x, text_y;

	// Convert the score to a string
	snprintf(score_str, sizeof(score_str), "Score: %d", score);

	// Access the preloaded font from the render context
	XFontStruct *font = c->fonts[s->txt_sz];

	if (!font) {
		fprintf(stderr, "Invalid font for size %d.\n", s->txt_sz);
		return;
	}

	// Set the font for the graphics context
	XSetFont(c->d, c->gc, font->fid);

	// Calculate the width of the text to center it within the geometry
	text_width = XTextWidth(font, score_str, strlen(score_str));

	// Set the foreground color based on the style's foreground color
	XSetForeground(c->d, c->gc, c->colors[s->fg]);

	// Calculate the X and Y positions to center the text within the given geometry
	text_x = g->x + (g->w - text_width) / 2;
	text_y = g->y + (g->h + font->ascent - font->descent) / 2;

	// Draw the score string at the calculated position
	XDrawString(c->d, c->w, c->gc, text_x, text_y, score_str, strlen(score_str));
}

/* Function to draw the final score */
void draw_final_score(struct geom *g, struct style *s, int score,
		int total_tasks, struct render_ctx *c) {
    char score_str[100];  // Buffer to store the formatted score string
    double accuracy = 0.0;
    int text_width, text_x, text_y;

    // Calculate the accuracy percentage
    if (total_tasks > 0) {
        accuracy = ((double)score / total_tasks) * 100.0;
    }

    // Format the final score string to display score, total tasks, and accuracy
    snprintf(score_str, sizeof(score_str), "Score: %d / %d (%.1f%%)", score, total_tasks, accuracy);

    // Access the preloaded font from the render context
    XFontStruct *font = c->fonts[s->txt_sz];
    
    if (!font) {
        fprintf(stderr, "Invalid font for size %d.\n", s->txt_sz);
        return;
    }

    // Set the font for the graphics context
    XSetFont(c->d, c->gc, font->fid);

    // Calculate the width of the text to center it within the geometry
    text_width = XTextWidth(font, score_str, strlen(score_str));

    // Set the foreground color based on the style's foreground color
    XSetForeground(c->d, c->gc, c->colors[s->fg]);

    // Calculate the X and Y positions to center the text within the given geometry
    text_x = g->x + (g->w - text_width) / 2;
    text_y = g->y + (g->h + font->ascent - font->descent) / 2;

    // Draw the final score string at the calculated position
    XDrawString(c->d, c->w, c->gc, text_x, text_y, score_str, strlen(score_str));
}

int
get_str_width(int weight, int strnum)
{
	return str_widths_by_weight[weight][strnum];
}
int
get_str_height(int weight)
{
	return str_heights_by_weight[weight];
}