#include "menu.h"
#include "icons.h"
static Animation loadThumb(const char *imgname, Framebuffer fb, int w, int h) {
char *imgpath = findImage(imgname);
if (!imgpath)
{ ERR("loadThumb: image not found: %s", imgname); return NULL; }
if (w < 1) w = 1;
if (h < 1) h = 1;
char *tpath = strprintf("%s/%s", thumbsPath(), filename(imgpath));
if (fileExists(tpath) && filetime(imgpath) < filetime(tpath)) {
Animation thumb = createAnimationEx(tpath, TRUE, FALSE, FALSE);
if (abs(animationGetOrigWidth(thumb) - w) <= 2 || abs(animationGetOrigHeight(thumb) - h) <= 2) {
free(tpath);
free(imgpath);
return thumb;
}
animationDestroy(thumb);
}
INF("loadThumb: create thumbnail: %s -> %s", imgpath, tpath);
Animation full = createAnimationEx(imgpath, TRUE, FALSE, FALSE);
int fw = animationGetOrigWidth(full);
int fh = animationGetOrigHeight(full);
int tw = w, th = h;
if (fw*(double)h > fh*(double)w) th = (int)round(w/(double)fw*fh);
else tw = (int)round(h/(double)fh*fw);
if (tw < 1) tw = 1; else if (tw > w) tw = w;
if (th < 1) th = 1; else if (th > h) th = h;
DBG("[%dx%d] -> [%dx%d]", fw, fh, tw, th);
saveState();
resetState();
target(fb);
background(COLOR_TRANSPARENT);
clear();
noStroke();
rectTextured(full, 0, 0, tw, th);
int iw, ih;
unsigned char *pixels;
Animation thumb = NULL;
if (imageFromViewport(&iw, &ih, &pixels)) {
if (ih > th) ih = th;
if (iw > tw) {
for(int i = 1; i < ih; ++i) memmove(pixels + i*tw*4, pixels + i*iw*4, tw*4);
iw = tw;
}
if (iw != tw || ih != th) {
WRN("loadThumb: thumbnail was truncated: %s, [%dx%d] -> [%dx%d]", imgpath, tw, th, iw, ih);
} else
if (!imageSave(tpath, iw, ih, pixels)) {
WRN("loadThumb: cannot save thumbnail to: %s", tpath);
thumb = createAnimationFromImageEx(iw, ih, pixels, FALSE, FALSE, TRUE, TRUE);
} else {
thumb = createAnimationEx(tpath, TRUE, FALSE, FALSE);
}
free(pixels);
} else {
WRN("loadThumb: cannot capture vieport: %s", imgpath);
unsigned char px[4*4*4] = {};
thumb = createAnimationFromImageEx(4, 4, px, FALSE, FALSE, TRUE, TRUE);
}
restoreState();
free(tpath);
free(imgpath);
return thumb;
}
int menuItemInit(MenuItem *mi, const char *path, const char *imgname, Vec p0, Vec p1, Framebuffer fb, int w, int h) {
memset(mi, 0, sizeof(*mi));
DBG("%s, %s", path ? path : "null", imgname ? imgname : "null");
Vec d = vsub(p1, p0);
if (!(fabs(d.x) > PRECISION) || !(fabs(d.y) > PRECISION)) return 0;
if (path) {
GameInfo *gi = loadGameInfo(path);
if (!gi) return 0;
mi->image = loadThumb(gi->imgname, fb, w, h);
if (!mi->image) { free(gi); return 0; }
int cnt = gi->rows*gi->cols;
mi->path = strprintf("%s", path);
mi->imgname = strprintf("%s", gi->imgname);
mi->percent = (cnt - gi->groups)/(cnt - 1.0);
mi->t = filetime(path);
free(gi);
} else
if (imgname) {
mi->image = loadThumb(imgname, fb, w, h);
if (!mi->image) return 0;
mi->imgname = strprintf("%s", imgname);
} else {
return 0;
}
Vec is = { animationGetOrigWidth(mi->image), animationGetOrigHeight(mi->image) };
if (is.x < 1) is.x = 1;
if (is.y < 1) is.y = 1;
Vec k = vabs(vdivv(is, d));
if (k.x > k.y) {
double l = (1 - k.y/k.x)/2;
mi->p0 = vec(p0.x, lintr(p0.y, p1.y, l));
mi->p1 = vec(p1.x, lintr(p0.y, p1.y, 1-l));
} else {
double l = (1 - k.x/k.y)/2;
mi->p0 = vec(lintr(p0.x, p1.x, l), p0.y);
mi->p1 = vec(lintr(p0.x, p1.x, 1-l), p1.y);
}
return 1;
}
void menuItemFree(MenuItem *mi) {
free(mi->path);
free(mi->imgname);
if (mi->image) animationDestroy(mi->image);
memset(mi, 0, sizeof(*mi));
}
static void splitRect(Vec p0, Vec p1, double percent, Vec *v0, int *v0c, Vec *v1, int *v1c) {
Vec s = vabs(vsub(p1, p0));
if (s.x < s.y) {
splitRect(vtranspose(p0), vtranspose(p1), percent, v0, v0c, v1, v1c);
for(int i = 0; i < *v0c; ++i) v0[i] = vtranspose(v0[i]);
for(int i = 0; i < *v1c; ++i) v1[i] = vtranspose(v1[i]);
return;
}
if (percent > 0.5) {
splitRect(p1, p0, 1 - percent - PRECISION, v1, v1c, v0, v0c);
return;
}
if (!(percent > PRECISION) || !(s.x > PRECISION) || !(s.y > PRECISION))
{ v0[0] = p0; v0[1] = vec(p1.x, p0.y); v0[2] = p1; v0[3] = vec(p0.x, p1.y); *v0c = 4; *v1c = 0; return; }
if (!(percent < 1-PRECISION))
{ v1[0] = p1; v1[1] = vec(p0.x, p1.y); v1[2] = p0; v1[3] = vec(p1.x, p0.y); *v0c = 0; *v1c = 4; return; }
*v0c = *v1c = 0;
v0[(*v0c)++] = p0;
v1[(*v1c)++] = p1;
double af = s.x*s.y;
double ap = af*percent;
double ac = s.y*s.y/2;
if (ap < ac) {
double ky = sqrt(ap/ac);
Vec a = { lintr(p0.x, p1.x, ky*s.y/s.x), p0.y };
Vec b = { p0.x, lintr(p0.y, p1.y, ky) };
v0[(*v0c)++] = a;
v0[(*v0c)++] = b;
v1[(*v1c)++] = vec(p0.x, p1.y);
v1[(*v1c)++] = b;
v1[(*v1c)++] = a;
v1[(*v1c)++] = vec(p1.x, p0.y);
} else {
double kx = (ap-ac)/(af-2*ac)*(s.x-s.y)/s.x;
Vec b = { lintr(p0.x, p1.x, kx), p1.y };
Vec a = { b.x + p1.y - p0.y, p0.y };
v0[(*v0c)++] = a;
v0[(*v0c)++] = b;
v0[(*v0c)++] = vec(p0.x, p1.y);
v1[(*v1c)++] = b;
v1[(*v1c)++] = a;
v1[(*v1c)++] = vec(p1.x, p0.y);
}
}
void menuItemDraw(MenuItem *mi) {
saveState();
Vec d = vsub(mi->p1, mi->p0);
if (mi->path) {
noStroke();
Vec v[2][5] = {};
int vc[2] = {};
splitRect(mi->p0, mi->p1, mi->percent, v[0], &vc[0], v[1], &vc[1]);
noStroke();
for(int i = 0; i < 2; ++i) {
fill(colorByRGBA(1, 1, 1, (2-i)*0.5));
fillTexture(mi->image, mi->p0.x, mi->p0.y, d.x, d.y, FALSE);
moveTo(v[i][0].x, v[i][0].y);
for(int j = 1; j < vc[i]; ++j) lineTo(v[i][j].x, v[i][j].y);
closePath();
}
noFill();
stroke(COLOR_BLUE);
rect(mi->p0.x, mi->p0.y, d.x, d.y);
} else {
stroke(COLOR_BLACK);
fill(COLOR_WHITE);
rectTextured(mi->image, mi->p0.x, mi->p0.y, d.x, d.y);
}
restoreState();
}
static Animation loadIcon(const void *data, size_t size) {
unsigned char *p = NULL; int w = 0, h = 0;
if (!imageLoadFromMemory(data, size, &w, &h, &p))
return createAnimationEmpty();
Animation a = createAnimationFromImageEx(w, h, p, FALSE, FALSE, TRUE, TRUE);
free(p);
return a;
}
void menuInit(Menu *m) {
memset(m, 0, sizeof(*m));
m->contentSize = vone();
#define LOADICON(x) loadIcon(dataIcon ## x, dataIcon ## x ## Size)
m->iconBack = LOADICON(Back);
m->iconCount[0] = LOADICON(Count1);
m->iconCount[1] = LOADICON(Count2);
m->iconCount[2] = LOADICON(Count3);
m->iconCount[3] = LOADICON(Count4);
m->iconTurn[0] = LOADICON(Noturn);
m->iconTurn[1] = LOADICON(Turn);
#undef LOADICON
}
void menuFree(Menu *m) {
if (gameStarted(&m->gm)) gameFree(&m->gm);
for(int i = 0; i < m->itemsCnt; ++i) menuItemFree(&m->items[i]);
free(m->items);
if (m->iconBack) animationDestroy(m->iconBack);
for(int i = 0; i < COUNTOF(m->iconCount); ++i)
if (m->iconCount[i]) animationDestroy(m->iconCount[i]);
for(int i = 0; i < COUNTOF(m->iconTurn); ++i)
if (m->iconTurn[i]) animationDestroy(m->iconTurn[i]);
memset(m, 0, sizeof(*m));
}
void menuUpdateItems(Menu* m) {
// remove old items, but copy images to keep them in memory
Animation animCopy = createAnimationEmpty();
for(int i = 0; i < m->itemsCnt; ++i) {
animationInsert(animCopy, -1, m->items[i].image);
menuItemFree(&m->items[i]);
}
free(m->items);
m->itemsCnt = 0;
Vec border = {1, 1};
Vec spacing = {1, 1};
Vec thumb = {16, 12};
int cols = 4;
int tw = 256;
int th = tw/4*3;
Framebuffer fb = createFramebuffer(tw, tw);
PathList *pls = saveFiles();
PathList *pli = imageFiles();
m->items = alloc((pls->cnt + pli->cnt)*ASIZEOF(*m->items));
int c = 0;
double y = border.y, ly = y;
for(int i = 0; i < pls->cnt; ++i) {
Vec p0 = { border.x + (thumb.x + spacing.x)*c, y };
Vec p1 = vadd(p0, thumb);
if (menuItemInit(&m->items[m->itemsCnt], pls->l[i], NULL, p0, p1, fb, tw, th)) {
if (++c >= cols) { c = 0; y += thumb.y + spacing.y; }
++m->itemsCnt; ly = p1.y;
}
}
if (c) { c = 0; y += thumb.y + spacing.y; }
if (m->itemsCnt) y += spacing.y;
for(int i = 0; i < pli->cnt; ++i) {
Vec p0 = { border.x + (thumb.x + spacing.x)*c, y };
Vec p1 = vadd(p0, thumb);
char *imgname = filebase(pli->l[i]);
if (menuItemInit(&m->items[m->itemsCnt], NULL, imgname, p0, p1, fb, tw, th)) {
if (++c >= cols) { c = 0; y += thumb.y + spacing.y; }
++m->itemsCnt; ly = p1.y;
}
free(imgname);
}
animationDestroy(animCopy);
free(pls);
free(pli);
framebufferDestroy(fb);
m->contentSize = vec( 2*border.x + thumb.x*cols + spacing.x*(cols-1), ly + border.y );
}
int menuSave(Menu *m) {
makeDirectoryRecursive(savesPath());
const char *fn = settingsFile();
FILE *f = fopen(fn, "w");
if (!f) return ERR("menuSave: cannnot open file for write: %s", fn);
fprintf(f, "puzzle1\n[%s]\n%d %d\n", m->gm.path ? m->gm.path : "", m->countMode, m->turnMode);
fflush(f);
if (ferror(f)) ERR("menuSave: cannnot write to file: %s", fn);
fclose(f);
return 1;
}
int menuLoad(Menu *m) {
const char *fn = settingsFile();
FILE *f = fopen(fn, "r");
if (!f) return 0;
char *l = trim(readline(f));
if (strcmp(l, "puzzle1")) {
ERR("menuLoad: wrong file version (expected: puzzle1): %s", l);
free(l); fclose(f); return 0;
}
l = unquote(readline(f), '[', ']');
int cm = -1, tm = -1;
fscanf(f, "%d %d", &cm, &tm);
fclose(f);
if ( cm < 0 || cm > COUNTOF(m->iconCount) || tm < 0 || tm > COUNTOF(m->iconTurn))
{ ERR("menuLoad: cannot parse file: %s", fn); free(l); return 0; }
m->countMode = cm;
m->turnMode = tm;
if (*l) gameLoad(&m->gm, l);
free(l);
return 1;
}
void menuChooseItem(Menu *m, MenuItem *mi) {
if (mi->path) {
gameLoad(&m->gm, mi->path);
return;
}
int cnt = m->countMode == 0 ? 12
: m->countMode == 1 ? 24
: m->countMode == 2 ? 48 : 96;
char *path = NULL;
for(int j = 0; j < 10000; ++j) {
path = strprintf("%s/%s_%04d.puzzle", savesPath(), mi->imgname, j);
if (fileExists(path)) { free(path); continue; }
gameCreate(&m->gm, path, mi->imgname, cnt, 0, m->turnMode, 0);
free(path);
break;
}
}
void menuClick(Menu *m, Vec screenSize, double cornerSize) {
double cs2 = cornerSize*cornerSize;
if (vlen2(vsub(vec(screenSize.x, 0), m->mouseDownPos)) <= cs2)
{ m->countMode = (m->countMode+1) % COUNTOF(m->iconCount); return; }
if (vlen2(vsub(screenSize, m->mouseDownPos)) <= cs2)
{ m->turnMode = (m->turnMode+1) % COUNTOF(m->iconTurn); return; }
Vec p = m->mouseDownPos;
p.y += m->scrollPos;
for(int i = 0; i < m->itemsCnt; ++i) {
MenuItem *mi = &m->items[i];
if ( !(p.x >= mi->p0.x) || !(p.x <= mi->p1.x)
|| !(p.y >= mi->p0.y) || !(p.y <= mi->p1.y) ) continue;
m->choosingItem = mi;
m->choosingItemFrames = 4;
return;
}
}
void menuDraw(Menu *m) {
if (m->choosingItem) {
if (--m->choosingItemFrames > 0) return;
MenuItem *mi = m->choosingItem;
m->choosingItem = NULL;
m->choosingItemFrames = 0;
menuChooseItem(m, mi);
}
int w = windowGetWidth();
int h = windowGetHeight();
double dt = windowGetFrameTime();
double k = w/m->contentSize.x;
Vec screenSize = { w/k, h/k };
double maxPath = m->contentSize.x*0.1;
double scrollMax = m->contentSize.y - screenSize.y;
double scrollBase = m->contentSize.x*dt*2;
double scrollDecc = m->contentSize.x*dt*2;
double scrollOver = m->contentSize.x*0.2;
if (!(scrollMax > 0)) scrollMax = 0;
saveState();
zoom(k);
strokeWidth(1/k);
double cornerSizeX = screenSize.x/8;
double cornerSizeY = screenSize.y/4;
double cornerSize = cornerSizeX < cornerSizeY ? cornerSizeX : cornerSizeY;
Vec mouse = { mouseTransformedX(), mouseTransformedY() };
if (mouseWentDown("left")) {
m->mouseDownPos = mouse;
m->mouseDownTime = 0;
m->mousePath = 0;
m->mouseScroll = m->scrollPos;
if (vlen2(mouse) <= cornerSize*cornerSize || gameWon(&m->gm))
{
if (gameStarted(&m->gm) && !gameWon(&m->gm)) gameSave(&m->gm);
m->closing = 1;
}
}
if (mouseWentUp("left")) m->closing = 0;
if (mouseDown("left")) {
m->mousePath += vlen(vsub(mouse, m->mouse));
m->mouseDownTime += dt;
if (!(m->mousePath < maxPath)) m->closing = 0;
}
if (!gameStarted(&m->gm)) {
if (mouseWentDown("left"))
memset(m->scrollHist, 0, sizeof(m->scrollHist));
if (mouseWentUp("left")) {
if ( m->mousePath < maxPath
&& m->mouseDownTime < 1
&& vlen(vsub(mouse, m->mouseDownPos)) < maxPath )
{
m->scrollVel = 0;
menuClick(m, screenSize, cornerSize);
} else {
double p1 = m->scrollPos, p0 = p1, t = 0;
for(int i = 0; i < MS_CNT; ++i) {
if (m->scrollHist[i][1] > PRECISION) {
p0 = m->scrollHist[i][0];
t += m->scrollHist[i][1];
if (i >= 3 && t > 0.2) break;
}
}
m->scrollVel = t > PRECISION ? (p1 - p0)/t : 0;
}
}
if (mouseDown("left")) {
m->scrollPos = m->mouseScroll + m->mouseDownPos.y - mouse.y;
memmove(m->scrollHist + 1, m->scrollHist, (MS_CNT-1)*sizeof(*m->scrollHist));
m->scrollHist[0][0] = m->scrollPos;
m->scrollHist[0][1] = dt;
} else {
if (m->scrollPos < -scrollOver && m->scrollVel < 0) m->scrollVel = 0;
if (m->scrollPos > scrollMax+scrollOver && m->scrollVel > 0) m->scrollVel = 0;
m->scrollPos += m->scrollVel*dt;
if (m->scrollVel < 0) {
m->scrollVel += scrollDecc;
if (!(m->scrollVel < 0)) m->scrollVel = 0;
} else {
m->scrollVel -= scrollDecc;
if (!(m->scrollVel > 0)) m->scrollVel = 0;
}
if (m->scrollPos < 0) {
m->scrollPos += scrollBase;
if (!(m->scrollPos < 0)) m->scrollPos = 0;
} else
if (m->scrollPos > scrollMax) {
m->scrollPos -= scrollBase;
if (!(m->scrollPos > scrollMax)) m->scrollPos = scrollMax;
}
}
saveState();
translate(0, -m->scrollPos);
for(int i = 0; i < m->itemsCnt; ++i)
menuItemDraw(&m->items[i]);
restoreState();
}
m->mouse = mouse;
if (gameStarted(&m->gm)) {
int wasGroups = m->gm.pz.groups;
int wasWon = gameWon(&m->gm);
saveState();
zoom(1/k);
gameDraw(&m->gm);
restoreState();
if (wasGroups != m->gm.pz.groups && !gameWon(&m->gm))
gameSave(&m->gm);
if (wasWon && gameWon(&m->gm) && fileExists(m->gm.path) && remove(m->gm.path))
ERR("cannot remove save-file for the won game: %s", m->gm.path);
}
saveState();
noStroke();
fill(colorByRGBA(1, 1, 1, 0.25));
rectTextured(m->iconBack, 0, 0, cornerSize, cornerSize);
if (!gameStarted(&m->gm)) {
rectTextured(m->iconCount[m->countMode], screenSize.x-cornerSize, 0, cornerSize, cornerSize);
rectTextured(m->iconTurn[m->turnMode], screenSize.x-cornerSize, screenSize.y-cornerSize, cornerSize, cornerSize);
}
restoreState();
if (m->closing) {
m->closingTransition += dt;
if (m->closingTransition > 1) {
if (gameStarted(&m->gm)) {
gameFree(&m->gm);
m->closing = 0;
m->closingTransition = 0;
menuUpdateItems(m);
} else {
m->closingTransition = 1;
windowStop();
}
}
} else {
m->closingTransition -= dt;
if (!(m->closingTransition > 0)) m->closingTransition = 0;
}
if (m->closingTransition > 0) {
saveState();
noStroke();
fill(gameStarted(&m->gm) ? colorByRGBA(0.05, 0.05, 0.05, 1) : COLOR_BLACK);
Vec c = m->mouseDownPos;
Vec d = vsub(screenSize, c);
double rmax2 = maxd( maxd(vlen2(vec(c.x, d.y)), vlen2(c)),
maxd(vlen2(vec(d.x, c.y)), vlen2(d)) );
double r0 = sqrt(rmax2) * (1 - m->closingTransition);
double r1 = r0 + vlen(screenSize);
arcPath(c.x-r0, c.y-r0, 2*r0, 2*r0, 180, -180);
arcPath(c.x-r1, c.y-r1, 2*r1, 2*r1, -180, 180);
closePath();
restoreState();
}
restoreState();
}