qtqmlblurswipeviewqtquickcontrols2

Qt QML SwipeView w/ background, how can I blur the right region as the page moves?


I have a SwipeView with a background image. It has two pages, both pages have a set of controls and text, and I've put an iOS-like blurred rounded rectangle underneath them so that it's easier to read but doesn't completely block out the nice background

I found this snippet at the end of this thread that led me to use a white rectangle combined with a semi translucent FastBlur that sampled the background: https://forum.qt.io/topic/38297/a-semi-transparent-rectangle-that-also-blurs/7

Rectangle {
    anchors.fill: fastBlur
    color: "white"
}

FastBlur {
    id: fastBlur

    height: 124

    width: parent.width
    radius: 40
    opacity: 0.55

    source: ShaderEffectSource {
        sourceItem: flickable
        sourceRect: Qt.rect(0, 0, fastBlur.width, fastBlur.height)
    }
}

When I move to the next page in the SwipeView, the background stays static but the blur doesn't, of course, because the sourceRect region didn't move relative to its parent, just that the parent is being moved relative to the background. The blur is still sampling the background from its original position, not the new position as it's moving through the swipeview

So I picked up the fact that I can get the SwipeView.contentItem.contentX (thanks to the answer at QML: Mid-swipe position of item in SwipeView!) But in both of my pages, I have to account differently for the contentX based on what order they're in

I set up an example blur project here to show what I mean. https://github.com/ftab/BlurTest

Specifically, at the sourceRect - https://github.com/ftab/BlurTest/blob/master/Page1Form.qml#L23

sourceRect: Qt.rect(
                fastBlur.x - swipeView.contentItem.contentX,
                fastBlur.y,
                fastBlur.width,
                fastBlur.height)

This gets the desired effect of the blur updating while it moves across the background, but only works because the blurred region is on the first page of the swipe view and contentX goes from 0 to 640. If this were on the second page, I'd have to do 640 - contentX + fastBlur.x, and then if I move it around again I have to adjust it again, etc.

Is there a better way to do this, or am I pretty much stuck hand editing the sourceRect whenever I add another page or change the order?

edit: complete QML file with 3 pages for example:

import QtQuick 2.7
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.0
import QtGraphicalEffects 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480

    SwipeView {
        id: swipeView
        anchors.fill: parent
        currentIndex: tabBar.currentIndex
        background: Image {
            id: imgBackground
            anchors.fill: parent
            fillMode: Image.PreserveAspectCrop
            source: "nature_1.jpg"
        }

        Item {
            id: item1

            Rectangle {
                id: recFrost
                anchors.fill: fastBlur
                color: "white"
            }
            FastBlur {
                id: fastBlur
                anchors.fill: recControls
                source: ShaderEffectSource {
                    sourceItem: imgBackground

                    sourceRect: Qt.rect(
                                    fastBlur.x - swipeView.contentItem.contentX,
                                    fastBlur.y,
                                    fastBlur.width,
                                    fastBlur.height)
                }
                radius: 32
                opacity: 0.55
            }
            Rectangle {
                id: recControls
                width: 364
                height: 89
                color: "#00000000"
                anchors.bottomMargin: 8
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.bottom: parent.bottom

                Label {
                    text: qsTr("First page")
                    anchors.centerIn: parent
                }
            }
        }

        Item {
            id: item2

            Rectangle {
                id: recFrost2
                anchors.fill: fastBlur2
                color: "white"
            }
            FastBlur {
                id: fastBlur2
                anchors.fill: recControls2
                source: ShaderEffectSource {
                    sourceItem: imgBackground

                    sourceRect: Qt.rect(
                                    swipeView.width - swipeView.contentItem.contentX + fastBlur2.x,
                                    fastBlur2.y,
                                    fastBlur2.width,
                                    fastBlur2.height)
                }
                radius: 32
                opacity: 0.55
            }
            Rectangle {
                id: recControls2
                width: 364
                height: 89
                color: "#00000000"
                anchors.centerIn: parent

                Label {
                    text: qsTr("Second page")
                    anchors.centerIn: parent
                }
            }
        }

        Item {
            id: item3

            Rectangle {
                id: recFrost3
                anchors.fill: fastBlur3
                color: "white"
            }
            FastBlur {
                id: fastBlur3
                anchors.fill: recControls3
                source: ShaderEffectSource {
                    sourceItem: imgBackground

                    sourceRect: Qt.rect(
                                    swipeView.width * 2 - swipeView.contentItem.contentX + fastBlur2.x,
                                    fastBlur3.y,
                                    fastBlur3.width,
                                    fastBlur3.height)
                }
                radius: 32
                opacity: 0.55
            }
            Rectangle {
                id: recControls3
                width: 364
                height: 89
                color: "#00000000"
                anchors.topMargin: 8
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.top: parent.top

                Label {
                    text: qsTr("Third page")
                    anchors.centerIn: parent
                }
            }
        }
    }

    footer: TabBar {
        id: tabBar
        currentIndex: swipeView.currentIndex
        TabButton {
            text: qsTr("First")
        }
        TabButton {
            text: qsTr("Second")
        }
        TabButton {
            text: qsTr("Third")
        }
    }
}

Solution

  • Instead of doing it with swipeView.width increments, you could use the x position of your parent page in the SwipeView :

    sourceRect: Qt.rect(fastBlur2.x - swipeView.contentItem.contentX + item2.x,
                        fastBlur2.y,
                        fastBlur2.width,
                        astBlur2.height)
    

    Alternatively you could Item's mapToItem(). However, since mapToItem is a function, using its return value won't trigger binding updates when one item moves relatively to the other, we'll have to gently hint the QML engine when to re-evaluate the binding.

    sourceRect: {
        swipeView.contentItem.contentX; /*here we access contentX in order to
        force the reevaluation of the entire expression when contentX changes.*/
        return fastBlur3.mapToItem(swipeView, 0, 0, fastBlur3.width, fastBlur3.height);
    }