Search code examples
qmlqt6clipcornerradius

Clipping a moving Rectangle within the bounds of a rounded Rectangle in QML


I am creating a custom style in QML and would like to have an indeterminate progress bar that looks like this, a rounded background with a rounded bar on the inside that moves from left to right.

Desired progress bar

The trouble I'm having is with behavior when the bar reaches the end. Using just the clip property causes the rounded bar to become square when in contact with the edge.

Progress bar, improper clipping

Rectangle {
    id: bg
    color: "black"
    width: 120; height: 8; radius: 4; x: 100; y: 50
    clip: true
    
    Rectangle {
        id: bar
        color: "orange"
        width: 60; height: 8; radius: 4
      
        NumberAnimation on x {
            running: true
            from: -(bar.width * 1.2 )
            to: bg.width * 1.2
            loops: Animation.Infinite
            duration: 1000
        } 
    }
}

This is because clip uses the bounding rectangle of the parent object. Fair enough.

I next tried using a MultiEffect (I'm using Qt6 so no OpacityMask) to apply to the bar, however after some experimenting I found that the masking properties of the MultiEffect do not take into account the position of the maskSource so this didn't work.

MultiEffect {
    source: bar
    anchors.fill: bar
    maskEnabled: true
    maskSource: bg
}

How can I achieve the desired behavior, ie having the bar be clipped by the background which has rounded corners?

I'm aware I can achieve a similar effect by growing and shrinking the bar's width when at the respective edges but I would like to avoid this if possible.


Solution

  • First point, there is OpacityMask in Qt6, but it is under Qt5Compat.GraphicalEffects.

    Second point, the problem is solvable with the Qt6 MultiEffect. The pain point is the maskSource must be specified slightly different than the OpacityMask equivalent with a necessary Item to create the layering.

    The animation bar needs to be updated to contain both the black background and the animating bar.

    [EDIT]

    For improved anti-aliasing, let's put (1) enable clip and radius in the source, (2) however, turn OFF antialiasing in the maskSource.

        Rectangle {
            id: barWithBackground
            width: 120; height: 8; x: 100; y: 50; radius: 4
            clip: true
            visible: false
            color: "black"
            Rectangle {
                id: bar
                color: "orange"
                width: 60; height: 8; radius: 4
                NumberAnimation on x {
                    running: true
                    from: -(bar.width * 1.2 )
                    to: barWithBackground.width * 1.2
                    loops: Animation.Infinite
                    duration: 1000
                }
            }
        }
    
        // OpacityMask
        // Qt5.* from QtGraphicalEffects
        // Qt6.* from Qt5Compat.GraphicalEffects
        // OpacityMask {
        //     source: barWithBackground
        //     anchors.fill: barWithBackground
        //     maskSource: roundedRectangleMask
        // }
    
        // Since Qt6.5
        MultiEffect {
            source: barWithBackground
            anchors.fill: barWithBackground
            maskEnabled: true
            maskSource: roundedRectangleMask
        }
    
        Item {
            id: roundedRectangleMask
            width: 120; height: 8
            layer.enabled: true
            visible: false
            Rectangle {
                color: "black"
                width: 120; height: 8; radius: 4
                antialiasing: false
            }
        }