chore(CPP): base for the window and layouting system

Demo controls migrations to Qt6
POC for an application layout with isolated components

Closes: #5902
This commit is contained in:
Stefan 2022-05-31 23:26:41 +02:00 committed by Stefan Dunca
parent 36a079aea8
commit cecfd7f8e8
53 changed files with 1132 additions and 81 deletions

View File

@ -32,3 +32,4 @@ add_subdirectory(app)
add_subdirectory(test) add_subdirectory(test)
# TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored # TODO: temporary not to duplicate resources until we switch to c++ app then it can be refactored
add_subdirectory(ui/imports/assets) add_subdirectory(ui/imports/assets)
add_subdirectory(ui/fonts)

View File

@ -12,17 +12,26 @@ qt6_add_executable(${PROJECT_NAME} "")
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status.Application URI Status.Application
VERSION 1.0 VERSION 1.0
QML_FILES QML_FILES
qml/main.qml qml/main.qml
qml/Status/Application/StatusWindow.qml qml/Status/Application/Decorators/SplashScreen.qml
qml/Status/Application/StatusContentView.qml
qml/Status/Application/MainShortcuts.qml
qml/Status/Application/MainView/MainView.qml qml/Status/Application/MainView/MainView.qml
qml/Status/Application/MainView/StatusApplicationSections.qml
qml/Status/Application/MainView/StatusApplicationSections/Wallet/WalletNavBarSection.qml
qml/Status/Application/Settings/ApplicationSettings.qml
qml/Status/Application/System/StatusTrayIcon.qml qml/Status/Application/System/StatusTrayIcon.qml
qml/Status/Application/Decorators/SplashScreen.qml qml/Status/Application/Workflows/CloseApplicationHandler.qml
qml/Status/Application/StatusContentView.qml
qml/Status/Application/MainShortcuts.qml
qml/Status/Application/StatusWindow.qml
OUTPUT_DIRECTORY OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application
) )
@ -37,6 +46,7 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_BINARY_DIR=${CMAKE_BINA
target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_SOURCE_DIR=${CMAKE_SOURCE_DIR}) target_compile_definitions(${PROJECT_NAME} PRIVATE BUILD_SOURCE_DIR=${CMAKE_SOURCE_DIR})
add_subdirectory(qml) add_subdirectory(qml)
add_subdirectory(qml/Status/Application/Navigation)
add_subdirectory(src) add_subdirectory(src)
add_subdirectory(res) add_subdirectory(res)
@ -53,11 +63,14 @@ target_link_libraries(${PROJECT_NAME}
PRIVATE PRIVATE
Qt6::Quick Qt6::Quick
StatusQ_Application_Navigation
# TODO: Use Status:: namespace # TODO: Use Status:: namespace
#Core #Core
Helpers Helpers
Onboarding Onboarding
Assets Assets
StatusQ
) )
# QtCreator needs this # QtCreator needs this

View File

