Re: Decreasing seams in scaled windows

John Ehresman <[email protected]> Mon, 22 Jun 2026 21:19:46 +0100
Newsgroups gmane.comp.lib.scintilla.devel
Message-ID <[email protected]>
--Apple-Mail=_923E9008-7E22-4697-94D5-DA496923509C
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="UTF-8"

The problem was that the upper left of the widget needs to be taken into ac=
count when snapping the rectangle. Here=E2=80=99s a revised patch based of =
what I sent previously (I can rearrange functions if you want). It does get=
 the scale factor from the environment, which should probably be reworked =
=E2=80=94 the issue is that Qt doesn=E2=80=99t expose it in its api that I =
know of and the Wayland=E2=80=99s magnification factor isn=E2=80=99t fixed =
at 2 like the factor for macOS retina displays.

I=E2=80=99ve only tested lightly on Wayland.

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/0F55D014-CF08-4085-94C9-CF7D66E4EEB3%40wingware.com.

--Apple-Mail=_923E9008-7E22-4697-94D5-DA496923509C
Content-Disposition: attachment;
	filename=QtScale-PlatQt2.patch
Content-Type: application/octet-stream;
	x-unix-mode=0664;
	name="QtScale-PlatQt2.patch"
Content-Transfer-Encoding: 7bit

--- a/qt/ScintillaEditBase/PlatQt.cpp
+++ b/qt/ScintillaEditBase/PlatQt.cpp
@@ -223,6 +223,55 @@
 	return variant.toDouble();
 }
 
