diff -r a2c9fad98106 qt/ScintillaEditBase/PlatQt.cpp --- a/qt/ScintillaEditBase/PlatQt.cpp Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/PlatQt.cpp Thu Jun 11 10:35:13 2026 +1000 @@ -183,13 +183,14 @@ SurfaceImpl::SurfaceImpl() = default; -SurfaceImpl::SurfaceImpl(int width, int height, SurfaceMode mode_) +SurfaceImpl::SurfaceImpl(int width, int height, SurfaceMode mode_, qreal scale_) { if (width < 1) width = 1; if (height < 1) height = 1; deviceOwned = true; device = new QPixmap(width, height); mode = mode_; + scale = scale_; } SurfaceImpl::~SurfaceImpl() @@ -212,21 +213,35 @@ painterOwned = false; } +double ScaleOfWindow(WindowID wid) +{ + QWidget *widget = window(wid); + QVariant variant = widget->property("ScintillaScale"); + return variant.toDouble(); +} + void SurfaceImpl::Init(WindowID wid) { Release(); - device = static_cast(wid); + device = window(wid); + scale = ScaleOfWindow(wid); } void SurfaceImpl::Init(SurfaceID sid, WindowID /*wid*/) { Release(); device = static_cast(sid); + scale = 0.0; } std::unique_ptr SurfaceImpl::AllocatePixMap(int width, int height) { - return std::make_unique(width, height, mode); + return std::make_unique(width, height, mode, scale); +} + +std::unique_ptr SurfaceImpl_AllocatePixMap(int width, int height, SurfaceMode mode, qreal scale) +{ + return std::make_unique(width, height, mode, scale); } void SurfaceImpl::SetMode(SurfaceMode mode_) @@ -309,7 +324,11 @@ int SurfaceImpl::DeviceHeightFont(int points) { - return points; + if (scale) { + return points * scale; + } else { + return points; + } } void SurfaceImpl::LineDraw(Point start, Point end, Stroke stroke) @@ -796,6 +815,10 @@ painter->setRenderHint(QPainter::TextAntialiasing, true); painter->setRenderHint(QPainter::Antialiasing, true); + + if (scale) { + painter->scale(1.0 / scale, 1.0 / scale); + } } return painter; @@ -811,11 +834,6 @@ namespace { -QWidget *window(WindowID wid) noexcept -{ - return static_cast(wid); -} - QRect ScreenRectangleForPoint(QPoint posGlobal) { #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) @@ -854,6 +872,9 @@ void Window::SetPositionRelative(PRectangle rc, const Window *relativeTo) { + const qreal scale = window(relativeTo->wid)->property("ScintillaScale").toDouble(); + if (scale) + rc = rc / scale; QPoint oPos = window(relativeTo->wid)->mapToGlobal(QPoint(0,0)); int ox = oPos.x(); int oy = oPos.y(); @@ -884,7 +905,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) @@ -901,8 +924,21 @@ void Window::InvalidateRectangle(PRectangle rc) { - if (wid) + 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 + window(wid)->update(); + return; + } +#endif + rc = rc / scale; + } window(wid)->update(QRectFromPRect(rc)); + } } void Window::SetCursor(Cursor curs) @@ -999,6 +1035,7 @@ int visibleRows{5}; QMap images; float imageScale{1.0}; + QWidget *owner = nullptr; }; ListBoxImpl::ListBoxImpl() noexcept = default; @@ -1012,6 +1049,7 @@ unicodeMode = unicodeMode_; QWidget *qparent = static_cast(parent.GetID()); + owner = qparent; ListWidget *list = new ListWidget(qparent); #if defined(Q_OS_WIN) @@ -1054,7 +1092,14 @@ ListWidget *list = GetWidget(); const FontAndCharacterSet *pfacs = AsFontAndCharacterSet(font); if (pfacs && pfacs->pfont) { - list->setFont(*(pfacs->pfont)); + QFont fontDeScaled = *(pfacs->pfont); + const qreal scaleOwner = ScaleOfWindow(owner); + if (scaleOwner) { + const qreal scale = window(owner)->devicePixelRatioF(); + qreal pointSize = fontDeScaled.pointSizeF() / scale; + fontDeScaled.setPointSizeF(pointSize); + } + list->setFont(fontDeScaled); } } void ListBoxImpl::SetAverageCharWidth(int /*width*/) {} @@ -1084,7 +1129,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 a2c9fad98106 qt/ScintillaEditBase/PlatQt.h --- a/qt/ScintillaEditBase/PlatQt.h Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/PlatQt.h Thu Jun 11 10:35:13 2026 +1000 @@ -74,6 +74,11 @@ return QPointF(qp.x, qp.y); } +inline QWidget *window(WindowID wid) noexcept +{ + return static_cast(wid); +} + class SurfaceImpl : public Surface { private: QPaintDevice *device = nullptr; @@ -81,6 +86,7 @@ bool deviceOwned = false; bool painterOwned = false; SurfaceMode mode; + qreal scale = 0.0; const char *codecName = nullptr; QTextCodec *codec = nullptr; @@ -88,7 +94,7 @@ public: SurfaceImpl(); - SurfaceImpl(int width, int height, SurfaceMode mode_); + SurfaceImpl(int width, int height, SurfaceMode mode_, qreal scale_); virtual ~SurfaceImpl() override; void Init(WindowID wid) override; @@ -164,6 +170,8 @@ QPainter *GetPainter(); }; +std::unique_ptr SurfaceImpl_AllocatePixMap(int width, int height, SurfaceMode mode, qreal scale); + } #endif diff -r a2c9fad98106 qt/ScintillaEditBase/ScintillaEditBase.cpp --- a/qt/ScintillaEditBase/ScintillaEditBase.cpp Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaEditBase.cpp Thu Jun 11 10:35:13 2026 +1000 @@ -103,6 +103,15 @@ return sqt->WndProc(static_cast(iMessage), wParam, reinterpret_cast(s)); } +bool ScintillaEditBase::Scaled() const { + return sqt->scaled; +} + +void ScintillaEditBase::SetScaled(bool scaled) { + sqt->scaled = scaled; + sqt->InvalidateStyleRedraw(); +} + void ScintillaEditBase::scrollHorizontal(int value) { sqt->HorizontalScrollTo(value); @@ -127,6 +136,13 @@ } 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->SetScaleProperty(); + sqt->InvalidateStyleRedraw(); + result = QAbstractScrollArea::event(event); +#endif } else { result = QAbstractScrollArea::event(event); } @@ -318,7 +334,7 @@ void ScintillaEditBase::mousePressEvent(QMouseEvent *event) { - const Point pos = PointFromQPoint(event->pos()); + const Point pos = PointFromQPoint(event->pos()) * (Scaled() ? devicePixelRatioF() : 1.0); emit buttonPressed(event); @@ -347,7 +363,7 @@ void ScintillaEditBase::mouseReleaseEvent(QMouseEvent *event) { - const QPoint point = event->pos(); + const QPoint point = event->pos() * (Scaled() ? devicePixelRatioF() : 1.0); if (event->button() == Qt::LeftButton) sqt->ButtonUpWithModifiers(PointFromQPoint(point), TimeOfEvent(time), ModifiersOfKeyboard()); @@ -367,7 +383,7 @@ void ScintillaEditBase::mouseMoveEvent(QMouseEvent *event) { - const Point pos = PointFromQPoint(event->pos()); + const Point pos = PointFromQPoint(event->pos()) * (Scaled() ? devicePixelRatioF() : 1.0); const bool shift = QApplication::keyboardModifiers() & Qt::ShiftModifier; const bool ctrl = QApplication::keyboardModifiers() & Qt::ControlModifier; diff -r a2c9fad98106 qt/ScintillaEditBase/ScintillaEditBase.h --- a/qt/ScintillaEditBase/ScintillaEditBase.h Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaEditBase.h Thu Jun 11 10:35:13 2026 +1000 @@ -71,6 +71,9 @@ uptr_t wParam = 0, const char *s = 0) const; + bool Scaled() const; + void SetScaled(bool scaled); + public slots: // Scroll events coming from GUI to be sent to Scintilla. void scrollHorizontal(int value); diff -r a2c9fad98106 qt/ScintillaEditBase/ScintillaQt.cpp --- a/qt/ScintillaEditBase/ScintillaQt.cpp Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaQt.cpp Thu Jun 11 10:35:13 2026 +1000 @@ -33,6 +33,7 @@ { wMain = scrollArea->viewport(); + scrollArea->viewport()->setProperty("ScintillaScale", 0.0); imeInteraction = IMEInteraction::Inline; @@ -715,7 +716,11 @@ if (!ct.wCallTip.Created()) { QWidget *pCallTip = new CallTipImpl(scrollArea->window()->windowHandle(), &ct); + const qreal scaleDevice = pCallTip->devicePixelRatioF(); + pCallTip->setProperty("ScintillaScale", scaled ? scaleDevice : 0.0); + ct.wCallTip = pCallTip; + rc = rc * scaleDevice; pCallTip->move(rc.left, rc.top); pCallTip->resize(rc.Width(), rc.Height()); } @@ -805,20 +810,55 @@ return returnValue; } +void ScintillaQt::SetScaleProperty() +{ + qreal scale = 0.0; + if (scaled) { + QWidget *widget = window(wMain.GetID()); + scale = widget->devicePixelRatioF(); + } + scrollArea->viewport()->setProperty("ScintillaScale", scale); +} + // Additions to merge in Scientific Toolworks widget structure void ScintillaQt::PartialPaint(const PRectangle &rect) { - rcPaint = rect; + SetScaleProperty(); + if (scaled) { + QWidget *widget = window(wMain.GetID()); + rcPaint = rect * widget->devicePixelRatioF(); + } else { + rcPaint = rect; + } paintState = PaintState::painting; PRectangle rcClient = GetClientRectangle(); paintingAllText = rcPaint.Contains(rcClient); AutoSurface surfacePaint(this); - Paint(surfacePaint, rcPaint); + if (scaled) { + // Create Bitmap of exactly window size then blit after Paint call + QWidget *widget = window(wMain.GetID()); + const qreal scale = widget->devicePixelRatioF(); + const QSize sz = widget->size() * scale; + const int w = sz.width(); + const int h = sz.height(); + std::unique_ptr surfPix = SurfaceImpl_AllocatePixMap(w, h, CurrentSurfaceMode(), 0.0); + PRectangle rcScaled = PRectangle::FromInts(0, 0, w, h); + Paint(surfPix.get(), rcScaled); + surfacePaint->Copy(rcScaled, Point(0,0), *surfPix); + } else { + Paint(surfacePaint, rcPaint); + } surfacePaint->Release(); if (paintState == PaintState::abandoned) { + if (scaled) { + // Should try more repair but just queue a full repaint. + scrollArea->viewport()->update(); + paintState = PaintState::notPainting; + return; + } // FIXME: Failure to paint the requested rectangle in each // paint event causes flicker on some platforms (Mac?) // Paint rect immediately. diff -r a2c9fad98106 qt/ScintillaEditBase/ScintillaQt.h --- a/qt/ScintillaEditBase/ScintillaQt.h Sat Jun 06 09:32:56 2026 +1000 +++ b/qt/ScintillaEditBase/ScintillaQt.h Thu Jun 11 10:35:13 2026 +1000 @@ -156,6 +156,8 @@ static sptr_t DirectStatusFunction(sptr_t ptr, unsigned int iMessage, uptr_t wParam, sptr_t lParam, int *pStatus); + void SetScaleProperty(); + protected: void PartialPaint(const PRectangle &rect); @@ -178,6 +180,8 @@ bool dragWasDropped; int rectangularSelectionModifier; + bool scaled = false; + friend class ::ScintillaEditBase; }; diff -r a2c9fad98106 src/Geometry.h --- a/src/Geometry.h Sat Jun 06 09:32:56 2026 +1000 +++ b/src/Geometry.h Thu Jun 11 10:35:13 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);