@ -2,11 +2,20 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Status.Application
import Status.Containers
import Status.Controls
import Status.Application.Navigation
/// Responsible for setup of user workflows after onboarding /// Responsible for setup of user workflows after onboarding
Item { Item {
id: root id: root
/// Emited when everything is loaded and UX ready required property ApplicationController appController
/// Emitted when everything is loaded and UX ready
signal ready() signal ready()
Component.onCompleted: root.ready() Component.onCompleted: root.ready()
@ -14,22 +23,40 @@ Item {
implicitWidth: mainLayout.implicitWidth implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight implicitHeight: mainLayout.implicitHeight
ColumnLayout { RowLayout {
id: mainLayout id: mainLayout
anchors.fill: parent anchors.fill: parent
RowLayout {} StatusNavigationBar {
Label { id: navBar
Layout.alignment: Qt.AlignHCenter
text: "TODO MainView" Layout.fillHeight: true
}
Button { sections: appSections.sectionsList
text: "Quit"
Layout.alignment: Qt.AlignHCenter
onClicked: Qt.quit()
} }
RowLayout {} ColumnLayout {
// Not visible all the time
StatusBanner {
Layout.fillWidth: true
//statusText: // TODO: appController.bannerController.text
//type: // TODO: appController.bannerController.type
visible: false // TODO: appController.bannerController.visible
}
Loader {
id: mainLoader
Layout.fillWidth: true
Layout.fillHeight: true
}
}
}
StatusApplicationSections {
id: appSections
// Chat ...
// Wallet ...
} }
} }

View File

@ -0,0 +1,25 @@
import QtQml
import Status.Controls.Navigation
QtObject {
readonly property var sectionsList: [wallet, settings]
readonly property ApplicationSection wallet: ApplicationSection {
navButton: WalletButtonComponent
content: WalletContentComponent
component WalletButtonComponent: NavigationBarButton {
}
component WalletContentComponent: ApplicationContentView {
}
}
readonly property ApplicationSection settings: ApplicationSection {
navButton: SettingsButtonComponent
content: SettingsContentComponent
component SettingsButtonComponent: NavigationBarButton {
}
component SettingsContentComponent: ApplicationContentView {
}
}
}

View File

@ -0,0 +1,20 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Status.Application.Navigation
import Status.Controls.Navigation
NavigationBarSection {
id: root
implicitHeight: walletButton.implicitHeight
StatusNavigationButton {
id: walletButton
anchors.fill: parent
// TODO: icon, tooltip ...
}
}

View File

@ -0,0 +1,25 @@
# Controls specialized on user workflows
project(StatusQ_Application_Navigation)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
set_source_files_properties(Style.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Application.Navigation
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusNavigationBar.qml
StatusNavigationButton.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Application/Navigation
)

View File

@ -0,0 +1,29 @@
import QtQml
import QtQuick
import QtQuick.Layouts
import Status.Controls.Navigation
NavigationBar {
implicitHeight: mainLayout.implicitHeight
required property var sections
ColumnLayout {
id: mainLayout
MacTrafficLights {
Layout.margins: 13
}
Repeater {
model: sections
Loader {
Layout.fillWidth: true
sourceComponent: modelData.navButton
}
}
}
}

View File

@ -0,0 +1,6 @@
import QtQml
import Status.Controls.Navigation
NavigationBarButton {
}

View File

@ -0,0 +1,8 @@
import QtQml
import QtQuick
import Qt.labs.settings
Settings {
property bool quitOnClose: false
}

View File

@ -5,10 +5,16 @@ import QtQuick.Controls
import Status.Application import Status.Application
import Status.Onboarding import Status.Onboarding
/// Has entry responsibility for the main workflows import Status.Controls.Navigation
/*! Has entry responsibility for the main workflows
*/
Item { Item {
id: root id: root
required property ApplicationState appState
required property ApplicationController appController
implicitWidth: d.isViewLoaded ? d.loadedView.implicitWidth : 800 implicitWidth: d.isViewLoaded ? d.loadedView.implicitWidth : 800
implicitHeight: d.isViewLoaded ? d.loadedView.implicitHeight : 600 implicitHeight: d.isViewLoaded ? d.loadedView.implicitHeight : 600
@ -35,6 +41,7 @@ Item {
MainView { MainView {
onReady: splashScreenPopup.close() onReady: splashScreenPopup.close()
appController: root.appController
} }
} }

View File

@ -5,7 +5,12 @@ import Qt.labs.settings
import Status.Application import Status.Application
/** Administrative scope import Status.Controls.Navigation
import Status.Core.Theme
import "Workflows"
/*! Administrative scope
*/ */
Window { Window {
id: root id: root
@ -14,35 +19,73 @@ Window {
minimumHeight: 600 minimumHeight: 600
Component.onCompleted: { Component.onCompleted: {
width: mainLayout.implicitWidth width: contentView.implicitWidth
height: mainLayout.implicitHeight height: contentView.implicitHeight
} }
visible: true visible: true
title: qsTr(Qt.application.name) title: qsTr(Qt.application.name)
flags: Qt.FramelessWindowHint flags: Qt.FramelessWindowHint
color: "transparent"
ColumnLayout {
id: mainLayout
anchors.fill: parent
// TODO: nav-bar?
// StatusAppNavBar {
// }
StatusContentView {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
ApplicationController { ApplicationController {
id: appController id: appController
} }
Settings { Rectangle {
id: windowBackground
anchors.fill: parent
radius: Style.geometry.appCornersRadius
color: Theme.palette.appBackgroundColor
StatusContentView {
id: contentView
anchors.fill: parent
appState: appState
appController: appController
}
}
// Title gestures handler
MouseArea {
id: dragArea
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
height: Style.geometry.titleBarHeight
// lower than contentView to not steal events from user controls
z: contentView.z - 1
onDoubleClicked: root.visibility === Window.Maximized ? Window.window.showNormal() : Window.window.showMaximized()
property point prevMousePoint
onPressed: (mouse) => prevMousePoint = Qt.point(mouse.x, mouse.y)
onMouseXChanged: root.x += mouseX - prevMousePoint.x
onMouseYChanged: root.y += mouseY - prevMousePoint.y
}
ApplicationState {
id: appState
}
onClosing: function(close) {
close.accepted = closeHandler.canApplicationClose()
}
CloseApplicationHandler {
id: closeHandler
quitOnClose: appSettings.quitOnClose
onHideApplication: root.visible = false
}
ApplicationSettings {
id: appSettings
property alias x: root.x property alias x: root.x
property alias y: root.y property alias y: root.y
property alias width: root.width property alias width: root.width

View File

@ -0,0 +1,18 @@
import QtQml
QtObject {
id: root
required property bool quitOnClose
signal hideApplication()
function canApplicationClose() {
if(root.quitOnClose) {
root.hideApplication()
return false
}
return true
}
}

View File

@ -19,9 +19,11 @@ set_source_files_properties(qml/Status/Assets/Resources.qml PROPERTIES
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status.Assets URI Status.Assets
VERSION 1.0 VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work # TODO: temporary until we make qt_target_qml_sources work
QML_FILES QML_FILES
qml/Status/Assets/Resources.qml qml/Status/Assets/Resources.qml
# Required to suppress "qmllint may not work" warning # Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Assets/ OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Assets/
) )
@ -31,6 +33,7 @@ target_link_libraries(${PROJECT_NAME}
Qt6::Qml Qt6::Qml
# TODO: refactor when moved to C++ code # TODO: refactor when moved to C++ code
FontAssets
UiAssets UiAssets
) )

View File

@ -14,4 +14,7 @@ QtObject {
function gif(name) { function gif(name) {
return assetPath + "/gif/" + name + ".gif"; return assetPath + "/gif/" + name + ".gif";
} }
function png(name) {
return assetPath + "/png/" + name + ".png";
}
} }

View File

@ -14,9 +14,11 @@ qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core URI Status.Core
VERSION 1.0 VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work # TODO: temporary until we make qt_target_qml_sources work
QML_FILES QML_FILES
qml/Status/Core/DevTest.qml qml/Status/Core/DevTest.qml
# Required to suppress "qmllint may not work" warning # Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core

View File

@ -14,9 +14,11 @@ qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status.Onboarding URI Status.Onboarding
VERSION 1.0 VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work # TODO: temporary until we make qt_target_qml_sources work
QML_FILES QML_FILES
qml/Status/Onboarding/OnboardingView.qml qml/Status/Onboarding/OnboardingView.qml
# Required to suppress "qmllint may not work" warning # Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Onboarding/ OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Onboarding/
) )

View File

@ -2,6 +2,9 @@ import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Controls import QtQuick.Controls
import Status.Containers
import Status.Controls.Navigation
Item { Item {
id: root id: root
@ -15,7 +18,11 @@ Item {
anchors.fill: parent anchors.fill: parent
RowLayout {} MacTrafficLights {
Layout.margins: 13
}
LayoutSpacer {}
Label { Label {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
text: "TODO OnboardingWorkflow" text: "TODO OnboardingWorkflow"
@ -25,6 +32,6 @@ Item {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
onClicked: root.userLoggedIn() onClicked: root.userLoggedIn()
} }
RowLayout {} LayoutSpacer {}
} }
} }

View File

@ -12,16 +12,31 @@ qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status URI Status
VERSION 1.0 VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work # TODO: temporary until we make qt_target_qml_sources work
QML_FILES QML_FILES
# Required to suppress "qmllint may not work" warning # Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status
) )
add_subdirectory(qml/Status/Core/Theme) add_subdirectory(qml/Status/Containers)
add_subdirectory(qml/Status/Controls)
add_subdirectory(qml/Status/Core)
add_subdirectory(tests) add_subdirectory(tests)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
Assets
StatusQ_Containers
StatusQ_Controls
StatusQ_Core
)
# QtCreator needs this # QtCreator needs this
set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE) set(QML_IMPORT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/qml;${QML_IMPORT_PATH} CACHE STRING "For QtCreator" FORCE)
list(REMOVE_DUPLICATES QML_IMPORT_PATH) list(REMOVE_DUPLICATES QML_IMPORT_PATH)

