Re: Decreasing seams in scaled windows

John Ehresman <[email protected]> Wed, 17 Jun 2026 20:30:50 +0100
Newsgroups gmane.comp.lib.scintilla.devel
Message-ID <[email protected]>
--Apple-Mail=_8F0F36E6-E47F-4EAB-8BAC-CC936DA0FB6D
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="UTF-8"

> On Jun 17, 2026, at 4:38=E2=80=AFAM, Neil Hodgson <scintilladotorg@gmail.=
com> wrote:
>> to add *F() variants of methods that take or return pixel values =E2=80=
=94 these mimic how Qt works by always working in (possibly fractional) pix=
el units.
> ...
> Since this could cause failures in these projects, I'd like to provide
> some transition support. Perhaps by publishing a script that converts
> 'int' into 'pixels' in Scintilla.iface where appropriate so projects
> could update on their own schedule then force the update in a release
> or two.

The list could be in WidgetGen.py for now until .iface is updated or put te=
mporary comments in the .iface file.

> There will be decisions on how to expose this: does ScintillaTypes.h
> get a 'using Pixels =3D intptr_t;' with ScintillaCall.h then specifying
> 'Pixels' in methods? Is the documentation updated or left with 'int'?

Making it a C++ alias might be good =E2=80=94 but I rarely work with C++ th=
ese days so I=E2=80=99m not the one to ask.

>> The second patch uses full repaints on macOS because I intermittently se=
e a one pixel of selection color when I select and deselect lines (though I=
 can trigger it pretty easily).
>=20
> Yes, I have seen this too and do not understand it.

It=E2=80=99s apparently a bug with the Qt backing store on macOS. There are=
 2+ backing stores and when a paint with a clip is done, the portions outsi=
de the clip can be copied from another backing store and the portions in th=
e clip drawn by the widget. The problem seems to be that the copy from anot=
her backing store is done with logical pixel rects that are rounded to devi=
ce pixels and the clip is applied in device pixels =E2=80=94 which can lead=
 to 1+ device pixel lines that aren=E2=80=99t filled by either so they reta=
in whatever values they had originally. What seems to work is enlarging the=
 invalidated rect so it has coordinates that are multiples of device / logi=
cal pixels =E2=80=94 if there are 3 device pixels per logical pixels, the c=
oordinates need to be multiples of 3 so there aren=E2=80=99t any rounding g=
aps.

Attached is a patch that seems to work =E2=80=94 note that on some ratios, =
it will revert to a full redraw. I have only tried this on macOS but plan t=
o try it on the other platforms soon.

John

--=20
You received this message because you are subscribed to the Google Groups "=
scintilla-interest" group.
To unsubscribe from this group and stop receiving emails from it, send an e=
mail to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/scintilla-i=
nterest/8A65646B-CBAD-469B-B42B-FBC16EB8AFE4%40wingware.com.

--Apple-Mail=_8F0F36E6-E47F-4EAB-8BAC-CC936DA0FB6D
Content-Disposition: attachment;
	filename=QtScale-PlatQt.patch
Content-Type: application/octet-stream;
	x-unix-mode=0644;
	name="QtScale-PlatQt.patch"
Content-Transfer-Encoding: 7bit

diff -r 7d5e2d6cf92c qt/ScintillaEditBase/PlatQt.cpp
--- a/qt/ScintillaEditBase/PlatQt.cpp	Tue Jun 16 11:23:34 2026 +0100
+++ b/qt/ScintillaEditBase/PlatQt.cpp	Wed Jun 17 16:30:58 2026 +0100
@@ -223,6 +223,40 @@
 	return variant.toDouble();
 }
 
+#ifdef Q_OS_APPLE
+// Denominator of QT_SCALE_FACTOR when it is a simple fraction (1 for an integer
+// factor), else 0.  The macOS backing scale is 2 (Retina) or 1, so
+// QT_SCALE_FACTOR = devicePixelRatioF / that.
+static int ScaleFactorDenominator(WindowID wid)
+{
+	const double scale = ScaleOfWindow(wid);
+	// Divide out the backing scale: a fractional factor hides as an integer
+	// devicePixelRatioF (1.5x Retina -> 3.0); only sf shows the fraction.
+	const double backing = scale >= 2.0 ? 2.0 : 1.0;
+	const double sf = scale / backing;
+	for (int q = 1; q <= 8; ++q)
+		if (std::fabs(sf * q - std::round(sf * q)) < 1e-4)
+			return q;
+	return 0;
+}
+
+// Smallest rect containing rc whose edges lie on a q-pixel grid.
+static QRect SnapToGrid(QRect rc, int q)
+{
+	auto floorTo = [](int v, int q) {
+		int r = v % q;
+		if (r < 0)
+			r += q;
+		return v - r;
+	};
+	const int l = floorTo(rc.left(), q);
+	const int t = floorTo(rc.top(), q);
+	const int r = floorTo(rc.right()  + q, q) - 1;  // ceil exclusive edge
+	const int b = floorTo(rc.bottom() + q, q) - 1;
+	return QRect(QPoint(l, t), QPoint(r, b));
+}
+#endif
+
 double ScaleToMultiply(WindowID wid)
 {
 	const qreal scale = ScaleOfWindow(wid);
@@ -943,7 +977,27 @@
 				return;
 			}
 #endif
-			rc = rc / scale;
+			// Logical update rect containing the device rect.
+			QRect upd = QRectFFromPRect(rc / scale).toAlignedRect();
+#ifdef Q_OS_APPLE
+			// Work around Qt Cocoa logical -> device pixel rounding bug by
+			// enlarging the rect with SnapToGrid()
+			const int qSnap = ScaleFactorDenominator(wid);
+			if (qSnap == 0) {
+				window(wid)->update();
+				return;
+			}
+			if (qSnap > 1) {
+				upd = SnapToGrid(upd, qSnap);
+				const QSize sz = window(wid)->size();
+				if (upd.width() >= sz.width() && upd.height() >= sz.height()) {
+					window(wid)->update();
+					return;
+				}
+			}
+#endif
+			window(wid)->update(upd);
+			return;
 		}
 		window(wid)->update(QRectFromPRect(rc));
 	}

--Apple-Mail=_8F0F36E6-E47F-4EAB-8BAC-CC936DA0FB6D
Content-Type: text/plain; charset="UTF-8"




-- 
You received this message because you are subscribed to the Google Groups "scintilla-interest" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To view this discussion visit https://groups.google.com/d/msgid/scintilla-interest/8A65646B-CBAD-469B-B42B-FBC16EB8AFE4%40wingware.com.

--Apple-Mail=_8F0F36E6-E47F-4EAB-8BAC-CC936DA0FB6D--