Drumpad example (Qt Design Studio) - Initial project

This example contains the initial Qt Design Studio project to be used as a starting point for the Qt Design Studio integration tutorial. It is not an executable project as is, since it does not contain the required Python code developed along the tutorial.

For more details, see the Qt Design Studio integration tutorial.

To download the final project source code, visit Drumpad example (Qt Design Studio) - Final project.

Download this example

// prop: json-converted
// prop: auto-generated

import QmlProject

Project {
    mainFile: "DrumpadContent/App.qml"
    mainUiFile: "DrumpadContent/MainScreen.qml"
    targetDirectory: "/opt/Drumpad"
    enableCMakeGeneration: false
    enablePythonGeneration: false
    widgetApp: true
    importPaths: [ "." ]
    mockImports: [ "Mocks" ]

    qdsVersion: "4.5"
    quickVersion: "6.7"
    qt6Project: true
    qtForMCUs: false

    multilanguageSupport: true
    primaryLanguage: "en"
    supportedLanguages: [ "en" ]

    Environment {
        QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
        QT_AUTO_SCREEN_SCALE_FACTOR: "1"
        QT_ENABLE_HIGHDPI_SCALING: "0"
        QT_LOGGING_RULES: "qt.qml.connections=false"
        QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
    }

    QmlFiles {
        directory: "Drumpad"
    }

    QmlFiles {
        directory: "DrumpadContent"
    }

    QmlFiles {
        directory: "Generated"
    }

    Files {
        directory: "Sounds"
        filter: "*.mp3;*.wav"
    }

    QmlFiles {
        directory: "Mocks/Audio"
    }

    Files {
        files: [
            "qtquickcontrols2.conf"
        ]
    }

    Files {
        directory: "Drumpad"
        filter: "qmldir"
    }

    Files {
        directory: "DrumpadContent"
        filter: "*.ttf;*.otf"
    }
}
<RCC>
    <qresource>
        <file>Drumpad.qmlproject</file>
        <file>Drumpad/AvailableSoundsComboBox.qml</file>
        <file>Drumpad/CenteredFlow.qml</file>
        <file>Drumpad/Constants.qml</file>
        <file>Drumpad/PadButton.qml</file>
        <file>Drumpad/qmldir</file>
        <file>Drumpad/SoundEffectPlayer.qml</file>
        <file>Drumpad/StyledSpinBox.qml</file>
        <file>Drumpad/VolumeSlider.qml</file>
        <file>DrumpadContent/App.qml</file>
        <file>DrumpadContent/MainScreen.qml</file>
        <file>DrumpadContent/qmldir</file>
        <file>qtquickcontrols2.conf</file>
        <file>Sounds/Clap.wav</file>
    </qresource>
</RCC>
; This file can be edited to change the style of the application
; Read "Qt Quick Controls 2 Configuration File" for details:
; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html

[Controls]
Style=Basic
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import Audio

