From e45cd43aaab7af014607b2578ec68a5bbec1b609 Mon Sep 17 00:00:00 2001 From: Ben Harris Date: Mon, 7 Nov 2022 21:42:38 +0000 Subject: [PATCH] Teach the mid-end about device pixel ratios The device pixel ratio indicates how many physical pixels there are in the platonic ideal of a pixel, at least approximately. In Web browsers, the device pixel ratio is used to represent "retina" displays with particularly high pixel densities, and also to reflect user-driven zooming of the page to different text sizes. The mid-end uses the device pixel ratio to adjust the tile size at startup, and can also respond to changes in device pixel ratio by adjusting the time size later. This is accomplished through a new argument to midend_size() which can simply be passed as 1.0 in any front end that doesn't care about this. --- devel.but | 20 ++++++++++++++++++- emcc.c | 6 +++--- gtk.c | 4 ++-- midend.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++---- nestedvm.c | 4 ++-- osx.m | 6 +++--- puzzles.h | 3 ++- windows.c | 8 ++++---- 8 files changed, 87 insertions(+), 20 deletions(-) diff --git a/devel.but b/devel.but index d2b9cac..9f734e1 100644 --- a/devel.but +++ b/devel.but @@ -2972,7 +2972,7 @@ when finished with by passing it to the game's own \H{midend-size} \cw{midend_size()} -\c void midend_size(midend *me, int *x, int *y, bool user_size); +\c void midend_size(midend *me, int *x, int *y, bool user_size, double device_pixel_ratio); Tells the mid-end to figure out its window size. @@ -3029,6 +3029,24 @@ to use scroll bars for large puzzles), you can pass dimensions of \cw{INT_MAX} as input to this function. You should probably not do that \e{and} set the \c{user_size} flag, though! +The \cw{device_pixel_ratio} allows the front end to specify that its +pixels are unusually large or small (or should be treated as such). +The mid-end uses this to adjust the tile size, both at startup (if the +ratio is not 1) and if the ratio changes. + +A \cw{device_pixel_ratio} of 1 indicates normal-sized pixels. +\q{Normal} is not precisely defined, but it's about 4 pixels per +millimetre on a screen designed to be viewed from a metre away, or a +size such that text 15 pixels high is comfortably readable. Some +platforms have a concept of a logical pixel that this can be mapped +onto. For instance, Cascading Style Sheets (CSS) has a unit called +\cq{px} that only matches physical pixels at a \cw{device_pixel_ratio} +of 1. + +The \cw{device_pixel_ratio} indicates the number of physical pixels in +a normal-sized pixel, so values less than 1 indicate unusually large +pixels and values greater than 1 indicate unusually small pixels. + The midend relies on the frontend calling \cw{midend_new_game()} (\k{midend-new-game}) before calling \cw{midend_size()}. diff --git a/emcc.c b/emcc.c index 651fc5a..b7af686 100644 --- a/emcc.c +++ b/emcc.c @@ -195,7 +195,7 @@ static void resize(bool initial) int w, h; double dpr; w = h = INT_MAX; - midend_size(me, &w, &h, false); + midend_size(me, &w, &h, false, 1.0); if (initial) { dpr = js_get_device_pixel_ratio(); if (dpr != 1.0) { @@ -207,7 +207,7 @@ static void resize(bool initial) */ w *= dpr; h *= dpr; - midend_size(me, &w, &h, true); + midend_size(me, &w, &h, true, 1.0); } } js_canvas_set_size(w, h); @@ -219,7 +219,7 @@ static void resize(bool initial) /* Called from JS when the device pixel ratio changes */ void rescale_puzzle(int w, int h) { - midend_size(me, &w, &h, true); + midend_size(me, &w, &h, true, 1.0); if (canvas_w != w || canvas_h != h) { js_canvas_set_size(w, h); canvas_w = w; diff --git a/gtk.c b/gtk.c index 2373e38..3ded45a 100644 --- a/gtk.c +++ b/gtk.c @@ -1674,7 +1674,7 @@ static void resize_puzzle_to_area(frontend *fe, int x, int y) fe->w = x; fe->h = y; - midend_size(fe->me, &x, &y, true); + midend_size(fe->me, &x, &y, true, 1.0); fe->pw = x; fe->ph = y; #if GTK_CHECK_VERSION(3,10,0) @@ -2210,7 +2210,7 @@ static void get_size(frontend *fe, int *px, int *py) */ x = INT_MAX; y = INT_MAX; - midend_size(fe->me, &x, &y, false); + midend_size(fe->me, &x, &y, false, 1.0); *px = x; *py = y; } diff --git a/midend.c b/midend.c index 2489803..a8f9cf3 100644 --- a/midend.c +++ b/midend.c @@ -89,7 +89,8 @@ struct midend { int pressed_mouse_button; - int preferred_tilesize, tilesize, winwidth, winheight; + int preferred_tilesize, preferred_tilesize_dpr, tilesize; + int winwidth, winheight; void (*game_id_change_notify_function)(void *); void *game_id_change_notify_ctx; @@ -130,11 +131,14 @@ static const char *midend_deserialise_internal( void midend_reset_tilesize(midend *me) { me->preferred_tilesize = me->ourgame->preferred_tilesize; + me->preferred_tilesize_dpr = 1.0; { /* * Allow an environment-based override for the default tile * size by defining a variable along the lines of * `NET_TILESIZE=15'. + * + * XXX How should this interact with DPR? */ char buf[80], *e; @@ -306,10 +310,50 @@ static void midend_size_new_drawstate(midend *me) } } -void midend_size(midend *me, int *x, int *y, bool user_size) +/* + * There is no one correct way to convert tilesizes between device + * pixel ratios, because there's only a loosely-defined relationship + * between tilesize and the actual size of a puzzle. We define this + * function as the canonical conversion function so everything in the + * midend will be consistent. + */ +static int convert_tilesize(midend *me, int old_tilesize, + double old_dpr, double new_dpr) +{ + int x, y, rx, ry, min, max, mid; + game_params *defaults = me->ourgame->default_params(); + + if (new_dpr == old_dpr) + return old_tilesize; + me->ourgame->compute_size(defaults, old_tilesize, &x, &y); + x *= new_dpr / old_dpr; + y *= new_dpr / old_dpr; + + min = max = 1; + do { + max *= 2; + me->ourgame->compute_size(defaults, max, &rx, &ry); + } while (rx <= x && ry <= y); + + while (max - min > 1) { + int mid = (max + min) / 2; + me->ourgame->compute_size(defaults, mid, &rx, &ry); + if (rx <= x && ry <= y) + min = mid; + else + max = mid; + } + + me->ourgame->free_params(defaults); + return min; +} + +void midend_size(midend *me, int *x, int *y, bool user_size, + double device_pixel_ratio) { int min, max; int rx, ry; + int preferred_tilesize; /* * We can't set the size on the same drawstate twice. So if @@ -339,7 +383,9 @@ void midend_size(midend *me, int *x, int *y, bool user_size) me->ourgame->compute_size(me->params, max, &rx, &ry); } while (rx <= *x && ry <= *y); } else - max = me->preferred_tilesize + 1; + max = convert_tilesize(me, me->preferred_tilesize, + me->preferred_tilesize_dpr, + device_pixel_ratio) + 1; min = 1; /* @@ -362,9 +408,11 @@ void midend_size(midend *me, int *x, int *y, bool user_size) */ me->tilesize = min; - if (user_size) + if (user_size) { /* If the user requested a change in size, make it permanent. */ me->preferred_tilesize = me->tilesize; + me->preferred_tilesize_dpr = device_pixel_ratio; + } midend_size_new_drawstate(me); *x = me->winwidth; *y = me->winheight; diff --git a/nestedvm.c b/nestedvm.c index 947abe0..ec633d3 100644 --- a/nestedvm.c +++ b/nestedvm.c @@ -217,7 +217,7 @@ int jcallback_resize(int width, int height) int x, y; x = width; y = height; - midend_size(fe->me, &x, &y, true); + midend_size(fe->me, &x, &y, true, 1.0); fe->ox = (width - x) / 2; fe->oy = (height - y) / 2; fe->w = x; @@ -358,7 +358,7 @@ static void resize_fe(frontend *fe) x = INT_MAX; y = INT_MAX; - midend_size(fe->me, &x, &y, false); + midend_size(fe->me, &x, &y, false, 1.0); _call_java(3, x, y, 0); } diff --git a/osx.m b/osx.m index 36e9693..646cffc 100644 --- a/osx.m +++ b/osx.m @@ -524,7 +524,7 @@ struct frontend { frame.origin.x = 0; w = h = INT_MAX; - midend_size(me, &w, &h, false); + midend_size(me, &w, &h, false, 1.0); frame.size.width = w; frame.size.height = h; fe.w = w; @@ -559,7 +559,7 @@ struct frontend { */ midend_new_game(me); w = h = INT_MAX; - midend_size(me, &w, &h, false); + midend_size(me, &w, &h, false, 1.0); rect.size.width = w; rect.size.height = h; fe.w = w; @@ -959,7 +959,7 @@ struct frontend { int w, h; w = h = INT_MAX; - midend_size(me, &w, &h, false); + midend_size(me, &w, &h, false, 1.0); size.width = w; size.height = h; fe.w = w; diff --git a/puzzles.h b/puzzles.h index f0a7e99..1d4dd33 100644 --- a/puzzles.h +++ b/puzzles.h @@ -299,7 +299,8 @@ void midend_free(midend *me); const game *midend_which_game(midend *me); void midend_set_params(midend *me, game_params *params); game_params *midend_get_params(midend *me); -void midend_size(midend *me, int *x, int *y, bool user_size); +void midend_size(midend *me, int *x, int *y, bool user_size, + double device_pixel_ratio); void midend_reset_tilesize(midend *me); void midend_new_game(midend *me); void midend_restart_game(midend *me); diff --git a/windows.c b/windows.c index aa95b62..e58a150 100644 --- a/windows.c +++ b/windows.c @@ -1285,7 +1285,7 @@ static bool check_window_resize(frontend *fe, int cx, int cy, * See if we actually got the window size we wanted, and adjust * the puzzle size if not. */ - midend_size(fe->me, &x, &y, true); + midend_size(fe->me, &x, &y, true, 1.0); if (x != cx || y != cy) { /* * Resize the window, now we know what size we _really_ @@ -1611,7 +1611,7 @@ static int fe_set_midend(frontend *fe, midend *me) fe->statusbar = NULL; get_max_puzzle_size(fe, &x, &y); - midend_size(fe->me, &x, &y, false); + midend_size(fe->me, &x, &y, false, 1.0); r.left = r.top = 0; r.right = x; @@ -2374,12 +2374,12 @@ static void new_game_size(frontend *fe, float scale) int x, y; get_max_puzzle_size(fe, &x, &y); - midend_size(fe->me, &x, &y, false); + midend_size(fe->me, &x, &y, false, 1.0); if (scale != 1.0) { x = (int)((float)x * fe->puzz_scale); y = (int)((float)y * fe->puzz_scale); - midend_size(fe->me, &x, &y, true); + midend_size(fe->me, &x, &y, true, 1.0); } fe->ymin = (fe->xmin * y) / x;