shithub: pain

ref: ee5d653d61460a31666dc0bf8c9b011832a0e431
dir: /main.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>

#include "dat.h"
#include "fns.h"

#define MAXLAYERS 10
#define BACKGROUNDCOLOR 0x22272EFF
#define DEFAULTLAYERCOLOR DTransparent
#define DEFAULTCANVASBACKGROUNDCOLOR DWhite
#define CLEARCOLOR DTransparent
#define FPS 30
#define UPDATEPERIOD (1000/FPS)
#define Ar(r) (Dx(r)*Dy(r))
#define RECTOPPT(r, op)	((Rectangle){op(r.min),op(r.max)})
#define DEFAULTCANVASSIZE RECTANGLE(1920, 1080)
#define STROKEEND Endsquare
#define NOTIFICATIONFONTCOLOR DBlack
#define NOTIFICATIONBACKGROUNDCOLOR 0x72DEC2FF
#define ZOOMAPPLYDELAY (1000/6)

int CanvasMoved = 1;
Rectangle CanvasSize;
Rectangle ZoomedSize;
Point CanvasAt = {0};
Layer * RootLayer = nil;
Layer * CurrentLayer = nil;
Image * Background = nil;
Image * BorderImage = nil;
Image * ClearImage = nil;
int CurrentKeyboard = 0;
Image * BrushImage = nil;
Image * NotificationImage = nil;
Image * NotificationBackgroundImage = nil;
char * NotificationString = nil;
Image * ViewImage = nil;
Image * ZoomedImage = nil;
Image * CanvasBackgroundImage = nil;
ulong LastUpdate = 0;
int DrawAllLayers = 0;
float Zoom = 1;
float TargetZoom = 1;
float ZoomSensitivity = 0.025;
Mouse CurrentMouse = {0};
Mouse PastMouse = {0};
Point MousePosition = {0};
Point PastMousePosition = {0};
int RunLoop = 1;
int ZoomTaskId = -1;
uint LayerIDCounter = 0;

int
newlayerimage(Layer * l)
{
	if (l == nil) {
		werrstr("l = nil");
		return -1;
	}

	if (l->image != nil)
		freeimage(l->image);

	l->image = allocimage(display, CanvasSize, RGBA32, 0, DEFAULTLAYERCOLOR);
	if (l->image == nil) {
		werrstr("newlayer: %r");
		return -1;
	}
	l->changed = 1;
	return 0;
}

void
setcurrentlayer(Layer * l)
{
	if (l == nil)
		return;
	ntfprint("switched to layer %d", l->id);
	CurrentLayer = l;
}

Layer *
newlayer(void)
{
	Layer * l = nil, * e = nil;

	USED(l);
	USED(e);

	l = calloc(1, sizeof(Layer));
	if (l == nil)
		sysfatal("calloc: %r");
	l->changed = 1;

	if (newlayerimage(l))
		sysfatal("newlayerimage: %r");
	
	l->id = LayerIDCounter++;
	if (RootLayer == nil) {
		RootLayer = l;
		CurrentLayer = l;
	} else {
		for (e = RootLayer; e->next != nil; e = e->next)
			;
		e->next = l;
		l->prev = e;
		ntfprint("create new layer %d", l->id);
	}
	return l;
}

void
shownotification(void)
{
	if (NotificationImage == nil)
		if ((NotificationImage = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, NOTIFICATIONFONTCOLOR)) == nil)
			sysfatal("allocimage: %r");
	if (NotificationBackgroundImage == nil)
		if ((NotificationBackgroundImage = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, NOTIFICATIONBACKGROUNDCOLOR)) == nil)
			sysfatal("allocimage: %r");
	if (NotificationString == nil)
		return;
	stringbg(screen, screen->r.min, NotificationImage, ZP, font, NotificationString, NotificationBackgroundImage, ZP);
}

void
ntfprint(char * fmt, ...)
{
	va_list arg;

	if (NotificationString != nil)
		free(NotificationString);

	va_start(arg, fmt);
	NotificationString = vsmprint(fmt, arg);
	va_end(arg);
	shownotification();
}

