#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];
}