+// Define SCINTILLA_QT_BACKINGSTORE_FIXED when building against a Qt whose
+// backing store tracks dirty state at device-pixel resolution; that makes the
+// fractional-scaling workarounds below compile out entirely.
+
+#if !defined(Q_OS_WIN) && !defined(SCINTILLA_QT_BACKINGSTORE_FIXED)
+// Grow a logical update rect so its edges land on whole native pixels, in window
+// coordinates (Qt rounds the dirty region there, so the widget's offset is applied).
+static QRect SnapToNativeWindowPixelGrid(QWidget *widget, QRect upd)
+{
+	if (!widget)
+		return upd;
+	const qreal winDpr = widget->devicePixelRatioF();
+	double guiFactor = 0.0;
+	const QByteArray sf = qgetenv("QT_SCALE_FACTOR");
+	if (!sf.isEmpty()) {
+		bool ok = false;
+		const double v = sf.toDouble(&ok);
+		if (ok && v > 0.0)
+			guiFactor = v;
+	}
+	if (guiFactor <= 0.0)
+		return (winDpr == std::floor(winDpr)) ? upd : QRect();
+
+	int q = 0;
+	for (int n = 1; n <= 64; ++n) {
+		if (std::fabs(guiFactor * n - std::round(guiFactor * n)) < 1e-4) {
+			q = n;
+			break;
+		}
+	}
+	if (q == 0)
+		return QRect();
+	if (q == 1)
+		return upd;
+
+	const QPoint off = widget->mapTo(widget->window(), QPoint(0, 0));
+	auto floorTo = [q](int v) { int r = v % q; if (r < 0) r += q; return v - r; };
+	const int l = floorTo(upd.left()       + off.x())         - off.x();
+	const int t = floorTo(upd.top()        + off.y())         - off.y();
+	const int r = floorTo(upd.right()  + 1 + off.x() + q - 1) - off.x();
+	const int b = floorTo(upd.bottom() + 1 + off.y() + q - 1) - off.y();
+	const QRect snapped(QPoint(l, t), QPoint(r - 1, b - 1));
+	const QSize widgetSize = widget->size();
+	if (snapped.width() >= widgetSize.width() && snapped.height() >= widgetSize.height())
+		return QRect();
+	return snapped;
+}
+#endif
+
 double ScaleToMultiply(WindowID wid)
 {
 	const qreal scale = ScaleOfWindow(wid);
@@ -934,18 +983,28 @@
 {
 	if (wid) {
 		const qreal scale = ScaleOfWindow(wid);
-		if (scale) {
-#if !defined(Q_OS_WIN) && !defined(Q_OS_APPLE)
-			// Using X11 or Wayland, likely Linux but may be a BSD or similar
-			if (scale != 1.0 && scale != 2.0) {
-				// Fractional scaling leaves repaint debris, so redraw completely
+		QRect upd;
+		
+		if (!scale) {
+			upd = QRectFromPRect(rc);
+		}
+		else {
+			upd = QRectFFromPRect(rc / scale).toAlignedRect();
+
+#if !defined(Q_OS_WIN) && !defined(SCINTILLA_QT_BACKINGSTORE_FIXED)
+			// Snap to whole native pixels to avoid the fractional-scale backing-store
+			// seam; a null result means a full redraw is needed.
+			const QRect snapped =
+				SnapToNativeWindowPixelGrid(window(wid), upd);
+			if (snapped.isNull()) {
 				window(wid)->update();
 				return;
 			}
+			upd = snapped;
 #endif
-			rc = rc / scale;
 		}
-		window(wid)->update(QRectFromPRect(rc));
+
+		window(wid)->update(upd);
 	}
 }
 

--Apple-Mail=_923E9008-7E22-4697-94D5-DA496923509C
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="UTF-8"

=20

> On Jun 22, 2026, at 12:18=E2=80=AFPM, John Ehresman <[email protected]> wr=
ote:
>=20
> Neil,
>=20
> Wayland has a similar dirty region bug and I have a local patch to fix it=
. It=E2=80=99s a Qt bug =E2=80=94 I see more debris lines in the list widge=
t and menus more than I do in scintilla. But the snap workaround doesn=E2=
=80=99t seem to fix it and I=E2=80=99m trying to figure out why.
>=20
> I see the debris on Wayland when the display magnification is 200% and I =
add a Qt scale factor of 1.5 (or other) on top of that, but I was unable to=
 see debris with a display magnification of 100%. What display magnificatio=
n are you using?
>=20
> Thanks,
>=20
> John
>=20
>> On Jun 21, 2026, at 12:21=E2=80=AFAM, Neil Hodgson <scintilladotorg@gmai=
l.com> wrote:
>>=20
>> John:
>>=20
>>> Here=E2=80=99s a version that always snaps.
>>=20
>> That worked for me on macOS.
>>=20
>>> The backing store has a grid of what is dirty and that grid is only hal=
f the size of the backing store on retina displays. Making it full size see=
ms to fix the one pixel lines.
>>=20
>> The Wayland/Linux lines may be a similar bug. Using the snap code
>> (with changes to ensure it runs) fixes some cases but not others. It
>> appears to fix 125% and 133% but not 150%. For 150% the problem moves
>> a little - likely a single pixel.
>> https://www.scintilla.org/QtScaleSnap150Wayland.png
>>=20
>> To get this for 150% (with Ubuntu 26.04 LTS Wayland Qt 6.10.2 inside
>> VirtualBox):
>> * Disable the `if defined(Q_OS_APPLE)` checks
>> * Remove the `if (backing =3D=3D 1)` checks since there is no 'retina'
>> pixel-doubling
>> * Remove the early return for scales other than 1.0 and 2.0 in
>> InvalidateRectangle
>>=20
>> This produces the expected 'q' of 2 (150%), 3 (133%), and 4 (125%) and
>> the corresponding snapped rectangle value.
>>=20
>> Neil
>>=20
>> --=20
>> You received this message because you are subscribed to the Google Group=
s "scintilla-interest" group.
>> To unsubscribe from this group and stop receiving emails from it, send a=
n email to [email protected].
>> To view this discussion visit https://groups.google.com/d/msgid/scintill=
a-interest/CACWkrTiX45E9qpTcUezPOyDHwDOi8oCbCirY0JnAAFwSkY5faA%40mail.gmail=
.com.
>> <EnableSnap.patch>

--=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/0F55D014-CF08-4085-94C9-CF7D66E4EEB3%40wingware.com.

--Apple-Mail=_923E9008-7E22-4697-94D5-DA496923509C--