Fabian Menco
Fabian Menco

Reputation: 112

DropShadow for translucent items

I have a problem to create a shadow in an item. The item is not completely opaque and the drawn shadow appears behind the item reducing the transparency effect.

I need something as the picture to the right, but what I got with my attempts is shown to the left. I need you to look through the object, because the background is not solid. .

I tried to use maskEf but the object becomes completely opaque. The closest solution I've managed to define is to use another element of the same shape but fully transparent and with solid edge. However I don't like the solid edge, any suggestions?

First attempt. This makes opacity equal to 1 in rec1:

Rectangle {
    id: rec1; color: "white"; opacity: 0.5
    anchors.fill: parent; radius: CalcSize.getW(8)
    layer.enabled: true
    layer.effect: DropShadow {
    id: shadowEf
        anchors.fill: rec1
        source: rec1
        horizontalOffset: 3
        verticalOffset: 3
        radius: 15
        samples: 16
        color: "red"
        transparentBorder: true
    }
}

Second attempt. This maintains opacity of rec1 but show the border of sourceMaskEf

DropShadow {
    id: shadowEf
    anchors.fill: sourceMaskEf
    source: sourceMaskEf
    horizontalOffset: 3
    verticalOffset: 3
    radius: 15
    samples: 16
    color: "red"
    transparentBorder: true
}

Rectangle {
    id: sourceMaskEf; color: "transparent"
    anchors.fill: rec1; radius: rec1.radius
    border { width: offset; color: "white"; }
}

OpacityMask {
    id: maskEf
    opacity: 1
    anchors.fill: rec1
    source: ShaderEffectSource {
            sourceItem: shadowEf
            hideSource: false
        }
    maskSource: ShaderEffectSource {
            sourceItem: sourceMaskEf
            hideSource: false // if set true the shadow is hide to
        }
    cached: true
}

Rectangle {
    id: rec1; color: "white"; opacity: 0.5
    anchors.fill: parent; radius: CalcSize.getW(8)
}

Edit

Well, after the suggestion of BaCaRoZzo, this is my solution. It is much closer to what I'm looking for:

Component {
    id: fondoItemPromo
    Item {
        id: item1; opacity: 0.5
        layer.enabled: true; anchors.fill: parent
        anchors.margins: CalcSize.getW(5) //Just for test
        Rectangle {
            id: rec1; color: "white"
            anchors.fill: parent; radius: CalcSize.getW(8)
            Item {
                id: item2; opacity: 0.5; layer.enabled: true
                anchors.fill: parent; clip: true
                Rectangle {
                    id: rec2; color: "white"
                    anchors.fill: parent; radius: CalcSize.getW(8)
                    layer.enabled: true
                }
                DropShadow {
                    anchors.fill: rec2
                    source: rec2
                    transparentBorder: true
                    horizontalOffset: 3
                    verticalOffset: 3
                    radius: 15
                    samples: 16
                    color: "black"; clip: true
                }
            }
        }
    }
}

However, the shadow does not extend beyond the limits of the element, as can be seen in the corners:

.

Any suggestions?

Upvotes: 2

Views: 2996

Answers (1)

BaCaRoZzo
BaCaRoZzo

Reputation: 7682

Assuming also the shadow should be semi-transparent - the overall effect would be pretty ugly otherwise - you can solve the issue with the following approach:

import QtQuick 2.4
import QtQuick.Window 2.2
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.3

ApplicationWindow {
    width: 200
    height: 300
    visible: true
    color: "steelblue"
    
    Item {
        id: layered
        opacity: 0.2
        layer.enabled: true
        anchors.fill: parent
        
        Rectangle {
            id: rec1
            width: 100
            height: 100
            anchors.centerIn: parent
            radius: 8
        }
        
        DropShadow {
            id: drop
            anchors.fill: rec1
            source: rec1
            horizontalOffset: 5
            verticalOffset: 5
            radius: 15
            samples: 16
            color: "red"
            transparentBorder: true
        }
    }
}

Here is the resulting Rectangle barely visible w.r.t. the background color and with the correct shadow applied:

enter image description here

Edit

The effect can be combined at various level as done in this example.

