shithub: blie

ref: 13590f759547f4ef544b251f2966b4ed2d82038c
dir: /blie.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <event.h>
#include <keyboard.h>
#include <cursor.h>
#include <bio.h>
#include "blie.h"

void
usage(void)
{
	fprint(2, "usage: %s [-cd] dir\n", argv0);
	exits(nil);
}

int bliedebug = 0;
int headless = 0;
char Nlayers[] = "layers";
char *root = nil;

Vdata vdata = {
	.layerwinwidth = 150,
	.keyoffset = 10,
	.keyzoom = 0.2,
};

static Memimage*
memcol(int chan, int color)
{
	Memimage *i;
	i = allocmemimage(Rect(0, 0, 1, 1), chan);
	memfillcolor(i, color);
	i->flags |= Fsimple|Frepl;
	i->clipr = Rect(-0x3FFFFFF, -0x3FFFFFF, 0x3FFFFFF, 0x3FFFFFF);
	return i;
}

static void
initvdata(void)
{
	Point p;
	
	p = stringsize(font, "|H");
	vdata.fontheight = p.y;
	
	vdata.gray = allocimagemix(display, DBlack, DWhite);
	vdata.mgray = allocmemimage(Rect(0, 0, 1, 1), GREY8);
	memfillcolor(vdata.mgray, 0xaaaaaaff);
	vdata.mgray->flags |= Frepl|Fbytes|Fsimple;
	
	vdata.transparent = allocimage(display, Rect(0, 0, 50, 50), RGB24, 1, 0xccccccff);
	draw(vdata.transparent, Rect(0, 25, 25, 50), vdata.gray, nil, ZP);
	draw(vdata.transparent, Rect(25, 0, 50, 25), vdata.gray, nil, ZP);
	
	vdata.mtransparent = allocmemimage(Rect(0, 0, 50, 50), RGB24);
	vdata.mtransparent->flags |= Frepl|Fbytes;
	memfillcolor(vdata.mtransparent, 0xccccccff);
	vdata.mgray->clipr = vdata.mtransparent->r;
	memimagedraw(vdata.mtransparent, Rect(0, 25, 25, 50), vdata.mgray, ZP, nil, ZP, S);
	memimagedraw(vdata.mtransparent, Rect(25, 0, 50, 25), vdata.mgray, ZP, nil, ZP, S);
}

Vstate vstate = {
	.zoom = 1.0,
	.offset = { 0, 0 },
	.dirty = 3,
	.maxquality = 1,
};

void
setdrawingdirty(int d)
{
	vstate.dirty = d;
}

void
setmodedirty(int d)
{
	vstate.mdirty = d;
}

typedef struct Estate Estate;
struct Estate {
	Editor *ed;
	Layer *l;
};
Estate estate;

typedef struct Dstate Dstate;
struct Dstate {
	Cursor *cursor;
	Image *cursimg;
	Image *cursorblit;
	Point bloffset;
	Point curoffset;
};
Dstate dstate;

typedef struct Panels Panels;
struct Panels {
	Image *tools;
	Image *layers;
	Image *drawing;
};
Panels panels;
Screen *scr;
#define NWIN 12
Image *windows[NWIN];
void (*winfunc[NWIN])(Image*);

static Image**
addwindow(Image *i, void (*f)(Image*))
{
	int n;
	for (n = 0; n < NWIN; n++) {
		if (!windows[n]) {
			windows[n] = i;
			winfunc[n] = f;
			if (f)
				f(windows[n]);
			return &windows[n];
		}
	}
	sysfatal("no more windows available!");
}

void
changecursor(Cursor *c, Image *i, Point off)
{
	dstate.cursor = c;
	dstate.cursimg = i;
	dstate.curoffset = off;
}

static void
initlayer(Layer *l, int, int, void*)
{
	if (l->editor && l->editor->initlayer)
		l->editor->initlayer(l);
}

int
loadfile(void)
{
	Biobuf *b;
	int fd;
	char *s;
	
	b = Bopen(Nlayers, OREAD);
	if (!b) {
		fd = create(Nlayers, OREAD, 0666);
		close(fd);
		b = Bopen(Nlayers, OREAD);
	}
	if (!b)
		sysfatal("unable to open layers file: %r");
	
	while (s = Brdstr(b, '\n', 1))
		addlayer(s);
	
	foreachlayer(initlayer, nil);
	
	return 1;
}

