Re: Decreasing seams in scaled windows
Neil <[email protected]> Fri, 22 May 2026 22:23:17 -0700 (PDT)
| Newsgroups | gmane.comp.lib.scintilla.devel |
|---|---|
| Message-ID | <[email protected]> |
------=_Part_121574_68914752.1779513797096 Content-Type: multipart/alternative; boundary="----=_Part_121575_153248915.1779513797096" ------=_Part_121575_153248915.1779513797096 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Mitchell: =20 I applied this patch to Qt 6 on macOS and I=E2=80=99m seeing some really we= ird=20 effects. Thanks for looking at the change. Some of these can be fixed and others are= =20 from decisions made when implementing scaling for Win32 and may require=20 application additions or new APIs. =20 I=E2=80=99m on a Retina display, so that=E2=80=99s probably messing with th= e=20 devicePixelRatio() computations. Yes, macOS scales differently which makes it easy for macOS apps but may=20 cause problems when Qt treats scaling more like Windows. On Windows, scale factors between 100% and 200% are common - I mostly use a= =20 4K monitor with Windows and Windows recommends 150%. At 200% (as is common on macOS), it is reasonable to double all dimensions,= =20 making simple lines 2 pixels wide. At 150%, some people will prefer 1 pixel= =20 wide lines and some 2. Using floating point widths such as a 1.5 pixel wide= =20 lines will lead to 'fuzzy' features that are widely disliked. So I left=20 most features with their original pixel widths with the intent of making=20 more choices available so that applications and users can choose something= =20 that works for them and they can choose when to switch between feature=20 sizes. See attached noscale.png and yesscale.png images. The arrows in the latter= =20 point to the weird things I=E2=80=99m seeing:=20 - hard to see indicators Most indicators do not scale. IndicatorStyle::Diagonal, in particular,=20 tries to fit 3 pixel high underlines in the text descender area which is=20 quite tight so may result in clipping. With 'noscale.png' macOS Retina scaling, 3 pixels =3D 6 physical pixels whi= ch=20 looks quite good. 'yesscale.png' is using 3 physical pixels so looks too=20 faint. A new indicator (which I think should have a new name) could be=20 implemented in various ways. The descender height could determine the=20 height and spacing of the diagonal lines although that will decrease=20 density. The stroke width could be made wider although it should probably= =20 remain an integer - a 1.5 pixel wide line here produces an less distinct=20 blob. =20 - hard to see margin symbols The margins are specified in physical pixels so the application should (if= =20 desired) scale the argument to SCI_SETMARGINWIDTHN. The reason Scintilla=20 doesn't automatically scale these is that symbol margins are particularly= =20 sensitive to tweaking and I wanted applications to control exactly how wide= =20 margins are. SciTE provides a '$(scale)' function that may be used until=20 more control is wanted. =20 - too large autocompletion window The scale factor was applied twice to the font size and the size and=20 position of the window so there is an update with an inverse scale=20 in ListBoxImpl::SetFont and Window::SetPositionRelative then a compensating= =20 scale in ListBoxImpl::GetDesiredRect. This is just part of the division between scaled graphics coordinates used= =20 by this patch and the need to coordinate with the window layer. Scaling=20 could be handled more thoroughly with the factor flowing through the data= =20 structures although this would be more intrusive. The implementation here= =20 is trying to get this working with a small change to the edge of Scintilla,= =20 just in the Qt platform layer. A QtScale7.patch that includes these changes is attached. =20 I looked at the documentation to try and find an option to set scaling, but= =20 I couldn=E2=80=99t find it. I assume the patch is supposed to detect and ap= ply=20 scaling via the devicePixelRatio() computations. Yes, this is trying to be automatic, at least for basic uses. There could= =20 be an API either to set a scaling factor or just to control whether the=20 scaling is enabled. Also experimented with Qt 5.15.10 and the only symbol missing=20 was QEvent::DevicePixelRatioChange so there is an #if to avoid that before= =20 it is available. All the devicePixelRatioF calls appear to work on Qt 5=20 which is great. The DevicePixelRatioChange event was intercepted as=20 otherwise Haven started up with 200% instead of 150% scaling - it appears= =20 that the accurate scaling factor is only discovered after the window is=20 shown. There is a further problem with current Ubuntu+Wayland in that the windows= =20 for autocompletion and calltips fail as they do not have an owning window= =20 as their transientParent. Wayland doesn't like applications showing=20 top-level windows at arbitrary locations and wants these to be attached to= =20 owning windows. This has been a minor problem in the past with GTK but it= =20 mostly led to windows located poorly rather than failing. The relevant=20 API setTransientParent can't be called after the window creation as=20 creation has already failed and there may need to be some set up during the= =20 right stage of window construction / realization. This is orthogonal to=20 scaling so may be something that one of the users of Qt on Linux can=20 investigate. Neil --=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/eccb497b-61ac-43bd-8d08-3d6422101954n%40googlegroups.com. ------=_Part_121575_153248915.1779513797096 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable <div><div dir=3D"auto">Mitchell:<br /></div><div>=C2=A0</div><blockquote st= yle=3D"margin: 0px 0px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204)= ; padding-left: 1ex;">I applied this patch to Qt 6 on macOS and I=E2=80=99m= seeing some really weird effects.</blockquote><div><br /></div><div>Thanks= for looking at the change. Some of these can be fixed and others are from = decisions made when implementing scaling for Win32 and may require applicat= ion additions or new APIs.</div><div>=C2=A0</div><blockquote style=3D"margi= n: 0px 0px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-le= ft: 1ex;"> I=E2=80=99m on a Retina display, so that=E2=80=99s probably mess= ing with the devicePixelRatio() computations.</blockquote><div><br /></div>= <div>Yes, macOS scales differently which makes it easy for macOS apps but m= ay cause problems when Qt treats scaling more like Windows.</div><div><br /= ></div><div>On Windows, scale factors between 100% and 200% are common - I = mostly use a 4K monitor with Windows and Windows recommends 150%.</div><div= ><br /></div><div>At 200% (as is common on macOS), it is reasonable to doub= le all dimensions, making simple lines 2 pixels wide. At 150%, some people = will prefer 1 pixel wide lines and some 2. Using floating point widths such= as a 1.5 pixel wide lines will lead to 'fuzzy' features that are widely di= sliked. So I left most features with their original pixel widths with the i= ntent of making more choices available so that applications and users can c= hoose something that works for them and they can choose when to switch betw= een feature sizes.</div><div><br /></div><blockquote style=3D"margin: 0px 0= px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;= ">See attached noscale.png and yesscale.png images. The arrows in the latte= r point to the weird things I=E2=80=99m seeing: <br />- hard to see indicators</blockquote><div><br /></div><div>Most indic= ators do not scale. IndicatorStyle::Diagonal, in particular, tries to fit 3= pixel high underlines in the text descender area which is quite tight so m= ay result in clipping.</div><div><br /></div><div>With 'noscale.png' macOS = Retina scaling, 3 pixels =3D 6 physical pixels which looks quite good. 'yes= scale.png' is using 3 physical pixels so looks too faint. A new indicator (= which I think should have a new name) could be implemented in various ways.= The descender height could determine the height and spacing of the diagona= l lines although that will decrease density. The stroke width could be made= wider although it should probably remain an integer - a 1.5 pixel wide lin= e here produces an less distinct blob.</div><div>=C2=A0</div><blockquote st= yle=3D"margin: 0px 0px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204)= ; padding-left: 1ex;">- hard to see margin symbols</blockquote><div><br /><= /div><div>The margins are specified in physical pixels so the application s= hould (if desired) scale the argument to=C2=A0SCI_SETMARGINWIDTHN. The reas= on Scintilla doesn't automatically scale these is that symbol margins are p= articularly sensitive to tweaking and I wanted applications to control exac= tly how wide margins are. SciTE provides a '$(scale)' function that may be = used until more control is wanted.</div><div>=C2=A0</div><blockquote style= =3D"margin: 0px 0px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204); p= adding-left: 1ex;">- too large autocompletion window</blockquote><div><br /= ></div><div>The scale factor was applied twice to the font size and the siz= e and position of the window so there is an update with an inverse scale in= =C2=A0ListBoxImpl::SetFont and Window::SetPositionRelative then a compensat= ing scale in ListBoxImpl::GetDesiredRect.</div><div><br /></div><div>This i= s just part of the division between scaled graphics coordinates used by thi= s patch and the need to coordinate with the window layer. Scaling could be = handled more thoroughly with the factor flowing through the data structures= although this would be more intrusive. The implementation here is trying t= o get this working with a small change to the edge of Scintilla, just in th= e Qt platform layer.</div><div><br /></div><div>A QtScale7.patch that inclu= des these changes is attached.</div><div>=C2=A0</div><blockquote style=3D"m= argin: 0px 0px 0px 0.8ex; border-left: 1px solid rgb(204, 204, 204); paddin= g-left: 1ex;">I looked at the documentation to try and find an option to se= t scaling, but I couldn=E2=80=99t find it. I assume the patch is supposed t= o detect and apply scaling via the devicePixelRatio() computations.</blockq= uote><div><br /></div><div>Yes, this is trying to be automatic, at least fo= r basic uses. There could be an API either to set a scaling factor or just = to control whether the scaling is enabled.</div><div><br /></div><div>Also = experimented with Qt 5.15.10 and the only symbol missing was=C2=A0QEvent::D= evicePixelRatioChange so there is an #if to avoid that before it is availab= le. All the=C2=A0devicePixelRatioF calls appear to work on Qt 5 which is gr= eat. The DevicePixelRatioChange=C2=A0 event was intercepted as otherwise Ha= ven started up with 200% instead of 150% scaling - it appears that the accu= rate scaling factor is only discovered after the window is shown.</div><div= ><br /></div><div>There is a further problem with current Ubuntu+Wayland in= that the windows for autocompletion and calltips fail as they do not have = an owning window as their=C2=A0transientParent. Wayland doesn't like applic= ations showing top-level windows at arbitrary locations and wants these to = be attached to owning windows. This has been a minor problem in the past wi= th GTK but it mostly led to windows located poorly rather than failing. The= relevant API=C2=A0setTransientParent can't be called after the window crea= tion as creation has already failed and there may need to be some set up du= ring the right stage of window construction / realization. This is orthogon= al to scaling so may be something that one of the users of Qt on Linux can = investigate.</div><div><br /></div><div>Neil</div></div> <p></p> -- <br /> You received this message because you are subscribed to the Google Groups &= quot;scintilla-interest" group.<br /> To unsubscribe from this group and stop receiving emails from it, send an e= mail to <a href=3D"mailto:[email protected]">= [email protected]</a>.<br /> To view this discussion visit <a href=3D"https://groups.google.com/d/msgid/= scintilla-interest/eccb497b-61ac-43bd-8d08-3d6422101954n%40googlegroups.com= ?utm_medium=3Demail&utm_source=3Dfooter">https://groups.google.com/d/msgid/= scintilla-interest/eccb497b-61ac-43bd-8d08-3d6422101954n%40googlegroups.com= </a>.<br /> ------=_Part_121575_153248915.1779513797096-- ------=_Part_121574_68914752.1779513797096 Content-Type: text/plain; charset=US-ASCII; name=QtScale7.patch Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=QtScale7.patch X-Attachment-Id: cdb8f773-00f0-4732-af3a-32ce014bc7cf Content-ID: <cdb8f773-00f0-4732-af3a-32ce014bc7cf> diff -r 17e8e54ba9a1 qt/ScintillaEditBase/PlatQt.cpp --- a/qt/ScintillaEditBase/PlatQt.cpp Fri May 15 09:12:15 2026 +1000 +++ b/qt/ScintillaEditBase/PlatQt.cpp Sat May 23 14:06:04 2026 +1000 @@ -228,6 +228,11 @@ return std::make_unique<SurfaceImpl>(width, height, mode); } +std::unique_ptr<Surface> SurfaceImpl_AllocatePixMap(int width, int height, SurfaceMode mode) +{ + return std::make_unique<SurfaceImpl>(width, height, mode); +} + void SurfaceImpl::SetMode(SurfaceMode mode_) { mode = mode_; @@ -308,7 +313,7 @@ int SurfaceImpl::DeviceHeightFont(int points) { - return points; + return points * device->devicePixelRatioF(); } void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke) @@ -795,6 +800,9 @@ painter->setRenderHint(QPainter::TextAntialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true); + + const qreal scale = device->devicePixelRatioF(); + painter->scale(1.0 / scale, 1.0 / scale); } return painter; @@ -853,6 +861,8 @@ void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) { + const qreal scale = wid ? window(wid)->devicePixelRatioF() : 1.0; + rc = rc / scale; QPoint oPos = window(relativeTo->wid)->mapToGlobal(QPoint(0,0)); int ox = oPos.x(); int oy = oPos.y(); @@ -883,7 +893,9 @@ PRectangle Window::GetClientPosition() const { // The client position is the window position - return GetPosition(); + const qreal scale = wid ? window(wid)->devicePixelRatioF() : 1.0; + const PRectangle rc = GetPosition(); + return rc * scale; } void Window::Show(bool show) @@ -900,8 +912,15 @@ void Window::InvalidateRectangle(PRectangle rc) { - if (wid) - window(wid)->update(QRectFromPRect(rc)); + if (wid) { + //const qreal scale = window(wid)->devicePixelRatioF(); + // Avoids aliasing leaving a partial bottom pixel + //rc = rc / scale; + //rc.top -= 20; + //rc.bottom += 2; + //window(wid)->update(QRectFromPRect(rc)); + window(wid)->update(); + } } void Window::SetCursor(Cursor curs) @@ -1051,7 +1070,11 @@ ListWidget *list = GetWidget(); const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font); if (pfacs && pfacs->pfont) { - list->setFont(*(pfacs->pfont)); + QFont fontDeScaled = *(pfacs->pfont); + const qreal scale = window(wid)->devicePixelRatioF(); + qreal pointSize = fontDeScaled.pointSizeF() / scale; + fontDeScaled.setPointSizeF(pointSize); + list->setFont(fontDeScaled); } } void ListBoxImpl::SetAverageCharWidth(int /*width*/) {} @@ -1081,7 +1104,8 @@ width += style->pixelMetric(QStyle::PM_ScrollBarExtent); } - return PRectangle(0, 0, width, height); + const qreal scale = window(wid)->devicePixelRatioF(); + return PRectangle(0, 0, width, height) * scale; } int ListBoxImpl::CaretFromEdge() { diff -r 17e8e54ba9a1 qt/ScintillaEditBase/PlatQt.h --- a/qt/ScintillaEditBase/PlatQt.h Fri May 15 09:12:15 2026 +1000 +++ b/qt/ScintillaEditBase/PlatQt.h Sat May 23 14:06:04 2026 +1000 @@ -164,6 +164,8 @@ QPainter *GetPainter(); }; +std::unique_ptr<Surface> SurfaceImpl_AllocatePixMap(int width, int height, SurfaceMode mode); + } #endif diff -r 17e8e54ba9a1 qt/ScintillaEditBase/ScintillaEditBase.cpp --- a/qt/ScintillaEditBase/ScintillaEditBase.cpp Fri May 15 09:12:15 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaEditBase.cpp Sat May 23 14:06:04 2026 +1000 @@ -127,6 +127,12 @@ } else if (event->type() == QEvent::Hide) { setMouseTracking(false); result = QAbstractScrollArea::event(event); +#if QT_VERSION >= QT_VERSION_CHECK(6, 6, 0) + } else if (event->type() == QEvent::DevicePixelRatioChange) { + // Reset cached data + sqt->InvalidateStyleRedraw(); + result = QAbstractScrollArea::event(event); +#endif } else { result = QAbstractScrollArea::event(event); } @@ -318,7 +324,7 @@ void ScintillaEditBase::mousePressEvent(QMouseEvent *event) { - const Point pos = PointFromQPoint(event->pos()); + const Point pos = PointFromQPoint(event->pos()) * devicePixelRatioF(); emit buttonPressed(event); @@ -347,7 +353,8 @@ void ScintillaEditBase::mouseReleaseEvent(QMouseEvent *event) { - const QPoint point = event->pos(); + const QPoint point = event->pos() * devicePixelRatioF(); + if (event->button() == Qt::LeftButton) sqt->ButtonUpWithModifiers(PointFromQPoint(point), TimeOfEvent(time), ModifiersOfKeyboard()); @@ -367,7 +374,7 @@ void ScintillaEditBase::mouseMoveEvent(QMouseEvent *event) { - const Point pos = PointFromQPoint(event->pos()); + const Point pos = PointFromQPoint(event->pos()) * devicePixelRatioF(); const bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; const bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; diff -r 17e8e54ba9a1 qt/ScintillaEditBase/ScintillaQt.cpp --- a/qt/ScintillaEditBase/ScintillaQt.cpp Fri May 15 09:12:15 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaQt.cpp Sat May 23 14:06:04 2026 +1000 @@ -801,13 +801,22 @@ void ScintillaQt::PartialPaint(const PRectangle &rect) { - rcPaint = rect; + QWidget *widget = static_cast<QWidget *>(wMain.GetID()); + const qreal scale = widget->devicePixelRatioF(); + + rcPaint = rect * scale; paintState = PaintState::painting; PRectangle rcClient = GetClientRectangle(); paintingAllText = rcPaint.Contains(rcClient); + // Create Bitmap of exactly window size then blit after Paint call + const int w = static_cast<int>(rcClient.Width() * scale); + const int h = static_cast<int>(rcClient.Height() * scale); + std::unique_ptr<Surface> surfPix = SurfaceImpl_AllocatePixMap(w, h, CurrentSurfaceMode()); + PRectangle rcScaled = PRectangle::FromInts(0,0,w,h); + Paint(surfPix.get(), rcScaled); AutoSurface surfacePaint(this); - Paint(surfacePaint, rcPaint); + surfacePaint->Copy(rcScaled, Point(0,0), *surfPix); surfacePaint->Release(); if (paintState == PaintState::abandoned) { diff -r 17e8e54ba9a1 src/Geometry.h --- a/src/Geometry.h Fri May 15 09:12:15 2026 +1000 +++ b/src/Geometry.h Sat May 23 14:06:04 2026 +1000 @@ -45,6 +45,10 @@ return Point(x - other.x, y - other.y); } + constexpr Point operator*(XYPOSITION multiplier) const noexcept { + return Point(x * multiplier, y * multiplier); + } + // Other automatically defined methods (assignment, copy constructor, destructor) are fine }; @@ -101,6 +105,15 @@ return (rc.left == left) && (rc.right == right) && (rc.top == top) && (rc.bottom == bottom); } + + constexpr PRectangle operator*(XYPOSITION multiplier) const noexcept { + return PRectangle(left * multiplier, top * multiplier, right * multiplier, bottom * multiplier); + } + + constexpr PRectangle operator/(XYPOSITION divisor) const noexcept { + return PRectangle(left / divisor, top / divisor, right / divisor, bottom / divisor); + } + constexpr bool Contains(Point pt) const noexcept { return (pt.x >= left) && (pt.x <= right) && (pt.y >= top) && (pt.y <= bottom); ------=_Part_121574_68914752.1779513797096--