Kieran E
Kieran E

Reputation: 3666

MacOS dock-like component in QML

Using QtQuick, I have a row of 5 images in a repeater. I'd like to implement an animation on hover that's similar to the MacOS dock animation. Here's a picture for reference:

MacOS Dock

To further break it down, here's what I'm trying to accomplish. These images, on hover, should act as follows:

Here is the code I have so far

  Row {
    spacing: 2
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 30
    anchors.horizontalCenter: parent.horizontalCenter

    Repeater {
      id: iconRepeater
      model: iconColors()
      Image {
        source: "icons/" + modelData + ".png"
        scale: mouseArea.containsMouse ? 1.5 : 1.0
        MouseArea {
          id: mouseArea
          anchors.fill: parent
          hoverEnabled: true
          onClicked: endTimer()
        }
        Behavior on scale {
          PropertyAnimation {
            duration: 75
          }
        }
      }
    }
  }

This expands the image you hover over, but I cannot seem to also effect the neighbors. Any advice is appreciated!

Upvotes: 1

Views: 631

Answers (2)

dtech
dtech

Reputation: 49279

I'd suggest a little more robust solution, where you have control over the zoom factor and the spread and decay of influence:

enter image description here

  Column {
    Slider {
      id: foff
      from: 1
      to: 5
      stepSize: 1
      value: 2
      snapMode: Slider.SnapAlways
    }
    Slider {
      id: sf
      from: 0.5
      to: 2.5
      stepSize: 0.5
      value: 0.5
      snapMode: Slider.SnapAlways
    }
    Slider {
      id: dmp
      from: 1
      to: 5
      stepSize: 1
      value: 1
      snapMode: Slider.SnapAlways
    }
  }

  Row {
    id: row
    anchors.bottom: parent.bottom
    anchors.bottomMargin: 30
    anchors.horizontalCenter: parent.horizontalCenter

    property int falloff: foff.value // how many adjacent elements are affected
    property int current: -1
    property real scaleFactor: sf.value // that's how much extra it scales
    property real damp: dmp.value // decay of influence 

    Repeater {
      id: iconRepeater
      model: 10
      Rectangle {
        width: 50 * pseudoScale
        height: width
        anchors.bottom: parent.bottom
        color: "red"
        border.color: "black"
        property real pseudoScale: {
          if (row.current == -1) return 1
          else {
            var diff = Math.abs(index - row.current)
            diff = Math.max(0, row.falloff - diff)
            var damp = row.falloff - Math.max(1, diff)
            var sc = row.scaleFactor
            if (damp) sc /= damp * row.damp
            diff = diff / row.falloff * sc + 1
            return diff
          }
        }
        MouseArea {
          id: mouseArea
          anchors.fill: parent
          hoverEnabled: true
          onContainsMouseChanged: row.current = containsMouse ? index : -1
        }
        Behavior on pseudoScale {
          PropertyAnimation {
            duration: 150
          }
        }
      }
    }
  }

Upvotes: 7

derM
derM

Reputation: 13691

It could be something along the lines of this:

Row {
    anchors {
        bottom: parent.bottom
        left: parent.left
        right: parent.right
    }

    Repeater {
        id: rep
        model: ['red', 'yellow', 'pink', 'green', 'teal', 'orchid', 'blue', 'orange']
        property int currentIndex: -10

        delegate: Rectangle {
            anchors.bottom: parent.bottom
            // Calculate the width depending on the currently hovered element
            width: (rep.currentIndex === index ? 100 : ((rep.currentIndex - index) === 1 || (rep.currentIndex - index) === -1 ? 80 : 50))
            height: width
            radius: width / 2
            color: modelData
            MouseArea {
                anchors.fill: parent
                hoverEnabled: true
                // onEntered/Exited did not react. This will work.
                onContainsMouseChanged: {
                    if (containsMouse) rep.currentIndex = index
                    else rep.currentIndex = -10 // -10 is safe
                }
            }
            // Makes the movement smooth
            Behavior on width {
                NumberAnimation {}
            }
        }
    }
}

I tried to put in the necessary explainations as comment in the code. The only thing that would need some tweeking is, that the dots will be initially shifted when the first resizing happens. Putting it on a flickable and some manual labour for the right position handeling could deal with that. Basically you need to shift the flickable by half the width change (in my case ~ 55 or so) to the left, when the mouse enters, and to the right when it leaves again.

You could also do so with a ListView, most likely, but due to the ever changing estimated size of the background, it might be more challanging to get the positioning right.

Upvotes: 1

Related Questions