View File

@ -0,0 +1,26 @@
# Custom container and layouts
project(StatusQ_Containers)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Containers
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
LayoutSpacer.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Containers
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
)

View File

@ -0,0 +1,4 @@
import QtQuick.Layouts
GridLayout {
}

View File

@ -0,0 +1,30 @@
# Custom controls
project(StatusQ_Controls)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Controls
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusBanner.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Controls
)
add_subdirectory(Navigation)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
StatusQ_Controls_Navigation
)

View File

@ -0,0 +1,7 @@
import QtQuick
/*!
Template for application section content
*/
Item {
}

View File

@ -0,0 +1,12 @@
import QtQml
/*!
An application section with button and content view
*/
QtObject {
required property NavigationBarButtonComponent navButton
required property ApplicationContentView content
component NavigationBarButtonComponent: NavigationBarButton {}
component ApplicationContentViewComponent: ApplicationContentView {}
}

View File

@ -0,0 +1,7 @@
import QtQml
/*!
Keep general application related stated used by custom controls
*/
QtObject {
}

View File

@ -0,0 +1,29 @@
# Controls specialized on user workflows
project(StatusQ_Controls_Navigation)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
set_source_files_properties(Style.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Controls.Navigation
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
ApplicationContentView.qml
ApplicationSection.qml
ApplicationState.qml
MacTrafficLights.qml
NavigationBar.qml
NavigationBarButton.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Controls/Navigation
)