void
drawcanvas(void)
{
	int sr, i;
	Point p;
	Rectangle vr;

	sr = 0;
	
	if (ViewImage == nil) {
		ViewImage = allocimage(display, CanvasSize, RGBA32, 1, DEFAULTCANVASBACKGROUNDCOLOR);
		if (ViewImage == nil)
			sysfatal("allocimage: %r");
		if (nameimage(ViewImage, "pain.view", 1) < 0)
			sysfatal("failed to name view image: %r");
		sr = 1;
	}

	if (ZoomedImage == nil) {
		ZoomedImage = allocimage(display, ZoomedSize, RGBA32, 1, DNofill);
		if (ZoomedImage == nil)
			sysfatal("failed to allocate ZoomedImage: %r");
		sr = 1;
	}

	for (Layer * l = RootLayer; l != nil; l = l->next)
		if (sr || l->changed || DrawAllLayers) {
			draw(ViewImage, ViewImage->r, l->image, nil, l->offset);
			l->changed = 0;
			sr = 1;
		}

	if (Background == nil)
		setbackground(BACKGROUNDCOLOR);

	vr = rectaddpt(ZoomedSize, CanvasAt);
	i = rectclip(&vr,Rect(0, 0, Dx(screen->r), Dy(screen->r)));
	if (i && ((CanvasMoved && Ar(vr) != Ar(ZoomedSize)) || sr)) {
		p = CanvasAt;
		if (p.x < 0) {
			vr.max.x += -p.x;
			p.x = 0;
		}
		if (p.y < 0) {
			vr.max.y += -p.y;
			p.y = 0;
		}
		vr = rectsubpt(vr, p);
		if (resizeimage(ZoomedImage, vr, Zoom, ViewImage, ZP) < 0)
			sysfatal("resizeimage: %r");
	}

	if (CanvasMoved || sr) {
		draw(screen, screen->r, Background, nil, ZP);
		draw(screen, RECTOPPT(ZoomedSize, screentoglobalatcanvaspt), ZoomedImage, nil, ZP);
	}
	shownotification();
	CanvasMoved = 0;
	DrawAllLayers = 0;
}

ulong
msec(void)
{
	return (ulong)(nsec()/1000000);
}

void
setbackground(ulong col)
{	
	if (Background != nil)
		freeimage(Background);

	Background = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, col);
	if (Background == nil)
		sysfatal("setbackground: %r");
}

static void
zoomrect(Rectangle * zs, Point * ca, float z)
{
	int osx, osy;
	Point c;
 
	c = subpt(CanvasAt, MousePosition);
	osx = Dx(ZoomedSize);
	osy = Dy(ZoomedSize);

	zs->min = ZP;
	zs->max = mulptf(CanvasSize.max, z);

	*ca = addpt(
		Pt(Dx(*zs)*c.x/osx, Dy(*zs)*c.y/osy), 
		MousePosition
	);
}

void
applyzoom(void)
{
	Zoom = TargetZoom;
	zoomrect(&ZoomedSize, &CanvasAt, Zoom);
	if (ZoomedImage != nil) {
		freeimage(ZoomedImage);
		ZoomedImage = nil;
	}

 	DrawAllLayers = 1;
	ZoomTaskId = -1;
}

void
zoom(float z)
{
	Rectangle zs;
	Point ca;

	if ((TargetZoom + z) < 0)
		TargetZoom = 1;
	else
		TargetZoom += z;

	if (TargetZoom >= 9)
		TargetZoom = 9;

	zoomrect(&zs, &ca, TargetZoom);

	removetask(ZoomTaskId);
	if ((ZoomTaskId = newtask(msec() + ZOOMAPPLYDELAY, applyzoom)) < 0) {
		werrstr("newtask: no tasks available");
		return;
	}
	ntfprint("zoom set to %f", TargetZoom);
}

void
stroke()
{
	Point t, f;
	Layer * l;
	Rectangle viewAtCanvas, offsetView;

	f = PastMousePosition;
	t = MousePosition;
	l = CurrentLayer;

	viewAtCanvas = rectaddpt(ZoomedSize, CanvasAt);
	offsetView = rectsubpt(viewAtCanvas, mulpt(l->offset, Zoom));
	if (!ptinrect(t, viewAtCanvas) || !ptinrect(t, offsetView))
		return;

	if (!ptinrect(f, viewAtCanvas))
		f = t;

	if (BrushImage == nil)
		setbrushcolor(DBlack);
	
	// Draw on the image layer
	f = screentocanvaspt(subpt(f, viewAtCanvas.min));
	t = screentocanvaspt(subpt(t, viewAtCanvas.min));
	line(l->image, addpt(f, l->offset), addpt(t, l->offset), STROKEEND, STROKEEND, 1, BrushImage, ZP);

	// Draw on the zoomed image
	f = mulptf(f, Zoom);
	t = mulptf(t, Zoom);
	if (ZoomedImage != nil)
		line(ZoomedImage, addpt(f, l->offset), addpt(t, l->offset), STROKEEND, STROKEEND, Zoom, BrushImage, ZP);

	// Draw on the screen
	f = screentoglobalatcanvaspt(f);
	t = screentoglobalatcanvaspt(t);
	line(screen, f, t, STROKEEND, STROKEEND, 1*Zoom, BrushImage, ZP);
	
}

