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