View File

@ -0,0 +1,125 @@
import QtQuick
import QtQuick.Controls
import Status.Core.Theme
import Status.Assets
Item {
id: root
property color inactiveColor: Style.isLightTheme ? "#10000000" : "#10FFFFFF"
property color inactiveBorderColor: inactiveColor
property bool showActive: true
width: layout.implicitWidth
height: layout.implicitHeight
Row {
id: layout
spacing: 8
anchors.top: parent.top
anchors.left: parent.left
TrafficLightButton {
colors: ButtonColors{
pressed: "#B24F47"
active: Qt.lighter("#E9685C", 1.07)
inactive: root.inactiveColor
}
borderColors: ButtonColors {
pressed: "#943229"
active: "#D14C40"
inactive: root.inactiveBorderColor
}
imagePrefix: "close"
onClicked: Window.window.close()
}
TrafficLightButton {
colors: ButtonColors{
pressed: "#878E3B"
active: Qt.lighter("#EDB84C", 1.07)
inactive: root.inactiveColor
}
borderColors: ButtonColors {
pressed: "#986E29"
active: "#D79F3D"
inactive: root.inactiveBorderColor
}
imagePrefix: "minimise"
imageScale: 0.64
imageVCenterOffset: 0.5
onClicked: Window.window.showMinimized()
}
TrafficLightButton {
colors: ButtonColors{
pressed: "#48943f"
active: Qt.lighter("#62C454", 1.06)
inactive: root.inactiveColor
}
borderColors: ButtonColors {
pressed: "#357225"
active: "#53A73E"
inactive: root.inactiveBorderColor
}
imagePrefix: "maximize"
onClicked: Window.visibility === Window.FullScreen ? Window.window.showNormal() : Window.window.showFullScreen()
}
}
component ButtonColors: QtObject {
required property color pressed
required property color active
required property color inactive
}
component TrafficLightButton: AbstractButton {
id: button
required property ButtonColors colors
required property ButtonColors borderColors
required property string imagePrefix
property real imageScale: 0.52
property real imageVCenterOffset: 0
implicitWidth: 12
implicitHeight: 12
hoverEnabled: true
padding: 0
contentItem: Image {
anchors.centerIn: parent
visible: allMouseArea.containsMouse
source: Resources.png(`traffic_lights/${imagePrefix}${button.pressed ? "_pressed" : ""}`)
anchors.verticalCenterOffset: button.imageVCenterOffset
scale: button.imageScale
fillMode: Image.PreserveAspectFit
antialiasing: true
}
background: Rectangle {
radius: width / 2
opacity: enabled ? 1 : 0.3
color: button.down ? colors.pressed
: Window.active ? colors.active
: colors.inactive
border.color: button.down ? borderColors.pressed
: Window.active ? borderColors.active
: borderColors.inactive
border.width: Style.isLightTheme ? 0.5 : 0
}
z: allMouseArea.z + 1
}
MouseArea {
id: allMouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.NoButton
}
}

View File

@ -0,0 +1,11 @@
import QtQuick
import QtQuick.Layouts
/*!
Template for side NavigationBar
The width is given, the rest of the controls have to adapt to the width
*/
Item {
implicitWidth: 78
}

View File

@ -0,0 +1,8 @@
import QtQuick 2.0
/*!
Template for a NavigationBar square button
*/
Item {
height: width
}

View File

@ -0,0 +1,7 @@
import QtQuick
/*!
Template for a Navigation Bar section
*/
Item {
}

View File