int nextlayerdirty; // while docomp, is next layer dirty?

static void
docomp(Layer *l, int id, int, void *aux)
{
	int i;
	Memimage **img = (Memimage**)aux;
	Editor *ed = l->editor;
	Memimage *(*c)(Layer*, Memimage*);
	
	i = (id + 1)%2;
	
	if (bliedebug)
		fprint(2, "composite: %s\n", l->name);
	
	c = ed->composite ? ed->composite : ecomposite;
	
	if (nextlayerdirty)
		dirtylayer(l);
	
	if (!l->composited) {
		l->composited = c(l, img[i]);
		nextlayerdirty = 1;
	}
	img[id%2] = l->composited;
	img[i] = nil;
}

void
outputcomposite(void)
{
	Memimage *img[2]; /* swapchain */
	int i;
	
	img[0] = nil;
	img[1] = nil;
	i = (foreachlayer(docomp, img) + 1) % 2;
	nextlayerdirty = 0;
	writememimage(1, img[i]);
}

static void
redrawdrawing(void)
{
	Memimage *img[2]; /* swapchain */
	int i;
	
	draw(panels.drawing, panels.drawing->r, vdata.transparent, nil, ZP);
	
	if (estate.ed && estate.ed->overlay) {
		if (estate.ed->overlay(estate.l, nil)) {
			estate.ed->overlay(estate.l, panels.drawing);
			goto Cursorfix;
		}
	}
	img[0] = nil;
	img[1] = nil;
	i = (foreachlayer(docomp, img) + 1) % 2;
	nextlayerdirty = 0;

	sampleview(panels.drawing, img[i], 1);
	
	if (estate.ed && estate.ed->overlay)
		estate.ed->overlay(estate.l, panels.drawing);
	
	/* fix cursor */
Cursorfix:
	if (dstate.cursorblit) {
		freeimage(dstate.cursorblit);
		dstate.cursorblit = nil;
	}
}

static void
redrawtools(void)
{
	if (estate.ed) {
		estate.ed->drawtools(estate.l, panels.tools);
	} else {
		draw(panels.tools, panels.tools->r, display->white, nil, ZP);
	}
	line(panels.tools,
		Pt(panels.tools->r.min.x, panels.tools->r.max.y-1),
		subpt(panels.tools->r.max, Pt(0, 1)),
		Endsquare, Endsquare, 0, display->black, ZP);
}

static void
redraw(int force)
{
	int i;
	redrawlayers(panels.layers, estate.l);
	if (force)
		setdrawingdirty(Dpan);
	redrawdrawing();
	redrawtools();
	
	for (i = 0; i < NWIN; i++) {
		if (windows[i]) {
			topwindow(windows[i]);
			if (winfunc[i])
				winfunc[i](windows[i]);
		}
	}
	flushimage(display, 1);
}

static void
screeninit(void)
{
	Rectangle tr;
	
	freescreen(scr);
	scr = allocscreen(screen, display->black, 0);
	freeimage(panels.tools);
	freeimage(panels.layers);
	freeimage(panels.drawing);
	
	if (estate.ed) {
		tr = rectaddpt(estate.ed->toolrect(estate.l), screen->r.min);
		tr.max.x = screen->r.max.x;
	} else {
		tr = screen->r;
		tr.max.y = tr.min.y + 10;
	}
	panels.tools = allocwindow(scr, tr, 0, 0xCCCCCCFF);
	
	tr.min.y = tr.max.y;
	tr.min.x = screen->r.max.x - vdata.layerwinwidth;
	tr.max = screen->r.max;
	panels.layers = allocwindow(scr, tr, 0, 0xCCCCCCFF);
	
	tr.min.x = screen->r.min.x;
	tr.min.y = panels.tools->r.max.y;
	tr.max.x = panels.layers->r.min.x;
	tr.max.y = screen->r.max.y;
	panels.drawing = allocwindow(scr, tr, 0, 0xCC00CCFF);
	flushimage(display, 1);
}

Image**
reqwin(Rectangle r, ulong color, void (*f)(Image*))
{
	Image *i;
	i = allocwindow(scr, r, 0, color);
//	originwindow(i, Pt(0, 0), r.min);
	return addwindow(i, f);
}