Point
globaltoscreenpt(Point p)
{
	return subpt(p, screen->r.min);
}

Point
globaltoscreenatcanvaspt(Point p)
{
	return addpt(globaltoscreenpt(p), CanvasAt);
}

Point
screentocanvaspt(Point p)
{
	return	(Point){(p.x * (Dx(CanvasSize)) / Dx(ZoomedSize)),
					(p.y * (Dy(CanvasSize)) / Dy(ZoomedSize))};
}

Point
canvastoscreenpt(Point p)
{
	return	(Point){(p.x * (Dx(ZoomedSize)) / Dx(CanvasSize)),
					(p.y * (Dy(ZoomedSize)) / Dy(CanvasSize))};
}

Point
screentoglobalpt(Point p)
{
	return addpt(p, screen->r.min);
}

Point
screentoglobalatcanvaspt(Point p)
{
	return addpt(screentoglobalpt(p), CanvasAt);
}

void
clearlayer(Layer * l)
{
	if (l->image != nil)
		freeimage(l->image);
	if ((l->image = allocimage(display, CanvasSize, RGBA32, 1, 
		l == RootLayer ? DEFAULTLAYERCOLOR : DTransparent)) == nil)
		sysfatal("clearlayer: %r");
	l->changed = 1;
	DrawAllLayers = 1;
	clearview();
}

void
clearview(void)
{
	if (ViewImage == nil)
		return;
	
	nameimage(ViewImage, "", 0);
	freeimage(ViewImage);
	ViewImage = nil;
}

void
setbrushimage(Image * i)
{
	if (BrushImage == nil)
		freeimage(BrushImage);
	BrushImage = i;
}

void
setbrushcolor(ulong c)
{
	Image * i;
	
	i = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, c);
	if (i == nil)
		sysfatal("setbrushcolor: %r");
	setbrushimage(i);
}

void
setcanvassize(Rectangle r)
{
	CanvasSize = r;
	Image * i = nil;

	USED(i);
	for (Layer * l = RootLayer; l != nil; l = l->next) {
		i = allocimage(display, r, RGBA32, 1, DTransparent);
		if (i == nil)
			sysfatal("setcanvassize: %r");
		draw(i, i->r, l->image, nil, ZP);
	}
}

static void
handlekeyboard(int kbdc)
{
	// bindings.c:/^runbindings/
	runbindings(BKeyboard, CurrentKeyboard = kbdc);
}

static void
handlemouse(Mouse mouse)
{
	CurrentMouse = mouse;
	MousePosition = globaltoscreenpt(mouse.xy);
	runbindings(BMouse, mouse.buttons);
	PastMousePosition = MousePosition;
}

void
eresized(int)
{	
	if(getwindow(display, Refnone) < 0)
		sysfatal("getwindow: %r");
	setbackground(BACKGROUNDCOLOR);
	CanvasMoved = 1;
	drawcanvas();
}

void
centercanvas(void)
{
	CanvasAt = addpt(CanvasAt, Pt(Dx(screen->r)/2, Dy(screen->r)/2));
	CanvasAt = subpt(CanvasAt, Pt((Dx(ZoomedSize)/2), (Dy(ZoomedSize)/2)));
}

static void
cleannup(void)
{
	fsclose();
}

void
main(int argc, char * argv[])
{
	ulong d;
	Event e;

	USED(argc);
	USED(argv);

	if (initdraw(nil, nil, "pain") < 0)
		sysfatal("initdraw: %r\n");

 	if (fsinit("pain") < 1)
 		sysfatal("fsinit: %r");

 	if (!atexit(cleannup))
 		sysfatal("atexit: %r");

	if ((BorderImage = allocimage(display, RECTANGLE(1, 1), RGBA32, 1, DBlack)) == nil)
		sysfatal("allocimage: %r");
	
	setcanvassize(RECTANGLE(Dx(screen->r), Dy(screen->r)));
	ZoomedSize = CanvasSize;
	newlayer();
	applyzoom();
	setdefaultbindings();
	centercanvas();

	einit(Emouse | Ekeyboard);
	etimer(0, UPDATEPERIOD);
	drawcanvas();
	for(;RunLoop;) {
		runtasks();
		switch (event(&e))
		{
		case Ekeyboard: 
			handlekeyboard(e.kbdc);
			break;
		case Emouse:
			handlemouse(e.mouse);
			break;
		case 0:
			break;
		}

		d = msec();
		if (d < LastUpdate)
			LastUpdate = 0;

		d -= LastUpdate;
	 	if (d < UPDATEPERIOD)
	 		sleep(1);
		else {
			drawcanvas();
			LastUpdate = msec();
		}
	}

	return;
}