@ -0,0 +1,138 @@
import QtQuick
import Status.Core
import Status.Core.Theme
/*!
\qmltype StatusBanner
\inherits Column
\inqmlmodule StatusQ.Controls
\since StatusQ.Controls 0.1
\brief It displays a banner with a custom text, size and type. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-column.html}{Column}.
The \c StatusBanner displays a banner with a custom text, size and type (Info, Danger, Success or Warning).
Example of how the control looks like:
\image status_banner.png
Example of how to use it:
\qml
StatusBanner {
width: parent.width
visible: popup.userIsBlocked
type: StatusBanner.Type.Danger
statusText: qsTr("Blocked")
}
\endqml
For a list of components available see StatusQ.
*/
Column {
id: statusBanner
/*!
\qmlproperty string StatusBanner::statusText
This property holds the text the banner will display.
*/
property string statusText
/*!
\qmlproperty string StatusBanner::type
This property holds type of banner. Possible values are:
\qml
enum Type {
Info, // 0
Danger, // 1
Success, // 2
Warning // 3
}
\endqml
*/
property int type: StatusBanner.Type.Info
/*!
\qmlproperty string StatusBanner::textPixels
This property holds the pixels size of the text inside the banner.
*/
property int textPixels: 15
/*!
\qmlproperty string StatusBanner::statusBannerHeight
This property holds the height of the banner rectangle.
*/
property int statusBannerHeight: 38
// "private" properties
QtObject {
id: d
property color backgroundColor
property color bordersColor
property color fontColor
}
// TODO: move it to C++
enum Type {
Info, // 0
Danger, // 1
Success, // 2
Warning // 3
}
// Component definition
Rectangle {
id: topDiv
color: d.bordersColor
height: 1
width: parent.width
}
Rectangle {
id: box
width: parent.width
height: statusBanner.statusBannerHeight
color: d.backgroundColor
StatusBaseText {
id: statusTxt
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: statusBanner.textPixels
text: statusBanner.statusText
color: d.fontColor
}
}
Rectangle {
id: bottomDiv
color: d.bordersColor
height: 1
width: parent.width
}
// Behavior
states: [
State {
when: statusBanner.type === StatusBanner.Type.Info
PropertyChanges { target: d; backgroundColor: Theme.palette.primaryColor3}
PropertyChanges { target: d; bordersColor: Theme.palette.primaryColor2}
PropertyChanges { target: d; fontColor: Theme.palette.primaryColor1}
},
State {
when: statusBanner.type === StatusBanner.Type.Danger
PropertyChanges { target: d; backgroundColor: Theme.palette.dangerColor3}
PropertyChanges { target: d; bordersColor: Theme.palette.dangerColor2}
PropertyChanges { target: d; fontColor: Theme.palette.dangerColor1}
},
State {
when: statusBanner.type === StatusBanner.Type.Success
PropertyChanges { target: d; backgroundColor: Theme.palette.successColor2}
PropertyChanges { target: d; bordersColor: Theme.palette.successColor2}
PropertyChanges { target: d; fontColor: Theme.palette.successColor1}
},
State {
when: statusBanner.type === StatusBanner.Type.Warning
PropertyChanges { target: d; backgroundColor: Theme.palette.pinColor3}
PropertyChanges { target: d; bordersColor: Theme.palette.pinColor2}
PropertyChanges { target: d; fontColor: Theme.palette.pinColor1}
}
]
}

View File

@ -0,0 +1,30 @@
# QML generic elements used by the other components
project(StatusQ_Core)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
QML_FILES
StatusBaseText.qml
# Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core
)
add_subdirectory(Theme)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Quick
Qt6::Qml
StatusQ_Core_Theme
)

View File

@ -0,0 +1,31 @@
import QtQuick
import Status.Core.Theme
/*!
\qmltype StatusBaseText
\inherits Text
\inqmlmodule StatusQ.Core
\since StatusQ.Core
\brief Displays multiple lines of text. Inherits \l{https://doc.qt.io/qt-5/qml-qtquick-text.html}{Text}.
The \c StatusBaseText item displays text.
For example:
\qml
StatusBaseText {
width: 240
text: qsTr("Hello World!")
font.pixelSize: 24
color: Theme.pallete.directColor1
}
\endqml
\image status_base_text.png
For a list of components available see StatusQ.
*/
Text {
font.family: Theme.baseFont.name
}

View File

