ref: 9c7d5576c074bd7b9d0fe8a249df31da2df31c9e
dir: /plan9.c/
#include <npe.h>
#include <thread.h>
#include <draw.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
#undef PI
#include "puzzles.h"
#ifdef COMBINED
#error Plan 9 should not be COMBINED
#endif
struct frontend {
Image *image;
midend *me;
Image *background;
Image **colors;
int ncolors;
Point ZP;
int topbarh;
Controlset *cs;
Channel *c;
int showframe;
Rectangle rect;
};
frontend *fe;
void
frontend_default_colour(frontend *fe, float *output)
{
output[0] = .9;
output[1] = .9;
output[2] = .9;
}
void
get_random_seed(void **randseed, int *randseedsize)
{
long *t = malloc(sizeof(long));
assert(t);
time(t);
*randseed = (void*)t;
*randseedsize = sizeof(long);
}
void
deactivate_timer(frontend *fe)
{
}
void activate_timer(frontend *fe)
{
}
void fatal(const char *fmt, ...)
{
va_list ap;
fprint(2, "fatal error: ");
va_start(ap, fmt);
vfprint(2, fmt, ap);
va_end(ap);
fprint(2, "\n");
sysfatal("error");
}
#ifdef DEBUGGING
void debug_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprint(1, fmt, ap);
va_end(ap);
}
#endif
void
usage(void)
{
fprint(2, "usage: %s [-ho] [--options]\n", argv0);
exits(nil);
}
static void p9_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int color, const char *text)
{
frontend *fe = (frontend*)handle;
string(fe->image, Pt(x, y), fe->colors[color], ZP, font, text);
}
static void
p9_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
frontend *fe = (frontend*)handle;
draw(fe->image, Rect(x, y, x+w, y+h), fe->colors[color], nil, ZP);
}
static void
p9_draw_line(void *handle, int x1, int y1, int x2, int y2, int color)
{
frontend *fe = (frontend*)handle;
line(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, 1, fe->colors[color], ZP);
}
static void
p9_draw_thick_line(void *handle, float thickness, float x1, float y1, float x2, float y2, int color)
{
frontend *fe = (frontend*)handle;
line(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, thickness, fe->colors[color], ZP);
}
static void
p9_draw_poly(void *handle, const int *coords, int npoints, int fillcolor, int outlinecolor)
{
Point *points;
frontend *fe = (frontend*)handle;
points = malloc(npoints * sizeof(Point));
for (int i = 0; i < npoints; i++) {
points[i].x = coords[i*2+0];
points[i].y = coords[i*2+1];
}
if (fillcolor > 0)
fillpoly(fe->image, points, npoints, 0, fe->colors[fillcolor], ZP);
if (outlinecolor > 0)
poly(fe->image, points, npoints, Endsquare, Endsquare, 1, fe->colors[outlinecolor], ZP);
free(points);
}
static void
p9_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor)
{
frontend *fe = (frontend*)handle;
Point c = Pt(cx, cy);
fillellipse(fe->image, c, radius, radius, fe->colors[fillcolor], ZP);
ellipse(fe->image, c, radius, radius, 0, fe->colors[outlinecolor], ZP);
}
static void
p9_draw_update(void *handle, int x, int y, int w, int h)
{
}
static void
p9_clip(void *handle, int x, int y, int w, int h)
{
}
static void
p9_unclip(void *handle)
{
}
static void
p9_start_draw(void *handle)
{
}
static void
p9_end_draw(void *handle)
{
frontend* fe = (frontend*)handle;
chanprint(fe->cs->ctl, "c_game image frame");
}
static void
p9_status_bar(void *handle, const char *text)
{
frontend *fe = (frontend*)handle;
chanprint(fe->cs->ctl, "l_status value %q", text);
return;
draw(fe->image, Rect(fe->ZP.x+10, fe->image->r.max.y-20, fe->image->r.max.x, fe->image->r.max.y), fe->background, nil, ZP);
string(fe->image, Pt(fe->ZP.x+10, fe->image->r.max.y-20), display->black, ZP, font, text);
}
static blitter*
p9_blitter_new(void *handle, int w, int h)
{
return nil;
}
static void
p9_blitter_free(void *handle, blitter *bl)
{
}
static void
p9_blitter_save(void *handle, blitter *bl, int x, int y)
{
}
static void
p9_blitter_load(void *handle, blitter *bl, int x, int y)
{
}
static const drawing_api p9_drawing = {
p9_draw_text,
p9_draw_rect,
p9_draw_line,
p9_draw_poly,
p9_draw_circle,
p9_draw_update,
p9_clip,
p9_unclip,
p9_start_draw,
p9_end_draw,
p9_status_bar,
p9_blitter_new,
p9_blitter_free,
p9_blitter_save,
p9_blitter_load,
nil, nil, nil, nil, nil, nil, nil, nil, /* {begin,end}_{doc,page,puzzle}, line_width, line_dotted */
nil, /* text_fallback */
#ifdef NO_THICK_LINE
nil,
#else
p9_draw_thick_line,
#endif
};
static int rgb2col(int r, int g, int b)
{
return (r<<24) | (g<<16) | (b<<8) | 0xFF;
}
static frontend*
new_window(void)
{
frontend *fe;
fe = mallocz(sizeof(frontend), 1);
if (!fe)
sysfatal("error: out of memory!");
fe->me = midend_new(fe, &thegame, &p9_drawing, fe);
return fe;
}
void
initui(Controlset *cs, Channel *c)
{
Control *b_game, *b_settings, *c_game, *c_settings, *stackmain, *menu;
Control *l_status;
Point p;
menu = createrow(cs, "rowmain");
chanprint(cs->ctl, "rowmain border 1");
stackmain = createstack(cs, "stackmain");
chanprint(cs->ctl, "stackmain border 1");
controlwire(stackmain, "event", c);
b_game = createtextbutton(cs, "b_game");
p = stringsize(font, "game");
chanprint(cs->ctl, "b_game border 1");
chanprint(cs->ctl, "b_game align center");
chanprint(cs->ctl, "b_game text game");
chanprint(cs->ctl, "b_game image i_white");
chanprint(cs->ctl, "b_game light i_black");
chanprint(cs->ctl, "b_game size %d %d 100 %d", p.x, p.y, p.y);
c_game = createbox(cs, "c_game");
chanprint(cs->ctl, "c_game image frame");
chanprint(cs->ctl, "c_game border 1");
controlwire(c_game, "event", c);
controlwire(b_game, "event", c);
b_settings = createtextbutton(cs, "b_settings");
p = stringsize(font, "settings");
chanprint(cs->ctl, "b_settings text settings");
chanprint(cs->ctl, "b_settings border 1");
chanprint(cs->ctl, "b_settings size %d %d 100 %d", p.x, p.y, p.y);
c_settings = createcolumn(cs, "c_settings");
controlwire(c_settings, "event", c);
controlwire(b_settings, "event", c);
l_status = createlabel(cs, "l_status");
chanprint(cs->ctl, "stackmain add c_game c_settings");
chanprint(cs->ctl, "rowmain add b_game\nrowmain add b_settings");
activate(b_game);
activate(b_settings);
activate(c_game);
}
void
initfe(frontend *fe, Channel *c)
{
float *colors;
int ncolors;
int r, g, b;
int col;
float bgcol[3];
fe->topbarh = 20;
fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
fe->ZP = screen->r.min;
fe->ZP.y += fe->topbarh;
frontend_default_colour(fe, bgcol);
fe->background = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(bgcol[0]*255., bgcol[1]*255., bgcol[2]*255.));
colors = midend_colours(fe->me, &ncolors);
fe->colors = mallocz(ncolors * sizeof(Image*), 1);
for (int i = 0; i < ncolors; i++) {
r = colors[i*3+0] * 255.;
g = colors[i*3+1] * 255.;
b = colors[i*3+2] * 255.;
fe->colors[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, rgb2col(r, g, b));
}
free(colors);
fe->cs = newcontrolset(fe->image, nil, nil, nil);
fe->cs->clicktotype = 1;
fe->c = c;
ctldeletequits = 1;
namectlimage(fe->image, "frame");
namectlimage(display->black, "i_black");
namectlimage(display->white, "i_white");
initui(fe->cs, fe->c);
}
void
resize(void)
{
int x, y;
x = screen->r.max.x - screen->r.min.x;
y = screen->r.max.y - screen->r.min.y;
midend_size(fe->me, &x, &y, 1, 1.);
}
void
resizecontrolset(Controlset *cs)
{
Rectangle rmenu, rarea, sarea;
Control *ctl;
if (getwindow(display, Refnone) < 0) {
sysfatal("resize failed: %r");
}
rmenu = screen->r;
rmenu.max.y = rmenu.min.y + 16;
rarea = screen->r;
rarea.min.y = rmenu.min.y + 16;
rarea.max.y = rarea.max.y - 16;
sarea = screen->r;
sarea.min.y = sarea.max.y - 16;
if (fe->image) {
freeimage(fe->image);
fe->image = nil;
}
fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
fe->rect = rarea;
draw(screen, screen->r, fe->background, nil, ZP);
resize();
midend_redraw(fe->me);
chanprint(cs->ctl, "rowmain rect %R\nrowmain show", rmenu);
chanprint(cs->ctl, "c_game rect %R\nc_settings rect %R", rarea, rarea);
chanprint(cs->ctl, "stackmain rect %R\nstackmain show", rarea);
chanprint(cs->ctl, "stackmain reveal %d", fe->showframe);
chanprint(cs->ctl, "l_status rect %R\nl_status show", sarea);
ctl = controlcalled("c_game");
fe->ZP = ctl->rect.min;
}
void
printoptions(config_item *c)
{
char *t;
char *s = nil, *cnames[16], *ckws[16];
int n = 0, m;
config_item *cfg = midend_get_config(fe->me, CFG_PREFS, &t);
print("Options:\n");
while (cfg->type != C_END) {
switch (cfg->type) {
case C_STRING:
s = cfg->u.string.sval;
break;
case C_BOOLEAN:
s = cfg->u.boolean.bval ? "1" : "0";
break;
case C_CHOICES:
print(" Choices:\n");
n = getfields(cfg->u.choices.choicenames, cnames, 16, 1, ":");
m = getfields(cfg->u.choices.choicekws, ckws, 16, 1, ":");
assert(n == m && cfg->u.choices.selected < n);
s = ckws[cfg->u.choices.selected];
break;
}
print("--%s=%s\n %s\n", cfg->kw, s, cfg->name);
if (cfg->type == C_CHOICES) {
print(" Choices:\n");
for (int i = 0; i < n; i++) {
print(" %s: %s\n", ckws[i], cnames[i]);
}
}
cfg++;
}
}
void
parseoption(config_item *cfg, char *keyval)
{
char *arg[2];
int n;
n = getfields(keyval, arg, 2, 1, "=");
if (n != 2)
usage(); // exits
while (cfg && cfg->type != C_END)
if (strcmp(cfg->kw, arg[0]) != 0)
cfg++;
if (!cfg || cfg->type == C_END) {
fprint(2, "no valid option\n");
return;
}
print("%s : %s\n", cfg->kw, cfg->name);
return;
switch (cfg->type) {
case C_STRING:
cfg->u.string.sval = arg[1];
print("is string");
break;
case C_BOOLEAN:
n = atoi(arg[1]);
print("is boolean");
cfg->u.boolean.bval = n ? 1 : 0;
break;
case C_CHOICES:
// TODO
print("is choices");
fprint(2, "not implemented yet!\n");
break;
case C_END:
default:
print("not found");
break;
}
}
void
showframe(int frame)
{
fe->showframe = frame;
chanprint(fe->cs->ctl, "stackmain reveal %d", frame);
}
int
keyev(int k)
{
switch (k) {
case 'q':
case 127:
return 1; /* return 1 to quit */
default:
if (k >= 0 && midend_process_key(fe->me, 0, 0, k) == PKR_QUIT)
return 1;
}
return 0;
}
void
threadmain(int argc, char **argv)
{
int x, y, n;
long l;
char *s, *args[6];
int doprintoptions = 0;
char *wintitle;
Channel *c;
config_item *cfg;
int changedprefs = 0;
fe = new_window();
wintitle = nil;
cfg = midend_get_config(fe->me, CFG_SETTINGS, &wintitle);
ARGBEGIN{
case 'h':
usage();
break;
case 'o':
doprintoptions++;
break;
case '-':
parseoption(cfg, ARGF());
changedprefs++;
break;
}ARGEND;
if (changedprefs) {
s = midend_set_config(fe->me, CFG_SETTINGS, cfg);
if (s) {
fprint(2, "error: %s\n", s);
exits("error");
}
}
if (doprintoptions) {
printoptions(cfg);
exits(nil);
}
if (initdraw(nil, nil, wintitle) < 0) {
sysfatal("initdraw failed: %r");
}
initcontrols();
c = chancreate(sizeof(char*), 0);
initfe(fe, c);
midend_new_game(fe->me);
resizecontrolset(fe->cs);
for (;;) {
s = recvp(fe->c);
n = tokenize(s, args, nelem(args));
if (strcmp(args[0], "c_settings:") == 0) {
print("c_settings event: %s\n", args[1]);
} else
if (strcmp(args[0], "c_game:") == 0) {
if (strcmp(args[1], "mouse") == 0) {
x = atoi(args[2]+1) - fe->ZP.x; /* ignore '[' */
y = atoi(args[3]) - fe->ZP.y;
n = atoi(args[4]);
if (n&1)
midend_process_key(fe->me, x, y, LEFT_BUTTON);
if (n&2)
midend_process_key(fe->me, x, y, MIDDLE_BUTTON);
if (n&4)
midend_process_key(fe->me, x, y, RIGHT_BUTTON);
} else
if (strcmp(args[1], "key") == 0) {
l = strtol(args[2], nil, 0);
if (keyev(l))
break;
} else
print("c_game event: %s\n", args[1]);
} else
if (strcmp(args[0], "b_game:") == 0) {
print("b_game pressed\n");
showframe(0);
} else
if (strcmp(args[0], "b_settings:") == 0) {
print("b_settings pressed\n");
showframe(1);
} else
print("event from %s: %s\n", args[0], args[1]);
}
threadexitsall(nil);
}