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

Inconsistent behavior when changing Flickable margins

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P2: Important
    • None
    • 6.8
    • Quick: Other
    • None
    • All

    Description

      Flickable has margin properties, e.g. topMargin. These are documented as 

      These properties hold the margins around the content. This space is reserved in addition to the contentWidth and contentHeight.

      Before reading further, what's your understanding of this property and its behavior?

      Behaviors

      The behavior of setting these properties is a bit inconsistent as described below.

      Initial value

      Setting a value on construction of the Flickable

      Flickable { topMargin: 100; } 

      Results in

      Shifting the content down to make room for the 100px margin. Which results in a contentY of -100, since it's the " surface coordinate currently at the top-left corner of the Flickable."

      This is handled by QQuickFlickable::componentComplete(), which resets the contentY:

        if (!d->vData.explicitValue && d->vData.startMargin != 0.)
              setContentY(-minYExtent()); 

      After construction, atYBeginning is true.

      Updating value

      Updating a margin (topMargin e.g.) after component completion is a bit inconsistent.

      Reducing the margin when atYBeginning == true

      Reducing the margin from 100 to 50 results in

      Shifting the content closer to the Flickable's 0,0 position, updating the contentY to match. atYBeginning is true both before and after the change.

      Increasing the margin when atYBeginning == true

      Increasing the margin from 100 to 150 results in

      This does not currently shift the content down to match the new top margin, which is inconsistent with reducing the margin, and inconsistent with the componentCompleted behavior, where we do shift the content down. 

      atYBeginning is false after the change.

      Implementation-wise both reducing and increasing the margin is handled by QQuickFlickable::setTopMargin() calling fixupY()

      if (!d->pressed && !d->hData.moving && !d->vData.moving) {
              d->fixupMode = QQuickFlickablePrivate::Immediate;
              d->fixupY();
          } 

      Ending up in QQuickFlickablePrivate::fixup(), which is documented as

          This function should be called after the contentItem has been moved, either programmatically,
          or by the timeline (as a result of a flick).
          It ensures that the contentItem will be moved back into bounds,
          in case it was flicked outside of the visible area.

      The fixup() function is normally used to bring the content back into bounds after the user has scrolled or dragged the content, which also helps explain the inconsistent behavior:

      When increasing the top margin, the content item is technically still within bounds, so fixup() doesn't see a need to do anything.

      Reducing the margin when atYBeginning == false

      Reducing the margin from 100 to 50, when the content has been scrolled to a contentY of 75, results in (pre and post)

      The topMargin is reduced by 50, but the contentY is not shifted relatively to its existing position by 50. Instead it's shifted by 25, to match the new margin.

      Again, this is a (side) effect of implementing QQuickFlickable::setTopMargin() via fixupY(), which focused on ensuring the content is within bounds, and doesn't handle use-cases such as relative movement of content.

      Increasing the margin when atYBeginning == false

      Similar to when atYBeginning is true this does not change the contentY, because technically the contentItem is still within bounds.

      Reducing the margin when the user is actively dragging

      When the margin is reduced when the user is actively dragging the content we skip the immediate fixup in setTopMargin

      if (!d->pressed && !d->hData.moving && !d->vData.moving) {
          d->fixupMode = QQuickFlickablePrivate::Immediate;
          d->fixupY();
      } 

      However we have updated d->vData.startMargin, and when the user then moves the mouse we use d->vData.startMargin to compute the new content position, resulting a "jump" of the content Y.

      This feels like a bug in QQuickFlickablePrivate::drag. The user shouldn't see any change in the Y position until after releasing the mouse, at which point the return-to-bounds logic will potentially have a new bounds to return to.

      Challenge

      The inconsistent behavior for margin adjustments presents a challenge when the margins are dynamic, which we now see with the safe areas:

       

      Flickable { topMargin: SafeArea.margins.top; }
      

      The safe area margins may be 0 when the flickable is completed, and will then change once the window is shown on screen.

       

      With the current behavior, the flickable's initial content position will not be inside the safe area.

      Users have to add custom logic to handle the margin update

      Flickable {
          topMargin: SafeArea.margins.top
          onTopMarginChanged: {
              if (!dragging)
                  contentY = -topMargin
          }
          // Repeated for each margin (top, left, right, bottom)
      }
       

      Changing behaviors

      If we change behavior for how margin update affect contentX/Y, what behavior do we want?

      • Always keep content within bounds
        • This is the current behavior, which results in no change when increasing margins
      • Always shift content with same relative change to margins
        • This would make increasing and reducing margins consistent
        • But would be a behavior change for reduced margins, as we today shift it by absolute position
      • Reset contentX/Y if margin is changed when isAtBeginning is true
        • This would match the behavior of componentCompleted, where the content is at its initial position
        • If the content item has been moved, we do not touch the position on margin change

      Do we want a flag on Flickable to choose the behavior? 

      Attachments

        Issue Links

          For Gerrit Dashboard: QTBUG-131478
          # Subject Branch Project Status CR V

          Activity

            People

              vestbo Tor Arne Vestbø
              vestbo Tor Arne Vestbø
              Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There is 1 open Gerrit change