rockbox/apps/plugins/puzzles/rockbox.c
Franklin Wei d9a55ac816 puzzles: fix blitting when zoomed
Both blitter_save() and blitter_load() functioned incorrectly when
zoomed in -- blitter_save() would copy from the wrong location, and
blitter_load() would ignore the y-coordinate of the destination.

Change-Id: I7c85debf5953575f72c4a81e3dbcf514202c3aed
2017-11-04 12:09:16 -04:00

3026 lines
78 KiB
C

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2017 Franklin Wei
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
***************************************************************************/
/* rockbox frontend for puzzles */
/* This file contains the majority of the rockbox-specific code for
* the sgt-puzzles port. It implements a set of functions for the
* backend to call to actually run the games, as well as rockbox UI
* code (menus, input, etc). For a good overview of the rest of the
* puzzles code, see:
*
* <https://www.chiark.greenend.org.uk/~sgtatham/puzzles/devel/>.
*/
#include "plugin.h"
#include "help.h"
#include "lz4tiny.h"
#include "src/puzzles.h"
#include "lib/keymaps.h"
#include "lib/playback_control.h"
#include "lib/simple_viewer.h"
#include "lib/xlcd.h"
#include "fixedpoint.h"
/* how many ticks between timer callbacks */
#define TIMER_INTERVAL (HZ / 50)
/* no c200v2 */
#if PLUGIN_BUFFER_SIZE > 0x14000
#define DEBUG_MENU
#define FONT_CACHING
#endif
/* background color (mimicking from the JS frontend) */
#define BG_R .9f /* very light gray */
#define BG_G .9f
#define BG_B .9f
#define BG_COLOR LCD_RGBPACK((int)(255*BG_R), (int)(255*BG_G), (int)(255*BG_B))
#define ERROR_COLOR LCD_RGBPACK(255, 0, 0)
#define MAX_FONTS (MAXUSERFONTS - 2)
#define FONT_TABLE PLUGIN_GAMES_DATA_DIR "/.sgt-puzzles.fnttab"
/* font bundle size range */
#define BUNDLE_MIN 7
#define BUNDLE_MAX 36
#define BUNDLE_COUNT (BUNDLE_MAX - BUNDLE_MIN + 1)
/* max length of C_STRING config vals */
#define MAX_STRLEN 128
/* try to increment a numeric config value up to this much */
#define CHOOSER_MAX_INCR 2
/* max font table line */
#define MAX_LINE 128
/* Sorry. */
#define MURICA
#ifdef MURICA
#define midend_serialize midend_serialise
#define midend_deserialize midend_deserialise
#define frontend_default_color frontend_default_colour
#define midend_colors midend_colours
#endif
/* zoom stuff */
#define ZOOM_FACTOR 3
#define PAN_X (MIN(LCD_HEIGHT, LCD_WIDTH) / 4)
#define PAN_Y (MIN(LCD_HEIGHT, LCD_WIDTH) / 4)
/* utility macros */
#undef ABS
#define ABS(a) ((a)<0?-(a):(a))
#define SWAP(a, b, t) do { t = a; a = b; b = t; } while(0);
#define fp_fpart(f, bits) ((f) & ((1 << (bits)) - 1))
#define fp_rfpart(f, bits) ((1 << (bits)) - fp_fpart(f, bits))
#define FRACBITS 16
static void fix_size(void);
static int pause_menu(void);
static inline void plot(fb_data *fb, int w, int h,
unsigned x, unsigned y, unsigned long a,
unsigned long r1, unsigned long g1, unsigned long b1,
unsigned cl, unsigned cr, unsigned cu, unsigned cd);
static midend *me = NULL;
static unsigned *colors = NULL;
static int ncolors = 0;
static long last_keystate = 0;
#if defined(FOR_REAL) && defined(DEBUG_MENU)
/* the "debug menu" is hidden by default in order to avoid the
* naturally ensuing complaints from users */
static bool debug_mode = false;
static int help_times = 0;
#endif
/* clipping stuff */
static struct viewport clip_rect;
static bool clipped = false, zoom_enabled = false, view_mode = true;
extern bool audiobuf_available; /* defined in rbmalloc.c */
static fb_data *zoom_fb; /* dynamically allocated */
static int zoom_w, zoom_h, zoom_clipu, zoom_clipd, zoom_clipl, zoom_clipr;
static int cur_font = FONT_UI;
static bool need_draw_update = false;
static int ud_l = 0, ud_u = 0, ud_r = LCD_WIDTH, ud_d = LCD_HEIGHT;
static char *titlebar = NULL;
static bool want_redraw = true, accept_input = true;
/* last timer call */
static long last_tstamp;
static volatile bool timer_on = false;
static bool load_success;
/* debug settings */
/* did I mention there's a secret debug menu? */
static struct settings_t {
int slowmo_factor;
bool timerflash, clipoff, shortcuts, no_aa, polyanim;
} settings;
/* re-implementations of many rockbox primitives, adapted to draw into
* a custom framebuffer. */
static void zoom_drawpixel(int x, int y)
{
if(y < zoom_clipu || y >= zoom_clipd)
return;
if(x < zoom_clipl || x >= zoom_clipr)
return;
#if LCD_DEPTH == 24
/* I hate these */
unsigned int pix = rb->lcd_get_foreground();
zoom_fb[y * zoom_w + x].b = RGB_UNPACK_BLUE(pix);
zoom_fb[y * zoom_w + x].g = RGB_UNPACK_GREEN(pix);
zoom_fb[y * zoom_w + x].r = RGB_UNPACK_RED(pix);
#else
zoom_fb[y * zoom_w + x] = rb->lcd_get_foreground();
#endif
}
static void zoom_hline(int l, int r, int y)
{
if(y < zoom_clipu || y >= zoom_clipd)
return;
if(l > r)
{
int t = l;
l = r;
r = t;
}
if(l < zoom_clipl)
l = zoom_clipl;
if(r >= zoom_clipr)
r = zoom_clipr;
#if LCD_DEPTH == 24
fb_data pixel = { RGB_UNPACK_BLUE(rb->lcd_get_foreground()),
RGB_UNPACK_GREEN(rb->lcd_get_foreground()),
RGB_UNPACK_RED(rb->lcd_get_foreground()) };
#else
fb_data pixel = rb->lcd_get_foreground();
#endif
fb_data *ptr = zoom_fb + y * zoom_w + l;
for(int i = 0; i < r - l; ++i)
*ptr++ = pixel;
}
static void zoom_fillcircle(int cx, int cy, int radius)
{
/* copied straight from xlcd_draw.c */
int d = 3 - (radius * 2);
int x = 0;
int y = radius;
while(x <= y)
{
zoom_hline(cx - x, cx + x, cy + y);
zoom_hline(cx - x, cx + x, cy - y);
zoom_hline(cx - y, cx + y, cy + x);
zoom_hline(cx - y, cx + y, cy - x);
if(d < 0)
{
d += (x * 4) + 6;
}
else
{
d += ((x - y) * 4) + 10;
--y;
}
++x;
}
}
static void zoom_drawcircle(int cx, int cy, int radius)
{
int d = 3 - (radius * 2);
int x = 0;
int y = radius;
while(x <= y)
{
zoom_drawpixel(cx + x, cy + y);
zoom_drawpixel(cx - x, cy + y);
zoom_drawpixel(cx + x, cy - y);
zoom_drawpixel(cx - x, cy - y);
zoom_drawpixel(cx + y, cy + x);
zoom_drawpixel(cx - y, cy + x);
zoom_drawpixel(cx + y, cy - x);
zoom_drawpixel(cx - y, cy - x);
if(d < 0)
{
d += (x * 4) + 6;
}
else
{
d += ((x - y) * 4) + 10;
--y;
}
++x;
}
}
/* This format is pretty crazy: each byte holds the states of 8 pixels
* in a column, with bit 0 being the topmost pixel. Who needs cache
* efficiency? */
static void zoom_mono_bitmap(const unsigned char *bits, int x, int y, int w, int h)
{
unsigned int pix = rb->lcd_get_foreground();
for(int i = 0; i < h / 8 + 1; ++i)
{
for(int j = 0; j < w; ++j)
{
unsigned char column = bits[i * w + j];
for(int dy = 0; dy < 8; ++dy)
{
if(column & 1)
{
#if LCD_DEPTH == 24
zoom_fb[(y + i * 8 + dy) * zoom_w + x + j].b = RGB_UNPACK_BLUE(pix);
zoom_fb[(y + i * 8 + dy) * zoom_w + x + j].g = RGB_UNPACK_GREEN(pix);
zoom_fb[(y + i * 8 + dy) * zoom_w + x + j].r = RGB_UNPACK_RED(pix);
#else
zoom_fb[(y + i * 8 + dy) * zoom_w + x + j] = pix;
#endif
}
column >>= 1;
}
}
}
}
/* Rockbox's alpha format is actually pretty sane: each byte holds
* alpha values for two horizontally adjacent pixels. Low half is
* leftmost pixel. See lcd-16bit-common.c for more info. */
static void zoom_alpha_bitmap(const unsigned char *bits, int x, int y, int w, int h)
{
const unsigned char *ptr = bits;
unsigned char buf;
int n_read = 0; /* how many 4-bit nibbles we've read (read new when even) */
unsigned int pix = rb->lcd_get_foreground();
unsigned int r = RGB_UNPACK_RED(pix), g = RGB_UNPACK_GREEN(pix), b = RGB_UNPACK_BLUE(pix);
for(int i = 0; i < h; ++i)
{
for(int j = 0; j < w; ++j)
{
if(n_read % 2 == 0)
{
buf = *ptr++;
}
int pix_alpha = (n_read++ % 2) ? buf >> 4 : buf & 0xF; /* in reverse order */
pix_alpha = 15 - pix_alpha; /* correct order now, still 0-F */
int plot_alpha = (pix_alpha << 4) | pix_alpha; /* so 0xF -> 0xFF, 0x1 -> 0x11, etc. */
plot(zoom_fb, zoom_w, zoom_h, x + j, y + i, plot_alpha, r, g, b,
0, zoom_w, 0, zoom_h);
}
}
}
/* font management */
static struct bundled_font {
int status; /* -3 = never tried loading, or unloaded, -2 = failed to load, >= -1: loaded successfully */
int last_use;
} *loaded_fonts = NULL; /* monospace are first, then proportional */
static int n_fonts, access_counter = -1;
/* called on exit and before entering help viewer (workaround for a
possible bug in simple_viewer) */
static void unload_fonts(void)
{
for(int i = 0; i < 2 * BUNDLE_COUNT; ++i)
if(loaded_fonts[i].status > 0) /* don't unload FONT_UI */
{
rb->font_unload(loaded_fonts[i].status);
loaded_fonts[i].status = -3;
}
access_counter = -1;
cur_font = FONT_UI;
rb->lcd_setfont(cur_font);
}
static void init_fonttab(void)
{
loaded_fonts = smalloc(2 * BUNDLE_COUNT * sizeof(struct bundled_font));
for(int i = 0; i < 2 * BUNDLE_COUNT; ++i)
loaded_fonts[i].status = -3;
access_counter = 0;
n_fonts = 0;
}
static void font_path(char *buf, int type, int size)
{
if(size < 10) /* Deja Vu only goes down to 10px, below that it's a giant blob */
{
if(size < 7)
size = 7; /* we're not going to force anyone to read 05-Tiny :P */
/* we also don't care about monospace/proportional at this point */
switch(size)
{
case 7:
rb->snprintf(buf, MAX_PATH, FONT_DIR "/07-Fixed.fnt");
break;
case 8:
rb->snprintf(buf, MAX_PATH, FONT_DIR "/08-Rockfont.fnt");
break;
case 9:
rb->snprintf(buf, MAX_PATH, FONT_DIR "/09-Fixed.fnt");
break;
default:
assert(false);
}
}
else
rb->snprintf(buf, MAX_PATH, FONT_DIR "/%02d-%s.fnt", size, type == FONT_FIXED ? "DejaVuSansMono" : "DejaVuSans");
}
static void rb_setfont(int type, int size)
{
/* out of range (besides, no puzzle should ever need this large
of a font, anyways) */
if(size > BUNDLE_MAX)
size = BUNDLE_MAX;
if(size < 10)
{
if(size < 7) /* no teeny-tiny fonts */
size = 7;
/* assume monospace for these */
type = FONT_FIXED;
}
int font_idx = (type == FONT_FIXED ? 0 : BUNDLE_COUNT) + size - BUNDLE_MIN;
switch(loaded_fonts[font_idx].status)
{
case -3:
{
/* never loaded */
char buf[MAX_PATH];
font_path(buf, type, size);
if(n_fonts >= MAX_FONTS) /* safety margin, FIXME */
{
/* unload an old font */
int oldest_use = -1, oldest_idx = -1;
for(int i = 0; i < 2 * BUNDLE_COUNT; ++i)
{
if((loaded_fonts[i].status >= 0 && loaded_fonts[i].last_use < oldest_use) || oldest_use < 0)
{
oldest_use = loaded_fonts[i].last_use;
oldest_idx = i;
}
}
assert(oldest_idx >= 0);
rb->font_unload(loaded_fonts[oldest_idx].status);
loaded_fonts[oldest_idx].status = -3;
n_fonts--;
}
loaded_fonts[font_idx].status = rb->font_load(buf);
if(loaded_fonts[font_idx].status < 0)
goto fallback;
loaded_fonts[font_idx].last_use = access_counter++;
n_fonts++;
cur_font = loaded_fonts[font_idx].status;
rb->lcd_setfont(cur_font);
break;
}
case -2:
case -1:
goto fallback;
default:
loaded_fonts[font_idx].last_use = access_counter++;
cur_font = loaded_fonts[font_idx].status;
rb->lcd_setfont(cur_font);
break;
}
return;
fallback:
cur_font = type == FONT_FIXED ? FONT_SYSFIXED : FONT_UI;
rb->lcd_setfont(cur_font);
return;
}
/*** Drawing API ***/
static void offset_coords(int *x, int *y)
{
if(clipped)
{
*x -= clip_rect.x;
*y -= clip_rect.y;
}
}
static void rb_color(int n)
{
if(n < 0)
{
fatal("bad color %d", n);
return;
}
rb->lcd_set_foreground(colors[n]);
}
/* clipping is implemented through viewports and offsetting
* coordinates */
static void rb_clip(void *handle, int x, int y, int w, int h)
{
if(!zoom_enabled)
{
if(!settings.clipoff)
{
LOGF("rb_clip(%d %d %d %d)", x, y, w, h);
clip_rect.x = MAX(0, x);
clip_rect.y = MAX(0, y);
clip_rect.width = MIN(LCD_WIDTH, w);
clip_rect.height = MIN(LCD_HEIGHT, h);
clip_rect.font = FONT_UI;
clip_rect.drawmode = DRMODE_SOLID;
#if LCD_DEPTH > 1
clip_rect.fg_pattern = LCD_DEFAULT_FG;
clip_rect.bg_pattern = LCD_DEFAULT_BG;
#endif
rb->lcd_set_viewport(&clip_rect);
clipped = true;
}
}
else
{
zoom_clipu = y;
zoom_clipd = y + h;
zoom_clipl = x;
zoom_clipr = x + w;
}
}
static void rb_unclip(void *handle)
{
if(!zoom_enabled)
{
LOGF("rb_unclip");
rb->lcd_set_viewport(NULL);
clipped = false;
}
else
{
zoom_clipu = 0;
zoom_clipd = zoom_h;
zoom_clipl = 0;
zoom_clipr = zoom_w;
}
}
static void rb_draw_text(void *handle, int x, int y, int fonttype,
int fontsize, int align, int color, const char *text)
{
(void) fontsize;
if(!zoom_enabled)
{
LOGF("rb_draw_text(%d %d %s)", x, y, text);
offset_coords(&x, &y);
rb_setfont(fonttype, fontsize);
int w, h;
rb->lcd_getstringsize(text, &w, &h);
if(align & ALIGN_VNORMAL)
y -= h;
else if(align & ALIGN_VCENTRE)
y -= h / 2;
if(align & ALIGN_HCENTRE)
x -= w / 2;
else if(align & ALIGN_HRIGHT)
x -= w;
rb_color(color);
rb->lcd_set_drawmode(DRMODE_FG);
rb->lcd_putsxy(x, y, text);
rb->lcd_set_drawmode(DRMODE_SOLID);
}
else
{
rb_color(color);
rb_setfont(fonttype, fontsize); /* size will be clamped if too large */
int w, h;
rb->lcd_getstringsize(text, &w, &h);
if(align & ALIGN_VNORMAL)
y -= h;
else if(align & ALIGN_VCENTRE)
y -= h / 2;
if(align & ALIGN_HCENTRE)
x -= w / 2;
else if(align & ALIGN_HRIGHT)
x -= w;
/* we need to access the font bitmap directly */
struct font *pf = rb->font_get(cur_font);
while(*text)
{
/* still only reads 1 byte */
unsigned short c = *text++;
const unsigned char *bits = rb->font_get_bits(pf, c);
int width = rb->font_get_width(pf, c);
/* straight from lcd-bitmap-common.c */
#if defined(HAVE_LCD_COLOR)
if (pf->depth)
{
/* lcd_alpha_bitmap_part isn't exported directly. However,
* we can create a null bitmap struct with only an alpha
* channel to make lcd_bmp_part call it for us. */
zoom_alpha_bitmap(bits, x, y, width, pf->height);
}
else
#endif
zoom_mono_bitmap(bits, x, y, width, pf->height);
x += width;
}
}
}
static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color)
{
if(!zoom_enabled)
{
LOGF("rb_draw_rect(%d, %d, %d, %d, %d)", x, y, w, h, color);
rb_color(color);
offset_coords(&x, &y);
rb->lcd_fillrect(x, y, w, h);
}
else
{
/* TODO: clipping */
rb_color(color);
for(int i = y; i < y + h; ++i)
{
zoom_hline(x, x + w, i);
}
}
}
/* a goes from 0-255, with a = 255 being fully opaque and a = 0 transparent */
static inline void plot(fb_data *fb, int w, int h,
unsigned x, unsigned y, unsigned long a,
unsigned long r1, unsigned long g1, unsigned long b1,
unsigned cl, unsigned cr, unsigned cu, unsigned cd)
{
/* This is really quite possibly the least efficient way of doing
this. A better way would be in draw_antialiased_line(), but the
problem with that is that the algorithms I investigated at
least were incorrect at least part of the time and didn't make
drawing much faster overall. */
if(!(cl <= x && x < cr && cu <= y && y < cd))
return;
fb_data *ptr = fb + y * w + x;
fb_data orig = *ptr;
unsigned long r2, g2, b2;
#if LCD_DEPTH != 24
r2 = RGB_UNPACK_RED(orig);
g2 = RGB_UNPACK_GREEN(orig);
b2 = RGB_UNPACK_BLUE(orig);
#else
r2 = orig.r;
g2 = orig.g;
b2 = orig.b;
#endif
unsigned long r, g, b;
r = ((r1 * a) + (r2 * (256 - a))) >> 8;
g = ((g1 * a) + (g2 * (256 - a))) >> 8;
b = ((b1 * a) + (b2 * (256 - a))) >> 8;
#if LCD_DEPTH != 24
*ptr = LCD_RGBPACK(r, g, b);
#else
*ptr = (fb_data) {b, g, r};
#endif
}
/* speed benchmark: 34392 lines/sec vs 112687 non-antialiased
* lines/sec at full optimization on ipod6g */
/* expects UN-OFFSET coordinates, directly access framebuffer */
static void draw_antialiased_line(fb_data *fb, int w, int h, int x0, int y0, int x1, int y1)
{
/* fixed-point Wu's algorithm, modified for integer-only endpoints */
/* passed to plot() to avoid re-calculation */
unsigned short l = 0, r = w, u = 0, d = h;
if(!zoom_enabled)
{
if(clipped)
{
l = clip_rect.x;
r = clip_rect.x + clip_rect.width;
u = clip_rect.y;
d = clip_rect.y + clip_rect.height;
}
}
else
{
l = zoom_clipl;
r = zoom_clipr;
u = zoom_clipu;
d = zoom_clipd;
}
bool steep = ABS(y1 - y0) > ABS(x1 - x0);
int tmp;
if(steep)
{
SWAP(x0, y0, tmp);
SWAP(x1, y1, tmp);
}
if(x0 > x1)
{
SWAP(x0, x1, tmp);
SWAP(y0, y1, tmp);
}
int dx, dy;
dx = x1 - x0;
dy = y1 - y0;
if(!(dx << FRACBITS))
return; /* bail out */
long gradient = fp_div(dy << FRACBITS, dx << FRACBITS, FRACBITS);
long intery = (y0 << FRACBITS);
unsigned color = rb->lcd_get_foreground();
unsigned long r1, g1, b1;
r1 = RGB_UNPACK_RED(color);
g1 = RGB_UNPACK_GREEN(color);
b1 = RGB_UNPACK_BLUE(color);
/* main loop */
if(steep)
{
for(int x = x0; x <= x1; ++x, intery += gradient)
{
unsigned y = intery >> FRACBITS;
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
plot(fb, w, h, y, x, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
plot(fb, w, h, y + 1, x, alpha, r1, g1, b1, l, r, u, d);
}
}
else
{
for(int x = x0; x <= x1; ++x, intery += gradient)
{
unsigned y = intery >> FRACBITS;
unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8);
plot(fb, w, h, x, y, (1 << 8) - alpha, r1, g1, b1, l, r, u, d);
plot(fb, w, h, x, y + 1, alpha, r1, g1, b1, l, r, u, d);
}
}
}
static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2,
int color)
{
if(!zoom_enabled)
{
LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color);
rb_color(color);
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb->lcd_drawline(x1, y1, x2, y2);
}
else
draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2);
}
else
{
/* draw_antialiased_line uses rb->lcd_get_foreground() to get
* the color */
rb_color(color);
draw_antialiased_line(zoom_fb, zoom_w, zoom_h, x1, y1, x2, y2);
}
}
#if 0
/*
* draw filled polygon
* originally by Sebastian Leonhardt (ulmutul)
* 'count' : number of coordinate pairs
* 'pxy': array of coordinates. pxy[0]=x0,pxy[1]=y0,...
* note: provide space for one extra coordinate, because the starting point
* will automatically be inserted as end point.
*/
/*
* helper function:
* find points of intersection between polygon and scanline
*/
#define MAX_INTERSECTION 32
static void fill_poly_line(int scanline, int count, int *pxy)
{
int i;
int j;
int num_of_intersects;
int direct, old_direct;
//intersections of every line with scanline (y-coord)
int intersection[MAX_INTERSECTION];
/* add starting point as ending point */
pxy[count*2] = pxy[0];
pxy[count*2+1] = pxy[1];
old_direct=0;
num_of_intersects=0;
for (i=0; i<count*2; i+=2) {
int x1=pxy[i];
int y1=pxy[i+1];
int x2=pxy[i+2];
int y2=pxy[i+3];
// skip if line is outside of scanline
if (y1 < y2) {
if (scanline < y1 || scanline > y2)
continue;
}
else {
if (scanline < y2 || scanline > y1)
continue;
}
// calculate x-coord of intersection
if (y1==y2) {
direct=0;
}
else {
direct = y1>y2 ? 1 : -1;
// omit double intersections, if both lines lead in the same direction
intersection[num_of_intersects] =
x1+((scanline-y1)*(x2-x1))/(y2-y1);
if ( (direct!=old_direct)
|| (intersection[num_of_intersects] != intersection[num_of_intersects-1])
)
++num_of_intersects;
}
old_direct = direct;
}
// sort points of intersection
for (i=0; i<num_of_intersects-1; ++i) {
for (j=i+1; j<num_of_intersects; ++j) {
if (intersection[j]<intersection[i]) {
int temp=intersection[i];
intersection[i]=intersection[j];
intersection[j]=temp;
}
}
}
// draw
for (i=0; i<num_of_intersects; i+=2) {
rb->lcd_hline(intersection[i], intersection[i+1], scanline);
}
}
/* two extra elements at end of pxy needed */
static void v_fillarea(int count, int *pxy)
{
int i;
int y1, y2;
// find min and max y coords
y1=y2=pxy[1];
for (i=3; i<count*2; i+=2) {
if (pxy[i] < y1) y1 = pxy[i];
else if (pxy[i] > y2) y2 = pxy[i];
}
for (i=y1; i<=y2; ++i) {
fill_poly_line(i, count, pxy);
}
}
#endif
/* I'm a horrible person: this was copy-pasta'd straight from
* xlcd_draw.c */
/* sort the given coordinates by increasing x value */
static void sort_points_by_increasing_x(int* x1, int* y1,
int* x2, int* y2,
int* x3, int* y3)
{
int x, y;
if (*x1 > *x3)
{
if (*x2 < *x3) /* x2 < x3 < x1 */
{
x = *x1; *x1 = *x2; *x2 = *x3; *x3 = x;
y = *y1; *y1 = *y2; *y2 = *y3; *y3 = y;
}
else if (*x2 > *x1) /* x3 < x1 < x2 */
{
x = *x1; *x1 = *x3; *x3 = *x2; *x2 = x;
y = *y1; *y1 = *y3; *y3 = *y2; *y2 = y;
}
else /* x3 <= x2 <= x1 */
{
x = *x1; *x1 = *x3; *x3 = x;
y = *y1; *y1 = *y3; *y3 = y;
}
}
else
{
if (*x2 < *x1) /* x2 < x1 <= x3 */
{
x = *x1; *x1 = *x2; *x2 = x;
y = *y1; *y1 = *y2; *y2 = y;
}
else if (*x2 > *x3) /* x1 <= x3 < x2 */
{
x = *x2; *x2 = *x3; *x3 = x;
y = *y2; *y2 = *y3; *y3 = y;
}
/* else already sorted */
}
}
#define sort_points_by_increasing_y(x1, y1, x2, y2, x3, y3) \
sort_points_by_increasing_x(y1, x1, y2, x2, y3, x3)
/* draw a filled triangle, using horizontal lines for speed */
static void zoom_filltriangle(int x1, int y1,
int x2, int y2,
int x3, int y3)
{
long fp_x1, fp_x2, fp_dx1, fp_dx2;
int y;
sort_points_by_increasing_y(&x1, &y1, &x2, &y2, &x3, &y3);
if (y1 < y3) /* draw */
{
fp_dx1 = ((x3 - x1) << 16) / (y3 - y1);
fp_x1 = (x1 << 16) + (1<<15) + (fp_dx1 >> 1);
if (y1 < y2) /* first part */
{
fp_dx2 = ((x2 - x1) << 16) / (y2 - y1);
fp_x2 = (x1 << 16) + (1<<15) + (fp_dx2 >> 1);
for (y = y1; y < y2; y++)
{
zoom_hline(fp_x1 >> 16, fp_x2 >> 16, y);
fp_x1 += fp_dx1;
fp_x2 += fp_dx2;
}
}
if (y2 < y3) /* second part */
{
fp_dx2 = ((x3 - x2) << 16) / (y3 - y2);
fp_x2 = (x2 << 16) + (1<<15) + (fp_dx2 >> 1);
for (y = y2; y < y3; y++)
{
zoom_hline(fp_x1 >> 16, fp_x2 >> 16, y);
fp_x1 += fp_dx1;
fp_x2 += fp_dx2;
}
}
}
}
/* Should probably refactor this */
static void rb_draw_poly(void *handle, int *coords, int npoints,
int fillcolor, int outlinecolor)
{
if(!zoom_enabled)
{
LOGF("rb_draw_poly");
if(fillcolor >= 0)
{
rb_color(fillcolor);
#if 1
/* serious hack: draw a bunch of triangles between adjacent points */
/* this generally works, even with some concave polygons */
for(int i = 2; i < npoints; ++i)
{
int x1, y1, x2, y2, x3, y3;
x1 = coords[0];
y1 = coords[1];
x2 = coords[(i - 1) * 2];
y2 = coords[(i - 1) * 2 + 1];
x3 = coords[i * 2];
y3 = coords[i * 2 + 1];
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
offset_coords(&x3, &y3);
xlcd_filltriangle(x1, y1,
x2, y2,
x3, y3);
#ifdef DEBUG_MENU
if(settings.polyanim)
{
rb->lcd_update();
rb->sleep(HZ/4);
}
#endif
#if 0
/* debug code */
rb->lcd_set_foreground(LCD_RGBPACK(255,0,0));
rb->lcd_drawpixel(x1, y1);
rb->lcd_drawpixel(x2, y2);
rb->lcd_drawpixel(x3, y3);
rb->lcd_update();
rb->sleep(HZ);
rb_color(fillcolor);
rb->lcd_drawpixel(x1, y1);
rb->lcd_drawpixel(x2, y2);
rb->lcd_drawpixel(x3, y3);
rb->lcd_update();
#endif
}
#else
int *pxy = smalloc(sizeof(int) * 2 * npoints + 2);
/* copy points, offsetted */
for(int i = 0; i < npoints; ++i)
{
pxy[2 * i + 0] = coords[2 * i + 0];
pxy[2 * i + 1] = coords[2 * i + 1];
offset_coords(&pxy[2*i+0], &pxy[2*i+1]);
}
v_fillarea(npoints, pxy);
sfree(pxy);
#endif
}
/* draw outlines last so they're not covered by the fill */
assert(outlinecolor >= 0);
rb_color(outlinecolor);
for(int i = 1; i < npoints; ++i)
{
int x1, y1, x2, y2;
x1 = coords[2 * (i - 1)];
y1 = coords[2 * (i - 1) + 1];
x2 = coords[2 * i];
y2 = coords[2 * i + 1];
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb->lcd_drawline(x1, y1,
x2, y2);
}
else
draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2);
#ifdef DEBUG_MENU
if(settings.polyanim)
{
rb->lcd_update();
rb->sleep(HZ/4);
}
#endif
}
int x1, y1, x2, y2;
x1 = coords[0];
y1 = coords[1];
x2 = coords[2 * (npoints - 1)];
y2 = coords[2 * (npoints - 1) + 1];
if(settings.no_aa)
{
offset_coords(&x1, &y1);
offset_coords(&x2, &y2);
rb->lcd_drawline(x1, y1,
x2, y2);
}
else
draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2);
}
else
{
LOGF("rb_draw_poly");
if(fillcolor >= 0)
{
rb_color(fillcolor);
/* serious hack: draw a bunch of triangles between adjacent points */
/* this generally works, even with some concave polygons */
for(int i = 2; i < npoints; ++i)
{
int x1, y1, x2, y2, x3, y3;
x1 = coords[0];
y1 = coords[1];
x2 = coords[(i - 1) * 2];
y2 = coords[(i - 1) * 2 + 1];
x3 = coords[i * 2];
y3 = coords[i * 2 + 1];
zoom_filltriangle(x1, y1,
x2, y2,
x3, y3);
}
}
/* draw outlines last so they're not covered by the fill */
assert(outlinecolor >= 0);
rb_color(outlinecolor);
for(int i = 1; i < npoints; ++i)
{
int x1, y1, x2, y2;
x1 = coords[2 * (i - 1)];
y1 = coords[2 * (i - 1) + 1];
x2 = coords[2 * i];
y2 = coords[2 * i + 1];
draw_antialiased_line(zoom_fb, zoom_w, zoom_h, x1, y1, x2, y2);
}
int x1, y1, x2, y2;
x1 = coords[0];
y1 = coords[1];
x2 = coords[2 * (npoints - 1)];
y2 = coords[2 * (npoints - 1) + 1];
draw_antialiased_line(zoom_fb, zoom_w, zoom_h, x1, y1, x2, y2);
}
}
static void rb_draw_circle(void *handle, int cx, int cy, int radius,
int fillcolor, int outlinecolor)
{
if(!zoom_enabled)
{
LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius);
offset_coords(&cx, &cy);
if(fillcolor >= 0)
{
rb_color(fillcolor);
xlcd_fillcircle(cx, cy, radius - 1);
}
assert(outlinecolor >= 0);
rb_color(outlinecolor);
xlcd_drawcircle(cx, cy, radius - 1);
}
else
{
if(fillcolor >= 0)
{
rb_color(fillcolor);
zoom_fillcircle(cx, cy, radius - 1);
}
assert(outlinecolor >= 0);
rb_color(outlinecolor);
zoom_drawcircle(cx, cy, radius - 1);
}
}
struct blitter {
bool have_data;
int x, y;
struct bitmap bmp;
};
/* originally from emcc.c */
static void trim_rect(int *x, int *y, int *w, int *h)
{
int x0, x1, y0, y1;
/*
* Reduce the size of the copied rectangle to stop it going
* outside the bounds of the canvas.
*/
/* Transform from x,y,w,h form into coordinates of all edges */
x0 = *x;
y0 = *y;
x1 = *x + *w;
y1 = *y + *h;
int screenw = zoom_enabled ? zoom_w : LCD_WIDTH;
int screenh = zoom_enabled ? zoom_h : LCD_HEIGHT;
/* Clip each coordinate at both extremes of the canvas */
x0 = (x0 < 0 ? 0 : x0 > screenw - 1 ? screenw - 1: x0);
x1 = (x1 < 0 ? 0 : x1 > screenw - 1 ? screenw - 1: x1);
y0 = (y0 < 0 ? 0 : y0 > screenh - 1 ? screenh - 1: y0);
y1 = (y1 < 0 ? 0 : y1 > screenh - 1 ? screenh - 1: y1);
/* Transform back into x,y,w,h to return */
*x = x0;
*y = y0;
*w = x1 - x0;
*h = y1 - y0;
}
static blitter *rb_blitter_new(void *handle, int w, int h)
{
LOGF("rb_blitter_new");
blitter *b = snew(blitter);
b->bmp.width = w;
b->bmp.height = h;
b->bmp.data = smalloc(w * h * sizeof(fb_data));
b->have_data = false;
return b;
}
static void rb_blitter_free(void *handle, blitter *bl)
{
LOGF("rb_blitter_free");
sfree(bl->bmp.data);
sfree(bl);
return;
}
/* copy a section of the framebuffer */
static void rb_blitter_save(void *handle, blitter *bl, int x, int y)
{
/* no viewport offset */
#if defined(LCD_STRIDEFORMAT) && (LCD_STRIDEFORMAT == VERTICAL_STRIDE)
#error no vertical stride
#else
if(bl->bmp.data)
{
int w = bl->bmp.width, h = bl->bmp.height;
int screen_w = zoom_enabled ? zoom_w : LCD_WIDTH;
trim_rect(&x, &y, &w, &h);
fb_data *fb = zoom_enabled ? zoom_fb : rb->lcd_framebuffer;
LOGF("rb_blitter_save(%d, %d, %d, %d)", x, y, w, h);
for(int i = 0; i < h; ++i)
{
/* copy line-by-line */
rb->memcpy(bl->bmp.data + sizeof(fb_data) * i * w,
fb + (y + i) * screen_w + x,
w * sizeof(fb_data));
}
bl->x = x;
bl->y = y;
bl->have_data = true;
}
#endif
}
static void rb_blitter_load(void *handle, blitter *bl, int x, int y)
{
LOGF("rb_blitter_load");
if(!bl->have_data)
return;
int w = bl->bmp.width, h = bl->bmp.height;
if(x == BLITTER_FROMSAVED) x = bl->x;
if(y == BLITTER_FROMSAVED) y = bl->y;
if(!zoom_enabled)
offset_coords(&x, &y);
trim_rect(&x, &y, &w, &h);
if(!zoom_enabled)
{
rb->lcd_bitmap((fb_data*)bl->bmp.data, x, y, w, h);
}
else
{
for(int i = 0; i < h; ++i)
{
rb->memcpy(zoom_fb + (y + i) * zoom_w + x,
bl->bmp.data + sizeof(fb_data) * i * w,
w * sizeof(fb_data));
}
}
}
static void rb_draw_update(void *handle, int x, int y, int w, int h)
{
LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h);
/* It seems that the puzzles use a different definition of
* "updating" the display than Rockbox does; by calling this
* function, it tells us that it has either already drawn to the
* updated area (as rockbox assumes), or that it WILL draw to the
* said area. Thus we simply remember a rectangle that contains
* all the updated regions and update it at the very end. */
/* adapted from gtk.c */
if (!need_draw_update || ud_l > x ) ud_l = x;
if (!need_draw_update || ud_r < x+w) ud_r = x+w;
if (!need_draw_update || ud_u > y ) ud_u = y;
if (!need_draw_update || ud_d < y+h) ud_d = y+h;
need_draw_update = true;
}
static void rb_start_draw(void *handle)
{
(void) handle;
/* ... mumble mumble ... not ... reentrant ... mumble mumble ... */
need_draw_update = false;
ud_l = 0;
ud_r = LCD_WIDTH;
ud_u = 0;
ud_d = LCD_HEIGHT;
}
static void rb_end_draw(void *handle)
{
(void) handle;
if(!zoom_enabled)
{
LOGF("rb_end_draw");
if(need_draw_update)
rb->lcd_update_rect(MAX(0, ud_l), MAX(0, ud_u), MIN(LCD_WIDTH, ud_r - ud_l), MIN(LCD_HEIGHT, ud_d - ud_u));
}
else
{
/* stubbed */
}
}
static void rb_status_bar(void *handle, const char *text)
{
if(titlebar)
sfree(titlebar);
titlebar = dupstr(text);
LOGF("game title is %s\n", text);
}
static int get_titleheight(void)
{
return rb->font_get(FONT_UI)->height;
}
static void draw_title(bool clear_first)
{
const char *base;
if(titlebar)
base = titlebar;
else
base = midend_which_game(me)->name;
char str[128];
rb->snprintf(str, sizeof(str), "%s%s", base, zoom_enabled ? (view_mode ? " (viewing)" : " (interaction)") : "");
/* quick hack */
bool orig_clipped;
if(!zoom_enabled)
{
orig_clipped = clipped;
if(orig_clipped)
rb_unclip(NULL);
}
int w, h;
cur_font = FONT_UI;
rb->lcd_setfont(cur_font);
rb->lcd_getstringsize(str, &w, &h);
rb->lcd_set_foreground(BG_COLOR);
rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h);
rb->lcd_set_drawmode(DRMODE_FG);
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_putsxy(0, LCD_HEIGHT - h, str);
if(!zoom_enabled)
{
if(orig_clipped)
rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
}
}
static char *rb_text_fallback(void *handle, const char *const *strings,
int nstrings)
{
return dupstr(strings[0]);
}
const drawing_api rb_drawing = {
rb_draw_text,
rb_draw_rect,
rb_draw_line,
rb_draw_poly,
rb_draw_circle,
rb_draw_update,
rb_clip,
rb_unclip,
rb_start_draw,
rb_end_draw,
rb_status_bar,
rb_blitter_new,
rb_blitter_free,
rb_blitter_save,
rb_blitter_load,
NULL, NULL, NULL, NULL, NULL, NULL, /* {begin,end}_{doc,page,puzzle} */
NULL, NULL, /* line_width, line_dotted */
rb_text_fallback,
NULL,
};
/** functions exported to puzzles code **/
void fatal(const char *fmt, ...)
{
va_list ap;
rb->splash(HZ, "FATAL");
va_start(ap, fmt);
char buf[80];
rb->vsnprintf(buf, 80, fmt, ap);
rb->splash(HZ * 2, buf);
va_end(ap);
exit(1);
}
void get_random_seed(void **randseed, int *randseedsize)
{
*randseed = snew(long);
long seed = *rb->current_tick;
rb->memcpy(*randseed, &seed, sizeof(seed));
*randseedsize = sizeof(long);
}
static void timer_cb(void)
{
#if LCD_DEPTH != 24
if(settings.timerflash)
{
static bool what = false;
what = !what;
if(what)
rb->lcd_framebuffer[0] = LCD_BLACK;
else
rb->lcd_framebuffer[0] = LCD_WHITE;
rb->lcd_update();
}
#endif
LOGF("timer callback");
midend_timer(me, ((float)(*rb->current_tick - last_tstamp) / (float)HZ) / settings.slowmo_factor);
last_tstamp = *rb->current_tick;
}
void activate_timer(frontend *fe)
{
last_tstamp = *rb->current_tick;
timer_on = true;
}
void deactivate_timer(frontend *fe)
{
timer_on = false;
}
void frontend_default_color(frontend *fe, float *out)
{
*out++ = BG_R;
*out++ = BG_G;
*out++ = BG_B;
}
/** frontend code -- mostly UI stuff **/
/* set do_pausemenu to false to just return -1 on BTN_PAUSE and do
* nothing else. */
static int process_input(int tmo, bool do_pausemenu)
{
LOGF("process_input start");
LOGF("------------------");
int state = 0;
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false); /* about to block for button input */
#endif
int button = rb->button_get_w_tmo(tmo);
/* weird stuff */
exit_on_usb(button);
/* these games require a second input on long-press */
if(accept_input && (button == (BTN_FIRE | BUTTON_REPEAT)) &&
(strcmp("Mines", midend_which_game(me)->name) != 0 ||
strcmp("Magnets", midend_which_game(me)->name) != 0))
{
accept_input = false;
return ' ';
}
button = rb->button_status();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
if(button == BTN_PAUSE)
{
if(do_pausemenu)
{
want_redraw = false;
/* quick hack to preserve the clipping state */
bool orig_clipped = clipped;
if(orig_clipped)
rb_unclip(NULL);
int rc = pause_menu();
if(orig_clipped)
rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
last_keystate = 0;
accept_input = true;
return rc;
}
else
return -1;
}
/* these games require, for one reason or another, that events
* fire upon buttons being released rather than when they are
* pressed */
if(strcmp("Inertia", midend_which_game(me)->name) == 0 ||
strcmp("Mines", midend_which_game(me)->name) == 0 ||
strcmp("Magnets", midend_which_game(me)->name) == 0 ||
strcmp("Map", midend_which_game(me)->name) == 0)
{
LOGF("received button 0x%08x", button);
unsigned released = ~button & last_keystate;
last_keystate = button;
if(!button)
{
if(!accept_input)
{
LOGF("ignoring, all keys released but not accepting input before, can accept input later");
accept_input = true;
return 0;
}
}
if(!released || !accept_input)
{
LOGF("released keys detected: 0x%08x", released);
LOGF("ignoring, either no keys released or not accepting input");
return 0;
}
if(button)
{
LOGF("ignoring input from now until all released");
accept_input = false;
}
button |= released;
LOGF("accepting event 0x%08x", button);
}
/* default is to ignore repeats except for untangle */
else if(strcmp("Untangle", midend_which_game(me)->name) != 0)
{
/* start accepting input again after a release */
if(!button)
{
accept_input = true;
return 0;
}
/* ignore repeats */
/* Untangle gets special treatment */
if(!accept_input)
return 0;
accept_input = false;
}
switch(button)
{
case BTN_UP:
state = CURSOR_UP;
break;
case BTN_DOWN:
state = CURSOR_DOWN;
break;
case BTN_LEFT:
state = CURSOR_LEFT;
break;
case BTN_RIGHT:
state = CURSOR_RIGHT;
break;
/* handle diagonals (mainly for Inertia) */
case BTN_DOWN | BTN_LEFT:
#ifdef BTN_DOWN_LEFT
case BTN_DOWN_LEFT:
#endif
state = '1' | MOD_NUM_KEYPAD;
break;
case BTN_DOWN | BTN_RIGHT:
#ifdef BTN_DOWN_RIGHT
case BTN_DOWN_RIGHT:
#endif
state = '3' | MOD_NUM_KEYPAD;
break;
case BTN_UP | BTN_LEFT:
#ifdef BTN_UP_LEFT
case BTN_UP_LEFT:
#endif
state = '7' | MOD_NUM_KEYPAD;
break;
case BTN_UP | BTN_RIGHT:
#ifdef BTN_UP_RIGHT
case BTN_UP_RIGHT:
#endif
state = '9' | MOD_NUM_KEYPAD;
break;
case BTN_FIRE:
if(!strcmp("Fifteen", midend_which_game(me)->name))
state = 'h'; /* hint */
else
state = CURSOR_SELECT;
break;
default:
break;
}
if(settings.shortcuts)
{
static bool shortcuts_ok = true;
switch(button)
{
case BTN_LEFT | BTN_FIRE:
if(shortcuts_ok)
midend_process_key(me, 0, 0, 'u');
shortcuts_ok = false;
break;
case BTN_RIGHT | BTN_FIRE:
if(shortcuts_ok)
midend_process_key(me, 0, 0, 'r');
shortcuts_ok = false;
break;
case 0:
shortcuts_ok = true;
break;
default:
break;
}
}
LOGF("process_input done");
LOGF("------------------");
return state;
}
/* either pan around a zoomed-in image or play zoomed-in */
static void zoom(void)
{
rb->splash(0, "Please wait...");
zoom_w = LCD_WIDTH * ZOOM_FACTOR, zoom_h = LCD_HEIGHT * ZOOM_FACTOR;
zoom_clipu = 0;
zoom_clipd = zoom_h;
zoom_clipl = 0;
zoom_clipr = zoom_w;
midend_size(me, &zoom_w, &zoom_h, TRUE);
/* Allocating the framebuffer will mostly likely grab the
* audiobuffer, which will make impossible to load new fonts, and
* lead to zoomed puzzles being drawn with the default fallback
* fonts. As a semi-workaround, we go ahead and load the biggest available
* monospace and proportional fonts. */
rb_setfont(FONT_FIXED, BUNDLE_MAX);
rb_setfont(FONT_VARIABLE, BUNDLE_MAX);
zoom_fb = smalloc(zoom_w * zoom_h * sizeof(fb_data));
if(!zoom_fb)
{
rb->splashf(HZ * 2, "Not enough memory to allocate %d KB framebuffer!", zoom_w * zoom_h * sizeof(fb_data) / 1024);
return;
}
zoom_enabled = true;
/* draws go to the zoom framebuffer */
midend_force_redraw(me);
int x = 0, y = 0;
rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h),
0, 0, LCD_WIDTH, LCD_HEIGHT);
draw_title(false); /* false since we don't want to use more screen space than we need. */
rb->lcd_update();
/* Here's how this works: pressing select (or the target's
* equivalent, it's whatever BTN_FIRE is) while in viewing mode
* will toggle the mode to interaction mode. In interaction mode,
* the buttons will behave as normal and be sent to the puzzle,
* except for the pause/quit (BTN_PAUSE) button, which will return
* to view mode. Finally, when in view mode, pause/quit will
* return to the pause menu. */
view_mode = true;
/* pan around the image */
while(1)
{
if(view_mode)
{
int button = rb->button_get_w_tmo(timer_on ? TIMER_INTERVAL : -1);
switch(button)
{
case BTN_UP:
y -= PAN_Y; /* clamped later */
break;
case BTN_DOWN:
y += PAN_Y; /* clamped later */
break;
case BTN_LEFT:
x -= PAN_X; /* clamped later */
break;
case BTN_RIGHT:
x += PAN_X; /* clamped later */
break;
case BTN_PAUSE:
zoom_enabled = false;
sfree(zoom_fb);
fix_size();
return;
case BTN_FIRE:
view_mode = false;
continue;
default:
break;
}
if(y < 0)
y = 0;
if(x < 0)
x = 0;
if(y + LCD_HEIGHT >= zoom_h)
y = zoom_h - LCD_HEIGHT;
if(x + LCD_WIDTH >= zoom_w)
x = zoom_w - LCD_WIDTH;
if(timer_on)
timer_cb();
/* goes to zoom_fb */
midend_redraw(me);
rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h),
0, 0, LCD_WIDTH, LCD_HEIGHT);
draw_title(false);
rb->lcd_update();
rb->yield();
}
else
{
/* basically a copy-pasta'd main loop */
int button = process_input(timer_on ? TIMER_INTERVAL : -1, false);
if(button < 0)
{
view_mode = true;
continue;
}
if(button)
midend_process_key(me, 0, 0, button);
if(timer_on)
timer_cb();
if(want_redraw)
midend_redraw(me);
rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h),
0, 0, LCD_WIDTH, LCD_HEIGHT);
draw_title(false);
rb->lcd_update();
rb->yield();
}
}
}
static const char *config_choices_formatter(int sel, void *data, char *buf, size_t len)
{
/* we can't rely on being called in any particular order */
char *list = dupstr(data);
char delimbuf[2] = { *list, 0 };
char *save = NULL;
char *str = rb->strtok_r(list, delimbuf, &save);
for(int i = 0; i < sel; ++i)
str = rb->strtok_r(NULL, delimbuf, &save);
rb->snprintf(buf, len, "%s", str);
sfree(list);
return buf;
}
static int list_choose(const char *list_str, const char *title, int sel)
{
char delim = *list_str;
const char *ptr = list_str + 1;
int n = 0;
while(ptr)
{
n++;
ptr = strchr(ptr + 1, delim);
}
struct gui_synclist list;
rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL);
rb->gui_synclist_set_icon_callback(&list, NULL);
rb->gui_synclist_set_nb_items(&list, n);
rb->gui_synclist_limit_scroll(&list, false);
rb->gui_synclist_select_item(&list, sel);
rb->gui_synclist_set_title(&list, (char*)title, NOICON);
while (1)
{
rb->gui_synclist_draw(&list);
int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON))
continue;
switch(button)
{
case ACTION_STD_OK:
return rb->gui_synclist_get_sel_pos(&list);
case ACTION_STD_PREV:
case ACTION_STD_CANCEL:
return -1;
default:
break;
}
}
}
static bool is_integer(const char *str)
{
while(*str)
{
char c = *str++;
if(!isdigit(c) && c != '-')
return false;
}
return true;
}
static void int_chooser(config_item *cfgs, int idx, int val)
{
config_item *cfg = cfgs + idx;
int old_val = val;
rb->snprintf(cfg->u.string.sval, MAX_STRLEN, "%d", val);
rb->lcd_clear_display();
while(1)
{
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_puts(0, 0, cfg->name);
rb->lcd_putsf(0, 1, "< %d >", val);
rb->lcd_update();
rb->lcd_set_foreground(ERROR_COLOR);
int d = 0;
int button = rb->button_get(true);
switch(button)
{
case BTN_RIGHT:
case BTN_RIGHT | BUTTON_REPEAT:
d = 1;
break;
case BTN_LEFT:
case BTN_LEFT | BUTTON_REPEAT:
d = -1;
break;
case BTN_FIRE:
/* config is already set */
rb->lcd_scroll_stop();
return;
case BTN_PAUSE:
if(val != old_val)
rb->splash(HZ, "Canceled.");
val = old_val;
rb->snprintf(cfg->u.string.sval, MAX_STRLEN, "%d", val);
rb->lcd_scroll_stop();
return;
}
if(d)
{
const char *ret;
for(int i = 0; i < CHOOSER_MAX_INCR; ++i)
{
val += d;
rb->snprintf(cfg->u.string.sval, MAX_STRLEN, "%d", val);
ret = midend_set_config(me, CFG_SETTINGS, cfgs);
if(!ret)
{
/* clear any error message */
rb->lcd_clear_display();
rb->lcd_scroll_stop();
break;
}
}
/* failure */
if(ret)
{
/* bright, annoying red */
rb->lcd_set_foreground(ERROR_COLOR);
rb->lcd_puts_scroll(0, 2, ret);
/* reset value */
val -= d * CHOOSER_MAX_INCR;
rb->snprintf(cfg->u.string.sval, MAX_STRLEN, "%d", val);
assert(!midend_set_config(me, CFG_SETTINGS, cfgs));
}
}
}
}
/* return value is only meaningful when type == C_STRING, where it
* indicates whether cfg->sval has been freed or otherwise altered */
static bool do_configure_item(config_item *cfgs, int idx)
{
config_item *cfg = cfgs + idx;
switch(cfg->type)
{
case C_STRING:
{
char *newstr = smalloc(MAX_STRLEN);
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_set_background(LCD_BLACK);
if(is_integer(cfg->u.string.sval))
{
int val = atoi(cfg->u.string.sval);
/* we now free the original string and give int_chooser()
* a clean buffer to work with */
sfree(cfg->u.string.sval);
cfg->u.string.sval = newstr;
int_chooser(cfgs, idx, val);
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_set_background(LCD_BLACK);
return true;
}
rb->strlcpy(newstr, cfg->u.string.sval, MAX_STRLEN);
if(rb->kbd_input(newstr, MAX_STRLEN) < 0)
{
sfree(newstr);
return false;
}
sfree(cfg->u.string.sval);
cfg->u.string.sval = newstr;
return true;
}
case C_BOOLEAN:
{
bool res = cfg->u.boolean.bval != 0;
rb->set_bool(cfg->name, &res);
/* seems to reset backdrop */
rb->lcd_set_backdrop(NULL);
cfg->u.boolean.bval = res;
break;
}
case C_CHOICES:
{
int sel = list_choose(cfg->u.choices.choicenames, cfg->name, cfg->u.choices.selected);
if(sel >= 0)
cfg->u.choices.selected = sel;
break;
}
default:
fatal("");
break;
}
return false;
}
const char *config_formatter(int sel, void *data, char *buf, size_t len)
{
config_item *cfg = data;
cfg += sel;
rb->snprintf(buf, len, "%s", cfg->name);
return buf;
}
static bool config_menu(void)
{
char *title;
config_item *config = midend_get_config(me, CFG_SETTINGS, &title);
cur_font = FONT_UI;
rb->lcd_setfont(cur_font);
bool success = false;
if(!config)
{
rb->splash(HZ, "Nothing to configure.");
goto done;
}
/* count */
int n = 0;
config_item *ptr = config;
while(ptr->type != C_END)
{
n++;
ptr++;
}
/* display a list */
struct gui_synclist list;
rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL);
rb->gui_synclist_set_icon_callback(&list, NULL);
rb->gui_synclist_set_nb_items(&list, n);
rb->gui_synclist_limit_scroll(&list, false);
rb->gui_synclist_select_item(&list, 0);
bool done = false;
rb->gui_synclist_set_title(&list, title, NOICON);
while (!done)
{
rb->gui_synclist_draw(&list);
int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON))
continue;
switch(button)
{
case ACTION_STD_OK:
{
int pos = rb->gui_synclist_get_sel_pos(&list);
/* store the initial state */
config_item old;
memcpy(&old, config + pos, sizeof(old));
char *old_str = NULL;
if(old.type == C_STRING)
old_str = dupstr(old.u.string.sval);
bool freed_str = do_configure_item(config, pos);
const char *err = midend_set_config(me, CFG_SETTINGS, config);
if(err)
{
rb->splash(HZ, err);
/* restore old state */
memcpy(config + pos, &old, sizeof(old));
if(old.type == C_STRING && freed_str)
config[pos].u.string.sval = old_str;
}
else
{
if(old.type == C_STRING)
{
/* success, and we duplicated the old string when
* we didn't need to, so free it now */
sfree(old_str);
}
success = true;
}
break;
}
case ACTION_STD_PREV:
case ACTION_STD_CANCEL:
done = true;
break;
default:
break;
}
}
done:
sfree(title);
free_cfg(config);
return success;
}
static const char *preset_formatter(int sel, void *data, char *buf, size_t len)
{
struct preset_menu *menu = data;
rb->snprintf(buf, len, "%s", menu->entries[sel].title);
return buf;
}
/* main worker function */
/* returns the index of the selected item on success, -1 on failure */
static int do_preset_menu(struct preset_menu *menu, char *title, int selected)
{
if(!menu->n_entries)
return false;
/* display a list */
struct gui_synclist list;
rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL);
rb->gui_synclist_set_icon_callback(&list, NULL);
rb->gui_synclist_set_nb_items(&list, menu->n_entries);
rb->gui_synclist_limit_scroll(&list, false);
rb->gui_synclist_select_item(&list, selected);
static char def[] = "Game Type";
rb->gui_synclist_set_title(&list, title ? title : def, NOICON);
while(1)
{
rb->gui_synclist_draw(&list);
int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK);
if(rb->gui_synclist_do_button(&list, &button, LIST_WRAP_ON))
continue;
switch(button)
{
case ACTION_STD_OK:
{
int sel = rb->gui_synclist_get_sel_pos(&list);
struct preset_menu_entry *entry = menu->entries + sel;
if(entry->params)
{
midend_set_params(me, entry->params);
return sel;
}
else
{
/* recurse */
if(do_preset_menu(entry->submenu, entry->title, 0)) /* select first one */
return sel;
}
break;
}
case ACTION_STD_PREV:
case ACTION_STD_CANCEL:
return -1;
default:
break;
}
}
}
static bool presets_menu(void)
{
/* figure out the index of the current preset
* if it's in a submenu, give up and default to the first item */
struct preset_menu *top = midend_get_presets(me, NULL);
int sel = 0;
for(int i = 0; i < top->n_entries; ++i)
{
if(top->entries[i].id == midend_which_preset(me))
{
sel = i;
break;
}
}
return do_preset_menu(midend_get_presets(me, NULL), NULL, sel) >= 0;
}
static void quick_help(void)
{
#if defined(FOR_REAL) && defined(DEBUG_MENU)
if(++help_times >= 5)
{
rb->splash(HZ, "You are now a developer!");
debug_mode = true;
}
#endif
rb->splash(0, quick_help_text);
rb->button_get(true);
return;
}
static void full_help(const char *name)
{
unsigned old_bg = rb->lcd_get_background();
bool orig_clipped = clipped;
if(orig_clipped)
rb_unclip(NULL);
rb->lcd_set_foreground(LCD_WHITE);
rb->lcd_set_background(LCD_BLACK);
unload_fonts();
cur_font = FONT_UI;
rb->lcd_setfont(cur_font);
char *buf = smalloc(help_text_len);
LZ4_decompress_tiny(help_text, buf, help_text_len);
view_text(name, buf);
sfree(buf);
rb->lcd_set_background(old_bg);
if(orig_clipped)
rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
}
static void init_default_settings(void)
{
settings.slowmo_factor = 1;
settings.timerflash = false;
settings.clipoff = false;
settings.shortcuts = false;
settings.no_aa = false;
settings.polyanim = false;
}
#ifdef DEBUG_MENU
static void bench_aa(void)
{
rb->sleep(0);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(true);
#endif
int next = *rb->current_tick + HZ;
int i = 0;
while(*rb->current_tick < next)
{
draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, 0, 0, 20, 31);
++i;
}
rb->splashf(HZ, "%d AA lines/sec", i);
next = *rb->current_tick + HZ;
int j = 0;
while(*rb->current_tick < next)
{
rb->lcd_drawline(0, 0, 20, 31);
++j;
}
rb->splashf(HZ, "%d normal lines/sec", j);
rb->splashf(HZ, "Efficiency: %d%%", 100 * i / j);
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
}
static void debug_menu(void)
{
MENUITEM_STRINGLIST(menu, "Debug Menu", NULL,
"Slowmo factor",
"Randomize colors",
"Toggle flash pixel on timer",
"Toggle clip",
"Toggle shortcuts",
"Toggle antialias",
"Benchmark antialias",
"Toggle show poly steps",
"Back");
bool quit = false;
int sel = 0;
while(!quit)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
rb->set_int("Slowmo factor", "", UNIT_INT, &settings.slowmo_factor, NULL, 1, 1, 15, NULL);
break;
case 1:
{
unsigned *ptr = colors;
for(int i = 0; i < ncolors; ++i)
{
/* not seeded, who cares? */
*ptr++ = LCD_RGBPACK(rb->rand()%255, rb->rand()%255, rb->rand()%255);
}
break;
}
case 2:
settings.timerflash = !settings.timerflash;
break;
case 3:
settings.clipoff = !settings.clipoff;
break;
case 4:
settings.shortcuts = !settings.shortcuts;
break;
case 5:
settings.no_aa = !settings.no_aa;
break;
case 6:
bench_aa();
break;
case 7:
settings.polyanim = !settings.polyanim;
break;
case 8:
default:
quit = true;
break;
}
}
}
#endif
static int pausemenu_cb(int action, const struct menu_item_ex *this_item)
{
int i = (intptr_t) this_item;
if(action == ACTION_REQUEST_MENUITEM)
{
switch(i)
{
case 3:
if(!midend_can_undo(me))
return ACTION_EXIT_MENUITEM;
break;
case 4:
if(!midend_can_redo(me))
return ACTION_EXIT_MENUITEM;
break;
case 5:
if(!midend_which_game(me)->can_solve)
return ACTION_EXIT_MENUITEM;
break;
case 7:
break;
case 9:
if(audiobuf_available)
break;
else
return ACTION_EXIT_MENUITEM;
case 10:
if(!midend_get_presets(me, NULL)->n_entries)
return ACTION_EXIT_MENUITEM;
break;
case 11:
#if defined(FOR_REAL) && defined(DEBUG_MENU)
if(debug_mode)
break;
return ACTION_EXIT_MENUITEM;
#else
break;
#endif
case 12:
if(!midend_which_game(me)->can_configure)
return ACTION_EXIT_MENUITEM;
break;
default:
break;
}
}
return action;
}
static void clear_and_draw(void)
{
rb->lcd_clear_display();
rb->lcd_update();
midend_force_redraw(me);
draw_title(true);
}
static void reset_drawing(void)
{
rb->lcd_set_viewport(NULL);
rb->lcd_set_backdrop(NULL);
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(BG_COLOR);
}
static int pause_menu(void)
{
#define static auto
#define const
MENUITEM_STRINGLIST(menu, NULL, pausemenu_cb,
"Resume Game", // 0
"New Game", // 1
"Restart Game", // 2
"Undo", // 3
"Redo", // 4
"Solve", // 5
"Zoom In", // 6
"Quick Help", // 7
"Extensive Help", // 8
"Playback Control", // 9
"Game Type", // 10
"Debug Menu", // 11
"Configure Game", // 12
"Quit without Saving", // 13
"Quit"); // 14
#undef static
#undef const
/* HACK ALERT */
char title[32] = { 0 };
rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name);
menu__.desc = title;
#if defined(FOR_REAL) && defined(DEBUG_MENU)
help_times = 0;
#endif
bool quit = false;
int sel = 0;
while(!quit)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
quit = true;
break;
case 1:
midend_new_game(me);
fix_size();
quit = true;
break;
case 2:
midend_restart_game(me);
fix_size();
quit = true;
break;
case 3:
if(!midend_can_undo(me))
rb->splash(HZ, "Cannot undo.");
else
midend_process_key(me, 0, 0, 'u');
quit = true;
break;
case 4:
if(!midend_can_redo(me))
rb->splash(HZ, "Cannot redo.");
else
midend_process_key(me, 0, 0, 'r');
quit = true;
break;
case 5:
{
const char *msg = midend_solve(me);
if(msg)
rb->splash(HZ, msg);
quit = true;
break;
}
case 6:
zoom();
break;
case 7:
quick_help();
break;
case 8:
full_help(midend_which_game(me)->name);
break;
case 9:
playback_control(NULL);
break;
case 10:
if(presets_menu())
{
midend_new_game(me);
fix_size();
reset_drawing();
clear_and_draw();
quit = true;
}
break;
case 11:
#ifdef DEBUG_MENU
debug_menu();
#endif
break;
case 12:
if(config_menu())
{
midend_new_game(me);
fix_size();
reset_drawing();
clear_and_draw();
quit = true;
}
break;
case 13:
return -2;
case 14:
return -3;
default:
break;
}
}
rb->lcd_set_background(BG_COLOR);
rb->lcd_clear_display();
rb->lcd_update();
midend_force_redraw(me);
return 0;
}
/* points to pluginbuf, used by rbmalloc.c */
/* useless side note: originally giant_buffer was a statically
* allocated giant array (4096KB IIRC), hence its name. */
char *giant_buffer = NULL;
static size_t giant_buffer_len = 0; /* set on start */
static void fix_size(void)
{
int w = LCD_WIDTH, h = LCD_HEIGHT, h_x;
cur_font = FONT_UI;
rb->lcd_setfont(cur_font);
rb->lcd_getstringsize("X", NULL, &h_x);
h -= h_x;
midend_size(me, &w, &h, TRUE);
}
static void init_tlsf(void)
{
/* reset tlsf by nuking the signature */
/* will make any already-allocated memory point to garbage */
memset(giant_buffer, 0, 4);
init_memory_pool(giant_buffer_len, giant_buffer);
}
static int read_wrapper(void *ptr, void *buf, int len)
{
int fd = (int) ptr;
return rb->read(fd, buf, len);
}
static void write_wrapper(void *ptr, const void *buf, int len)
{
int fd = (int) ptr;
rb->write(fd, buf, len);
}
static void init_colors(void)
{
float *floatcolors = midend_colors(me, &ncolors);
/* convert them to packed RGB */
colors = smalloc(ncolors * sizeof(unsigned));
unsigned *ptr = colors;
float *floatptr = floatcolors;
for(int i = 0; i < ncolors; ++i)
{
int r = 255 * *(floatptr++);
int g = 255 * *(floatptr++);
int b = 255 * *(floatptr++);
LOGF("color %d is %d %d %d", i, r, g, b);
*ptr++ = LCD_RGBPACK(r, g, b);
}
sfree(floatcolors);
}
static const char *init_for_game(const game *gm, int load_fd, bool draw)
{
me = midend_new(NULL, gm, &rb_drawing, NULL);
if(load_fd < 0)
midend_new_game(me);
else
{
const char *ret = midend_deserialize(me, read_wrapper, (void*) load_fd);
if(ret)
return ret;
}
fix_size();
init_colors();
reset_drawing();
if(draw)
{
clear_and_draw();
}
return NULL;
}
static void shutdown_tlsf(void)
{
memset(giant_buffer, 0, 4);
}
static void exit_handler(void)
{
unload_fonts();
shutdown_tlsf();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
rb->cpu_boost(false);
#endif
}
#ifdef FONT_CACHING
/* try loading the fonts indicated in the on-disk font table */
static void load_fonts(void)
{
int fd = rb->open(FONT_TABLE, O_RDONLY);
if(fd < 0)
return;
uint64_t fontmask = 0;
while(1)
{
char linebuf[MAX_LINE], *ptr = linebuf;
int len = rb->read_line(fd, linebuf, sizeof(linebuf));
if(len <= 0)
break;
char *tok, *save;
tok = rb->strtok_r(ptr, ":", &save);
ptr = NULL;
if(!strcmp(tok, midend_which_game(me)->name))
{
uint32_t left, right;
tok = rb->strtok_r(ptr, ":", &save);
left = atoi(tok);
tok = rb->strtok_r(ptr, ":", &save);
right = atoi(tok);
fontmask = ((uint64_t)left << 31) | right;
break;
}
}
/* nothing to do */
if(!fontmask)
{
rb->close(fd);
return;
}
/* loop through each bit of the mask and try loading the
corresponding font */
for(int i = 0; i < 2 * BUNDLE_COUNT; ++i)
{
if(fontmask & ((uint64_t)1 << i))
{
int size = (i > BUNDLE_COUNT ? i - BUNDLE_COUNT : i) + BUNDLE_MIN;
int type = i > BUNDLE_COUNT ? FONT_VARIABLE : FONT_FIXED;
rb_setfont(type, size);
}
}
rb->close(fd);
}
/* remember which fonts were loaded */
static void save_fonts(void)
{
#if 2*BUNDLE_COUNT > 62
#error too many fonts for 62-bit mask
#endif
/* first assemble the bitmask */
uint64_t fontmask = 0;
for(int i = 0; i < 2 * BUNDLE_COUNT; ++i)
{
/* try loading if we attempted to load */
if(loaded_fonts[i].status >= -2)
{
fontmask |= (uint64_t)1 << i;
}
}
if(fontmask)
{
/* font table format is as follows:
* [GAME NAME]:[32-halves of bit mask in decimal][newline]
*/
int fd = rb->open(FONT_TABLE, O_RDONLY);
int outfd = rb->open(FONT_TABLE ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(outfd < 0)
return;
uint64_t oldmask = 0;
if(fd >= 0)
{
while(1)
{
char linebuf[MAX_LINE], *ptr = linebuf;
char origbuf[MAX_LINE];
int len = rb->read_line(fd, linebuf, sizeof(linebuf));
if(len <= 0)
break;
rb->memcpy(origbuf, linebuf, len);
char *tok, *save;
tok = rb->strtok_r(ptr, ":", &save);
ptr = NULL;
/* copy line if not matching */
if(strcmp(tok, midend_which_game(me)->name) != 0)
{
rb->write(outfd, origbuf, len);
rb->fdprintf(outfd, "\n");
}
else
{
/* matching, remember the old mask */
assert(oldmask == 0);
uint32_t left, right;
tok = rb->strtok_r(ptr, ":", &save);
left = atoi(tok);
tok = rb->strtok_r(ptr, ":", &save);
right = atoi(tok);
oldmask = ((uint64_t)left << 31) | right;
}
}
rb->close(fd);
}
uint64_t final = fontmask;
if(n_fonts < MAX_FONTS)
final |= oldmask;
uint32_t left = final >> 31;
uint32_t right = final & 0x7fffffff;
rb->fdprintf(outfd, "%s:%lu:%lu\n", midend_which_game(me)->name, left, right);
rb->close(outfd);
rb->rename(FONT_TABLE ".tmp", FONT_TABLE);
}
}
#endif
static void save_fname(char *buf)
{
rb->snprintf(buf, MAX_PATH, "%s/sgt-%s.sav", PLUGIN_GAMES_DATA_DIR, thegame.htmlhelp_topic);
}
/* expects a totally free me* pointer */
static bool load_game(void)
{
char fname[MAX_PATH];
save_fname(fname);
int fd = rb->open(fname, O_RDONLY);
if(fd < 0)
return false;
rb->splash(0, "Loading...");
char *game;
const char *ret = identify_game(&game, read_wrapper, (void*)fd);
if(!*game && ret)
{
sfree(game);
rb->splash(HZ, ret);
rb->close(fd);
return false;
}
else
{
/* seek to beginning */
rb->lseek(fd, 0, SEEK_SET);
if(!strcmp(game, thegame.name))
{
ret = init_for_game(&thegame, fd, false);
if(ret)
{
rb->splash(HZ, ret);
rb->close(fd);
rb->remove(fname);
return false;
}
rb->close(fd);
rb->remove(fname);
#ifdef FONT_CACHING
load_fonts();
#endif
/* success */
return true;
}
rb->splashf(HZ, "Failed loading save for %s!", game);
/* clean up, even on failure */
rb->close(fd);
rb->remove(fname);
return false;
}
}
static void save_game(void)
{
rb->splash(0, "Saving...");
char fname[MAX_PATH];
save_fname(fname);
/* save game */
int fd = rb->open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0666);
midend_serialize(me, write_wrapper, (void*) fd);
rb->close(fd);
#ifdef FONT_CACHING
save_fonts();
#endif
rb->lcd_update();
}
static int mainmenu_cb(int action, const struct menu_item_ex *this_item)
{
int i = (intptr_t) this_item;
if(action == ACTION_REQUEST_MENUITEM)
{
switch(i)
{
case 0:
case 7:
if(!load_success)
return ACTION_EXIT_MENUITEM;
break;
case 3:
break;
case 4:
if(audiobuf_available)
break;
else
return ACTION_EXIT_MENUITEM;
case 5:
if(!midend_get_presets(me, NULL)->n_entries)
return ACTION_EXIT_MENUITEM;
break;
case 6:
if(!midend_which_game(me)->can_configure)
return ACTION_EXIT_MENUITEM;
break;
default:
break;
}
}
return action;
}
enum plugin_status plugin_start(const void *param)
{
(void) param;
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
/* boost for init */
rb->cpu_boost(true);
#endif
giant_buffer = rb->plugin_get_buffer(&giant_buffer_len);
rb_atexit(exit_handler);
init_tlsf();
/* sanity check */
if(fabs(sqrt(3)/2 - sin(PI/3)) > .01)
{
return PLUGIN_ERROR;
}
init_default_settings();
init_fonttab();
load_success = load_game();
if(!load_success)
{
/* our main menu expects a ready-to-use midend */
init_for_game(&thegame, -1, false);
}
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
/* about to go to menu or button block */
rb->cpu_boost(false);
#endif
#if defined(FOR_REAL) && defined(DEBUG_MENU)
help_times = 0;
#endif
#define static auto
#define const
MENUITEM_STRINGLIST(menu, NULL, mainmenu_cb,
"Resume Game", // 0
"New Game", // 1
"Quick Help", // 2
"Extensive Help", // 3
"Playback Control", // 4
"Game Type", // 5
"Configure Game", // 6
"Quit without Saving", // 7
"Quit"); // 8
#undef static
#undef const
/* HACK ALERT */
char title[32] = { 0 };
rb->snprintf(title, sizeof(title), "%s Menu", midend_which_game(me)->name);
menu__.desc = title;
bool quit = false;
int sel = 0;
while(!quit)
{
switch(rb->do_menu(&menu, &sel, NULL, false))
{
case 0:
clear_and_draw();
goto game_loop;
case 1:
if(!load_success)
{
clear_and_draw();
goto game_loop;
}
quit = true;
break;
case 2:
quick_help();
break;
case 3:
full_help(midend_which_game(me)->name);
break;
case 4:
playback_control(NULL);
break;
case 5:
if(presets_menu())
{
midend_new_game(me);
fix_size();
init_colors();
reset_drawing();
clear_and_draw();
goto game_loop;
}
break;
case 6:
if(config_menu())
{
midend_new_game(me);
fix_size();
init_colors();
reset_drawing();
clear_and_draw();
goto game_loop;
}
break;
case 8:
if(load_success)
save_game();
/* fall through */
case 7:
/* we don't care about freeing anything because tlsf will
* be wiped out the next time around */
return PLUGIN_OK;
default:
break;
}
}
while(1)
{
init_for_game(&thegame, -1, true);
last_keystate = 0;
accept_input = true;
game_loop:
while(1)
{
want_redraw = true;
int theight = get_titleheight();
draw_title(true);
rb->lcd_update_rect(0, LCD_HEIGHT - theight, LCD_WIDTH, theight);
int button = process_input(timer_on ? TIMER_INTERVAL : -1, true);
if(button < 0)
{
rb_unclip(NULL);
deactivate_timer(NULL);
if(titlebar)
{
sfree(titlebar);
titlebar = NULL;
}
switch(button)
{
case -1:
/* new game */
midend_free(me);
break;
case -2:
/* quit without saving */
midend_free(me);
sfree(colors);
exit(PLUGIN_OK);
case -3:
/* save and quit */
save_game();
midend_free(me);
sfree(colors);
exit(PLUGIN_OK);
default:
break;
}
}
if(button)
midend_process_key(me, 0, 0, button);
draw_title(true); /* will draw to fb */
if(want_redraw)
midend_redraw(me);
/* push title to screen as well */
rb->lcd_update_rect(0, LCD_HEIGHT - theight, LCD_WIDTH, theight);
if(timer_on)
timer_cb();
rb->yield();
}
sfree(colors);
}
}