From 8b8e25f1954ab3313e3940cdd1348785d78b682e Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Fri, 27 Oct 2017 18:55:19 -0400 Subject: puzzles: add "Zoom In" option to improve usability This adds a "Zoom In" option to the pause menu of each puzzle, which displays the puzzle at triple size (subject to change). This should help with tiny screens, modulo memory concerns associated with allocating the temporary framebuffer to which drawing operations are redirected. Coincidentally, there's an upstream bug with Map that causes the cursor's positioning to be incorrectly displayed when zoomed. Change-Id: Ic8b7c2942acf558e295f4271dd7dc458cd336895 --- apps/plugins/puzzles/rockbox.c | 854 ++++++++++++++++++++++++++++++++--------- 1 file changed, 678 insertions(+), 176 deletions(-) (limited to 'apps/plugins/puzzles/rockbox.c') diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index 37d15b75b5..7b838ddb79 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -64,6 +64,11 @@ #define midend_colors midend_colours #endif +#define PAN_X LCD_WIDTH / 4 +#define PAN_Y LCD_WIDTH / 4 /* not a typo */ + +#define ZOOM_FACTOR 3 + static midend *me = NULL; static unsigned *colors = NULL; static int ncolors = 0; @@ -79,41 +84,206 @@ static int help_times = 0; static void fix_size(void); static struct viewport clip_rect; -static bool clipped = false; -extern bool audiobuf_available; +static bool clipped = false, audiobuf_available, zoom_enabled = false; + +static fb_data *zoom_fb; +static int zoom_w, zoom_h, zoom_clipu, zoom_clipd, zoom_clipl, zoom_clipr; +static int cur_font = FONT_UI; static struct settings_t { int slowmo_factor; bool timerflash, clipoff, shortcuts, no_aa, polyanim; } settings; +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); + + +/* 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; + + zoom_fb[y * zoom_w + x] = rb->lcd_get_foreground(); +} + +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; + + fb_data pixel = rb->lcd_get_foreground(); + 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) +{ + 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) + zoom_fb[(y + i * 8 + dy) * zoom_w + x + j] = LCD_BLACK; + 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) */ + 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, 0, 0, 0, + 0, zoom_w, 0, zoom_h); + } + } +} + /* clipping is implemented through viewports and offsetting * coordinates */ static void rb_clip(void *handle, int x, int y, int w, int h) { - if(!settings.clipoff) + if(!zoom_enabled) { - 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(!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; + clip_rect.fg_pattern = LCD_DEFAULT_FG; + clip_rect.bg_pattern = LCD_DEFAULT_BG; #endif - rb->lcd_set_viewport(&clip_rect); - clipped = true; + 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) { - LOGF("rb_unclip"); - rb->lcd_set_viewport(NULL); - clipped = false; + if(!zoom_enabled) + { + LOGF("rb_unclip"); + rb->lcd_set_viewport(NULL); + clipped = false; + } + else + { + zoom_clipu = 0; + zoom_clipd = LCD_HEIGHT; + zoom_clipl = 0; + zoom_clipr = LCD_WIDTH; + } } static void offset_coords(int *x, int *y) @@ -158,7 +328,8 @@ static void unload_fonts(void) loaded_fonts[i].status = -3; } access_counter = -1; - rb->lcd_setfont(FONT_UI); + cur_font = FONT_UI; + rb->lcd_setfont(cur_font); } static void init_fonttab(void) @@ -241,7 +412,8 @@ static void rb_setfont(int type, int size) goto fallback; loaded_fonts[font_idx].last_use = access_counter++; n_fonts++; - rb->lcd_setfont(loaded_fonts[font_idx].status); + cur_font = loaded_fonts[font_idx].status; + rb->lcd_setfont(cur_font); break; } case -2: @@ -249,15 +421,16 @@ static void rb_setfont(int type, int size) goto fallback; default: loaded_fonts[font_idx].last_use = access_counter++; - rb->lcd_setfont(loaded_fonts[font_idx].status); + cur_font = loaded_fonts[font_idx].status; + rb->lcd_setfont(cur_font); break; } return; fallback: - - rb->lcd_setfont(type == FONT_FIXED ? FONT_SYSFIXED : FONT_UI); + cur_font = type == FONT_FIXED ? FONT_SYSFIXED : FONT_UI; + rb->lcd_setfont(cur_font); return; } @@ -266,37 +439,95 @@ static void rb_draw_text(void *handle, int x, int y, int fonttype, int fontsize, int align, int color, char *text) { (void) fontsize; - LOGF("rb_draw_text(%d %d %s)", x, y, text); + 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; - offset_coords(&x, &y); + rb_color(color); + rb->lcd_set_drawmode(DRMODE_FG); + rb->lcd_putsxy(x, y, text); + rb->lcd_set_drawmode(DRMODE_SOLID); + } + else + { + rb_setfont(fonttype, fontsize); /* size will be clamped if too large */ - rb_setfont(fonttype, fontsize); + int w, h; + rb->lcd_getstringsize(text, &w, &h); - 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_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; - 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); - rb_color(color); - rb->lcd_set_drawmode(DRMODE_FG); - rb->lcd_putsxy(x, y, text); - rb->lcd_set_drawmode(DRMODE_SOLID); + 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) { - 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); + 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 */ + for(int i = y; i < y + h; ++i) + for(int j = x; j < x + w; ++j) + { + zoom_fb[i * zoom_w + j] = colors[color]; + } + } } #define SWAP(a, b, t) do { t = a; a = b; b = t; } while(0); @@ -306,8 +537,9 @@ static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color) #define FRACBITS 16 -/* most of our time drawing lines is spent in this function! */ -static inline void plot(unsigned x, unsigned y, unsigned long a, +/* 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) { @@ -319,7 +551,7 @@ static inline void plot(unsigned x, unsigned y, unsigned long a, if(!(cl <= x && x < cr && cu <= y && y < cd)) return; - fb_data *ptr = rb->lcd_framebuffer + y * LCD_WIDTH + x; + fb_data *ptr = fb + y * w + x; fb_data orig = *ptr; unsigned long r2, g2, b2; #if LCD_DEPTH != 24 @@ -351,18 +583,28 @@ static inline void plot(unsigned x, unsigned y, unsigned long a, * lines/sec at full optimization on ipod6g */ /* expects UN-OFFSET coordinates, directly access framebuffer */ -static void draw_antialiased_line(int x0, int y0, int x1, int y1) +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 = LCD_WIDTH, u = 0, d = LCD_HEIGHT; - if(clipped) + unsigned short l = 0, r = w, u = 0, d = h; + if(!zoom_enabled) { - l = clip_rect.x; - r = clip_rect.x + clip_rect.width; - u = clip_rect.y; - d = clip_rect.y + clip_rect.height; + 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); @@ -402,8 +644,8 @@ static void draw_antialiased_line(int x0, int y0, int x1, int y1) unsigned y = intery >> FRACBITS; unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8); - plot(y, x, (1 << 8) - alpha, r1, g1, b1, l, r, u, d); - plot(y + 1, x, alpha, r1, g1, b1, l, r, u, d); + 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 @@ -413,8 +655,8 @@ static void draw_antialiased_line(int x0, int y0, int x1, int y1) unsigned y = intery >> FRACBITS; unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8); - plot(x, y, (1 << 8) - alpha, r1, g1, b1, l, r, u, d); - plot(x, y + 1, alpha, r1, g1, b1, l, r, u, d); + 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); } } } @@ -422,16 +664,27 @@ static void draw_antialiased_line(int x0, int y0, int x1, int y1) static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, int color) { - LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color); - rb_color(color); - if(settings.no_aa) + if(!zoom_enabled) { - offset_coords(&x1, &y1); - offset_coords(&x2, &y2); - rb->lcd_drawline(x1, y1, x2, y2); + 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(x1, y1, x2, y2); + { + /* 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 @@ -489,7 +742,7 @@ static void fill_poly_line(int scanline, int count, int *pxy) intersection[num_of_intersects] = x1+((scanline-y1)*(x2-x1))/(y2-y1); if ( (direct!=old_direct) - || (intersection[num_of_intersects] != intersection[num_of_intersects-1]) + || (intersection[num_of_intersects] != intersection[num_of_intersects-1]) ) ++num_of_intersects; } @@ -531,32 +784,176 @@ static void v_fillarea(int count, int *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; + } + } + } +} + static void rb_draw_poly(void *handle, int *coords, int npoints, int fillcolor, int outlinecolor) { - LOGF("rb_draw_poly"); - - if(fillcolor >= 0) + if(!zoom_enabled) { - rb_color(fillcolor); + 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) + /* 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, 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); + 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) @@ -564,98 +961,103 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, 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]; + 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(x1, y1, x2, y2); + draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2); + } + else + { + LOGF("rb_draw_poly"); -#ifdef DEBUG_MENU - if(settings.polyanim) + if(fillcolor >= 0) { - rb->lcd_update(); - rb->sleep(HZ/4); + 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); + } } -#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); + /* 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); + } - rb->lcd_drawline(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); } - else - draw_antialiased_line(x1, y1, x2, y2); } static void rb_draw_circle(void *handle, int cx, int cy, int radius, int fillcolor, int outlinecolor) { - LOGF("rb_draw_circle(%d, %d, %d)", cx, cy, radius); - offset_coords(&cx, &cy); - - if(fillcolor >= 0) + if(!zoom_enabled) { - rb_color(fillcolor); - xlcd_fillcircle(cx, cy, radius - 1); + 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); - xlcd_drawcircle(cx, cy, radius - 1); + assert(outlinecolor >= 0); + rb_color(outlinecolor); + zoom_drawcircle(cx, cy, radius - 1); + } } struct blitter { @@ -680,11 +1082,14 @@ static void trim_rect(int *x, int *y, int *w, int *h) 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 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x0); - x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x1); - y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y0); - y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y1); + 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; @@ -723,12 +1128,13 @@ static void rb_blitter_save(void *handle, blitter *bl, int x, int y) { int w = bl->bmp.width, h = bl->bmp.height; 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, - rb->lcd_framebuffer + (y + i) * LCD_WIDTH + x, + fb + (y + i) * LCD_WIDTH + x, w * sizeof(fb_data)); } bl->x = x; @@ -748,9 +1154,24 @@ static void rb_blitter_load(void *handle, blitter *bl, int x, int y) if(x == BLITTER_FROMSAVED) x = bl->x; if(y == BLITTER_FROMSAVED) y = bl->y; - offset_coords(&x, &y); + if(!zoom_enabled) + offset_coords(&x, &y); + trim_rect(&x, &y, &w, &h); - rb->lcd_bitmap((fb_data*)bl->bmp.data, 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 + i * zoom_w + x, + bl->bmp.data + sizeof(fb_data) * i * w, + w * sizeof(fb_data)); + } + } } static bool need_draw_update = false; @@ -793,11 +1214,17 @@ static void rb_start_draw(void *handle) static void rb_end_draw(void *handle) { (void) handle; + if(!zoom_enabled) + { + LOGF("rb_end_draw"); - 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)); + 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 char *titlebar = NULL; @@ -824,7 +1251,8 @@ static void draw_title(void) rb_unclip(NULL); int h; - rb->lcd_setfont(FONT_UI); + cur_font = FONT_UI; + rb->lcd_setfont(cur_font); rb->lcd_getstringsize(str, NULL, &h); rb->lcd_set_foreground(BG_COLOR); @@ -866,6 +1294,73 @@ const drawing_api rb_drawing = { NULL, }; +/* render to a virtual framebuffer and let the user pan (but not make any moves) */ +static void zoom(void) +{ + 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); + + /* allocate a framebuffer */ + zoom_fb = smalloc(zoom_w * zoom_h * sizeof(fb_data)); + zoom_enabled = true; + + /* draws go to the enlarged 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); + rb->lcd_update(); + + /* pan around the image */ + while(1) + { + int button = rb->button_get(true); + 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; + 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; + + rb->lcd_bitmap_part(zoom_fb, x, y, STRIDE(SCREEN_MAIN, zoom_w, zoom_h), + 0, 0, LCD_WIDTH, LCD_HEIGHT); + rb->lcd_update(); + } +} + void frontend_default_color(frontend *fe, float *out) { *out++ = BG_R; @@ -1123,7 +1618,8 @@ static bool config_menu(void) char *title; config_item *config = midend_get_config(me, CFG_SETTINGS, &title); - rb->lcd_setfont(FONT_UI); + cur_font = FONT_UI; + rb->lcd_setfont(cur_font); bool success = false; @@ -1318,7 +1814,8 @@ static void full_help(const char *name) rb->lcd_set_foreground(LCD_WHITE); rb->lcd_set_background(LCD_BLACK); unload_fonts(); - rb->lcd_setfont(FONT_UI); + cur_font = FONT_UI; + rb->lcd_setfont(cur_font); char *buf = smalloc(help_text_len); LZ4_decompress_tiny(help_text, buf, help_text_len); @@ -1354,7 +1851,7 @@ static void bench_aa(void) int i = 0; while(*rb->current_tick < next) { - draw_antialiased_line(0, 0, 20, 31); + draw_antialiased_line(rb->lcd_framebuffer, LCD_WIDTH, LCD_HEIGHT, 0, 0, 20, 31); ++i; } rb->splashf(HZ, "%d AA lines/sec", i); @@ -1460,7 +1957,7 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item) if(!midend_get_presets(me, NULL)->n_entries) return ACTION_EXIT_MENUITEM; break; - case 10: + case 11: #if defined(FOR_REAL) && defined(DEBUG_MENU) if(debug_mode) break; @@ -1468,7 +1965,7 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item) #else break; #endif - case 11: + case 12: if(!midend_which_game(me)->can_configure) return ACTION_EXIT_MENUITEM; break; @@ -1507,6 +2004,7 @@ static int pause_menu(void) "Undo", "Redo", "Solve", + "Zoom In", "Quick Help", "Extensive Help", "Playback Control", @@ -1568,15 +2066,18 @@ static int pause_menu(void) break; } case 6: - quick_help(); + zoom(); break; case 7: - full_help(midend_which_game(me)->name); + quick_help(); break; case 8: - playback_control(NULL); + full_help(midend_which_game(me)->name); break; case 9: + playback_control(NULL); + break; + case 10: if(presets_menu()) { midend_new_game(me); @@ -1586,12 +2087,12 @@ static int pause_menu(void) quit = true; } break; - case 10: + case 11: #ifdef DEBUG_MENU debug_menu(); #endif break; - case 11: + case 12: if(config_menu()) { midend_new_game(me); @@ -1601,9 +2102,9 @@ static int pause_menu(void) quit = true; } break; - case 12: - return -2; case 13: + return -2; + case 14: return -3; default: break; @@ -1846,7 +2347,8 @@ static size_t giant_buffer_len = 0; /* set on start */ static void fix_size(void) { int w = LCD_WIDTH, h = LCD_HEIGHT, h_x; - rb->lcd_setfont(FONT_UI); + 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); -- cgit v1.2.3