Compare commits

..

12 Commits

Author SHA1 Message Date
e76fdaae9f Added controls to edit the role "amount", "factor" and "type" from the expandable item delegate. 2026-03-09 10:35:39 +01:00
609f777d13 Added an editable item delegate. Only Factor plus/minus button are working for now. (based on https://doc.qt.io/qt-6/qtquick-views-example.html as well) 2026-03-09 07:51:16 +01:00
ab5f9b1952 Displaying images in item delegates. 2026-03-07 12:07:17 +01:00
dc8a4683db Using a expandable list item as delegate. (adapted Qt example: https://doc.qt.io/qt-6/qtquick-views-example.html)
Icons not working yet!
2026-03-07 11:02:44 +01:00
5b9f796a6f Using a color scheme based on the elixir mentor gist clone tutorial. https://github.com/ElixirMentor/elixir_gist 2026-03-06 10:41:47 +01:00
ca59fc2b20 Selecting items by mouse click. 2026-03-05 12:43:51 +01:00
b2e3b49814 Added header and footer with gradients to the list view. 2026-03-05 12:43:25 +01:00
f2d4461bbc Moving the list item delegate into its own QML file. 2026-03-05 12:34:02 +01:00
f1ad289411 Using a ListView with a simple delegate component. 2026-03-05 10:53:52 +01:00
7b6979288a Tested a QML TableModel to view the model data. 2026-03-04 19:56:12 +01:00
2ff97c588c Accessing the core and the model from this QML UI project. 2026-03-03 18:58:34 +01:00
ed54d5e54e Initial basic QML app. 2026-03-03 09:33:03 +01:00
16 changed files with 692 additions and 0 deletions

56
CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
cmake_minimum_required(VERSION 3.16)
set(TARGET_APP "GenericQMLApp")
project(${TARGET_APP} VERSION 0.1.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick)
qt_standard_project_setup(REQUIRES 6.8)
qt_add_executable(${TARGET_APP}
main.cpp
)
qt_add_qml_module(${TARGET_APP}
URI GenericQML
QML_FILES
Main.qml
ListPage.qml
ListItemDelegate.qml
ExpandableItemDelegate.qml
EditableItemDelegate.qml
controls/PressAndHoldButton.qml
RESOURCES
icons/software-application.png
icons/moreUp.png icons/moreDown.png
icons/arrow-down.png icons/arrow-up.png
icons/list-delete.png
icons/minus-sign.png icons/plus-sign.png
)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
set_target_properties(${TARGET_APP} PROPERTIES
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.${TARGET_APP}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_include_directories(${TARGET_APP} PRIVATE ${CORE_LIB_DIR}/)
target_link_libraries(${TARGET_APP} PRIVATE GenericCore)
target_link_libraries(${TARGET_APP}
PRIVATE Qt6::Quick
)
include(GNUInstallDirs)
install(TARGETS ${TARGET_APP}
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

163
EditableItemDelegate.qml Normal file
View File

@ -0,0 +1,163 @@
import QtQuick
import "controls"
Item {
//! [0]
id: delegateItem
width: listView.width
height: 80
clip: true
required property int index
// required property string edit
// required property QtObject model
required property string name
required property string description
required property string info
required property int amount
required property real factor
Rectangle {
id: background
x: 2
y: 2
width: parent.width - x * 2
height: parent.height - y * 2
color: wccDarkLight
border.color: index === listView.currentIndex ? "blue" : wccDarkDefault
border.width: 3
radius: 5
}
Column {
id: arrows
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
Image {
source: "icons/arrow-up.png"
MouseArea {
anchors.fill: parent
// onClicked: fruitModel.move(delegateItem.index,
// delegateItem.index - 1, 1)
}
}
Image {
source: "icons/arrow-down.png"
MouseArea {
anchors.fill: parent
// onClicked: fruitModel.move(delegateItem.index,
// delegateItem.index + 1, 1)
}
}
}
Column {
anchors {
left: arrows.right
horizontalCenter: parent.horizontalCenter
bottom: parent.verticalCenter
}
Text {
anchors.horizontalCenter: parent.horizontalCenter
text: delegateItem.name
font.pixelSize: 15
color: "white"
}
Text {
text: delegateItem.description
color: "White"
}
}
Item {
anchors {
left: arrows.right
horizontalCenter: parent.horizontalCenter
top: parent.verticalCenter
bottom: parent.bottom
}
Row {
anchors.centerIn: parent
spacing: 10
PressAndHoldButton {
anchors.verticalCenter: parent.verticalCenter
source: "icons/plus-sign.png"
onClicked: delegateItem.factor = delegateItem.factor + 0.25
}
Text {
id: factorText
anchors.verticalCenter: parent.verticalCenter
text: 'Factor: ' + Number(delegateItem.factor).toFixed(2)
font.pixelSize: 15
color: "white"
font.bold: true
}
PressAndHoldButton {
anchors.verticalCenter: parent.verticalCenter
source: "icons/minus-sign.png"
onClicked: delegateItem.factor = Math.max(
0, delegateItem.factor - 0.25)
}
Image {
source: "icons/list-delete.png"
MouseArea {
anchors.fill: parent
onClicked: fruitModel.remove(delegateItem.index)
}
}
}
}
// Animate adding and removing of items:
//! [1]
SequentialAnimation {
id: addAnimation
PropertyAction {
target: delegateItem
property: "height"
value: 0
}
NumberAnimation {
target: delegateItem
property: "height"
to: 80
duration: 250
easing.type: Easing.InOutQuad
}
}
ListView.onAdd: addAnimation.start()
SequentialAnimation {
id: removeAnimation
PropertyAction {
target: delegateItem
property: "ListView.delayRemove"
value: true
}
NumberAnimation {
target: delegateItem
property: "height"
to: 0
duration: 250
easing.type: Easing.InOutQuad
}
// Make sure delayRemove is set back to false so that the item can be destroyed
PropertyAction {
target: delegateItem
property: "ListView.delayRemove"
value: false
}
}
ListView.onRemove: removeAnimation.start()
}

276
ExpandableItemDelegate.qml Normal file
View File

@ -0,0 +1,276 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Item {
id: item
required property int index
required property string name
required property string description
required property string info
required property int amount
required property real factor
required property string type
property real detailsOpacity: 0
width: ListView.view.width
height: 70
Rectangle {
id: background
x: 2
y: 2
width: parent.width - x * 2
height: parent.height - y * 2
color: wccDarkLight
border.color: index === listView.currentIndex ? "blue" : wccDarkDefault
border.width: 3
radius: 5
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
parent.ListView.view.currentIndex = parent.index
if (mouse.button === Qt.RightButton) {
item.state = 'Details'
}
}
}
Row {
id: topLayout
x: 10
y: 10
height: icon.height
width: parent.width
spacing: 10
Image {
id: icon
width: 50
height: 50
source: "icons/software-application.png"
}
Column {
width: background.width - icon.width - 20
height: icon.height
spacing: 5
Text {
text: item.name
font.bold: true
font.pointSize: 16
}
Text {
text: qsTr("Description")
font.bold: true
font.pointSize: 9
opacity: item.detailsOpacity
}
Text {
text: item.description
wrapMode: Text.WordWrap
width: parent.width
font.pointSize: 9
opacity: item.detailsOpacity
}
}
}
Item {
id: details
x: 10
width: parent.width - 20
anchors {
top: topLayout.bottom
topMargin: 10
bottom: parent.bottom
bottomMargin: 10
}
opacity: item.detailsOpacity
Text {
id: moreInfoTitle
anchors.top: parent.top
text: qsTr("Further information")
font.pointSize: 9
font.bold: true
}
Flickable {
id: flick
width: parent.width
anchors {
top: moreInfoTitle.bottom
bottom: parent.bottom
topMargin: 5
}
contentHeight: infoText.height
clip: true
ColumnLayout {
Text {
id: infoText
text: item.info
wrapMode: Text.WordWrap
width: details.width
}
Text {
id: amountText
text: "Amount: " + item.amount
wrapMode: Text.WordWrap
}
SpinBox {
value: item.amount
width: 80
height: 25
onValueModified: item.amount = value
}
Text {
id: factorText
text: "Factor: " + item.factor
wrapMode: Text.WordWrap
width: details.width
}
SpinBox {
id: spinBox
from: 0
value: decimalToInt(item.factor)
to: decimalToInt(100)
stepSize: decimalFactor
editable: true
property int decimals: 2
property real realValue: value / decimalFactor
readonly property int decimalFactor: Math.pow(10, decimals)
function decimalToInt(decimal) {
return decimal * decimalFactor
}
onValueModified: item.factor = value / decimalFactor
validator: DoubleValidator {
bottom: Math.min(spinBox.from, spinBox.to)
top: Math.max(spinBox.from, spinBox.to)
decimals: spinBox.decimals
notation: DoubleValidator.StandardNotation
}
textFromValue: function (value, locale) {
return Number(value / decimalFactor).toLocaleString(
locale, 'f', spinBox.decimals)
}
valueFromText: function (text, locale) {
return Math.round(Number.fromLocaleString(
locale, text) * decimalFactor)
}
}
Text {
id: typeText
text: "Type: " + item.type
wrapMode: Text.WordWrap
}
ComboBox {
// TODO use model from metadata.h (in some way)
model: ["A", "B", "C", ""]
// BUG type will probably not been updated due to undo/redo step
currentIndex: find(item.type)
Component.onCompleted: currentIndex = find(item.type)
onCurrentTextChanged: {
item.type = currentText
}
width: 80
height: 25
}
}
}
Image {
anchors {
right: flick.right
top: flick.top
}
source: "icons/moreUp.png"
opacity: flick.atYBeginning ? 0 : 1
}
Image {
anchors {
right: flick.right
bottom: flick.bottom
}
source: "icons/moreDown.png"
opacity: flick.atYEnd ? 0 : 1
}
}
Button {
y: 10
anchors {
right: background.right
rightMargin: 10
}
opacity: item.detailsOpacity
text: qsTr("Close")
onClicked: item.state = ''
}
states: State {
name: "Details"
PropertyChanges {
background.color: "white"
icon {
// Make picture bigger
width: 130
height: 130
}
item {
// Make details visible
detailsOpacity: 1
x: 0
// Fill the entire list area with the detailed view
height: listView.height
}
}
// Move the list so that this item is at the top.
PropertyChanges {
item.ListView.view.contentY: item.y
explicit: true
}
// Disallow flicking while we're in detailed view
PropertyChanges {
item.ListView.view.interactive: false
}
}
transitions: Transition {
// Make the state changes smooth
ParallelAnimation {
ColorAnimation {
property: "color"
duration: 500
}
NumberAnimation {
duration: 300
properties: "detailsOpacity,x,contentY,height,width"
}
}
}
}

24
ListItemDelegate.qml Normal file
View File

@ -0,0 +1,24 @@
import QtQuick
Item {
id: myItem
required property int index
required property string name
required property string info
property int fontSize: 16
width: parent.width
height: 40
Column {
Text {
text: '<b>Name:</b> ' + myItem.name
}
Text {
text: '<b>Info:</b> ' + myItem.info
}
}
MouseArea {
anchors.fill: parent
onClicked: parent.ListView.view.currentIndex = parent.index
}
}

61
ListPage.qml Normal file
View File

@ -0,0 +1,61 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls.Material
Page {
id: page
ListView {
id: listView
anchors.fill: parent
focus: true
clip: true
model: mainModel
// delegate: ListItemDelegate {}
delegate: ExpandableItemDelegate {}
// delegate: EditableItemDelegate {}
delegateModelAccess: DelegateModel.ReadWrite
header: bannercomponent
footer: Rectangle {
width: parent.width
height: 30
gradient: mainGradient
}
}
Component {
//instantiated when header is processed
id: bannercomponent
Rectangle {
id: banner
width: parent.width
height: 50
gradient: mainGradient
border {
color: wccPurpleDark
width: 2
}
Text {
anchors.centerIn: parent
text: window.title
font.pixelSize: 32
color: wccDarkLight
}
}
}
Gradient {
id: mainGradient
GradientStop {
position: 0.0
color: wccPurpleDefault
}
GradientStop {
position: 0.66
color: wccPurpleDark
}
}
}

32
Main.qml Normal file
View File

@ -0,0 +1,32 @@
import QtQuick
import QtQuick.Layouts
import QtQml.Models
Window {
id: window
width: 480
height: 800
visible: true
title: `${Application.name}`
property int fontSize: 16
property color textColor: "black"
property color wccDarkDark: "#010101"
property color wccDarkDefault: "#3C3B3B"
property color wccDarkLight: "#828282"
property color wccPurpleDark: "#631A61"
property color wccPurpleDefault: "#A834A5"
property color wccPurpleLight: "#E88FE5"
property color wccLavenderDark: "#8C52FF"
property color wccLavenderDefault: "#9D74EE"
property color wccLavenderLight: "#BC9AFF"
ListPage {
id: listPage
anchors.fill: parent
}
}

View File

@ -0,0 +1,45 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
Image {
id: container
property int repeatDelay: 300
property int repeatDuration: 75
property bool pressed
signal clicked
scale: pressed ? 0.9 : 1
function release() {
autoRepeatClicks.stop()
container.pressed = false
}
SequentialAnimation on pressed {
id: autoRepeatClicks
running: false
PropertyAction { target: container; property: "pressed"; value: true }
ScriptAction { script: container.clicked() }
PauseAnimation { duration: container.repeatDelay }
SequentialAnimation {
loops: Animation.Infinite
ScriptAction { script: container.clicked() }
PauseAnimation { duration: container.repeatDuration }
}
}
MouseArea {
anchors.fill: parent
onPressed: autoRepeatClicks.start()
onReleased: container.release()
onCanceled: container.release()
}
}

BIN
icons/arrow-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

BIN
icons/arrow-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

BIN
icons/list-delete.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

BIN
icons/minus-sign.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

BIN
icons/moreDown.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

BIN
icons/moreUp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 B

BIN
icons/plus-sign.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

35
main.cpp Normal file
View File

@ -0,0 +1,35 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <model/generalsortfiltermodel.h>
#include "genericcore.h"
#ifdef QT_DEBUG
#include "utils/messagehandler.h"
#endif
int main(int argc, char* argv[]) {
#ifdef QT_DEBUG
qInstallMessageHandler(consoleHandlerColoredVerboseInDarkTheme);
#endif
QGuiApplication app(argc, argv);
std::unique_ptr<GenericCore> core = std::make_unique<GenericCore>();
std::shared_ptr<GeneralSortFilterModel> mainModel = core->getSortFilterModel();
// qInfo() << "QMLApp Version:" << QMLAPP_VERSION;
qInfo() << "core->getString():" << core->toString();
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty(QStringLiteral("core"), core.get());
engine.rootContext()->setContextProperty(QStringLiteral("mainModel"), mainModel.get());
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreationFailed, &app,
[]() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
engine.loadFromModule("GenericQML", "Main");
return app.exec();
}