Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-127172

QQC2.ScrollBar needs more API for feature-parity with QScrollBar

    XMLWordPrintable

Details

    • All

    Description

      QtQuick.Controls counterpart of ScrollBar component needs more API to provide feature-parity with its QtWidgets QScrollBar twin. Namely, it should be able to:

      • Scroll in page steps or "single" steps. Currently QQC2.ScrollBar has only one property for pageStep, which is a relative value (not absolute/pixel size) and defaults to 0.1 (which is 10%). QQC2.ScrollBar is unable to handle mouse clicks outside of a scroll handle to perform a page step.
      • QQC2.ScrollBar doesn't support arrow indicators on either side, let alone double arrows like QtWidgets do. Well, in fact it does, but via some questionable __privateApi invented for a sole reason to implement "window" NativeStyle.
      • QQC2.ScrollBar does not support Middle Mouse Button press and drag to drag the handle around regardless of the "pageStep" setting.

      Here is how we work around these limitations in KDE qqc2-desktop-style framework (which is a QQC2 style implementation) using a MouseArea as a background component:

      import QtQuick
      import QtQuick.Templates as T
      import org.kde.qqc2desktopstyle.private as StylePrivate
      
      T.ScrollBar {
          id: controlRoot
      
          background: MouseArea {
              anchors.fill: parent
              hoverEnabled: true
              acceptedButtons: Qt.LeftButton | Qt.MiddleButton
              onExited: style.activeControl = "groove";
              onPressed: mouse => {
                  const jumpPosition = style.positionFromMouse(mouse);
                  if (mouse.buttons & Qt.MiddleButton) {
                      style.activeControl = "handle";
                      controlRoot.position = jumpPosition;
                      mouse.accepted = true;
                  } else if (style.activeControl === "down") {
                      buttonTimer.increment = 1;
                      buttonTimer.running = true;
                      mouse.accepted = true;
                  } else if (style.activeControl === "up") {
                      buttonTimer.increment = -1;
                      buttonTimer.running = true;
                      mouse.accepted = true;
                  } else if (style.activeControl === "downPage") {
                      if (style.scrollToClickPosition(mouse)) {
                          controlRoot.position = jumpPosition;
                      } else {
                          buttonTimer.increment = controlRoot.size;
                          buttonTimer.running = true;
                      }
                      mouse.accepted = true;
                  } else if (style.activeControl === "upPage") {
                      if (style.scrollToClickPosition(mouse)) {
                          controlRoot.position = jumpPosition;
                      } else {
                          buttonTimer.increment = -controlRoot.size;
                          buttonTimer.running = true;
                      }
                      mouse.accepted = true;
                  } else {
                      mouse.accepted = false;
                  }
              }
              onPositionChanged: mouse => {
                  style.activeControl = style.hitTest(mouse.x, mouse.y);
                  if (mouse.buttons & Qt.MiddleButton) {
                      style.activeControl = "handle";
                      controlRoot.position = style.positionFromMouse(mouse);
                      mouse.accepted = true;
                  }
              }
              onReleased: mouse => {
                  style.activeControl = style.hitTest(mouse.x, mouse.y);
                  buttonTimer.running = false;
                  mouse.accepted = false;
              }
              onCanceled: buttonTimer.running = false;
      
              Timer {
                  id: buttonTimer
                  property real increment
                  repeat: true
                  interval: 150
                  triggeredOnStart: true
                  onTriggered: {
                      if (increment === 1) {
                          controlRoot.increase();
                      } else if (increment === -1) {
                          controlRoot.decrease();
                      } else {
                          controlRoot.position = Math.min(1 - controlRoot.size, Math.max(0, controlRoot.position + increment));
                      }
                  }
              }
      
              StylePrivate.StyleItem {
                  id: style
      
                  function positionFromMouse(mouse/*: MouseEvent*/): real {
                      return Math.min(1 - controlRoot.size, Math.max(0,
                          (controlRoot.horizontal
                              ? mouse.x / width
                              : mouse.y / height
                          ) - controlRoot.size / 2
                      ));
                  }
              }
          }
      }
      

      The source code is here: https://invent.kde.org/frameworks/qqc2-desktop-style/-/blob/79c592b449f44a8a0a5df6ac31568656091a28a7/org.kde.desktop/ScrollBar.qml

      Somewhat related: our MouseArea implementation broke with the transition to Qt 6, but I found a workaround to set its z-index to something bigger than zero, so a ScrollBar won't intercept the mouse events itself. I'd wish we didn't have to do that, but we lack QQC API for that.

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            qt.team.quick.subscriptions Qt Quick and Widgets Team
            ratijas ivan tkachenko
            Votes:
            3 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:

              Gerrit Reviews

                There are no open Gerrit changes