void
unreqwin(Image **i)
{
	freeimage(*i);
	*i = nil;
	winfunc[i-windows] = nil;
}

void
eresized(int new)
{
	if (new && getwindow(display, Refnone) < 0)
		sysfatal("resize failed: %r");
	screeninit();
	redraw(1);
}

static void
activatelayer(Layer *l)
{
	estate.ed = l->editor;
	estate.l = l;
	screeninit();
	redraw(1);
}

static void
drawcursor(Point xy, int hide)
{
	Rectangle r;
	if (dstate.cursorblit) {
		draw(screen, dstate.cursorblit->r, dstate.cursorblit, nil,
			dstate.cursorblit->r.min);
		freeimage(dstate.cursorblit);
		dstate.cursorblit = nil;
	}
	if (hide || !dstate.cursimg)
		return;
	r = dstate.cursimg->r;
	r = rectaddpt(r, addpt(xy, dstate.curoffset));
	dstate.cursorblit = allocimage(display, r, screen->chan, 0, DWhite);
	drawop(dstate.cursorblit, dstate.cursorblit->r, screen, nil, dstate.cursorblit->r.min, S);
	drawop(screen, r, dstate.cursimg, nil, ZP, SoverD);

#ifdef NOP /* debug display */
	drawop(screen, screen->r, dstate.cursimg, nil, ZP, SoverD);
	drawop(screen, screen->r, dstate.cursorblit, nil,
		addpt(Pt(-50, 0), dstate.cursorblit->r.min), S);
#endif
}

static void
condredraw(Redrawwin w)
{
	if (vstate.mdirty) {
		redraw(1);
		vstate.mdirty = 0;
		return;
	}
	if (w & Rdrawing)
		redrawdrawing();
	if (w & Rtools)
		redrawtools();
	if (w & Rlayers)
		redrawlayers(panels.layers, estate.l);
}

static void
handlemouse(Event ev)
{
	Redrawwin n;
	Point xy;
	Mouse *m = &ev.mouse;
	xy = m->xy;
	vstate.lastmouse = ev.mouse;
	vstate.mousepos = xy;
	n = Rnil;
	if (ptinrect(m->xy, panels.layers->r)) {
		esetcursor(nil);
		drawcursor(m->xy, 1);
		if (m->buttons) {
			m->xy = subpt(m->xy, panels.layers->r.min);
			n = clicklayer(ev, estate.l, panels.layers, activatelayer);
			condredraw(n);
		}
		return;
	}
	if (estate.ed && ptinrect(m->xy, panels.tools->r)) {
		esetcursor(nil);
		drawcursor(m->xy, 1);
		m->xy = subpt(m->xy, panels.tools->r.min);
		if (estate.ed->toolinput)
			n = estate.ed->toolinput(estate.l, Emouse, ev);
		condredraw(n);
		return;
	}
	if (estate.ed && ptinrect(m->xy, panels.drawing->r)) {
		m->xy = subpt(m->xy, panels.drawing->r.min);
		m->xy = scalepos(m->xy);
		if (estate.ed->drawinput)
			n = estate.ed->drawinput(estate.l, Emouse, ev);
		condredraw(n);
		esetcursor(dstate.cursor);
		drawcursor(xy, 0);
		return;
	}
}

/* return 1 = handled */
static int
handledrawingkey(int kbdc)
{
	switch (kbdc) {
	case Kup:
		vstate.offset.y += vdata.keyoffset;
		goto Dirtypan;
	case Kdown:
		vstate.offset.y -= vdata.keyoffset;
		goto Dirtypan;
	case Kleft:
		vstate.offset.x += vdata.keyoffset;
		goto Dirtypan;
	case Kright:
		vstate.offset.x -= vdata.keyoffset;
		goto Dirtypan;
	case '.':
		vstate.zoom += vdata.keyzoom;
		goto Dirtyzoom;
	case ',':
		vstate.zoom -= vdata.keyzoom;
		if (vstate.zoom < 0.1)
			vstate.zoom = 0.1;
		goto Dirtyzoom;
	}
	return 0;
Dirtyzoom:
	vstate.dirty |= Dzoom;
Dirtypan:
	vstate.dirty |= Dpan;
	redrawdrawing();
	return 1;
}

