ref: 6d347772d007e854bfcbdaecbb38abd7ef56ac6f
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
//#define DIRECTDRAW
struct frontend {
Image *image;
midend *me;
Image *background;
Image **colors;
int ncolors;
Point ZP;
Controlset *cs;
Channel *c;
int showframe;
int timeractive;
};
frontend *fe = nil;
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)
{
fe->timeractive = 0;
}
void activate_timer(frontend *fe)
{
fe->timeractive = 1;
}
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);
}
char *showcmd = "c_game show";
#define ugf chanprint(fe->cs->ctl, showcmd);
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;
#ifdef DIRECTDRAW
string(screen, addpt(Pt(x, y), fe->ZP), fe->colors[color], ZP, font, text);
#else
string(fe->image, Pt(x, y), fe->colors[color], ZP, font, text);
ugf;
#endif
}
static void
p9_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
draw(screen, rectaddpt(Rect(x, y, x+w, y+h), fe->ZP), fe->colors[color], nil, ZP);
#else
draw(fe->image, Rect(x, y, x+w, y+h), fe->colors[color], nil, ZP);
ugf;
#endif
}
static void
p9_draw_line(void *handle, int x1, int y1, int x2, int y2, int color)
{
frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, 1, fe->colors[color], ZP);
#else
line(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, 1, fe->colors[color], ZP);
ugf;
#endif
}
static void
p9_draw_thick_line(void *handle, float thickness, float x1, float y1, float x2, float y2, int color)
{
frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
line(screen, addpt(Pt(x1, y1), fe->ZP), addpt(Pt(x2, y2), fe->ZP), Endsquare, Endsquare, thickness, fe->colors[color], ZP);
#else
line(fe->image, Pt(x1, y1), Pt(x2, y2), Endsquare, Endsquare, thickness, fe->colors[color], ZP);
ugf;
#endif
}
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++) {
#ifdef DIRECTDRAW
points[i].x = coords[i*2+0] + fe->ZP.x;
points[i].y = coords[i*2+1] + fe->ZP.y;
#else
points[i].x = coords[i*2+0];
points[i].y = coords[i*2+1];
#endif
}
#ifdef DIRECTDRAW
if (fillcolor > 0)
fillpoly(screen, points, npoints, 0, fe->colors[fillcolor], ZP);
if (outlinecolor > 0)
poly(screen, points, npoints, Endsquare, Endsquare, 1, fe->colors[outlinecolor], ZP);
#else
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);
ugf;
#endif
free(points);
}
static void
p9_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor)
{
frontend *fe = (frontend*)handle;
#ifdef DIRECTDRAW
Point c = addpt(Pt(cx, cy), fe->ZP);
fillellipse(screen, c, radius, radius, fe->colors[fillcolor], ZP);
ellipse(screen, c, radius, radius, 0, fe->colors[outlinecolor], ZP);
#else
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);
ugf;
#endif
}
static void
p9_draw_update(void *handle, int x, int y, int w, int h)
{
frontend *fe = (frontend*)handle;
chanprint(fe->cs->ctl, showcmd);
}
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)
{
}
static void
p9_status_bar(void *handle, const char *text)
{
frontend *fe = (frontend*)handle;
chanprint(fe->cs->ctl, "l_status value %q", 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");
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 size %d %d 500 %d", p.x, p.y, p.y);
c_game = createbox(cs, "c_game");
chanprint(cs->ctl, "c_game border 1");
#ifdef DIRECTDRAW
chanprint(cs->ctl, "c_game image background");
#else
chanprint(cs->ctl, "c_game image frame");
#endif
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 align center");
chanprint(cs->ctl, "b_settings size %d %d 500 %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
timerthread(void *v)
{
Channel *c;
long totaltime;
long newtime;
c = v;
totaltime = times(nil);
for (;;) {
sleep(100); /* minimum tick time */
newtime = times(nil);
chanprint(c, "tick: %ld\n", newtime - totaltime);
totaltime = newtime;
}
}
void
initfe(frontend *fe)
{
float *colors;
int ncolors;
int r, g, b;
int col;
float bgcol[3];
fe->image = allocimage(display, screen->r, screen->chan, 0, 0);
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(screen, nil, nil, nil);
fe->c = chancreate(sizeof(char*), 0);
ctldeletequits = 1;
namectlimage(fe->image, "frame");
namectlimage(display->black, "i_black");
namectlimage(display->white, "i_white");
namectlimage(fe->background, "background");
initui(fe->cs, fe->c);
// needs different timer option - atnotify(2) / alarm(2) ?
//threadcreate(timerthread, fe->c, 4096);
}
int windowset = 0;
Point
resize(int *resizenop)
{
int x, y, fd;
x = Dx(screen->r);
y = Dy(screen->r) - 2*font->height;
midend_size(fe->me, &x, &y, 1, 1.);
// to test
if (0 && !windowset) {
fd = open("/dev/wctl", OWRITE);
if (fd >= 0) {
fprint(fd, "resize -dx %d -dy %d\n", x+2, y + 2*font->height);
close(fd);
windowset = 1;
}
} else
windowset = 0;
/* do not resize if we're waiting for the window resize */
*resizenop = windowset;
return Pt(x, y);
}
void
resizecontrolset(Controlset *cs)
{
Rectangle rmenu, rarea, sarea;
int resizenop;
Control *ctl;
Point newsize;
if (getwindow(display, Refnone) < 0) {
sysfatal("resize failed: %r");
}
rmenu = screen->r;
rmenu.max.y = rmenu.min.y + font->height;
rarea = screen->r;
rarea.min.y = rmenu.min.y + font->height;
rarea.max.y = rarea.max.y - font->height;
sarea = screen->r;
sarea.min.y = sarea.max.y - font->height;
if (fe->image) {
freeimage(fe->image);
fe->image = nil;
}
newsize = resize(&resizenop);
fe->image = allocimage(display, Rect(0, 0, newsize.x, newsize.y), screen->chan, 0, 0);
if (0 && resizenop)
return;
draw(screen, screen->r, fe->background, nil, ZP);
#ifndef DIRECTDRAW
midend_force_redraw(fe->me);
#endif
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);
#ifdef DIRECTDRAW
if (fe->showframe == 0)
midend_force_redraw(fe->me);
#endif
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)
{
if (frame == 0)
midend_force_redraw(fe->me);
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
tick(float delta)
{
if (fe->timeractive) {
midend_timer(fe->me, delta);
}
}
void
threadmain(int argc, char **argv)
{
int x, y, n, r;
int lastmouse;
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();
initfe(fe);
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]);
r = -1;
if ( (lastmouse&1) && !(n&1))
r = midend_process_key(fe->me, x, y, LEFT_RELEASE);
if (!(lastmouse&1) && (n&1))
r = midend_process_key(fe->me, x, y, LEFT_BUTTON);
if ( (lastmouse&2) && !(n&2))
r = midend_process_key(fe->me, x, y, MIDDLE_RELEASE);
if (!(lastmouse&2) && (n&2))
r = midend_process_key(fe->me, x, y, MIDDLE_BUTTON);
if ( (lastmouse&4) && !(n&4))
r = midend_process_key(fe->me, x, y, RIGHT_RELEASE);
if (!(lastmouse&4) && (n&2))
r = midend_process_key(fe->me, x, y, RIGHT_BUTTON);
if (r >= 0) {
chanprint(fe->cs->ctl, showcmd);
}
lastmouse = n;
} else
if (strcmp(args[1], "key") == 0) {
l = strtol(args[2], nil, 0);
if (keyev(l))
break;
}
} else
if (strcmp(args[0], "b_game:") == 0) {
showframe(0);
chanprint(fe->cs->ctl, "b_game value 0");
} else
if (strcmp(args[0], "b_settings:") == 0) {
showframe(1);
chanprint(fe->cs->ctl, "b_settings value 0");
} else
if (strcmp(args[0], "tick:") == 0) {
l = strtol(args[1], nil, 0);
tick(l / 1000.);
} else
print("event from %s: %s\n", args[0], args[1]);
}
threadexitsall(nil);
}