summaryrefslogtreecommitdiff
path: root/apps/plugins/puzzles/rockbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/puzzles/rockbox.c')
-rw-r--r--apps/plugins/puzzles/rockbox.c535
1 files changed, 131 insertions, 404 deletions
diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c
index 263a19f421..27060208fc 100644
--- a/apps/plugins/puzzles/rockbox.c
+++ b/apps/plugins/puzzles/rockbox.c
@@ -7,7 +7,7 @@
7 * \/ \/ \/ \/ \/ 7 * \/ \/ \/ \/ \/
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2016-2020 Franklin Wei 10 * Copyright (C) 2016-2024 Franklin Wei
11 * 11 *
12 * This program is free software; you can redistribute it and/or 12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 13 * modify it under the terms of the GNU General Public License
@@ -24,10 +24,9 @@
24 * ================================ 24 * ================================
25 * 25 *
26 * This file contains the majority of the rockbox-specific code for 26 * This file contains the majority of the rockbox-specific code for
27 * the sgt-puzzles port. It implements a set of functions for the 27 * the sgt-puzzles port. It implements an API for the backend to call
28 * backend to call to actually run the games, as well as rockbox UI 28 * to run the games, as well as the rockbox UI code (menus, input,
29 * code (menus, input, etc). For a good overview of the rest of the 29 * etc). For a good overview of the rest of the puzzles code, see:
30 * puzzles code, see:
31 * 30 *
32 * <https://www.chiark.greenend.org.uk/~sgtatham/puzzles/devel/>. 31 * <https://www.chiark.greenend.org.uk/~sgtatham/puzzles/devel/>.
33 * 32 *
@@ -37,7 +36,7 @@
37 * Contents of this file 36 * Contents of this file
38 * --------------------- 37 * ---------------------
39 * 38 *
40 * By rough order of appearnce in this file: 39 * By rough order of appearance in this file:
41 * 40 *
42 * 1) "Zoom" feature 41 * 1) "Zoom" feature
43 * 42 *
@@ -62,8 +61,7 @@
62 * mode switching. In commit 5094aaa, this behavior was changed so 61 * mode switching. In commit 5094aaa, this behavior was changed so
63 * that the frontend can now query the backend for the on-screen 62 * that the frontend can now query the backend for the on-screen
64 * cursor location and move the viewport accordingly through the 63 * cursor location and move the viewport accordingly through the
65 * new midend_get_cursor_location() API (which is not yet merged 64 * new midend_get_cursor_location() API.
66 * into Simon's tree as of October 2020).
67 * 65 *
68 * 2) Font management 66 * 2) Font management
69 * 67 *
@@ -82,8 +80,8 @@
82 * 80 *
83 * 3) Drawing API 81 * 3) Drawing API
84 * 82 *
85 * The sgt-puzzles backend wants a set of function pointers to the 83 * The sgt-puzzles backend wants a set of function pointers to
86 * usual drawing primitives. [1] If the `zoom_enabled' switch is 84 * typical drawing primitives. [1] If the `zoom_enabled' switch is
87 * on, these call upon the "zoomed" drawing routines in (1). 85 * on, these call upon the "zoomed" drawing routines in (1).
88 * 86 *
89 * In the normal un-zoomed case, these functions generally rely on 87 * In the normal un-zoomed case, these functions generally rely on
@@ -108,7 +106,7 @@
108 * a) Mouse mode 106 * a) Mouse mode
109 * 107 *
110 * This mode is designed to accommodate puzzles without a 108 * This mode is designed to accommodate puzzles without a
111 * keyboard or cursor interface (currently only "Loopy"). We 109 * keyboard or cursor interface (currently only Loopyx). We
112 * remap the cursor keys to move an on-screen cursor rather 110 * remap the cursor keys to move an on-screen cursor rather
113 * than sending arrow keys to the game. 111 * than sending arrow keys to the game.
114 * 112 *
@@ -163,6 +161,11 @@
163 * cases by waiting until a key has been released before we 161 * cases by waiting until a key has been released before we
164 * send the input keystroke(s) to the game. 162 * send the input keystroke(s) to the game.
165 * 163 *
164 * e) Key repeat
165 *
166 * In some games, we would like to send repeated key events to
167 * allow long drags. Currently, this is only used in Untangle.
168 *
166 * 5) Game configuration and preset management 169 * 5) Game configuration and preset management
167 * 170 *
168 * The backend games specify a hierarchy of user-adjustable game 171 * The backend games specify a hierarchy of user-adjustable game
@@ -170,6 +173,10 @@
170 * generation, etc. Also supplied are a set of "presets" that 173 * generation, etc. Also supplied are a set of "presets" that
171 * specify a predetermined set of configuration parameters. 174 * specify a predetermined set of configuration parameters.
172 * 175 *
176 * In 2023, Simon introduced a User Preferences system that allows
177 * further customization of the game UI (e.g., "snap to grid" in
178 * Untangle). Rockbox support for this was added in July 2024.
179 *
173 * 6) In-game help 180 * 6) In-game help
174 * 181 *
175 * The sgt-puzzles manual (src/puzzles.but) contains a chapter 182 * The sgt-puzzles manual (src/puzzles.but) contains a chapter
@@ -315,6 +322,7 @@ static struct viewport clip_rect;
315static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false; 322static bool clipped = false, zoom_enabled = false, view_mode = true, mouse_mode = false;
316 323
317static int mouse_x, mouse_y; 324static int mouse_x, mouse_y;
325static bool mouse_dragging = false; /* for sticky mode only */
318 326
319extern bool audiobuf_available; /* defined in rbmalloc.c */ 327extern bool audiobuf_available; /* defined in rbmalloc.c */
320 328
@@ -339,6 +347,7 @@ static struct {
339 bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */ 347 bool ignore_repeats; /* ignore repeated button events (currently in all games but Untangle) */
340 bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */ 348 bool rclick_on_hold; /* if in mouse mode, send right-click on long-press of select */
341 bool numerical_chooser; /* repurpose select to activate a numerical chooser */ 349 bool numerical_chooser; /* repurpose select to activate a numerical chooser */
350 bool sticky_mouse; /* if mouse left button should be persistent and toggled on/off */
342} input_settings; 351} input_settings;
343 352
344static bool accept_input = true; 353static bool accept_input = true;
@@ -741,12 +750,13 @@ static void rb_color(int n)
741 fatal("bad color %d", n); 750 fatal("bad color %d", n);
742 return; 751 return;
743 } 752 }
744 rb->lcd_set_foreground(colors[n]); 753 if(colors)
754 rb->lcd_set_foreground(colors[n]);
745} 755}
746 756
747/* clipping is implemented through viewports and offsetting 757/* clipping is implemented through viewports and offsetting
748 * coordinates */ 758 * coordinates */
749static void rb_clip(void *handle, int x, int y, int w, int h) 759static void rb_clip(drawing *dr, int x, int y, int w, int h)
750{ 760{
751 if(!zoom_enabled) 761 if(!zoom_enabled)
752 { 762 {
@@ -776,7 +786,7 @@ static void rb_clip(void *handle, int x, int y, int w, int h)
776 } 786 }
777} 787}
778 788
779static void rb_unclip(void *handle) 789static void rb_unclip(drawing *dr)
780{ 790{
781 if(!zoom_enabled) 791 if(!zoom_enabled)
782 { 792 {
@@ -793,7 +803,7 @@ static void rb_unclip(void *handle)
793 } 803 }
794} 804}
795 805
796static void rb_draw_text(void *handle, int x, int y, int fonttype, 806static void rb_draw_text(drawing *dr, int x, int y, int fonttype,
797 int fontsize, int align, int color, const char *text) 807 int fontsize, int align, int color, const char *text)
798{ 808{
799 (void) fontsize; 809 (void) fontsize;
@@ -858,7 +868,7 @@ static void rb_draw_text(void *handle, int x, int y, int fonttype,
858 } 868 }
859} 869}
860 870
861static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color) 871static void rb_draw_rect(drawing *dr, int x, int y, int w, int h, int color)
862{ 872{
863 rb_color(color); 873 rb_color(color);
864 if(!zoom_enabled) 874 if(!zoom_enabled)
@@ -1000,7 +1010,7 @@ static void draw_antialiased_line(fb_data *fb, int w, int h, int x0, int y0, int
1000 } 1010 }
1001} 1011}
1002 1012
1003static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, 1013static void rb_draw_line(drawing *dr, int x1, int y1, int x2, int y2,
1004 int color) 1014 int color)
1005{ 1015{
1006 rb_color(color); 1016 rb_color(color);
@@ -1028,349 +1038,7 @@ static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2,
1028 } 1038 }
1029} 1039}
1030 1040
1031#if 0 1041static void rb_draw_circle(drawing *dr, int cx, int cy, int radius,
1032/*
1033 * draw filled polygon
1034 * originally by Sebastian Leonhardt (ulmutul)
1035 * 'count' : number of coordinate pairs
1036 * 'pxy': array of coordinates. pxy[0]=x0,pxy[1]=y0,...
1037 * note: provide space for one extra coordinate, because the starting point
1038 * will automatically be inserted as end point.
1039 */
1040
1041/*
1042 * helper function:
1043 * find points of intersection between polygon and scanline
1044 */
1045
1046#define MAX_INTERSECTION 32
1047
1048static void fill_poly_line(int scanline, int count, int *pxy)
1049{
1050 int i;
1051 int j;
1052 int num_of_intersects;
1053 int direct, old_direct;
1054 //intersections of every line with scanline (y-coord)
1055 int intersection[MAX_INTERSECTION];
1056 /* add starting point as ending point */
1057 pxy[count*2] = pxy[0];
1058 pxy[count*2+1] = pxy[1];
1059
1060 old_direct=0;
1061 num_of_intersects=0;
1062 for (i=0; i<count*2; i+=2) {
1063 int x1=pxy[i];
1064 int y1=pxy[i+1];
1065 int x2=pxy[i+2];
1066 int y2=pxy[i+3];
1067 // skip if line is outside of scanline
1068 if (y1 < y2) {
1069 if (scanline < y1 || scanline > y2)
1070 continue;
1071 }
1072 else {
1073 if (scanline < y2 || scanline > y1)
1074 continue;
1075 }
1076 // calculate x-coord of intersection
1077 if (y1==y2) {
1078 direct=0;
1079 }
1080 else {
1081 direct = y1>y2 ? 1 : -1;
1082 // omit double intersections, if both lines lead in the same direction
1083 intersection[num_of_intersects] =
1084 x1+((scanline-y1)*(x2-x1))/(y2-y1);
1085 if ( (direct!=old_direct)
1086 || (intersection[num_of_intersects] != intersection[num_of_intersects-1])
1087 )
1088 ++num_of_intersects;
1089 }
1090 old_direct = direct;
1091 }
1092
1093 // sort points of intersection
1094 for (i=0; i<num_of_intersects-1; ++i) {
1095 for (j=i+1; j<num_of_intersects; ++j) {
1096 if (intersection[j]<intersection[i]) {
1097 int temp=intersection[i];
1098 intersection[i]=intersection[j];
1099 intersection[j]=temp;
1100 }
1101 }
1102 }
1103 // draw
1104 for (i=0; i<num_of_intersects; i+=2) {
1105 rb->lcd_hline(intersection[i], intersection[i+1], scanline);
1106 }
1107}
1108
1109/* two extra elements at end of pxy needed */
1110static void v_fillarea(int count, int *pxy)
1111{
1112 int i;
1113 int y1, y2;
1114
1115 // find min and max y coords
1116 y1=y2=pxy[1];
1117 for (i=3; i<count*2; i+=2) {
1118 if (pxy[i] < y1) y1 = pxy[i];
1119 else if (pxy[i] > y2) y2 = pxy[i];
1120 }
1121
1122 for (i=y1; i<=y2; ++i) {
1123 fill_poly_line(i, count, pxy);
1124 }
1125}
1126#endif
1127
1128/* I'm a horrible person: this was copy-pasta'd straight from
1129 * xlcd_draw.c */
1130
1131/* sort the given coordinates by increasing x value */
1132static void sort_points_by_increasing_x(int* x1, int* y1,
1133 int* x2, int* y2,
1134 int* x3, int* y3)
1135{
1136 int x, y;
1137 if (*x1 > *x3)
1138 {
1139 if (*x2 < *x3) /* x2 < x3 < x1 */
1140 {
1141 x = *x1; *x1 = *x2; *x2 = *x3; *x3 = x;
1142 y = *y1; *y1 = *y2; *y2 = *y3; *y3 = y;
1143 }
1144 else if (*x2 > *x1) /* x3 < x1 < x2 */
1145 {
1146 x = *x1; *x1 = *x3; *x3 = *x2; *x2 = x;
1147 y = *y1; *y1 = *y3; *y3 = *y2; *y2 = y;
1148 }
1149 else /* x3 <= x2 <= x1 */
1150 {
1151 x = *x1; *x1 = *x3; *x3 = x;
1152 y = *y1; *y1 = *y3; *y3 = y;
1153 }
1154 }
1155 else
1156 {
1157 if (*x2 < *x1) /* x2 < x1 <= x3 */
1158 {
1159 x = *x1; *x1 = *x2; *x2 = x;
1160 y = *y1; *y1 = *y2; *y2 = y;
1161 }
1162 else if (*x2 > *x3) /* x1 <= x3 < x2 */
1163 {
1164 x = *x2; *x2 = *x3; *x3 = x;
1165 y = *y2; *y2 = *y3; *y3 = y;
1166 }
1167 /* else already sorted */
1168 }
1169}
1170
1171#define sort_points_by_increasing_y(x1, y1, x2, y2, x3, y3) \
1172 sort_points_by_increasing_x(y1, x1, y2, x2, y3, x3)
1173
1174/* draw a filled triangle, using horizontal lines for speed */
1175static void zoom_filltriangle(int x1, int y1,
1176 int x2, int y2,
1177 int x3, int y3)
1178{
1179 long fp_x1, fp_x2, fp_dx1, fp_dx2;
1180 int y;
1181 sort_points_by_increasing_y(&x1, &y1, &x2, &y2, &x3, &y3);
1182
1183 if (y1 < y3) /* draw */
1184 {
1185 fp_dx1 = ((x3 - x1) << 16) / (y3 - y1);
1186 fp_x1 = (x1 << 16) + (1<<15) + (fp_dx1 >> 1);
1187
1188 if (y1 < y2) /* first part */
1189 {
1190 fp_dx2 = ((x2 - x1) << 16) / (y2 - y1);
1191 fp_x2 = (x1 << 16) + (1<<15) + (fp_dx2 >> 1);
1192 for (y = y1; y < y2; y++)
1193 {
1194 zoom_hline(fp_x1 >> 16, fp_x2 >> 16, y);
1195 fp_x1 += fp_dx1;
1196 fp_x2 += fp_dx2;
1197 }
1198 }
1199 if (y2 < y3) /* second part */
1200 {
1201 fp_dx2 = ((x3 - x2) << 16) / (y3 - y2);
1202 fp_x2 = (x2 << 16) + (1<<15) + (fp_dx2 >> 1);
1203 for (y = y2; y < y3; y++)
1204 {
1205 zoom_hline(fp_x1 >> 16, fp_x2 >> 16, y);
1206 fp_x1 += fp_dx1;
1207 fp_x2 += fp_dx2;
1208 }
1209 }
1210 }
1211}
1212
1213/* Should probably refactor this */
1214static void rb_draw_poly(void *handle, int *coords, int npoints,
1215 int fillcolor, int outlinecolor)
1216{
1217 if(!zoom_enabled)
1218 {
1219 LOGF("rb_draw_poly");
1220
1221 if(fillcolor >= 0)
1222 {
1223 rb_color(fillcolor);
1224#if 1
1225 /* serious hack: draw a bunch of triangles between adjacent points */
1226 /* this generally works, even with some concave polygons */
1227 for(int i = 2; i < npoints; ++i)
1228 {
1229 int x1, y1, x2, y2, x3, y3;
1230 x1 = coords[0];
1231 y1 = coords[1];
1232 x2 = coords[(i - 1) * 2];
1233 y2 = coords[(i - 1) * 2 + 1];
1234 x3 = coords[i * 2];
1235 y3 = coords[i * 2 + 1];
1236 offset_coords(&x1, &y1);
1237 offset_coords(&x2, &y2);
1238 offset_coords(&x3, &y3);
1239 xlcd_filltriangle(x1, y1,
1240 x2, y2,
1241 x3, y3);
1242
1243#ifdef DEBUG_MENU
1244 if(debug_settings.polyanim)
1245 {
1246 rb->lcd_update();
1247 rb->sleep(HZ/4);
1248 }
1249#endif
1250#if 0
1251 /* debug code */
1252 rb->lcd_set_foreground(LCD_RGBPACK(255,0,0));
1253 rb->lcd_drawpixel(x1, y1);
1254 rb->lcd_drawpixel(x2, y2);
1255 rb->lcd_drawpixel(x3, y3);
1256 rb->lcd_update();
1257 rb->sleep(HZ);
1258 rb_color(fillcolor);
1259 rb->lcd_drawpixel(x1, y1);
1260 rb->lcd_drawpixel(x2, y2);
1261 rb->lcd_drawpixel(x3, y3);
1262 rb->lcd_update();
1263#endif
1264 }
1265#else
1266 int *pxy = smalloc(sizeof(int) * 2 * npoints + 2);
1267 /* copy points, offsetted */
1268 for(int i = 0; i < npoints; ++i)
1269 {
1270 pxy[2 * i + 0] = coords[2 * i + 0];
1271 pxy[2 * i + 1] = coords[2 * i + 1];
1272 offset_coords(&pxy[2*i+0], &pxy[2*i+1]);
1273 }
1274 v_fillarea(npoints, pxy);
1275 sfree(pxy);
1276#endif
1277 }
1278
1279 /* draw outlines last so they're not covered by the fill */
1280 assert(outlinecolor >= 0);
1281 rb_color(outlinecolor);
1282
1283 for(int i = 1; i < npoints; ++i)
1284 {
1285 int x1, y1, x2, y2;
1286 x1 = coords[2 * (i - 1)];
1287 y1 = coords[2 * (i - 1) + 1];
1288 x2 = coords[2 * i];
1289 y2 = coords[2 * i + 1];
1290 if(debug_settings.no_aa)
1291 {
1292 offset_coords(&x1, &y1);
1293 offset_coords(&x2, &y2);
1294 rb->lcd_drawline(x1, y1,
1295 x2, y2);
1296 }
1297 else
1298 draw_antialiased_line(lcd_fb, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2);
1299
1300#ifdef DEBUG_MENU
1301 if(debug_settings.polyanim)
1302 {
1303 rb->lcd_update();
1304 rb->sleep(HZ/4);
1305 }
1306#endif
1307 }
1308
1309 int x1, y1, x2, y2;
1310 x1 = coords[0];
1311 y1 = coords[1];
1312 x2 = coords[2 * (npoints - 1)];
1313 y2 = coords[2 * (npoints - 1) + 1];
1314 if(debug_settings.no_aa)
1315 {
1316 offset_coords(&x1, &y1);
1317 offset_coords(&x2, &y2);
1318
1319 rb->lcd_drawline(x1, y1,
1320 x2, y2);
1321 }
1322 else
1323 draw_antialiased_line(lcd_fb, LCD_WIDTH, LCD_HEIGHT, x1, y1, x2, y2);
1324 }
1325 else
1326 {
1327 LOGF("rb_draw_poly");
1328
1329 if(fillcolor >= 0)
1330 {
1331 rb_color(fillcolor);
1332
1333 /* serious hack: draw a bunch of triangles between adjacent points */
1334 /* this generally works, even with some concave polygons */
1335 for(int i = 2; i < npoints; ++i)
1336 {
1337 int x1, y1, x2, y2, x3, y3;
1338 x1 = coords[0];
1339 y1 = coords[1];
1340 x2 = coords[(i - 1) * 2];
1341 y2 = coords[(i - 1) * 2 + 1];
1342 x3 = coords[i * 2];
1343 y3 = coords[i * 2 + 1];
1344 zoom_filltriangle(x1, y1,
1345 x2, y2,
1346 x3, y3);
1347 }
1348 }
1349
1350 /* draw outlines last so they're not covered by the fill */
1351 assert(outlinecolor >= 0);
1352 rb_color(outlinecolor);
1353
1354 for(int i = 1; i < npoints; ++i)
1355 {
1356 int x1, y1, x2, y2;
1357 x1 = coords[2 * (i - 1)];
1358 y1 = coords[2 * (i - 1) + 1];
1359 x2 = coords[2 * i];
1360 y2 = coords[2 * i + 1];
1361 draw_antialiased_line(zoom_fb, zoom_w, zoom_h, x1, y1, x2, y2);
1362 }
1363
1364 int x1, y1, x2, y2;
1365 x1 = coords[0];
1366 y1 = coords[1];
1367 x2 = coords[2 * (npoints - 1)];
1368 y2 = coords[2 * (npoints - 1) + 1];
1369 draw_antialiased_line(zoom_fb, zoom_w, zoom_h, x1, y1, x2, y2);
1370 }
1371}
1372
1373static void rb_draw_circle(void *handle, int cx, int cy, int radius,
1374 int fillcolor, int outlinecolor) 1042 int fillcolor, int outlinecolor)
1375{ 1043{
1376 if(!zoom_enabled) 1044 if(!zoom_enabled)
@@ -1442,7 +1110,7 @@ static void trim_rect(int *x, int *y, int *w, int *h)
1442 *h = y1 - y0; 1110 *h = y1 - y0;
1443} 1111}
1444 1112
1445static blitter *rb_blitter_new(void *handle, int w, int h) 1113static blitter *rb_blitter_new(drawing *dr, int w, int h)
1446{ 1114{
1447 LOGF("rb_blitter_new"); 1115 LOGF("rb_blitter_new");
1448 blitter *b = snew(blitter); 1116 blitter *b = snew(blitter);
@@ -1453,7 +1121,7 @@ static blitter *rb_blitter_new(void *handle, int w, int h)
1453 return b; 1121 return b;
1454} 1122}
1455 1123
1456static void rb_blitter_free(void *handle, blitter *bl) 1124static void rb_blitter_free(drawing *dr, blitter *bl)
1457{ 1125{
1458 LOGF("rb_blitter_free"); 1126 LOGF("rb_blitter_free");
1459 sfree(bl->bmp.data); 1127 sfree(bl->bmp.data);
@@ -1462,7 +1130,7 @@ static void rb_blitter_free(void *handle, blitter *bl)
1462} 1130}
1463 1131
1464/* copy a section of the framebuffer */ 1132/* copy a section of the framebuffer */
1465static void rb_blitter_save(void *handle, blitter *bl, int x, int y) 1133static void rb_blitter_save(drawing *dr, blitter *bl, int x, int y)
1466{ 1134{
1467 /* no viewport offset */ 1135 /* no viewport offset */
1468#if LCD_STRIDEFORMAT == VERTICAL_STRIDE 1136#if LCD_STRIDEFORMAT == VERTICAL_STRIDE
@@ -1491,7 +1159,7 @@ static void rb_blitter_save(void *handle, blitter *bl, int x, int y)
1491#endif 1159#endif
1492} 1160}
1493 1161
1494static void rb_blitter_load(void *handle, blitter *bl, int x, int y) 1162static void rb_blitter_load(drawing *dr, blitter *bl, int x, int y)
1495{ 1163{
1496 LOGF("rb_blitter_load"); 1164 LOGF("rb_blitter_load");
1497 if(!bl->have_data) 1165 if(!bl->have_data)
@@ -1521,7 +1189,7 @@ static void rb_blitter_load(void *handle, blitter *bl, int x, int y)
1521 } 1189 }
1522} 1190}
1523 1191
1524static void rb_draw_update(void *handle, int x, int y, int w, int h) 1192static void rb_draw_update(drawing *dr, int x, int y, int w, int h)
1525{ 1193{
1526 LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h); 1194 LOGF("rb_draw_update(%d, %d, %d, %d)", x, y, w, h);
1527 1195
@@ -1545,9 +1213,9 @@ static void rb_draw_update(void *handle, int x, int y, int w, int h)
1545 need_draw_update = true; 1213 need_draw_update = true;
1546} 1214}
1547 1215
1548static void rb_start_draw(void *handle) 1216static void rb_start_draw(drawing *dr)
1549{ 1217{
1550 (void) handle; 1218 (void) dr;
1551 1219
1552 /* ... mumble mumble ... not ... reentrant ... mumble mumble ... */ 1220 /* ... mumble mumble ... not ... reentrant ... mumble mumble ... */
1553 1221
@@ -1558,9 +1226,9 @@ static void rb_start_draw(void *handle)
1558 ud_d = LCD_HEIGHT; 1226 ud_d = LCD_HEIGHT;
1559} 1227}
1560 1228
1561static void rb_end_draw(void *handle) 1229static void rb_end_draw(drawing *dr)
1562{ 1230{
1563 (void) handle; 1231 (void) dr;
1564 1232
1565 if(debug_settings.highlight_cursor) 1233 if(debug_settings.highlight_cursor)
1566 { 1234 {
@@ -1587,7 +1255,7 @@ static void rb_end_draw(void *handle)
1587#endif 1255#endif
1588} 1256}
1589 1257
1590static void rb_status_bar(void *handle, const char *text) 1258static void rb_status_bar(drawing *dr, const char *text)
1591{ 1259{
1592 if(titlebar) 1260 if(titlebar)
1593 sfree(titlebar); 1261 sfree(titlebar);
@@ -1619,7 +1287,8 @@ static void draw_title(bool clear_first)
1619 rb->lcd_setfont(cur_font = FONT_UI); 1287 rb->lcd_setfont(cur_font = FONT_UI);
1620 rb->lcd_getstringsize(str, &w, &h); 1288 rb->lcd_getstringsize(str, &w, &h);
1621 1289
1622 rb->lcd_set_foreground(BG_COLOR); 1290
1291 rb->lcd_set_foreground(colors ? colors[0] : BG_COLOR);
1623 rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h); 1292 rb->lcd_fillrect(0, LCD_HEIGHT - h, clear_first ? LCD_WIDTH : w, h);
1624 1293
1625 rb->lcd_set_drawmode(DRMODE_FG); 1294 rb->lcd_set_drawmode(DRMODE_FG);
@@ -1685,7 +1354,7 @@ static void draw_mouse(void)
1685 * glyph exists in a font) */ 1354 * glyph exists in a font) */
1686#if 0 1355#if 0
1687/* See: https://www.chiark.greenend.org.uk/~sgtatham/puzzles/devel/drawing.html#drawing-text-fallback */ 1356/* See: https://www.chiark.greenend.org.uk/~sgtatham/puzzles/devel/drawing.html#drawing-text-fallback */
1688static char *rb_text_fallback(void *handle, const char *const *strings, 1357static char *rb_text_fallback(drawing *dr, const char *const *strings,
1689 int nstrings) 1358 int nstrings)
1690{ 1359{
1691 struct font *pf = rb->font_get(cur_font); 1360 struct font *pf = rb->font_get(cur_font);
@@ -1718,10 +1387,11 @@ static char *rb_text_fallback(void *handle, const char *const *strings,
1718#endif 1387#endif
1719 1388
1720const drawing_api rb_drawing = { 1389const drawing_api rb_drawing = {
1390 1,
1721 rb_draw_text, 1391 rb_draw_text,
1722 rb_draw_rect, 1392 rb_draw_rect,
1723 rb_draw_line, 1393 rb_draw_line,
1724 rb_draw_poly, 1394 draw_polygon_fallback,
1725 rb_draw_circle, 1395 rb_draw_circle,
1726 rb_draw_update, 1396 rb_draw_update,
1727 rb_clip, 1397 rb_clip,
@@ -2016,9 +1686,17 @@ static int process_input(int tmo, bool do_pausemenu)
2016 LOGF("sending left click"); 1686 LOGF("sending left click");
2017 send_click(LEFT_BUTTON, true); /* right-click is handled earlier */ 1687 send_click(LEFT_BUTTON, true); /* right-click is handled earlier */
2018 } 1688 }
2019 } 1689 } else if(input_settings.sticky_mouse) {
2020 else 1690 if(pressed & BTN_FIRE) {
2021 { 1691 send_click(LEFT_BUTTON, false);
1692 accept_input = false;
1693 mouse_dragging = !mouse_dragging;
1694 } else if(mouse_dragging) {
1695 send_click(LEFT_DRAG, false);
1696 } else {
1697 send_click(LEFT_RELEASE, false);
1698 }
1699 } else {
2022 if(pressed & BTN_FIRE) { 1700 if(pressed & BTN_FIRE) {
2023 send_click(LEFT_BUTTON, false); 1701 send_click(LEFT_BUTTON, false);
2024 accept_input = false; 1702 accept_input = false;
@@ -2268,7 +1946,7 @@ static void zoom(void)
2268 zoom_clipl = 0; 1946 zoom_clipl = 0;
2269 zoom_clipr = zoom_w; 1947 zoom_clipr = zoom_w;
2270 1948
2271 midend_size(me, &zoom_w, &zoom_h, true); 1949 midend_size(me, &zoom_w, &zoom_h, true, 1.0);
2272 1950
2273 /* Allocating the framebuffer will mostly likely grab the 1951 /* Allocating the framebuffer will mostly likely grab the
2274 * audiobuffer, which will make impossible to load new fonts, and 1952 * audiobuffer, which will make impossible to load new fonts, and
@@ -2448,7 +2126,6 @@ static int list_choose(const char *list_str, const char *title, int sel)
2448 struct gui_synclist list; 2126 struct gui_synclist list;
2449 2127
2450 rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL); 2128 rb->gui_synclist_init(&list, &config_choices_formatter, (void*)list_str, false, 1, NULL);
2451 rb->gui_synclist_set_icon_callback(&list, NULL);
2452 rb->gui_synclist_set_nb_items(&list, n); 2129 rb->gui_synclist_set_nb_items(&list, n);
2453 2130
2454 rb->gui_synclist_select_item(&list, sel); 2131 rb->gui_synclist_select_item(&list, sel);
@@ -2634,10 +2311,10 @@ const char *config_formatter(int sel, void *data, char *buf, size_t len)
2634 return buf; 2311 return buf;
2635} 2312}
2636 2313
2637static bool config_menu(void) 2314static bool config_menu_core(int which)
2638{ 2315{
2639 char *title; 2316 char *title;
2640 config_item *config = midend_get_config(me, CFG_SETTINGS, &title); 2317 config_item *config = midend_get_config(me, which, &title);
2641 2318
2642 rb->lcd_setfont(cur_font = FONT_UI); 2319 rb->lcd_setfont(cur_font = FONT_UI);
2643 2320
@@ -2661,7 +2338,6 @@ static bool config_menu(void)
2661 struct gui_synclist list; 2338 struct gui_synclist list;
2662 2339
2663 rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL); 2340 rb->gui_synclist_init(&list, &config_formatter, config, false, 1, NULL);
2664 rb->gui_synclist_set_icon_callback(&list, NULL);
2665 rb->gui_synclist_set_nb_items(&list, n); 2341 rb->gui_synclist_set_nb_items(&list, n);
2666 2342
2667 rb->gui_synclist_select_item(&list, 0); 2343 rb->gui_synclist_select_item(&list, 0);
@@ -2689,7 +2365,7 @@ static bool config_menu(void)
2689 old_str = dupstr(old.u.string.sval); 2365 old_str = dupstr(old.u.string.sval);
2690 2366
2691 bool freed_str = do_configure_item(config, pos); 2367 bool freed_str = do_configure_item(config, pos);
2692 const char *err = midend_set_config(me, CFG_SETTINGS, config); 2368 const char *err = midend_set_config(me, which, config);
2693 2369
2694 if(err) 2370 if(err)
2695 { 2371 {
@@ -2728,6 +2404,16 @@ done:
2728 return success; 2404 return success;
2729} 2405}
2730 2406
2407static bool config_menu(void)
2408{
2409 return config_menu_core(CFG_SETTINGS);
2410}
2411
2412static bool preferences_menu(void)
2413{
2414 return config_menu_core(CFG_PREFS);
2415}
2416
2731static const char *preset_formatter(int sel, void *data, char *buf, size_t len) 2417static const char *preset_formatter(int sel, void *data, char *buf, size_t len)
2732{ 2418{
2733 struct preset_menu *menu = data; 2419 struct preset_menu *menu = data;
@@ -2746,7 +2432,6 @@ static int do_preset_menu(struct preset_menu *menu, char *title, int selected)
2746 struct gui_synclist list; 2432 struct gui_synclist list;
2747 2433
2748 rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL); 2434 rb->gui_synclist_init(&list, &preset_formatter, menu, false, 1, NULL);
2749 rb->gui_synclist_set_icon_callback(&list, NULL);
2750 rb->gui_synclist_set_nb_items(&list, menu->n_entries); 2435 rb->gui_synclist_set_nb_items(&list, menu->n_entries);
2751 2436
2752 rb->gui_synclist_select_item(&list, selected); 2437 rb->gui_synclist_select_item(&list, selected);
@@ -2809,6 +2494,7 @@ static bool presets_menu(void)
2809 2494
2810static void quick_help(void) 2495static void quick_help(void)
2811{ 2496{
2497#ifndef NO_HELP_TEXT
2812#if defined(FOR_REAL) && defined(DEBUG_MENU) 2498#if defined(FOR_REAL) && defined(DEBUG_MENU)
2813 if(++help_times >= 5) 2499 if(++help_times >= 5)
2814 { 2500 {
@@ -2819,11 +2505,12 @@ static void quick_help(void)
2819 2505
2820 rb->splash(0, quick_help_text); 2506 rb->splash(0, quick_help_text);
2821 rb->button_get(true); 2507 rb->button_get(true);
2822 return; 2508#endif
2823} 2509}
2824 2510
2825static void full_help(const char *name) 2511static void full_help(const char *name)
2826{ 2512{
2513#ifndef NO_HELP_TEXT
2827 unsigned old_bg = rb->lcd_get_background(); 2514 unsigned old_bg = rb->lcd_get_background();
2828 2515
2829 bool orig_clipped = clipped; 2516 bool orig_clipped = clipped;
@@ -2878,6 +2565,7 @@ static void full_help(const char *name)
2878 2565
2879 if(orig_clipped) 2566 if(orig_clipped)
2880 rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height); 2567 rb_clip(NULL, clip_rect.x, clip_rect.y, clip_rect.width, clip_rect.height);
2568#endif
2881} 2569}
2882 2570
2883static void init_default_settings(void) 2571static void init_default_settings(void)
@@ -3028,6 +2716,11 @@ static int pausemenu_cb(int action,
3028 if(!midend_which_game(me)->can_solve) 2716 if(!midend_which_game(me)->can_solve)
3029 return ACTION_EXIT_MENUITEM; 2717 return ACTION_EXIT_MENUITEM;
3030 break; 2718 break;
2719 case 7:
2720 case 8:
2721 if(!help_valid)
2722 return ACTION_EXIT_MENUITEM;
2723 break;
3031 case 9: 2724 case 9:
3032 if(audiobuf_available) 2725 if(audiobuf_available)
3033 break; 2726 break;
@@ -3078,7 +2771,7 @@ static void reset_drawing(void)
3078 rb->lcd_set_viewport(NULL); 2771 rb->lcd_set_viewport(NULL);
3079 rb->lcd_set_backdrop(NULL); 2772 rb->lcd_set_backdrop(NULL);
3080 rb->lcd_set_foreground(LCD_BLACK); 2773 rb->lcd_set_foreground(LCD_BLACK);
3081 rb->lcd_set_background(BG_COLOR); 2774 rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
3082} 2775}
3083 2776
3084/* Make a new game, but tell the user through a splash so they don't 2777/* Make a new game, but tell the user through a splash so they don't
@@ -3108,8 +2801,9 @@ static int pause_menu(void)
3108 "Game Type", // 10 2801 "Game Type", // 10
3109 "Debug Menu", // 11 2802 "Debug Menu", // 11
3110 "Configure Game", // 12 2803 "Configure Game", // 12
3111 "Quit without Saving", // 13 2804 "Preferences", // 13
3112 "Quit"); // 14 2805 "Quit without Saving", // 14
2806 "Quit"); // 15
3113 2807
3114#if defined(FOR_REAL) && defined(DEBUG_MENU) 2808#if defined(FOR_REAL) && defined(DEBUG_MENU)
3115 help_times = 0; 2809 help_times = 0;
@@ -3190,15 +2884,19 @@ static int pause_menu(void)
3190 quit = true; 2884 quit = true;
3191 } 2885 }
3192 break; 2886 break;
3193 case 13: 2887 case 13:
3194 return -2; 2888 preferences_menu();
2889 // do not go straight into game.
2890 break;
3195 case 14: 2891 case 14:
2892 return -2;
2893 case 15:
3196 return -3; 2894 return -3;
3197 default: 2895 default:
3198 break; 2896 break;
3199 } 2897 }
3200 } 2898 }
3201 rb->lcd_set_background(BG_COLOR); 2899 rb->lcd_set_background(colors ? colors[0] : BG_COLOR);
3202 rb->lcd_clear_display(); 2900 rb->lcd_clear_display();
3203 midend_force_redraw(me); 2901 midend_force_redraw(me);
3204 rb->lcd_update(); 2902 rb->lcd_update();
@@ -3217,7 +2915,7 @@ static void fix_size(void)
3217 rb->lcd_setfont(cur_font = FONT_UI); 2915 rb->lcd_setfont(cur_font = FONT_UI);
3218 rb->lcd_getstringsize("X", NULL, &h_x); 2916 rb->lcd_getstringsize("X", NULL, &h_x);
3219 h -= h_x; 2917 h -= h_x;
3220 midend_size(me, &w, &h, true); 2918 midend_size(me, &w, &h, true, 1.0);
3221} 2919}
3222 2920
3223static void init_tlsf(void) 2921static void init_tlsf(void)
@@ -3245,6 +2943,7 @@ static void init_colors(void)
3245 float *floatcolors = midend_colors(me, &ncolors); 2943 float *floatcolors = midend_colors(me, &ncolors);
3246 2944
3247 /* convert them to packed RGB */ 2945 /* convert them to packed RGB */
2946 sfree(colors);
3248 colors = smalloc(ncolors * sizeof(unsigned)); 2947 colors = smalloc(ncolors * sizeof(unsigned));
3249 unsigned *ptr = colors; 2948 unsigned *ptr = colors;
3250 float *floatptr = floatcolors; 2949 float *floatptr = floatcolors;
@@ -3277,32 +2976,39 @@ static bool string_in_list(const char *target, const char **list)
3277static void tune_input(const char *name) 2976static void tune_input(const char *name)
3278{ 2977{
3279 static const char *want_spacebar[] = { 2978 static const char *want_spacebar[] = {
2979 "Black Box",
2980 "Bridges",
2981 "Galaxies",
2982 "Keen",
3280 "Magnets", 2983 "Magnets",
3281 "Map", 2984 "Map",
3282 "Mines", 2985 "Mines",
3283 "Palisade", 2986 "Palisade",
2987 "Pattern",
3284 "Rectangles", 2988 "Rectangles",
2989 "Signpost",
2990 "Singles",
2991 "Solo",
2992 "Tents",
2993 "Towers",
2994 "Unequal",
2995 "Group",
3285 NULL 2996 NULL
3286 }; 2997 };
3287 2998
3288 /* these get a spacebar on long click - you must also add to the 2999 /* these get a spacebar on long click - this implicitly enables
3289 * falling_edge list below! */ 3000 * falling-edge button events (see below)! */
3290 input_settings.want_spacebar = string_in_list(name, want_spacebar); 3001 input_settings.want_spacebar = string_in_list(name, want_spacebar);
3291 3002
3292 static const char *falling_edge[] = { 3003 static const char *falling_edge[] = {
3293 "Inertia", 3004 "Inertia",
3294 "Magnets",
3295 "Map",
3296 "Mines",
3297 "Palisade",
3298 "Rectangles",
3299 NULL 3005 NULL
3300 }; 3006 };
3301 3007
3302 /* wait until a key is released to send an action (useful for 3008 /* wait until a key is released to send an action (useful for
3303 * chording in Inertia; must be enabled if the game needs a 3009 * chording in Inertia; must be enabled if the game needs a
3304 * spacebar) */ 3010 * spacebar) */
3305 input_settings.falling_edge = string_in_list(name, falling_edge); 3011 input_settings.falling_edge = string_in_list(name, falling_edge) || input_settings.want_spacebar;
3306 3012
3307 /* For want_spacebar to work, events must be sent on the falling 3013 /* For want_spacebar to work, events must be sent on the falling
3308 * edge */ 3014 * edge */
@@ -3322,6 +3028,7 @@ static void tune_input(const char *name)
3322 static const char *no_rclick_on_hold[] = { 3028 static const char *no_rclick_on_hold[] = {
3323 "Map", 3029 "Map",
3324 "Signpost", 3030 "Signpost",
3031 "Slide",
3325 "Untangle", 3032 "Untangle",
3326 NULL 3033 NULL
3327 }; 3034 };
@@ -3330,11 +3037,21 @@ static void tune_input(const char *name)
3330 3037
3331 static const char *mouse_games[] = { 3038 static const char *mouse_games[] = {
3332 "Loopy", 3039 "Loopy",
3040 "Slide",
3333 NULL 3041 NULL
3334 }; 3042 };
3335 3043
3336 mouse_mode = string_in_list(name, mouse_games); 3044 mouse_mode = string_in_list(name, mouse_games);
3337 3045
3046 static const char *sticky_mouse_games[] = {
3047 "Map",
3048 "Signpost",
3049 "Slide",
3050 "Untangle",
3051 };
3052
3053 input_settings.sticky_mouse = string_in_list(name, sticky_mouse_games);
3054
3338 static const char *number_chooser_games[] = { 3055 static const char *number_chooser_games[] = {
3339 "Filling", 3056 "Filling",
3340 "Keen", 3057 "Keen",
@@ -3627,8 +3344,11 @@ static int mainmenu_cb(int action,
3627 if(!load_success) 3344 if(!load_success)
3628 return ACTION_EXIT_MENUITEM; 3345 return ACTION_EXIT_MENUITEM;
3629 break; 3346 break;
3347 case 2:
3630 case 3: 3348 case 3:
3631 break; 3349 if(!help_valid)
3350 return ACTION_EXIT_MENUITEM;
3351 break;
3632 case 4: 3352 case 4:
3633 if(audiobuf_available) 3353 if(audiobuf_available)
3634 break; 3354 break;
@@ -3691,8 +3411,9 @@ static void puzzles_main(void)
3691 "Playback Control", // 4 3411 "Playback Control", // 4
3692 "Game Type", // 5 3412 "Game Type", // 5
3693 "Configure Game", // 6 3413 "Configure Game", // 6
3694 "Quit without Saving", // 7 3414 "Preferences", // 7
3695 "Quit"); // 8 3415 "Quit without Saving", // 8
3416 "Quit"); // 9
3696 3417
3697 bool quit = false; 3418 bool quit = false;
3698 int sel = 0; 3419 int sel = 0;
@@ -3738,11 +3459,14 @@ static void puzzles_main(void)
3738 goto game_loop; 3459 goto game_loop;
3739 } 3460 }
3740 break; 3461 break;
3741 case 8: 3462 case 7:
3463 preferences_menu();
3464 break;
3465 case 9:
3742 if(load_success) 3466 if(load_success)
3743 save_game(); 3467 save_game();
3744 /* fall through */ 3468 /* fall through */
3745 case 7: 3469 case 8:
3746 /* we don't care about freeing anything because tlsf will 3470 /* we don't care about freeing anything because tlsf will
3747 * be wiped out the next time around */ 3471 * be wiped out the next time around */
3748 return; 3472 return;
@@ -3787,12 +3511,14 @@ static void puzzles_main(void)
3787 /* quit without saving */ 3511 /* quit without saving */
3788 midend_free(me); 3512 midend_free(me);
3789 sfree(colors); 3513 sfree(colors);
3514 colors = NULL;
3790 return; 3515 return;
3791 case -3: 3516 case -3:
3792 /* save and quit */ 3517 /* save and quit */
3793 save_game(); 3518 save_game();
3794 midend_free(me); 3519 midend_free(me);
3795 sfree(colors); 3520 sfree(colors);
3521 colors = NULL;
3796 return; 3522 return;
3797 default: 3523 default:
3798 break; 3524 break;
@@ -3822,6 +3548,7 @@ static void puzzles_main(void)
3822 rb->yield(); 3548 rb->yield();
3823 } 3549 }
3824 sfree(colors); 3550 sfree(colors);
3551 colors = NULL;
3825 } 3552 }
3826} 3553}
3827 3554