static void
savetools(void)
{
	if (!(estate.l && estate.ed))
		return;
	if (estate.ed->savetools)
		estate.ed->savetools(estate.l);
}

static void
savelayer(Layer *l, int, int, void*)
{
	if (l->editor && l->editor->savedata)
		l->editor->savedata(l);
}

static void
savecurrentlayer(void)
{
	savelayer(estate.l, 0, 0, nil);
}

static void
savelayers(void)
{
	foreachlayer(savelayer, nil);
}

static Redrawwin
askcommand(Event ev)
{
	char cmd[256];
	char *args[5];
	int n;
	
	cmd[0] = 0;
	if (!eenter("cmd", cmd, sizeof(cmd), &ev.mouse))
		return Rnil;
	
	n = tokenize(cmd, args, 5);
	
	if (n == 2 && strcmp(args[0], "quality") == 0) {
		if (strcmp(args[1], "0") == 0
		 || strcmp(args[1], "nearest") == 0) {
			vstate.maxquality = 0;
			setdrawingdirty(Dzoom);
			return Rdrawing;
		}
		if (strcmp(args[1], "1") == 0
		 || strcmp(args[1], "bilinear") == 0) {
			vstate.maxquality = 1;
			setdrawingdirty(Dzoom);
			return Rdrawing;
		}
		return Rnil;
	}
	if (args[0][0] == 'w') {
		switch (args[0][1]) {
		case 'a':
			savelayers();
			savetools();
			return Rnil;
		case 't':
			savetools();
			return Rnil;
		case 0:
			savecurrentlayer();
			return Rnil;
		}
		return Rnil;
	}
	if (n == 2 && strcmp(args[0], "e") == 0) {
		// TODO: export all to file args[1]
		return Rnil;
	}
	return Rnil;
}

static void
handlekeyboard(Event ev)
{
	Redrawwin w;
	Mouse *m = &ev.mouse;
	
	/* global keys */
	switch (ev.kbdc) {
	case 'q':
	case Kdel:
		exits(nil);
	case '\t':
		w = askcommand(ev);
		condredraw(w);
		return;
	}
	
	if (ptinrect(m->xy, panels.layers->r)) {
		/* functionality: delete layer, add layer, change layer */
		/*
		l - label (prompt)
		a - add new layer (prompt editor, name, label)
		d - delete layer
		pgup - move layer up
		pgdn - move layer down
		*/
		return;
	}
	if (estate.ed && ptinrect(m->xy, panels.tools->r)) {
		m->xy = subpt(m->xy, panels.tools->r.min);
		if (estate.ed->toolinput) {
			w = estate.ed->toolinput(estate.l, Ekeyboard, ev);
			condredraw(w);
		}
		return;
	}
	if (ptinrect(m->xy, panels.drawing->r)) {
		if (handledrawingkey(ev.kbdc))
			return;
		if (estate.ed && estate.ed->drawinput) {
			m->xy = subpt(m->xy, panels.drawing->r.min);
			w = estate.ed->drawinput(estate.l, Ekeyboard, ev);
			condredraw(w);
		}
		return;
	}
}

void
main(int argc, char **argv)
{
	int outputonly = 0;
	
	Event ev;
	int e;
	
	ARGBEGIN{
	case 'c':
		outputonly++;
		break;
	case 'd':
		bliedebug++;
		break;
	}ARGEND;
	
	root = "";
	if (argc) {
		root = argv[0];
	}
	
	rfork(RFNAMEG);
	if (chdir(root))
		sysfatal("cannot chdir: %r");
	
	if (memimageinit())
		sysfatal("memimageinit: %r");
	
	headless = outputonly;
	if (!headless) {
		if (initdraw(nil, nil, "blie") < 0)
			sysfatal("initdraw: %r");
		initvdata();
	}
	loadeditors();
	
	if (!loadfile())
		sysfatal("cannot load data: %r");
	
	if (outputonly) {
		outputcomposite();
		exits(nil);
	}
	
	einit(Emouse|Ekeyboard);
	
	estate.ed = nil;
	estate.l = nil;
	screeninit();
	redraw(1);
	
	for (;;) {
		e = event(&ev);
		switch (e) {
		case Emouse:
			handlemouse(ev);
			break;
		case Ekeyboard:
			handlekeyboard(ev);
			break;
		}
	}
}