ComboBox {
    id: root

    property string currentFile: currentText ? `../Sounds/${currentText}` : ""
    required property int initialIndex

    model: audioFilesModel.getModel()

    background: Rectangle {
        border.color: root.pressed ? Constants.primaryColor : Constants.secondaryColor
        border.width: root.visualFocus ? 3 : 2
        color: root.pressed ? Constants.secondaryColor : "black"
        implicitHeight: 30
        radius: 2
    }
    contentItem: Text {
        color: "white"
        elide: Text.ElideRight
        leftPadding: 10
        rightPadding: root.indicator.width + 10
        text: root.displayText
        verticalAlignment: Text.AlignVCenter
    }
    delegate: ItemDelegate {
        id: delegate

        required property int index

        highlighted: root.highlightedIndex === index

        background: Rectangle {
            color: delegate.highlighted ? Constants.darkGray : "black"
            implicitWidth: delegate.contentItem.implicitWidth
            width: popup.width
        }
        contentItem: Text {
            anchors.fill: parent
            color: delegate.highlighted ? "#ff0000" : "white"
            elide: Text.ElideRight
            leftPadding: 10
            text: root.model[delegate.index]
            verticalAlignment: Text.AlignVCenter
        }
    }
    indicator: Canvas {
        id: canvas

        contextType: "2d"
        height: 8
        width: 12
        x: root.width - canvas.width - root.rightPadding
        y: root.topPadding + (root.availableHeight - canvas.height) / 2

        onPaint: {
            let margin = 2;
            context.reset();
            context.lineWidth = 2;
            context.strokeStyle = "white";
            context.lineCap = "round";
            context.beginPath();
            context.moveTo(margin, margin);
            context.lineTo(width / 2, height - margin);
            context.lineTo(width - margin, margin);
            context.stroke();
        }

        Connections {
            function onPressedChanged() {
                canvas.requestPaint();
            }

            target: root
        }
    }
    popup: Popup {
        id: popup

        implicitHeight: contentItem.implicitHeight
        implicitWidth: 200
        padding: 2
        y: root.height + 2

        background: Rectangle {
            border.color: Constants.primaryColor
            border.width: 2
            color: "black"
        }
        contentItem: ListView {
            clip: true
            currentIndex: root.highlightedIndex
            implicitHeight: Math.min(contentHeight, 200)
            model: popup.visible ? root.delegateModel : null
        }
    }

    Component.onCompleted: {
        currentIndex = root.initialIndex % model.length;
    }

    AudioFilesModel {
        id: audioFilesModel
    }
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick

// A Flow layout that centers its children horizontally
// Note that the implementation adds unnecessary spacing in rows that are not full
Flow {
    property int customMargin: (children.length && (children[0].width + spacing <= parentWidth))
        ? (parentWidth - rowWidth) / 2 + padding
        : padding
    property int parentWidth: parent.width - 2 * padding
    property int rowCount: children.length ? parentWidth / (children[0].width + spacing) : 0
    property int rowWidth: children.length
        ? rowCount * children[0].width + (rowCount - 1) * spacing + 2 * padding
        : 0

    anchors {
        leftMargin: customMargin
        rightMargin: customMargin
    }
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

pragma Singleton
import QtQuick

QtObject {
    readonly property string darkGray: "#333333"
    readonly property string mediumGray: "#9B9B9B"
    readonly property string primaryColor: "#FF0000"
    readonly property string secondaryColor: "#8C0000"
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Shapes

Rectangle {
    id: root

    property bool isPlaying: false
    property bool isError: false
    property bool isLoading: false
    property int cornerRadius: 10
    signal pressed()

    color: "transparent"

    Shape {
        anchors.fill: parent

        ShapePath {
            strokeColor: "black"
            strokeWidth: 2

            fillGradient: RadialGradient {
                centerRadius: root.height
                centerX: root.width / 2
                centerY: root.height / 2
                focalX: centerX
                focalY: centerY

                GradientStop {
                    position: 0
                    color: {
                        if (isError)
                            return "black";
                        if (isLoading)
                            return "yellow";
                        if (isPlaying)
                            return Qt.darker(Constants.primaryColor, 1.25);
                        return Qt.darker(Constants.secondaryColor, 1.25);
                    }
                }
                GradientStop {
                    position: 0.5
                    color: {
                        if (isError)
                            return Constants.darkGray;
                        if (isLoading)
                            return "orange";
                        if (isPlaying)
                            return Constants.primaryColor;
                        return Constants.secondaryColor;
                    }
                }
            }

            // Rounded shape path
            PathMove {
                x: root.cornerRadius
                y: 0
            }
            PathQuad {
                controlX: 0
                controlY: 0
                x: 0
                y: root.cornerRadius
            }
            PathLine {
                x: 0
                y: root.height - root.cornerRadius
            }
            PathQuad {
                controlX: 0
                controlY: root.height
                x: root.cornerRadius
                y: root.height
            }
            PathLine {
                x: root.width - root.cornerRadius
                y: root.height
            }
            PathQuad {
                controlX: root.width
                controlY: root.height
                x: root.width
                y: root.height - root.cornerRadius
            }
            PathLine {
                x: root.width
                y: root.cornerRadius
            }
            PathQuad {
                controlX: root.width
                controlY: 0
                x: root.width - root.cornerRadius
                y: 0
            }
            PathLine {
                x: root.cornerRadius
                y: 0
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: root.pressed()
    }
}
module Drumpad

AvailableSoundsComboBox 1.0 AvailableSoundsComboBox.qml
SoundEffectPlayer 1.0 SoundEffectPlayer.qml
CenteredFlow 1.0 CenteredFlow.qml
VolumeSlider 1.0 VolumeSlider.qml
StyledSpinBox 1.0 StyledSpinBox.qml
PadButton 1.0 PadButton.qml

singleton Constants 1.0 Constants.qml
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Layouts
import QtQuick.Dialogs
import QtMultimedia

import Drumpad
import Audio

Rectangle {
    id: root

    property string decodingError: ""
    required property int index
    property int status: SoundEffect.Null
    property bool isLoading: status == SoundEffect.Loading
    property bool isError: status == SoundEffect.Error || status == SoundEffect.Null
    property bool isReady: status == SoundEffect.Ready

    function play() {
        if (root.status == SoundEffect.Ready) {
            audioEngine.play();
        }
    }

    color: Constants.darkGray
    implicitHeight: layout.implicitHeight + 2 * layout.anchors.margins
    implicitWidth: layout.implicitWidth + 2 * layout.anchors.margins
    radius: 10

    onDecodingErrorChanged: {
        if (status == SoundEffect.Error && root.decodingError) {
            errorMessageDialog.text = root.decodingError;
            errorMessageDialog.open();
        }
    }

    AudioEngine {
        id: audioEngine

        file: availableSoundsComboBox.currentFile
        volume: volumeSlider.value

        onDecodingStatusChanged: (status, error) => {
            root.status = status;
            if (status == SoundEffect.Error && error) {
                root.decodingError = error;
            } else {
                root.decodingError = "";
            }
        }
    }

    MessageDialog {
        id: errorMessageDialog

        buttons: MessageDialog.Ok
        title: "Error decoding file"
    }

    ColumnLayout {
        id: layout

        anchors.fill: parent
        anchors.margins: 10
        spacing: 10

        RowLayout {
            spacing: 10

            Text {
                Layout.alignment: Qt.AlignVCenter
                Layout.fillWidth: true
                color: "white"
                text: `Player ${root.index + 1}`
            }
            AvailableSoundsComboBox {
                id: availableSoundsComboBox

                Layout.alignment: Qt.AlignCenter
                initialIndex: root.index
            }
        }

        WaveformItem {
            id: waveformItem

            file: audioEngine.file
            height: 100
            width: 300
        }

        Row {
            Layout.alignment: Qt.AlignCenter
            spacing: 10

            PadButton {
                id: padRectangle
                height: 100
                width: 100
                isPlaying: audioEngine.isPlaying
                isError: root.isError
                isLoading: root.isLoading
                onPressed: root.play()
            }

            VolumeSlider {
                id: volumeSlider

                height: padRectangle.height
                value: 0.75
                width: 16
            }
        }
    }
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

SpinBox {
    id: root

    property int innerPadding: 10

    height: contentItem.implicitHeight + innerPadding
    width: contentItem.width + up.indicator.implicitWidth + down.indicator.implicitWidth

    background: Rectangle {
        border.color: Constants.secondaryColor
    }

    contentItem: Text {
        color: "black"
        height: parent.height
        horizontalAlignment: Text.AlignHCenter
        text: root.textFromValue(root.value, root.locale)
        verticalAlignment: Text.AlignVCenter
        width: implicitWidth + innerPadding * 2
    }

    down.indicator: Rectangle {
        border.color: Constants.secondaryColor
        color: root.down.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
        height: parent.height
        implicitWidth: downText.implicitWidth + innerPadding * 2
        x: root.mirrored ? parent.width - width : 0

        Text {
            id: downText

            anchors.fill: parent
            color: "white"
            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
            fontSizeMode: Text.Fit
            horizontalAlignment: Text.AlignHCenter
            text: "-"
            verticalAlignment: Text.AlignVCenter
        }
    }

    up.indicator: Rectangle {
        border.color: Constants.secondaryColor
        color: root.up.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
        height: parent.height
        implicitWidth: upText.implicitWidth + innerPadding * 2
        x: root.mirrored ? 0 : parent.width - width

        Text {
            id: upText

            anchors.centerIn: parent
            anchors.fill: parent
            color: "white"
            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
            fontSizeMode: Text.Fit
            horizontalAlignment: Text.AlignHCenter
            text: "+"
            verticalAlignment: Text.AlignVCenter
        }
    }
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

Slider {
    id: root

    orientation: Qt.Vertical
    padding: 0

    background: Rectangle {
        color: Constants.mediumGray
        implicitHeight: root.height
        implicitWidth: root.width
        radius: width / 2

        Rectangle {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            color: Qt.lighter(Constants.primaryColor, 1 - (root.visualPosition * 0.3))
            height: (1 - root.visualPosition) * parent.height + (root.visualPosition * handle.height)
            radius: parent.width / 2
            width: parent.width
        }
    }

    handle: Rectangle {
        border.color: "#b0b0b0"
        border.width: 1
        color: root.pressed ? "#e0e0e0" : "#ffffff"
        height: root.width
        radius: width / 2
        width: root.width
        x: root.availableWidth / 2 - height / 2
        y: root.visualPosition * (root.availableHeight - height)
    }
}
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick 2.15
import QtQuick.Window 2.15
import Drumpad 1.0

Window {
    id: root

    height: 800
    title: "Drumpad"
    visible: true
    width: 1200

    MainScreen {
        id: mainScreen

        anchors.fill: parent
    }
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Drumpad
import Audio

Rectangle {
    id: root

    property QtObject soundEffectPlayer: Qt.createComponent("../Drumpad/SoundEffectPlayer.qml",
                                                            Component.PreferSynchronous)

    color: "black"
    focus: true

    Component.onCompleted: {
        // Initialize the default sound effect players
        for (var i = 0; i < audioPlayersSpinBox.value; i++) {
            root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
                index: i
            });
        }
    }
    Keys.onPressed: event => {
        if (event.key < Qt.Key_1 || event.key > Qt.Key_9) {
            // Ignore key out of scope
            return;
        }

        let digit = event.key - Qt.Key_1;
        if (digit < soundEffectPlayersFlow.children.length) {
            soundEffectPlayersFlow.children[digit].play();
        }
    }

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

        Row {
            id: audioPlayersCountRow

            Layout.alignment: Qt.AlignHCenter
            spacing: 5

            Text {
                anchors.verticalCenter: parent.verticalCenter
                color: "white"
                text: "Audio players:"
            }

            StyledSpinBox {
                id: audioPlayersSpinBox

                value: 5

                onValueModified: {
                    let soundPlayersCount = soundEffectPlayersFlow.children.length;
                    if (audioPlayersSpinBox.value < soundPlayersCount) {
                        // Remove extra sound effect players
                        soundEffectPlayersFlow.children.length = audioPlayersSpinBox.value;
                        return;
                    }

                    if (audioPlayersSpinBox.value < soundPlayersCount) {
                        return;
                    }
                    // Create more sound effect players
                    for (var i = soundPlayersCount; i < audioPlayersSpinBox.value; i++) {
                        root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
                            index: i
                        });
                    }
                }
            }
        }

        ScrollView {
            Layout.fillHeight: true
            Layout.fillWidth: true
            contentWidth: width

            background: Rectangle {
                color: "#232323"
            }

            CenteredFlow {
                id: soundEffectPlayersFlow

                anchors.fill: parent
                padding: 10
                spacing: 10
            }
        }
    }
}
module DrumpadContent

App 1.0 App.qml
MainScreen 1.0 MainScreen.qml
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtMultimedia

Item {
    id: root

    property double volume
    property url file

    MediaPlayer {
        id: player
        source: file
        audioOutput: AudioOutput {}
    }

    onVolumeChanged : {
        console.log("Mock: VolumeChanaged ", volume )
    }

    function play() {
        console.log("Mock: play()")
        player.play()
    }
}
module Audio
AudioEngine 1.0 AudioEngine.qml
WaveformItem 1.0 WaveformItem.qml
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

Rectangle {
    id: root
    width: 1920
    height: 1080
    color: "blue"
    property url file
}
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

Item {
    getFiles: function() {
        console.log("AudioFilesModel mock: getFiles()")
    }
}
module Components
AudioFilesModel 1.0 AudioFilesModel.qml