shithub: puzzles

Download patch

ref: 97a0dc0fee0b9e7d1cd488309e03a19e942d1a57
parent: d71ac73d8a4397c35b21ec08388a1c6f94691b64
author: Simon Tatham <anakin@pobox.com>
date: Tue Apr 7 02:50:20 EDT 2020

GTK 3: handle nontrivial window scale factors.

A user pointed out that if you run a GTK 3 puzzles with "GDK_SCALE=2"
in the environment, the main game drawing area is blurred. That's
because we're choosing the size of our backing Cairo surface based on
the number of _logical_ pixels in the window size, not taking into
account the fact that the non-unit scale factor means the number of
physical pixels is larger. Everything 'works' in the basis - Cairo
happily expands the smaller backing surface into the larger window -
but resolution is lost in the process.

Now we detect the window's scale factor, construct the backing surface
appropriately, and compensate for that scaling when drawing to the
surface and when blitting the surface to the window.

--- a/gtk.c
+++ b/gtk.c
@@ -203,7 +203,7 @@
     GtkWidget *cfgbox;
     void *paste_data;
     int paste_data_len;
-    int pw, ph;                        /* pixmap size (w, h are area size) */
+    int pw, ph, ps;  /* pixmap size (w, h are area size, s is GDK scale) */
     int ox, oy;                        /* offset of pixmap in drawing area */
 #ifdef OLD_FILESEL
     char *filesel_name;
@@ -336,6 +336,7 @@
 static void setup_drawing(frontend *fe)
 {
     fe->cr = cairo_create(fe->image);
+    cairo_scale(fe->cr, fe->ps, fe->ps);
     cairo_set_antialias(fe->cr, CAIRO_ANTIALIAS_GRAY);
     cairo_set_line_width(fe->cr, 1.0);
     cairo_set_line_cap(fe->cr, CAIRO_LINE_CAP_SQUARE);
@@ -647,7 +648,7 @@
 #ifndef USE_CAIRO_WITHOUT_PIXMAP
     if (!fe->headless) {
         fe->pixmap = gdk_pixmap_new(gtk_widget_get_window(fe->area),
-                                    fe->pw, fe->ph, -1);
+                                    fe->pw*fe->ps, fe->ph*fe->ps, -1);
     } else {
         fe->pixmap = NULL;
     }
@@ -654,7 +655,7 @@
 #endif
 
     fe->image = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
-					   fe->pw, fe->ph);
+					   fe->pw*fe->ps, fe->ph*fe->ps);
 
     wipe_and_maybe_destroy_cairo(fe, cairo_create(fe->image), true);
 #ifndef USE_CAIRO_WITHOUT_PIXMAP
@@ -1584,6 +1585,14 @@
     frontend *fe = (frontend *)data;
     GdkRectangle dirtyrect;
 
+    cairo_surface_t *target_surface = cairo_get_target(cr);
+    cairo_matrix_t m;
+    cairo_get_matrix(cr, &m);
+    double orig_sx, orig_sy;
+    cairo_surface_get_device_scale(target_surface, &orig_sx, &orig_sy);
+    cairo_surface_set_device_scale(target_surface, 1.0, 1.0);
+    cairo_translate(cr, m.x0 * (orig_sx - 1.0), m.y0 * (orig_sy - 1.0));
+
     gdk_cairo_get_clip_rectangle(cr, &dirtyrect);
     cairo_set_source_surface(cr, fe->image, fe->ox, fe->oy);
     cairo_rectangle(cr, dirtyrect.x, dirtyrect.y,
@@ -1590,6 +1599,8 @@
                     dirtyrect.width, dirtyrect.height);
     cairo_fill(cr);
 
+    cairo_surface_set_device_scale(target_surface, orig_sx, orig_sy);
+
     return true;
 }
 #else
@@ -1634,6 +1645,7 @@
 static void resize_puzzle_to_area(frontend *fe, int x, int y)
 {
     int oldw = fe->w, oldpw = fe->pw, oldh = fe->h, oldph = fe->ph;
+    int oldps = fe->ps;
 
     fe->w = x;
     fe->h = y;
@@ -1640,10 +1652,15 @@
     midend_size(fe->me, &x, &y, true);
     fe->pw = x;
     fe->ph = y;
+#if GTK_CHECK_VERSION(3,10,0)
+    fe->ps = gtk_widget_get_scale_factor(fe->area);
+#else
+    fe->ps = 1;
+#endif
     fe->ox = (fe->w - fe->pw) / 2;
     fe->oy = (fe->h - fe->ph) / 2;
 
-    if (oldw != fe->w || oldpw != fe->pw ||
+    if (oldw != fe->w || oldpw != fe->pw || oldps != fe->ps ||
         oldh != fe->h || oldph != fe->ph || !backing_store_ok(fe)) {
         if (backing_store_ok(fe))
             teardown_backing_store(fe);
@@ -3089,6 +3106,7 @@
     }
 #else
     fe->headless = headless;
+    fe->ps = 1; /* in headless mode, configure_area won't have set this */
 #endif
 
     fe->timer_active = false;