Given your edit, I think you have overcomplicated a bit the stuff here. The example I've given above served as a way to show that opacity should work as expected. Given the error you have shown I decided to provide a general solution which you should (hopefully) apply out of the box.

The white Rectangle acts as a container for the actual content. Hence, it should be defined in a different QML file. This way we can provide a default property, i.e. define where children Items should be positioned when added to the component. By adding aliases we are also able to fine tune the component, changing colors, shadow orientation and other graphical aspects. A possible definition of your component is the following:

// ShadowedComponent.qml    

import QtQuick 2.0
import QtGraphicalEffects 1.0

Item {
    opacity: 0.5
    layer.enabled: true
    clip: true
    property alias color: rec.color
    property alias dropColor: drop.color
    property alias voff: drop.verticalOffset
    property alias hoff: drop.horizontalOffset
    property alias radius: rec.radius
    property alias dropRadius: drop.radius
    property alias samples: drop.samples
    default property alias childrenz: rec.children //(1)

    property int margins: 20   //(2)

    Rectangle {
        id: rec
        width: 100
        height: 100
        anchors.fill: parent
        anchors.margins: margins
        radius: 8
        clip: true
    }

    DropShadow {
        id: drop
        anchors.fill: rec
        source: rec
        horizontalOffset: 5
        verticalOffset: 5
        radius: 15
        samples: 16
        color: "darkgray"
        transparentBorder: true
    }
}

The declaration in (1) is crucial as hinted in the previous text: with that we specify that any child of ShadowedComponent is automagically child of the inner Rectangle positioning it inside the component (with the desired alignement - see below). Also crucial is the property margins in (2): it gives the necessary gap for the shadow to correctly appear. A value equal to zero result in the error you get as the shadow cannot exceed boundaries of an item.

The component can be used like this:

ShadowedComponent {
    color: "red"
    dropColor: "black"
    voff: 3
    hoff: 3
    radius: 8
    dropRadius: 8
    samples: 16

    // inner content...
}

or, since all the properties have a default value, like this:

ShadowedComponent {

    // inner content...
}

Finally a possible usage example can be the following:

import QtQuick 2.4
import QtQuick.Window 2.2
import QtQuick.Controls 1.3
import QtQuick.Layouts 1.1

ApplicationWindow {
    width: 300
    height: 600
    visible: true

    Rectangle {
        anchors.fill: parent
        gradient:  Gradient {
            GradientStop { position: 0.0; color: "cyan" }
            GradientStop { position: 0.5; color: "#0099FF" }
            GradientStop { position: 1.0; color: "#6699FF" }
        }

        ColumnLayout {
            anchors.fill: parent
            anchors.margins: 10

            ShadowedComponent {
                Layout.fillHeight: true
                Layout.fillWidth: true
                voff: -5
                hoff: -10

                Image {
                    source: "http://avatarmaker.net/free-avatars/avatars/animals_216/cats_237/kitty_close_up_avatar_100x100_36619.jpg"
                    anchors.fill: parent
                    anchors.margins: 3
                }
            }

            ShadowedComponent {
                Layout.fillHeight: true
                Layout.fillWidth: true

                dropColor: "red"
                opacity: 0.7

                Text {
                    anchors.centerIn: parent
                    text: qsTr("QML ROCKS!!!")
                }
            }

            ShadowedComponent {
                Layout.fillHeight: true
                Layout.fillWidth: true
                voff: -5
                hoff: -10

                dropColor: "red"

                BusyIndicator {
                    anchors.centerIn: parent
                    running: true
                }
            }

            ShadowedComponent {
                 Layout.fillHeight: true
                 Layout.fillWidth: true
                 opacity: 0.6


                 Test {
                    anchors.fill: parent
                    opacity: 1
                    margins: 10

                    Text {
                        anchors.centerIn: parent
                        text: qsTr("QML ROCKS!!!")
                    }
                }
            }
        }
    }
}

Using a component defined in a different file you are able to compose it, declaratively, with any other (custom) component thanks to the default property. The overall result of our example is the following. Note how each component is unique in its overall appearence thanks to the numerous aliases we defined, and used. Note also that the component can be composed with itself (by also carefully tuning the margin w.r.t. the given shadow parameters):

enter image description here

Upvotes: 3

Related Questions