mirror of
https://github.com/stemoretti/BaseUI.git
synced 2025-11-17 07:02:54 -05:00
Heavy changes
This commit is contained in:
27
qml/App.qml
Normal file
27
qml/App.qml
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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>
|
||||
@ -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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
152
qml/DatePicker.qml
Normal 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
46
qml/EdgeEffect.qml
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
15
qml/HorizontalListDivider.qml
Normal file
15
qml/HorizontalListDivider.qml
Normal 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
20
qml/Icon.qml
Normal 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
8
qml/LabelBody.qml
Normal file
@ -0,0 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
opacity: Style.opacityBodyAndButton
|
||||
}
|
||||
8
qml/LabelBodySecondary.qml
Normal file
8
qml/LabelBodySecondary.qml
Normal file
@ -0,0 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
opacity: Style.opacityBodySecondary
|
||||
}
|
||||
@ -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
9
qml/LabelTitle.qml
Normal file
@ -0,0 +1,9 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Style.fontSizeTitle
|
||||
opacity: Style.opacityTitle
|
||||
}
|
||||
42
qml/ListViewEdgeEffect.qml
Normal file
42
qml/ListViewEdgeEffect.qml
Normal 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
59
qml/OptionsDialog.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
45
qml/PopupInfo.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
31
qml/SettingsCheckItem.qml
Normal 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
36
qml/SettingsItem.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
18
qml/SettingsSectionTitle.qml
Normal file
18
qml/SettingsSectionTitle.qml
Normal 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
|
||||
}
|
||||
@ -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
256
qml/TimeCircle.qml
Normal 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
206
qml/TimePickerCircular.qml
Normal 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
142
qml/TimePickerTumbler.qml
Normal 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
23
qml/shaders/clock.frag
Normal 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
17
qml/shaders/icon.frag
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user