Heavy changes

This commit is contained in:
Stefano Moretti
2023-04-21 18:07:17 +02:00
parent 32a567a950
commit 11606b8f39
52 changed files with 1578 additions and 748 deletions

27
qml/App.qml Normal file
View File

@ -0,0 +1,27 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
ApplicationWindow {
id: root
property alias initialPage: stackView.initialItem
visible: true
locale: Qt.locale("en_US")
header: stackView.currentItem?.appToolBar
StackView {
id: stackView
anchors.fill: parent
// make sure that the phone physical back button will get key events
onCurrentItemChanged: stackView.currentItem.forceActiveFocus()
}
Material.primary: Style.primaryColor
Material.accent: Style.accentColor
Material.theme: Style.isDarkTheme ? Material.Dark : Material.Light
}

View File

@ -1,21 +1,18 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import BaseUI 1.0
import QtQuick
import QtQuick.Controls
Page {
id: root
property StackView stack: StackView.view
readonly property StackView stack: StackView.view
property alias appToolBar: appToolBar
property alias leftButton: appToolBar.leftButton
property alias rightButtons: appToolBar.rightButtons
function pop(item, operation) {
if (stack.currentItem != root)
if (root.stack.currentItem != root)
return false
return stack.pop(item, operation)
return root.stack.pop(item, operation)
}
function back() {
@ -23,7 +20,7 @@ Page {
}
Keys.onBackPressed: function(event) {
if (stack.depth > 1) {
if (root.stack.depth > 1) {
event.accepted = true
back()
} else {
@ -31,15 +28,8 @@ Page {
}
}
Action {
id: backAction
icon.source: Icons.arrow_back
onTriggered: root.back()
}
AppToolBar {
id: appToolBar
title: root.title
leftButton: stack && stack.depth > 1 ? backAction : null
}
}

View File

@ -1,8 +1,10 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ToolBar {
id: root
property Action leftButton
property list<Action> rightButtons
@ -14,12 +16,12 @@ ToolBar {
anchors { fill: parent; leftMargin: 4; rightMargin: 4 }
ToolButton {
icon.source: leftButton ? leftButton.icon.source : ""
icon.source: root.leftButton?.icon.source ?? ""
icon.color: Style.textOnPrimary
focusPolicy: Qt.NoFocus
opacity: Style.opacityTitle
enabled: leftButton && leftButton.enabled
onClicked: leftButton.trigger()
enabled: root.leftButton && root.leftButton.enabled
onClicked: root.leftButton.trigger()
}
LabelTitle {
id: titleLabel
@ -28,14 +30,14 @@ ToolBar {
Layout.fillWidth: true
}
Repeater {
model: rightButtons.length
model: root.rightButtons.length
delegate: ToolButton {
icon.source: rightButtons[index].icon.source
icon.source: root.rightButtons[index].icon.source
icon.color: Style.textOnPrimary
focusPolicy: Qt.NoFocus
opacity: Style.opacityTitle
enabled: rightButtons[index].enabled
onClicked: rightButtons[index].trigger()
enabled: root.rightButtons[index].enabled
onClicked: root.rightButtons[index].trigger()
}
}
}

View File

@ -1,28 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
ApplicationWindow {
property alias pageStack: stackView
property alias initialPage: stackView.initialItem
visible: true
locale: Qt.locale("en_US")
header: stackView.currentItem ? stackView.currentItem.appToolBar : null
StackView {
id: stackView
anchors.fill: parent
onCurrentItemChanged: {
// make sure that the phone physical back button will get key events
currentItem.forceActiveFocus()
}
}
Material.primary: Style.primaryColor
Material.accent: Style.accentColor
Material.theme: Style.isDarkTheme ? Material.Dark : Material.Light
}

View File

@ -1,9 +0,0 @@
// ekke (Ekkehard Gentz) @ekkescorner
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
Label {
Layout.fillWidth: true
opacity: Style.opacityBodyAndButton
}

View File

@ -1,10 +0,0 @@
// ekke (Ekkehard Gentz) @ekkescorner
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
Label {
Layout.fillWidth: true
font.pixelSize: Style.fontSizeTitle
opacity: Style.opacityTitle
}

View File

@ -1,84 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
PopupModalBase {
property bool selectAccentColor: false
property color currentColor: Material.primary
property string currentColorName: colorModel.get(currentIndex).title
property int currentIndex: 0
signal colorSelected(color c)
implicitWidth: parent.width * 0.9
implicitHeight: Math.min(colorsList.contentHeight, parent.height * 0.9)
ListView {
id: colorsList
anchors.fill: parent
clip: true
delegate: ItemDelegate {
width: colorsList.width
contentItem: RowLayout {
spacing: 0
Rectangle {
visible: selectAccentColor
color: Material.primary
Layout.margins: 0
Layout.minimumHeight: 32
Layout.minimumWidth: 48
}
Rectangle {
color: Material.color(model.bg)
Layout.margins: 0
Layout.minimumHeight: 32
Layout.minimumWidth: 32
}
LabelBody {
text: model.title
Layout.leftMargin: 6
Layout.fillWidth: true
}
}
onClicked: {
colorSelected(Material.color(model.bg))
currentIndex = index
close()
}
}
model: ListModel {
id: colorModel
ListElement { title: "Material Red"; bg: Material.Red }
ListElement { title: "Material Pink"; bg: Material.Pink }
ListElement { title: "Material Purple"; bg: Material.Purple }
ListElement { title: "Material DeepPurple"; bg: Material.DeepPurple }
ListElement { title: "Material Indigo"; bg: Material.Indigo }
ListElement { title: "Material Blue"; bg: Material.Blue }
ListElement { title: "Material LightBlue"; bg: Material.LightBlue }
ListElement { title: "Material Cyan"; bg: Material.Cyan }
ListElement { title: "Material Teal"; bg: Material.Teal }
ListElement { title: "Material Green"; bg: Material.Green }
ListElement { title: "Material LightGreen"; bg: Material.LightGreen }
ListElement { title: "Material Lime"; bg: Material.Lime }
ListElement { title: "Material Yellow"; bg: Material.Yellow }
ListElement { title: "Material Amber"; bg: Material.Amber }
ListElement { title: "Material Orange"; bg: Material.Orange }
ListElement { title: "Material DeepOrange"; bg: Material.DeepOrange }
ListElement { title: "Material Brown"; bg: Material.Brown }
ListElement { title: "Material Grey"; bg: Material.Grey }
ListElement { title: "Material BlueGrey"; bg: Material.BlueGrey }
}
}
Component.onCompleted: {
for (var i = 0; i < colorModel.count; ++i) {
var tmp = colorModel.get(i)
if (Material.color(tmp.bg) === currentColor) {
currentIndex = i
return
}
}
}
}

View File

@ -1,40 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
PopupModalBase {
id: root
property alias text: popupLabel.text
closePolicy: Popup.CloseOnEscape
ColumnLayout {
spacing: 10
width: parent.width
LabelSubheading {
id: popupLabel
Layout.fillWidth: true
topPadding: 20
leftPadding: 8
rightPadding: 8
color: Style.popupTextColor
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
linkColor: Style.isDarkTheme ? "lightblue" : "blue"
onLinkActivated: Qt.openUrlExternally(link)
}
ButtonFlat {
Layout.alignment: Qt.AlignHCenter
text: "OK"
textColor: Style.accentColor
onClicked: root.close()
}
}
}

View File

@ -1,32 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
PopupModalBase {
id: root
property alias model: internalList.model
property alias currentIndex: internalList.currentIndex
property var delegateFunction
signal clicked(var data, int index)
implicitWidth: parent.width * 0.9
implicitHeight: Math.min(internalList.contentHeight, parent.height * 0.9)
ListView {
id: internalList
anchors.fill: parent
clip: true
highlightMoveDuration: 0
delegate: ItemDelegate {
id: internalDelegate
width: parent.width
implicitHeight: 40
text: delegateFunction(modelData)
onClicked: root.clicked(modelData, index)
}
onCurrentIndexChanged: {
internalList.positionViewAtIndex(currentIndex, ListView.Center)
}
}
}

View File

@ -1,14 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
Popup {
id: root
modal: true
dim: true
padding: 0
x: (parent.width - width) / 2
y: (parent.height - height) / 2
implicitWidth: Math.min(contentWidth, parent.width * 0.9)
implicitHeight: Math.min(contentHeight, parent.height * 0.9)
}

View File

@ -1,48 +0,0 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
ItemDelegate {
id: root
property alias title: titleLabel.text
property string subtitle
property string subtitlePlaceholder
property alias check: settingSwitch
Layout.fillWidth: true
contentItem: RowLayout {
ColumnLayout {
spacing: 2
LabelSubheading {
id: titleLabel
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
LabelBody {
id: subtitleLabel
visible: text.length > 0 || root.subtitlePlaceholder.length > 0
opacity: 0.6
wrapMode: Text.WordWrap
elide: Text.ElideMiddle
text: root.subtitle.length > 0 ? root.subtitle : root.subtitlePlaceholder
Layout.fillWidth: true
}
}
Item {
Layout.fillWidth: true
}
Switch {
id: settingSwitch
visible: false
}
}
}

View File

@ -1,18 +0,0 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Label {
leftPadding: 16
topPadding: 6
bottomPadding: 6
font.bold: true
font.pixelSize: Style.fontSizeBodyAndButton
color: Style.isDarkTheme ? "white" : "black"
background: Rectangle {
color: Style.isDarkTheme ? Qt.darker("gray") : "lightgray"
}
Layout.fillWidth: true
}

View File

@ -1,23 +0,0 @@
<RCC>
<qresource prefix="/imports/BaseUI">
<file>qmldir</file>
<file>App.qml</file>
<file>AppStackPage.qml</file>
<file>AppToolBar.qml</file>
<file>ButtonFlat.qml</file>
<file>ButtonRaised.qml</file>
<file>HorizontalDivider.qml</file>
<file>LabelBody.qml</file>
<file>LabelSubheading.qml</file>
<file>LabelTitle.qml</file>
<file>PopupColorSelection.qml</file>
<file>PopupError.qml</file>
<file>PopupInfo.qml</file>
<file>PopupList.qml</file>
<file>PopupModalBase.qml</file>
<file>PopupToast.qml</file>
<file>SettingsItem.qml</file>
<file>SettingsSectionTitle.qml</file>
<file>Style.qml</file>
</qresource>
</RCC>

View File

@ -1,21 +0,0 @@
module BaseUI
App 1.0 App.qml
AppStackPage 1.0 AppStackPage.qml
AppToolBar 1.0 AppToolBar.qml
ButtonFlat 1.0 ButtonFlat.qml
ButtonRaised 1.0 ButtonRaised.qml
HorizontalDivider 1.0 HorizontalDivider.qml
LabelBody 1.0 LabelBody.qml
LabelSubheading 1.0 LabelSubheading.qml
LabelTitle 1.0 LabelTitle.qml
PopupColorSelection 1.0 PopupColorSelection.qml
PopupError 1.0 PopupError.qml
PopupInfo 1.0 PopupInfo.qml
PopupList 1.0 PopupList.qml
PopupModalBase 1.0 PopupModalBase.qml
PopupToast 1.0 PopupToast.qml
SettingsItem 1.0 SettingsItem.qml
SettingsSectionTitle 1.0 SettingsSectionTitle.qml
singleton Style 1.0 Style.qml

View File

@ -1,9 +1,9 @@
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Button {
id: button
id: root
property alias textColor: buttonText.color
property alias buttonColor: buttonBackground.color
@ -12,17 +12,16 @@ Button {
leftPadding: 6
rightPadding: 6
Layout.minimumWidth: 80
contentItem: Text {
id: buttonText
text: button.text
text: root.text
opacity: enabled ? 1.0 : 0.3
color: Style.textOnPrimary
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font.capitalization: Font.AllUppercase
font.weight: Font.Medium
}
background: Rectangle {
@ -30,16 +29,6 @@ Button {
implicitHeight: 48
color: Style.primaryColor
radius: 2
opacity: button.pressed ? 0.75 : 1.0
/*
layer.enabled: true
layer.effect: DropShadow {
verticalOffset: 2
horizontalOffset: 1
color: dropShadow
samples: button.pressed ? 20 : 10
spread: 0.5
}
*/
opacity: root.pressed ? 0.75 : 1.0
}
}

View File

@ -1,10 +1,9 @@
// ekke (Ekkehard Gentz) @ekkescorner
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Button {
id: button
id: root
property alias textColor: buttonText.color
@ -12,11 +11,9 @@ Button {
leftPadding: 6
rightPadding: 6
Layout.minimumWidth: 88
contentItem: Text {
id: buttonText
text: button.text
text: root.text
opacity: enabled ? 1.0 : 0.3
color: Style.flatButtonTextColor
horizontalAlignment: Text.AlignHCenter
@ -29,8 +26,8 @@ Button {
background: Rectangle {
id: buttonBackground
implicitHeight: 48
color: button.pressed ? buttonText.color : "transparent"
color: root.pressed ? buttonText.color : "transparent"
radius: 2
opacity: button.pressed ? 0.12 : 1.0
opacity: root.pressed ? 0.12 : 1.0
}
}

152
qml/DatePicker.qml Normal file
View File

@ -0,0 +1,152 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import BaseUI as UI
Item {
id: root
property string locale: "en_US"
property date selectedDate: new Date()
readonly property int day: selectedDate.getDate()
readonly property int month: selectedDate.getMonth()
readonly property int year: selectedDate.getFullYear()
readonly property string dateString: year + "-" + _zeroPad(month + 1) + "-" + _zeroPad(day)
function _zeroPad(n) { return n > 9 ? n : '0' + n }
implicitHeight: column.implicitHeight
implicitWidth: column.implicitWidth
ColumnLayout {
id: column
spacing: 0
anchors.fill: parent
Rectangle {
implicitHeight: monthRow.implicitHeight
color: Material.primary
Layout.fillWidth: true
RowLayout {
id: monthRow
spacing: 6
width: parent.width
ToolButton {
leftPadding: 12
rightPadding: 12
icon.source: UI.Icons.keyboard_arrow_left
icon.color: Style.textOnPrimary
onClicked: {
if (monthGrid.month > 0) {
monthGrid.month--
} else {
monthGrid.month = 11
monthGrid.year--
}
}
}
LabelTitle {
text: Qt.locale(root.locale).monthName(monthGrid.month) + " " + monthGrid.year
elide: Text.ElideRight
color: Style.textOnPrimary
horizontalAlignment: Qt.AlignHCenter
Layout.fillWidth: true
}
ToolButton {
leftPadding: 12
rightPadding: 12
icon.source: UI.Icons.keyboard_arrow_right
icon.color: Style.textOnPrimary
onClicked: {
if (monthGrid.month < 11) {
monthGrid.month++
} else {
monthGrid.month = 0
monthGrid.year++
}
}
}
}
}
DayOfWeekRow {
id: dayOfWeekRow
leftPadding: 24
rightPadding: 24
Layout.fillWidth: true
font.bold: false
locale: Qt.locale(root.locale)
delegate: LabelBodySecondary {
text: model.shortName
font: dayOfWeekRow.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
MonthGrid {
id: monthGrid
rightPadding: 24
leftPadding: 24
Layout.fillWidth: true
month: root.month
year: root.year
locale: Qt.locale(root.locale)
onClicked: function(d) {
// Important: check the month to avoid clicking on days outside where opacity 0
if (d.getMonth() === monthGrid.month) {
root.selectedDate = d
console.log("tapped on a date ")
} else {
console.log("outside valid month " + d.getMonth())
}
}
delegate: Label {
id: dayLabel
readonly property bool selected:
model.day === root.day
&& model.month === root.month
&& model.year === root.year
text: model.day
font.bold: model.today ? true : false
font.pixelSize: Style.fontSizeTitle
opacity: model.month === monthGrid.month ? 1 : 0.3
color: selected
? Style.textOnPrimary
: (model.today ? Style.accentColor : Material.foreground)
minimumPointSize: 8
fontSizeMode: Text.Fit
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
background: Rectangle {
anchors.centerIn: parent
width: Math.min(parent.height + 2, parent.width + 2)
height: Math.min(parent.height + 2, parent.width + 2)
radius: width / 2
color: parent.selected ? Style.primaryColor : "transparent"
}
}
}
}
}

46
qml/EdgeEffect.qml Normal file
View File

@ -0,0 +1,46 @@
import QtQuick
Item {
id: root
enum Side {
Top,
Bottom
}
required property int overshoot
required property int maxOvershoot
property int side: EdgeEffect.Side.Top
property color color: "gray"
implicitHeight: 30
onColorChanged: canvas.requestPaint()
Canvas {
id: canvas
anchors.fill: parent
opacity: root.overshoot / root.maxOvershoot
onPaint: {
if (root.side === EdgeEffect.Side.Top) {
var y1 = 0
var y2 = height
} else {
var y1 = height
var y2 = 0
}
var ctx = getContext("2d")
ctx.save()
ctx.reset()
ctx.fillStyle = root.color
ctx.beginPath()
ctx.moveTo(0, y1)
ctx.bezierCurveTo(width / 4, y2, 3 * width / 4, y2, width, y1)
ctx.fill()
ctx.restore()
}
}
}

View File

@ -1,13 +1,10 @@
// ekke (Ekkehard Gentz) @ekkescorner
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick
import QtQuick.Layouts
Item {
height: 8
Layout.fillWidth: true
// anchors.left: parent.left
// anchors.right: parent.right
// anchors.margins: 6
// https://www.google.com/design/spec/components/dividers.html#dividers-types-of-dividers
Rectangle {
anchors.centerIn: parent

View File

@ -0,0 +1,15 @@
import QtQuick
import QtQuick.Layouts
// special divider for list elements
// using height 1 ensures that it looks good if highlighted
Item {
height: 1
Layout.fillWidth: true
Rectangle {
width: parent.width
height: 1
opacity: Style.dividerOpacity
color: Style.dividerColor
}
}

20
qml/Icon.qml Normal file
View File

@ -0,0 +1,20 @@
import QtQuick
Item {
id: root
property alias icon: internal.source
property color color: undefined
Image {
id: internal
anchors.fill: parent
layer.enabled: root.color != undefined
layer.samplerName: "maskSource"
layer.effect: ShaderEffect {
property color color: root.color
fragmentShader: "shaders/icon.frag.qsb"
}
}
}

8
qml/LabelBody.qml Normal file
View File

@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Label {
Layout.fillWidth: true
opacity: Style.opacityBodyAndButton
}

View File

@ -0,0 +1,8 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Label {
Layout.fillWidth: true
opacity: Style.opacityBodySecondary
}

View File

@ -1,7 +1,6 @@
// ekke (Ekkehard Gentz) @ekkescorner
import QtQuick 2.12
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Label {
Layout.fillWidth: true

9
qml/LabelTitle.qml Normal file
View File

@ -0,0 +1,9 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Label {
Layout.fillWidth: true
font.pixelSize: Style.fontSizeTitle
opacity: Style.opacityTitle
}

View File

@ -0,0 +1,42 @@
import QtQuick
import QtQuick.Controls
ListView {
id: root
boundsMovement: Flickable.StopAtBounds
boundsBehavior: Flickable.DragOverBounds
// XXX: disable optimizations
cacheBuffer: height * 1000
add: Transition {
NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: 200 }
}
moveDisplaced: Transition {
NumberAnimation { property: "y"; duration: 200 }
}
removeDisplaced: Transition {
NumberAnimation { property: "y"; duration: 200 }
}
ScrollIndicator.vertical: ScrollIndicator { }
EdgeEffect {
width: root.width
anchors.top: root.top
side: EdgeEffect.Side.Top
overshoot: root.verticalOvershoot < 0 ? -root.verticalOvershoot : 0
maxOvershoot: root.height
color: Style.isDarkTheme ? "gray" : "darkgray"
}
EdgeEffect {
width: root.width
anchors.bottom: root.bottom
side: EdgeEffect.Side.Bottom
overshoot: root.verticalOvershoot > 0 ? root.verticalOvershoot : 0
maxOvershoot: root.height
color: Style.isDarkTheme ? "gray" : "darkgray"
}
}

59
qml/OptionsDialog.qml Normal file
View File

@ -0,0 +1,59 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Dialog {
id: root
property alias model: listView.model
property alias delegate: listView.delegate
parent: Overlay.overlay
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: Math.min(Math.max(header.implicitWidth, listView.contentItem.childrenRect.width), parent.width * 0.9)
height: Math.min(implicitHeight + listView.contentHeight, parent.height * 0.9)
padding: 0
modal: true
dim: true
closePolicy: Popup.CloseOnEscape
onClosed: listView.positionViewAtBeginning()
contentItem: ColumnLayout {
spacing: 0
HorizontalListDivider {
opacity: listView.contentY - listView.originY > 0 ? 1 : 0
Behavior on opacity { NumberAnimation {} }
}
ListViewEdgeEffect {
id: listView
clip: true
Layout.fillWidth: true
Layout.fillHeight: true
}
HorizontalListDivider {
opacity: listView.contentHeight - listView.contentY - listView.height > 0 ? 1 : 0
Behavior on opacity { NumberAnimation {} }
}
}
footer: DialogButtonBox {
alignment: Qt.AlignRight
ButtonFlat {
text: qsTr("Cancel")
textColor: Style.accentColor
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
}
}

View File

@ -1,10 +1,10 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick.Layouts 1.12
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Window
import BaseUI 1.0
import BaseUI as UI
Popup {
id: root
@ -45,13 +45,11 @@ Popup {
RowLayout {
width: parent.width
Image {
id: alarmIcon
smooth: true
source: Icons.error + "color=white"
sourceSize.width: 36
sourceSize.height: 36
Icon {
width: 36
height: 36
icon: UI.Icons.error
color: "white"
}
Label {

45
qml/PopupInfo.qml Normal file
View File

@ -0,0 +1,45 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Dialog {
id: root
property alias text: popupLabel.text
x: (parent.width - width) / 2
y: (parent.height - height) / 2
modal: true
dim: true
closePolicy: Popup.CloseOnEscape
contentItem: LabelSubheading {
id: popupLabel
Layout.fillWidth: true
topPadding: 20
leftPadding: 8
rightPadding: 8
color: Style.popupTextColor
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
linkColor: Style.isDarkTheme ? "lightblue" : "blue"
onLinkActivated: Qt.openUrlExternally(link)
}
footer: DialogButtonBox {
alignment: Qt.AlignHCenter
standardButtons: DialogButtonBox.Ok
onAccepted: root.close()
ButtonFlat {
text: "OK"
textColor: Style.accentColor
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
}

View File

@ -1,7 +1,7 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls.Material 2.12
import QtQuick
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Window
Popup {
id: root

31
qml/SettingsCheckItem.qml Normal file
View File

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
CheckDelegate {
id: root
property alias title: root.text
property string subtitle
property string subtitlePlaceholder
contentItem: ColumnLayout {
spacing: 2
LabelSubheading {
rightPadding: root.indicator.width + root.spacing
text: root.text
elide: Text.ElideRight
Layout.fillWidth: true
}
LabelBody {
rightPadding: root.indicator.width + root.spacing
text: root.subtitle.length > 0 ? root.subtitle : root.subtitlePlaceholder
visible: text.length > 0 || root.subtitlePlaceholder.length > 0
opacity: 0.6
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
}
}

36
qml/SettingsItem.qml Normal file
View File

@ -0,0 +1,36 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
ItemDelegate {
id: root
property alias title: titleLabel.text
property string subtitle
property string subtitlePlaceholder
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 2
LabelSubheading {
id: titleLabel
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
LabelBody {
id: subtitleLabel
visible: text.length > 0 || root.subtitlePlaceholder.length > 0
opacity: 0.6
wrapMode: Text.WordWrap
elide: Text.ElideMiddle
text: root.subtitle.length > 0 ? root.subtitle : root.subtitlePlaceholder
Layout.fillWidth: true
}
}
}

View File

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Label {
leftPadding: 16
topPadding: 6
bottomPadding: 6
font.bold: true
font.pixelSize: Style.fontSizeBodyAndButton
color: Style.isDarkTheme ? "white" : Qt.lighter("gray", 1.1)
background: Rectangle {
color: Style.isDarkTheme ? Qt.darker("gray") : Qt.lighter("lightgray", 1.1)
}
Layout.fillWidth: true
}

View File

@ -1,7 +1,7 @@
pragma Singleton
import QtQuick 2.12
import QtQuick.Controls.Material 2.12
import QtQuick
import QtQuick.Controls.Material
QtObject {
property bool isDarkTheme: false

256
qml/TimeCircle.qml Normal file
View File

@ -0,0 +1,256 @@
import QtQuick
Item {
id: root
required property Item screen
property int hours: 0
property int minutes: 0
readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes)
readonly property bool isPM: hours >= 12
property bool pickMinutes: false
property bool time24h: false
property color clockColor: "gray"
property color clockHandColor: "blue"
property color labelsColor: "white"
property color labelsSelectedColor: labelsColor
property color labelDotColor: labelsColor
property real innerRadius: clock.radius * 0.5
property real outerRadius: clock.radius * 0.8
property int labelsSize: 20
property int clockHandCircleSize: 2 * labelsSize
function update() {
circle.pos = circle.mapToItem(root.screen, 0, circle.height)
circle.pos.y = Window.height - circle.pos.y
}
function _zeroPad(n) { return n > 9 ? n : '0' + n }
function _getSelectedAngle(fullAngle) {
if (root.pickMinutes)
return fullAngle / 60 * root.minutes
else if (root.hours >= 12)
return fullAngle / 12 * (root.hours - 12)
else
return fullAngle / 12 * root.hours
}
implicitWidth: labelsSize * 12
implicitHeight: implicitWidth
onPickMinutesChanged: {
handAnimation.enabled = circleAnimation.enabled = true
disableAnimationTimer.start()
}
Timer {
id: disableAnimationTimer
interval: 400
repeat: false
onTriggered: handAnimation.enabled = circleAnimation.enabled = false
}
Rectangle {
id: clock
width: Math.min(root.width, root.height)
height: width
radius: width / 2
color: root.clockColor
MouseArea {
property bool isHold: false
function getSectorFromAngle(rad, sectors) {
let index = Math.round(rad / (2 * Math.PI) * sectors)
return index < 0 ? index + sectors : index
}
function selectTime(mouse, tap) {
let x = mouse.x - width / 2
let y = -(mouse.y - height / 2)
let angle = Math.atan2(x, y)
if (root.pickMinutes) {
if (tap)
root.minutes = getSectorFromAngle(angle, 12) * 5
else
root.minutes = getSectorFromAngle(angle, 60)
} else {
let hour = getSectorFromAngle(angle, 12)
if (root.time24h) {
let radius = (root.outerRadius + root.innerRadius) / 2
if (Qt.vector2d(x, y).length() > radius) {
if (hour == 0)
hour = 12
} else if (hour != 0) {
hour += 12
}
} else if (root.isPM) {
hour += 12
}
root.hours = hour
}
}
anchors.fill: parent
pressAndHoldInterval: 100
onClicked: (mouse) => { selectTime(mouse, true); root.pickMinutes = true }
onPositionChanged: (mouse) => { if (isHold) selectTime(mouse) }
onPressAndHold: (mouse) => { isHold = true; selectTime(mouse) }
onReleased: { if (isHold) { isHold = false; root.pickMinutes = true } }
}
// clock hand
Rectangle {
id: hand
x: clock.width / 2 - width / 2
y: clock.height / 2 - height
width: 2
height: root.pickMinutes
|| !root.time24h
|| (root.hours != 0 && root.hours <= 12)
? root.outerRadius
: root.innerRadius
transformOrigin: Item.Bottom
rotation: _getSelectedAngle(360)
color: root.clockHandColor
antialiasing: true
Behavior on rotation {
id: handAnimation
enabled: false
NumberAnimation { duration: 400 }
}
}
// label background
Rectangle {
id: circle
property point pos: Qt.point(x, y)
property real angle: _getSelectedAngle(2 * Math.PI)
x: clock.width / 2 + hand.height * Math.sin(angle) - width / 2
y: clock.height / 2 - hand.height * Math.cos(angle) - height / 2
width: root.clockHandCircleSize
height: width
radius: width / 2
color: root.clockHandColor
onXChanged: pos.x = mapToItem(root.screen, 0, 0).x
// OpenGL origin is bottom left
onYChanged: pos.y = Window.height - mapToItem(root.screen, 0, height).y
Rectangle {
width: 4
height: width
radius: width / 2
anchors.centerIn: parent
visible: root.pickMinutes && root.minutes % 5
color: root.labelDotColor
}
Behavior on angle {
id: circleAnimation
enabled: false
NumberAnimation { duration: 400 }
}
}
// centerpoint
Rectangle {
anchors.centerIn: parent
width: 10
height: width
radius: width / 2
color: root.clockHandColor
}
Repeater {
anchors.centerIn: parent
model: [ 0, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 ]
delegate: Text {
required property int modelData
required property int index
property real angle: 2 * Math.PI * index / 12
x: clock.width / 2 + root.innerRadius * Math.sin(angle) - width / 2
y: clock.height / 2 - root.innerRadius * Math.cos(angle) - height / 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: root.labelsSize
visible: root.time24h
opacity: root.pickMinutes ? 0 : 1
color: root.labelsColor
text: modelData
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } }
}
}
Repeater {
anchors.centerIn: parent
model: [ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ]
delegate: Text {
required property int modelData
required property int index
property real angle: 2 * Math.PI * index / 12
x: clock.width / 2 + root.outerRadius * Math.sin(angle) - width / 2
y: clock.height / 2 - root.outerRadius * Math.cos(angle) - height / 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: root.labelsSize
opacity: root.pickMinutes ? 0 : 1
color: root.labelsColor
text: modelData
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } }
}
}
Repeater {
anchors.centerIn: parent
model: 60
delegate: Text {
required property int modelData
required property int index
property real angle: 2 * Math.PI * index / 60
x: clock.width / 2 + root.outerRadius * Math.sin(angle) - width / 2
y: clock.height / 2 - root.outerRadius * Math.cos(angle) - height / 2
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: root.labelsSize
visible: modelData % 5 == 0
opacity: root.pickMinutes ? 1 : 0
color: root.labelsColor
text: _zeroPad(modelData)
layer.enabled: true
layer.samplerName: "maskSource"
layer.effect: shaderEffect
Behavior on opacity { NumberAnimation { duration: 200 } }
}
}
}
Component {
id: shaderEffect
ShaderEffect {
property point pos: circle.pos
property real radius: root.labelsSize
property color color: root.labelsSelectedColor
property real dpi: Screen.devicePixelRatio
fragmentShader: "shaders/clock.frag.qsb"
}
}
}

206
qml/TimePickerCircular.qml Normal file
View File

@ -0,0 +1,206 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
Dialog {
id: root
property alias time24h: timePicker.time24h
readonly property string timeString: timePicker.timeString + ":00"
function setTime(hour, minute) {
timePicker.hours = hour
timePicker.minutes = minute
}
readonly property bool _isLandscape: parent.width > parent.height
function _zeroPad(n) { return n > 9 ? n : '0' + n }
parent: Overlay.overlay
x: (parent.width - width) / 2
y: (parent.height - height) / 2
padding: 0
topPadding: 0
modal: true
dim: true
focus: true
onOpened: timePicker.update()
onClosed: timePicker.pickMinutes = false
on_IsLandscapeChanged: updateTimer.restart()
Timer {
id: updateTimer
interval: 1
onTriggered: timePicker.update()
}
header: Pane {
bottomPadding: 0
LabelSubheading {
text: qsTr("Select time")
font.bold: true
opacity: 1
}
}
contentItem: Pane {
topPadding: 0
bottomPadding: 0
GridLayout {
flow: root._isLandscape ? GridLayout.LeftToRight : GridLayout.TopToBottom
GridLayout {
flow: root._isLandscape ? GridLayout.TopToBottom : GridLayout.LeftToRight
Layout.alignment: Qt.AlignCenter
RowLayout {
spacing: 0
Layout.alignment: Qt.AlignVCenter
Label {
color: timePicker.pickMinutes ? Material.foreground : Style.primaryColor
font.pixelSize: Style.fontSizeDisplay3
text: {
let hours = timePicker.hours
if (!timePicker.time24h) {
if (timePicker.isPM) {
if (timePicker.hours != 12)
hours = timePicker.hours - 12
} else {
if (timePicker.hours == 0)
hours = 12
}
}
_zeroPad(hours)
}
background: Rectangle {
color: timePicker.pickMinutes ? Qt.darker(Material.background, 1.1) : Qt.lighter(Style.primaryColor)
radius: 4
}
MouseArea {
anchors.fill: parent
onClicked: timePicker.pickMinutes = false
}
}
Label {
color: Material.foreground
font.pixelSize: Style.fontSizeDisplay3
text: ":"
}
Label {
color: timePicker.pickMinutes ? Style.primaryColor : Material.foreground
font.pixelSize: Style.fontSizeDisplay3
text: _zeroPad(timePicker.minutes)
background: Rectangle {
color: timePicker.pickMinutes ? Qt.lighter(Style.primaryColor) : Qt.darker(Material.background, 1.1)
radius: 4
}
MouseArea {
anchors.fill: parent
onClicked: timePicker.pickMinutes = true
}
}
}
ColumnLayout {
visible: !timePicker.time24h
Layout.alignment: Qt.AlignHCenter
Rectangle {
width: amLabel.width + 4
height: amLabel.height + 4
radius: 4
color: timePicker.isPM ? Material.background : Style.primaryColor
Label {
id: amLabel
anchors.centerIn: parent
font.pixelSize: Style.fontSizeTitle
color: timePicker.isPM ? Material.foreground : Style.textOnPrimary
text: "AM"
}
MouseArea {
anchors.fill: parent
onClicked: {
if (timePicker.isPM)
timePicker.hours -= 12
}
}
}
Rectangle {
width: pmLabel.width + 4
height: pmLabel.height + 4
radius: 4
color: timePicker.isPM ? Style.primaryColor : Material.background
Label {
id: pmLabel
anchors.centerIn: parent
font.pixelSize: Style.fontSizeTitle
color: timePicker.isPM ? Style.textOnPrimary : Material.foreground
text: "PM"
}
MouseArea {
anchors.fill: parent
onClicked: {
if (!timePicker.isPM)
timePicker.hours += 12
}
}
}
}
}
TimeCircle {
id: timePicker
screen: Overlay.overlay
clockColor: Qt.darker(Material.background, 1.1)
clockHandColor: Style.primaryColor
labelsColor: Style.isDarkTheme ? "#FFFFFF" : "#000000"
labelsSelectedColor: Style.textOnPrimary
labelDotColor: Style.textOnPrimary
labelsSize: Style.fontSizeTitle
Layout.alignment: Qt.AlignCenter
Layout.leftMargin: root._isLandscape ? 30 : 0
}
}
}
footer: DialogButtonBox {
alignment: Qt.AlignRight
background: Pane {}
ButtonFlat {
text: qsTr("Cancel")
textColor: Style.primaryColor
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
ButtonFlat {
text: qsTr("OK")
textColor: Style.primaryColor
implicitWidth: 80
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
}

142
qml/TimePickerTumbler.qml Normal file
View File

@ -0,0 +1,142 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Dialog {
id: root
readonly property int hours: {
if (root.timeAMPM) {
if (amPmTumbler.currentIndex === 0) {
if (hoursTumbler.currentIndex === 0)
12
else
hoursTumbler.currentIndex + 12
} else {
if (hoursTumbler.currentIndex === 12)
0
else
hoursTumbler.currentIndex
}
} else {
hoursTumbler.currentIndex
}
}
readonly property int minutes: minutesTumbler.currentIndex
readonly property string timeString: _zeroPad(hours) + ":" + _zeroPad(minutes) + ":00"
property bool timeAMPM: false
function setTime(hour, minute) {
if (root.timeAMPM) {
if (hour >= 12) {
hour -= 12
// XXX: doesn't work. why?
// amPmTumbler.positionViewAtIndex(0, Tumbler.Center)
amPmTumbler.currentIndex = 0
} else {
// amPmTumbler.positionViewAtIndex(1, Tumbler.Center)
amPmTumbler.currentIndex = 1
}
}
hoursTumbler.positionViewAtIndex(hour, Tumbler.Center)
minutesTumbler.positionViewAtIndex(minute, Tumbler.Center)
}
function _zeroPad(n) { return n > 9 ? n : '0' + n }
parent: Overlay.overlay
x: (parent.width - width) / 2
y: (parent.height - height) / 2
padding: 0
topPadding: 0
modal: true
dim: true
focus: true
header: Pane {
LabelSubheading {
text: qsTr("Select time")
font.bold: true
opacity: 1
}
}
contentItem: Pane {
RowLayout {
id: tumblerRow
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Tumbler {
id: hoursTumbler
model: root.timeAMPM ? [ 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ] : 24
delegate: delegateComponent
}
Label {
text: ":"
font.pixelSize: 40
}
Tumbler {
id: minutesTumbler
model: 60
delegate: delegateComponent
}
Tumbler {
id: amPmTumbler
visible: root.timeAMPM
model: ["PM", "AM"]
delegate: Label {
text: modelData
opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 40
}
}
}
}
footer: DialogButtonBox {
alignment: Qt.AlignRight
background: Pane {}
ButtonFlat {
text: qsTr("Cancel")
textColor: Style.primaryColor
// implicitWidth: 80
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
}
ButtonFlat {
text: qsTr("OK")
textColor: Style.primaryColor
implicitWidth: 80
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
}
}
Component {
id: delegateComponent
Label {
text: _zeroPad(modelData)
opacity: 1.0 - Math.abs(Tumbler.displacement) / (Tumbler.tumbler.visibleItemCount / 2)
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.pixelSize: 40
}
}
}

23
qml/shaders/clock.frag Normal file
View File

@ -0,0 +1,23 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 pos;
float radius;
vec4 color;
float dpi;
} ubuf;
layout(binding = 2) uniform sampler2D maskSource;
void main()
{
if (distance(gl_FragCoord.xy / ubuf.dpi, ubuf.pos + ubuf.radius) < ubuf.radius)
fragColor = ubuf.color * texture(maskSource, qt_TexCoord0).a * ubuf.qt_Opacity;
else
fragColor = texture(maskSource, qt_TexCoord0).rgba * ubuf.qt_Opacity;
}

17
qml/shaders/icon.frag Normal file
View File

@ -0,0 +1,17 @@
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec4 color;
} ubuf;
layout(binding = 2) uniform sampler2D maskSource;
void main()
{
fragColor = ubuf.color * texture(maskSource, qt_TexCoord0).a * ubuf.qt_Opacity;
}