DEV Community

Cover image for Replicating Microsoft Acrylic with QML
LorenDB
LorenDB

Posted on

Replicating Microsoft Acrylic with QML

A while back, I got to thinking about the great look of Microsoft 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"
    }
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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 aliases 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
    }
}
Enter fullscreen mode Exit fullscreen mode

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
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

(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.

Latest comments (0)