[pim/mimetreeparser] src/quick: Fix horizontal scroll too

Claudio Cambra <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 037c5b63e2c2d8565a530f56bdb2bc0d985c83bb by Claudio Cambra.
Committed on 16/08/2026 at 16:51.
Pushed by clau-cambra into branch 'master'.

Fix horizontal scroll too

Fix horizontal scroll

M  +77   -64   src/quick/qml/private/HtmlPart.qml
M  +49   -25   src/quick/wheelinterceptor.cpp
M  +4    -0    src/quick/wheelinterceptor.h

https://invent.kde.org/pim/mimetreeparser/-/commit/037c5b63e2c2d8565a530f56bdb2bc0d985c83bb

diff --git a/src/quick/qml/private/HtmlPart.qml b/src/quick/qml/private/HtmlPart.qml
index c968345..4890334 100644
--- a/src/quick/qml/private/HtmlPart.qml
+++ b/src/quick/qml/private/HtmlPart.qml
@@ -27,82 +27,95 @@ Item {
         htmlView.loadHtml(content, "file:///");
     }
 
-    QQC2.ScrollView {
+    Flickable {
+        id: flickable
         anchors.fill: parent
-        Flickable {
-            id: flickable
+        clip: true
+        boundsBehavior: Flickable.StopAtBounds
+        contentWidth: root.contentWidth
 
-            clip: true
-            boundsBehavior: Flickable.StopAtBounds
+        WebEngineView {
+            id: htmlView
+            objectName: "htmlView"
+            anchors.fill: parent
 
-            WebEngineView {
-                id: htmlView
-                objectName: "htmlView"
-                anchors.fill: parent
-
-                Component.onCompleted: loadHtml(content, "file:///")
-                onLoadingChanged: loadingInfo => {
-                    if (loadingInfo.status === WebEngineView.LoadFailedStatus) {
-                        console.warn("Failed to load html content.")
-                        console.warn("Error is ", loadingInfo.errorString)
-                    }
-                    root.contentWidth = Math.max(contentsSize.width, flickable.minimumSize)
-
-                    if (loadingInfo.status === WebEngineView.LoadSucceededStatus) {
-                        runJavaScript("[document.body.scrollHeight, document.body.scrollWidth, document.documentElement.scrollHeight]", function(result) {
-                            root.contentHeight = Math.min(Math.max(result[0], result[2]), 4000);
-                            root.contentWidth = Math.min(Math.max(result[1], flickable.width), 2000)
-                        });
-                    }
-                }
-                onLinkHovered: hoveredUrl => {
-                    // Qt 6.6.1 needs to toString otherwise we get a compile error
-                    // https://bugreports.qt.io/browse/QTBUG-119165
-                    console.debug("Link hovered ", hoveredUrl.toString())
-                }
-                onNavigationRequested: request => {
-                    if (request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) {
-                        Qt.openUrlExternally(request.url)
-                        request.action = WebEngineNavigationRequest.IgnoreRequest
-                    }
+            Component.onCompleted: loadHtml(content, "file:///")
+            onLoadingChanged: loadingInfo => {
+                if (loadingInfo.status === WebEngineView.LoadFailedStatus) {
+                    console.warn("Failed to load html content.")
+                    console.warn("Error is ", loadingInfo.errorString)
                 }
-                settings {
-                    webGLEnabled: false
-                    touchIconsEnabled: false
-                    spatialNavigationEnabled: false
-                    screenCaptureEnabled: false
-                    pluginsEnabled: false
-                    localStorageEnabled: false
-                    localContentCanAccessRemoteUrls: false
-                    localContentCanAccessFileUrls: false
-                    linksIncludedInFocusChain: false
-                    javascriptEnabled: true
-                    javascriptCanOpenWindows: false
-                    javascriptCanAccessClipboard: false
-                    hyperlinkAuditingEnabled: false
-                    fullScreenSupportEnabled: false
-                    errorPageEnabled: false
-                    //defaultTextEncoding: ???
-                    autoLoadImages: root.autoLoadImages
-                    autoLoadIconsForPage: false
-                    accelerated2dCanvasEnabled: false
-                    //The webview should not steal focus
-                    focusOnNavigationEnabled: false
+                root.contentWidth = Math.max(contentsSize.width, root.minimumSize)
+                if (loadingInfo.status === WebEngineView.LoadSucceededStatus) {
+                    runJavaScript("[document.body.scrollHeight, document.body.scrollWidth, document.documentElement.scrollHeight]", function(result) {
+                        root.contentHeight = Math.min(Math.max(result[0], result[2]), 4000);
+                        root.contentWidth = Math.min(Math.max(result[1], root.width), 2000)
+                    });
                 }
-                profile {
-                    offTheRecord: true
-                    httpCacheType: WebEngineProfile.NoCache
-                    persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies
-                }
-                onContextMenuRequested: request => {
-                    request.accepted = true
+            }
+            onLinkHovered: hoveredUrl => {
+                // Qt 6.6.1 needs to toString otherwise we get a compile error
+                // https://bugreports.qt.io/browse/QTBUG-119165
+                console.debug("Link hovered ", hoveredUrl.toString())
+            }
+            onNavigationRequested: request => {
+                if (request.navigationType === WebEngineNavigationRequest.LinkClickedNavigation) {
+                    Qt.openUrlExternally(request.url)
+                    request.action = WebEngineNavigationRequest.IgnoreRequest
                 }
             }
+            settings {
+                webGLEnabled: false
+                touchIconsEnabled: false
+                spatialNavigationEnabled: false
+                screenCaptureEnabled: false
+                pluginsEnabled: false
+                localStorageEnabled: false
+                localContentCanAccessRemoteUrls: false
+                localContentCanAccessFileUrls: false
+                linksIncludedInFocusChain: false
+                javascriptEnabled: true
+                javascriptCanOpenWindows: false
+                javascriptCanAccessClipboard: false
+                hyperlinkAuditingEnabled: false
+                fullScreenSupportEnabled: false
+                errorPageEnabled: false
+                //defaultTextEncoding: ???
+                autoLoadImages: root.autoLoadImages
+                autoLoadIconsForPage: false
+                accelerated2dCanvasEnabled: false
+                //The webview should not steal focus
+                focusOnNavigationEnabled: false
+            }
+            profile {
+                offTheRecord: true
+                httpCacheType: WebEngineProfile.NoCache
+                persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies
+            }
+            onContextMenuRequested: request => {
+                request.accepted = true
+            }
         }
     }
 
     WheelInterceptor {
+        id: wheelInterceptor
         source: htmlView
         target: flickable
     }
+
+    QQC2.ScrollBar {
+        id: hScrollBar
+        orientation: Qt.Horizontal
+        parent: wheelInterceptor.scrollTarget ? wheelInterceptor.scrollTarget : root
+        anchors.left: parent ? parent.left : undefined
+        anchors.right: parent ? parent.right : undefined
+        anchors.bottom: parent ? parent.bottom : undefined
+        visible: root.contentWidth > root.width
+        size: root.width / Math.max(1, root.contentWidth)
+        position: flickable.contentX / Math.max(1, root.contentWidth - root.width)
+        onPositionChanged: flickable.contentX = position * (root.contentWidth - root.width)
+    }
+
+    Component.onCompleted: wheelInterceptor.hScrollBar = hScrollBar
 }
diff --git a/src/quick/wheelinterceptor.cpp b/src/quick/wheelinterceptor.cpp
index a99114c..e097e81 100644
--- a/src/quick/wheelinterceptor.cpp
+++ b/src/quick/wheelinterceptor.cpp
@@ -5,6 +5,7 @@
 
 #include <QCoreApplication>
 #include <QGuiApplication>
+#include <QMetaObject>
 #include <QStyleHints>
 #include <QWheelEvent>
 
@@ -38,7 +39,6 @@ QQuickItem *WheelInterceptor::target() const
 {
     return m_target;
 }
-
 void WheelInterceptor::setTarget(QQuickItem *target)
 {
     if (m_target == target) {
@@ -48,6 +48,11 @@ void WheelInterceptor::setTarget(QQuickItem *target)
     Q_EMIT targetChanged();
 }
 
+QQuickItem *WheelInterceptor::scrollTarget() const
+{
+    return m_scrollTarget;
+}
+
 void WheelInterceptor::updateScrollTarget()
 {
     m_scrollTarget = nullptr;
@@ -63,34 +68,53 @@ void WheelInterceptor::updateScrollTarget()
         current = current->parentItem();
     }
 
-    if (!m_scrollTarget && m_target) {
-        current = m_target;
-        while (current) {
-            if (current->inherits("QQuickFlickable")) {
-                m_scrollTarget = current;
-            }
-            current = current->parentItem();
-        }
-    }
+    Q_EMIT scrollTargetChanged();
+}
+
+static void applyScroll(QQuickItem *target, qreal pixelDelta)
+{
+    const auto viewH = target->height();
+    const auto contentH = target->property("contentHeight").toReal();
+    const auto currentY = target->property("contentY").toReal();
+    const auto maxScrollY = qMax(0.0, contentH - viewH);
+    target->setProperty("contentY", qBound(0.0, currentY + pixelDelta, maxScrollY));
+}
+
+static void applyHScroll(QQuickItem *target, qreal pixelDelta)
+{
+    const auto viewW = target->width();
+    const auto contentW = target->property("contentWidth").toReal();
+    const auto currentX = target->property("contentX").toReal();
+    const auto maxScrollX = qMax(0.0, contentW - viewW);
+    target->setProperty("contentX", qBound(0.0, currentX + pixelDelta, maxScrollX));
 }
 
 bool WheelInterceptor::eventFilter(QObject *obj, QEvent *event)
 {
     Q_UNUSED(obj);
-    if (event->type() == QEvent::Wheel && m_source && m_scrollTarget) {
-        auto wheelEvent = static_cast<QWheelEvent *>(event);
-        const auto localPos = m_source->mapFromGlobal(wheelEvent->globalPosition().toPoint());
-        if (m_source->contains(localPos)) {
-            const auto viewH = m_scrollTarget->height();
-            const auto contentH = m_scrollTarget->property("contentHeight").toReal();
-            const auto currentY = m_scrollTarget->property("contentY").toReal();
-            const auto maxScroll = qMax(0.0, contentH - viewH);
-            const auto delta = wheelEvent->angleDelta().y();
-            const auto scrollLines = QGuiApplication::styleHints()->wheelScrollLines();
-            const auto pixels = -(delta / 120.0) * scrollLines * 20.0;
-            m_scrollTarget->setProperty("contentY", qBound(0.0, currentY + pixels, maxScroll));
-            return true;
-        }
+    if (event->type() != QEvent::Wheel || !m_source) {
+        return false;
+    }
+    auto wheelEvent = static_cast<QWheelEvent *>(event);
+    const auto localPos = m_source->mapFromGlobal(wheelEvent->globalPosition().toPoint());
+    if (!m_source->contains(localPos)) {
+        return false;
+    }
+
+    const auto scrollLines = QGuiApplication::styleHints()->wheelScrollLines();
+    const auto factor = scrollLines * 20.0 / 120.0;
+    const auto deltaY = -wheelEvent->angleDelta().y() * factor;
+    const auto deltaX = -wheelEvent->angleDelta().x() * factor;
+
+    if (m_target) {
+        applyScroll(m_target, deltaY);
+        applyHScroll(m_target, deltaX);
+    } else if (deltaX != 0.0) {
+        const auto js = QStringLiteral("window.scrollBy(%1, 0)").arg(deltaX);
+        QMetaObject::invokeMethod(m_source, "runJavaScript", Q_ARG(QString, js));
+    }
+    if (m_scrollTarget) {
+        applyScroll(m_scrollTarget, deltaY);
     }
-    return false;
+    return true;
 }
diff --git a/src/quick/wheelinterceptor.h b/src/quick/wheelinterceptor.h
index 6b0650f..bbdfc63 100644
--- a/src/quick/wheelinterceptor.h
+++ b/src/quick/wheelinterceptor.h
@@ -13,6 +13,7 @@ class WheelInterceptor : public QObject
     QML_ELEMENT
     Q_PROPERTY(QQuickItem *source READ source WRITE setSource NOTIFY sourceChanged)
     Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
+    Q_PROPERTY(QQuickItem *scrollTarget READ scrollTarget NOTIFY scrollTargetChanged)
 
 public:
     explicit WheelInterceptor(QObject *parent = nullptr);
@@ -23,12 +24,15 @@ public:
     QQuickItem *target() const;
     void setTarget(QQuickItem *target);
 
+    QQuickItem *scrollTarget() const;
+
 protected:
     bool eventFilter(QObject *obj, QEvent *event) override;
 
 Q_SIGNALS:
     void sourceChanged();
     void targetChanged();
+    void scrollTargetChanged();
 
 private:
     void updateScrollTarget();
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.