@ -6,22 +6,34 @@ set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED) find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup() qt6_standard_project_setup()
set_source_files_properties(Style.qml PROPERTIES set_source_files_properties(
QT_QML_SINGLETON_TYPE TRUE StatusColors.qml
Style.qml
Theme.qml
Utils.qml
PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
) )
qt6_add_qml_module(${PROJECT_NAME} qt6_add_qml_module(${PROJECT_NAME}
URI Status.Core.Theme URI Status.Core.Theme
VERSION 1.0 VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work # TODO: temporary until we make qt_target_qml_sources work
QML_FILES QML_FILES
StatusColors.qml
StatusDarkPalette.qml StatusDarkPalette.qml
StatusDarkTheme.qml StatusDarkTheme.qml
StatusLayouting.qml
StatusLightPalette.qml StatusLightPalette.qml
StatusLightTheme.qml StatusLightTheme.qml
StatusPalette.qml StatusPalette.qml
StatusTheme.qml StatusTheme.qml
Style.qml Style.qml
Theme.qml
Utils.qml
# Required to suppress "qmllint may not work" warning # Required to suppress "qmllint may not work" warning
OUTPUT_DIRECTORY OUTPUT_DIRECTORY
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core/Theme ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Status/Core/Theme

View File

@ -0,0 +1,68 @@
pragma Singleton
import QtQml
import QtQuick
/*!
Define the base color values
*/
QtObject {
readonly property color black: '#000000'
readonly property color white: '#FFFFFF'
readonly property color blue: '#4360DF'
readonly property color blue2: '#2946C4'
readonly property color blue3: '#88B0FF'
readonly property color blue4: '#869EFF'
readonly property color blue5: '#AAC6FF'
readonly property color blue6: '#ECEFFC'
readonly property color brown: '#8B3131'
readonly property color brown2: '#9B832F'
readonly property color brown3: '#AD4343'
readonly property color cyan: '#51D0F0'
readonly property color graphite: '#212121'
readonly property color graphite2: '#252525'
readonly property color graphite3: '#2C2C2C'
readonly property color graphite4: '#373737'
readonly property color graphite5: '#909090'
readonly property color green: '#4EBC60'
readonly property color green2: '#7CDA00'
readonly property color green3: '#60C370'
readonly property color green4: '#93DB33'
readonly property color green5: '#9EA85D'
readonly property color green6: '#AFB551'
readonly property color grey: '#F0F2F5'
readonly property color grey2: '#F6F8FA'
readonly property color grey3: '#E9EDF1'
readonly property color grey4: '#EEF2F5'
readonly property color grey5: '#939BA1'
readonly property color moss: '#26A69A'
readonly property color moss2: '#10A88E'
readonly property color orange: '#FE8F59'
readonly property color orange2: '#FF9F0F'
readonly property color orange3: '#FFA67B'
readonly property color orange4: '#FE8F59'
readonly property color purple: '#887AF9'
readonly property color red: '#FF2D55'
readonly property color red2: '#FA6565'
readonly property color red3: '#FF5C7B'
readonly property color turquoise: '#0DA4C9'
readonly property color turquoise2: '#07BCE9'
readonly property color turquoise3: '#7BE5FF'
readonly property color turquoise4: '#0DA4C9'
readonly property color violet: '#D37EF4'
readonly property color yellow: '#FFCA0F'
readonly property color yellow2: '#EAD27B'
}

View File

@ -1,5 +1,23 @@
import QtQuick import QtQuick
StatusPalette { StatusPalette {
baseColor3: StatusColors.graphite3
appBackgroundColor: baseColor3
dangerColor1: StatusColors.red3
dangerColor2: Utils.addAlphaTo(StatusColors.red3, 0.3)
dangerColor3: Utils.addAlphaTo(StatusColors.red3, 0.2)
successColor1: StatusColors.green3
successColor2: Utils.addAlphaTo(StatusColors.green3, 0.2)
mentionColor1: StatusColors.turquoise3
mentionColor2: Utils.addAlphaTo(StatusColors.turquoise4, 0.3)
mentionColor3: Utils.addAlphaTo(StatusColors.turquoise4, 0.2)
mentionColor4: Utils.addAlphaTo(StatusColors.turquoise4, 0.1)
pinColor1: StatusColors.orange3
pinColor2: Utils.addAlphaTo(StatusColors.orange4, 0.2)
pinColor3: Utils.addAlphaTo(StatusColors.orange4, 0.1)
} }

View File

@ -1,6 +1,6 @@
import QtQuick import QtQuick
StatusTheme { StatusTheme {
readonly property string name: "dark" name: "dark"
readonly property StatusPalette palette: StatusDarkPalette {} palette: StatusDarkPalette {}
} }

View File

@ -0,0 +1,8 @@
import QtQml
QtObject {
readonly property int titleBarHeight: 25
readonly property real appCornersRadius: 5
}

View File

@ -1,5 +1,27 @@
import QtQuick import QtQuick
StatusPalette { StatusPalette {
baseColor3: StatusColors.grey3
appBackgroundColor: "white"
primaryColor1: StatusColors.blue
primaryColor2: Utils.addAlphaTo(StatusColors.blue, 0.2)
primaryColor3: Utils.addAlphaTo(StatusColors.blue, 0.1)
dangerColor1: StatusColors.red
dangerColor2: Utils.addAlphaTo(StatusColors.red, 0.2)
dangerColor3: Utils.addAlphaTo(StatusColors.red, 0.1)
successColor1: StatusColors.green
successColor2: Utils.addAlphaTo(StatusColors.green, 0.1)
mentionColor1: StatusColors.turquoise
mentionColor2: Utils.addAlphaTo(StatusColors.turquoise2, 0.3)
mentionColor3: Utils.addAlphaTo(StatusColors.turquoise2, 0.2)
mentionColor4: Utils.addAlphaTo(StatusColors.turquoise2, 0.1)
pinColor1: StatusColors.orange
pinColor2: Utils.addAlphaTo(StatusColors.orange2, 0.2)
pinColor3: Utils.addAlphaTo(StatusColors.orange2, 0.1)
} }

View File

@ -1,6 +1,7 @@
import QtQuick import QtQuick
StatusTheme { StatusTheme {
readonly property string name: "light" name: "light"
readonly property StatusPalette palette: StatusLightPalette {}
palette: StatusLightPalette {}
} }

View File

@ -1,4 +1,32 @@
import QtQuick import QtQuick
/*!
Base interface for the palette requirements of presentation layer
*/
QtObject { QtObject {
} // Generic colors defined by the design style
required property color baseColor3
// Application base colors
required property color appBackgroundColor
required property color primaryColor1
required property color primaryColor2
required property color primaryColor3
required property color dangerColor1
required property color dangerColor2
required property color dangerColor3
required property color successColor1
required property color successColor2
required property color mentionColor1
required property color mentionColor2
required property color mentionColor3
required property color mentionColor4
required property color pinColor1
required property color pinColor2
required property color pinColor3
}

View File

@ -1,5 +1,8 @@
import QtQuick import QtQuick
/*!
Base interface for the look and feel requirements of the presentation layer
*/
Item { Item {
required property string name required property string name
required property StatusPalette palette required property StatusPalette palette

View File

@ -1,27 +1,17 @@
pragma Singleton pragma Singleton
import QtQuick import QtQuick
//import QtQuick.Controls.Universal
QtObject { /*!
property StatusTheme theme: lightTheme The main entry point into presentation layer customization
property StatusPalette palette: theme.palette */
readonly property StatusTheme lightTheme: StatusLightTheme {} Item {
readonly property StatusTheme darkTheme: StatusDarkTheme {} readonly property StatusPalette palette: Theme.palette
readonly property StatusTheme theme: Theme.current
property var changeTheme: function (palette, isCurrentSystemThemeDark) { readonly property alias geometry: geometryObject
switch (theme) {
case Universal.Light: StatusLayouting {
theme = lightTheme; id: geometryObject
break;
case Universal.Dark:
theme = darkTheme;
break;
case Universal.System:
current = isCurrentSystemThemeDark? darkTheme : lightTheme;
break;
default:
console.warning('Unknown theme. Valid themes are "light" and "dark"')
}
} }
} }

View File

@ -0,0 +1,37 @@
pragma Singleton
import QtQuick
import QtQuick.Controls.Universal 2.12
/*!
Convenience type for easy access to StatusTheme
*/
QtObject {
property StatusTheme current: lightTheme
property bool isLightTheme: current === lightTheme
readonly property StatusTheme lightTheme: StatusLightTheme {}
readonly property StatusTheme darkTheme: StatusDarkTheme {}
property QtObject baseFont: FontLoader {
source: "qrc:/Status/FontsAssets/Inter/Inter-Regular.otf"
}
property StatusPalette palette: current.palette
property var changeTheme: function (universalTheme, isCurrentSystemThemeDark) {
switch (universalTheme) {
case Universal.Light:
current = lightTheme;
break;
case Universal.Dark:
current = darkTheme;
break;
case Universal.System:
current = isCurrentSystemThemeDark? darkTheme : lightTheme;
break;
default:
console.warning('Unknown theme. Valid themes are "light" and "dark"')
}
}
}

View File

@ -0,0 +1,16 @@
pragma Singleton
import QtQuick 2.0
/*!
Helper functions for colors and sizes transformations
\note Consider moving some heavy used functions to C++ for type optimizations.
\note Consider that Qt6 transpile QML files in C++ which are then optimized by compiler;
however, types like \c QVariant is providing limited options compared to native types
*/
QtObject {
function addAlphaTo(baseColor, alpha) {
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, alpha)
}
}

View File

@ -35,10 +35,12 @@ target_include_directories(${PROJECT_NAME}
${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
) )
add_subdirectory(src) add_subdirectory(TestHelpers)
target_link_libraries(${PROJECT_NAME} PRIVATE target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::QuickTest Qt6::QuickTest
Qt6::Qml Qt6::Qml
Qt6::Quick Qt6::Quick
Status::TestHelpers
) )

View File

@ -0,0 +1,47 @@
# Base library. Expect most of the module libraries to depend on it
#
cmake_minimum_required(VERSION 3.21)
project(TestHelpers
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(GTest REQUIRED)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Quick Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.TestHelpers
VERSION 1.0
)
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
target_sources(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.h
${CMAKE_CURRENT_SOURCE_DIR}/IOTestHelpers.h
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/IOTestHelpers.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)
target_link_libraries(${PROJECT_NAME}
PUBLIC
Qt6::Quick
Qt6::Qml
PRIVATE
GTest::gtest_main
)
add_library(Status::TestHelpers ALIAS TestHelpers)

View File

@ -0,0 +1,36 @@
#include "IOTestHelpers.h"
#include <gtest/gtest.h>
namespace fs = std::filesystem;
namespace Status::Testing {
fs::path createTestFolder(const std::string& testName)
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream timeOss;
timeOss << std::put_time(&tm, "%d-%m-%Y_%H-%M-%S");
auto tmpPath = fs::path(testing::TempDir())/(testName + "-" + timeOss.str());
fs::create_directories(tmpPath);
return tmpPath;
}
AutoCleanTempTestDir::AutoCleanTempTestDir(const std::string &testName)
: m_testFolder(createTestFolder(testName))
{
}
AutoCleanTempTestDir::~AutoCleanTempTestDir()
{
fs::remove_all(m_testFolder);
}
const std::filesystem::path& AutoCleanTempTestDir::testFolder()
{
return m_testFolder;
}
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <filesystem>
#include <string>
namespace Status::Testing {
class AutoCleanTempTestDir {
public:
/// Creates a temporary folder to be used in tests. The folder content's will
/// be removed when out of scope
explicit AutoCleanTempTestDir(const std::string& testName);
~AutoCleanTempTestDir();
const std::filesystem::path& testFolder();
private:
const std::filesystem::path m_testFolder;
};
}

View File

@ -3,6 +3,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
namespace Status::Testing {
std::weak_ptr<QString> MonitorQtOutput::m_qtMessageOutputForSharing; std::weak_ptr<QString> MonitorQtOutput::m_qtMessageOutputForSharing;
std::mutex MonitorQtOutput::m_mutex; std::mutex MonitorQtOutput::m_mutex;
QtMessageHandler MonitorQtOutput::m_previousHandler = nullptr; QtMessageHandler MonitorQtOutput::m_previousHandler = nullptr;
@ -66,3 +68,5 @@ MonitorQtOutput::restartCapturing()
m_previousHandler = prev; m_previousHandler = prev;
m_start = m_thisMessageOutput->length(); m_start = m_thisMessageOutput->length();
} }
}

