Replicating Microsoft Acrylic with QML
Tags: qt, qml
Published: 2021-04-05 19:21:07

A while back, I got to thinking about the great look of [Microsoft Acrylic](https://docs.microsoft.com/en-us/windows/uwp/design/style/acrylic). Being a Linux user, I didn't actually have Acrylic available on my system, but I wanted to try to create something similar in QML. Unfortunately, I wasn't able to figure out how to use it as an app background material with the desktop showing through, but I did manage to create something that should be useful for creating impressive apps.

The current implementation of QML Acrylic is not perfect. Putting items on top of the Acrylic can influence its appearance, and if the items are changing (think a blinking text cursor), the Acrylic will change with it. However, I hope to improve it. (Who knows? Maybe I'll have to post a second, improved Acrylic later on.)

To start off, let's create a QML window that displays an image. Fire up Qt Creator (or whatever IDE you use) and create `main.qml`:

```
import QtQuick 2.12
import QtQuick.Window 2.12

Window {
    id: root

    visible: true
    width: 640
    height: 480
    title: qsTr("Acrylic")


    Image {
        id: img

        fillMode: Image.PreserveAspectCrop
        anchors.fill: parent
        source: "pic.jpeg"
    }
}
```

where `pic.jpeg` is the image you're using. Run this to make sure it displays a window containing an image before you move on.

Now let's think a bit about how to create an Acrylic-style rectangular pane. From screenshots available on the Web, we can tell that it's rectangular, white (OK, depends on your system theme, but white is common), and partly transparent. So, let's create a Rectangle that meets these criteria and that is a child of `img`:

```
Rectangle {
    id: rect

    anchors.centerIn: parent
    color: "white"
    opacity: 0.4
    width: img.width * 1/2
    height: img.height * 1/2
}
```

Running this should produce a boring-looking rectangle in the middle of the window. Now, let's add a blur as a child of `rect` (after adding `import QtGraphicalEffects 1.0` to the top of `main.qml`):

```
GaussianBlur {
    anchors.fill: parent
    source: parent
    radius: 50
    // this formula is suggested by Qt
    samples: 1 + radius * 2
}
```

Wait, that doesn't do anything! What's going on?

Well, it turns out that this only blurs the rectangle, and leaves the image alone. To blur the whole thing, we'll need to add a `ShaderEffectSource`. Also, we'll need to move the `ShaderEffectSource` and the `GaussianBlur` out of the Rectangle to make everything behave properly (if you don't believe me, just try it yourself!). This gives us a final result of:

```
import QtQuick 2.12
import QtQuick.Window 2.12
import QtGraphicalEffects 1.0

Window {
    id: root

    visible: true
    width: 640
    height: 480
    title: qsTr("Acrylic")


    Image {
        id: img

        fillMode: Image.PreserveAspectCrop
        anchors.fill: parent
        source: "pic.jpeg"

        Rectangle {
            id: rect

            anchors.centerIn: parent
            color: "white"
            opacity: 0.4
            width: img.width * 1/2
            height: img.height * 1/2
        }

        ShaderEffectSource {
            id: ses
    
            anchors.fill: rect
            sourceItem: img
            sourceRect: Qt.rect(x, y, width, height)
        }
    
        GaussianBlur {
            radius: 50
            anchors.fill: ses
            source: ses
            samples: 1 + radius * 2
        }
    }
}
```

which gives us a nice, Acrylic-y pane. You can also do like I did and split the Acrylic out into a reusable file with some `alias`es for easy configuration:

```
import QtQuick 2.12
import QtGraphicalEffects 1.0

Item {
    id: acrylicRoot

    property alias color: rect.color
    property alias acrylicOpacity: rect.opacity

    Rectangle {
        id: rect

        anchors.fill: parent
        color: "white"
        opacity: 0.4
    }

    ShaderEffectSource {
        id: ses

        anchors.fill: parent
        sourceItem: acrylicRoot.parent
        sourceRect: Qt.rect(acrylicRoot.x, acrylicRoot.y, acrylicRoot.width, acrylicRoot.height)
    }

    GaussianBlur {
        radius: 50
        anchors.fill: ses
        source: ses
        samples: 1 + radius * 2
    }
}
```

and then stick it into any old QML scene, like so:

```
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12

Window {
    id: root

    visible: true
    width: 640
    height: 480
    title: qsTr("Acrylic")


    Image {
        id: img

        fillMode: Image.PreserveAspectCrop
        anchors.fill: parent
        source: "pic.jpeg"

        Acrylic {
            anchors.centerIn: parent
            width: parent.width * 1/2
            height: parent.height * 1/2

            Label {
                anchors.centerIn: parent
                text: "Acrylic"
                font.pointSize: 32
            }
        }
    }
}
```

(This basic code, by the way, was used to generate the screenshot at the top of this article.)

The `Label` in the above code provides an interesting bug: the `ShaderEffectSource` is grabbing the `Label` for part of its source, which means that the `Label` influences the blur. In fact, whenever the Acrylic is updated, the `ShaderEffectSource` is grabbing the blur as part of its source instead of just getting what's underneath of it. This is rather undesirable behavior. Do I know how to fix this? No. But it certainly will be an interesting topic to investigate.

Found this useful? Let me know in the comments below what you're doing with this.