ref: ee5d653d61460a31666dc0bf8c9b011832a0e431
dir: /main.c/
#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;
}