View File

@ -6,6 +6,8 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
namespace Status::Testing {
/// ///
/// \brief Monitor output for tests and declaratively control message handler availability /// \brief Monitor output for tests and declaratively control message handler availability
/// ///
@ -41,3 +43,5 @@ private:
std::shared_ptr<QString> m_thisMessageOutput; std::shared_ptr<QString> m_thisMessageOutput;
int m_start = 0; int m_start = 0;
}; };
}

View File

@ -1,6 +0,0 @@
target_include_directories(${PROJECT_NAME}
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}
)
add_subdirectory(TestHelpers)

View File

@ -1,8 +0,0 @@
target_sources(${PROJECT_NAME}
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.h
${CMAKE_CURRENT_SOURCE_DIR}/MonitorQtOutput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt
)

28
ui/fonts/CMakeLists.txt Normal file
View File

@ -0,0 +1,28 @@
# Temporary library not to duplicate resources
# TODO: refactor it when switching to C++ code into Assets resource library linked or embed with the app
#
cmake_minimum_required(VERSION 3.21)
project(FontAssets
VERSION 0.1.0
LANGUAGES CXX)
set(QT_NO_CREATE_VERSIONLESS_FUNCTIONS true)
find_package(Qt6 ${STATUS_QT_VERSION} COMPONENTS Qml REQUIRED)
qt6_standard_project_setup()
qt6_add_qml_module(${PROJECT_NAME}
URI Status.FontsAssets
VERSION 1.0
# TODO: temporary until we make qt_target_qml_sources work
RESOURCES
Inter/Inter-Regular.otf
RESOURCE_PREFIX ""
)
target_link_libraries(${PROJECT_NAME}
PRIVATE
Qt6::Qml
)