Merge branch 'main' into feat/llm-chat-tab
C++ / fmt (pull_request) Failing after 3s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 46s
C++ / fmt (pull_request) Failing after 3s
C++ / build (pull_request) Failing after 9s
C++ / clang-tidy (pull_request) Failing after 9s
JS/TS / fmt (pull_request) Successful in 9s
JS/TS / lint (pull_request) Successful in 10s
Python / static (pull_request) Successful in 28s
Rust / build (pull_request) Successful in 49s
Python / verify (pull_request) Successful in 1m40s
Rust / fmt (pull_request) Successful in 27s
Rust / clippy (pull_request) Successful in 46s
This commit is contained in:
@@ -60,12 +60,11 @@ ItemList {
|
||||
anchors.centerIn: parent
|
||||
color: device.active ? Colors.palette.m3onPrimary : Colors.palette.m3onSecondaryContainer
|
||||
fill: device.active ? 1 : 0
|
||||
font.pointSize: Tokens.font.size.large
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
text: root.iconName
|
||||
|
||||
Behavior on fill {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,13 +72,13 @@ ItemList {
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
font: Tokens.font.body.small
|
||||
text: device.modelData?.description || device.modelData?.name || qsTr("Unknown")
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3primary
|
||||
font.pointSize: Tokens.font.size.large
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
opacity: device.active ? 1 : 0
|
||||
text: "check"
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ Item {
|
||||
smoothing: Tokens.rounding.largeIncreased
|
||||
|
||||
Behavior on color {
|
||||
CAnim {
|
||||
}
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +48,7 @@ Item {
|
||||
radius: root.open ? Tokens.rounding.largeIncreased : Tokens.rounding.largeIncreased
|
||||
|
||||
Behavior on anchors.margins {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
@@ -122,7 +120,7 @@ Item {
|
||||
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
text: "view_apps"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,21 +10,25 @@ import qs.Services
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool acceptAllowed: true
|
||||
required property string acceptLabel
|
||||
required property Component content
|
||||
required property string header
|
||||
property int horizontalContentMargin
|
||||
required property string icon
|
||||
required property string label
|
||||
required property Item rootParent
|
||||
required property string settingAnchor
|
||||
|
||||
property bool acceptAllowed: true
|
||||
property bool first: false
|
||||
property int horizontalContentMargin
|
||||
property string icon
|
||||
property alias iconLabel: openButton.iconLabel
|
||||
property string subtext: ""
|
||||
property bool last: false
|
||||
property bool open
|
||||
property real openHeight: Math.min(rootParent.height * 0.8, 600)
|
||||
property real openWidth: Math.min(rootParent.width * 0.8, 400)
|
||||
required property Item rootParent
|
||||
property alias rowButton: openButton
|
||||
property bool separateContent
|
||||
property bool first: false
|
||||
property bool last: false
|
||||
|
||||
signal accepted
|
||||
signal cancelled
|
||||
@@ -58,6 +62,7 @@ Item {
|
||||
enabled: false
|
||||
hoverEnabled: enabled
|
||||
parent: root.open ? root.rootParent : root
|
||||
preventStealing: true
|
||||
|
||||
onClicked: root.open = false
|
||||
}
|
||||
@@ -77,7 +82,8 @@ Item {
|
||||
backdrop.enabled: true
|
||||
dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.radius: Tokens.rounding.largeIncreased
|
||||
dialogBg.topLeftRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.topRightRadius: Tokens.rounding.largeIncreased
|
||||
dialogContent.opacity: 1
|
||||
dialogWrapper.height: root.openHeight
|
||||
dialogWrapper.width: root.openWidth
|
||||
@@ -105,7 +111,7 @@ Item {
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,radius,bottomLeftRadius,bottomRightRadius"
|
||||
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
@@ -171,6 +177,8 @@ Item {
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: MouseArea {
|
||||
preventStealing: true
|
||||
|
||||
onWheel: event => event.accepted = true
|
||||
|
||||
ColumnLayout {
|
||||
@@ -180,7 +188,7 @@ Item {
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font: Tokens.font.title.builders.large.weight(Font.Normal).build()
|
||||
text: root.header
|
||||
}
|
||||
|
||||
@@ -221,6 +229,7 @@ Item {
|
||||
TextButton {
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
isRound: true
|
||||
stateLayer.preventStealing: true
|
||||
text: qsTr("Cancel")
|
||||
type: TextButton.Text
|
||||
verticalPadding: Tokens.padding.small
|
||||
@@ -235,6 +244,7 @@ Item {
|
||||
enabled: root.acceptAllowed
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
isRound: true
|
||||
stateLayer.preventStealing: true
|
||||
text: root.acceptLabel
|
||||
type: TextButton.Text
|
||||
verticalPadding: Tokens.padding.small
|
||||
|
||||
@@ -9,6 +9,8 @@ DialogRowButton {
|
||||
id: root
|
||||
|
||||
required property var model
|
||||
|
||||
property var initialSelect
|
||||
property var selectedItem
|
||||
property var initialSelect
|
||||
|
||||
@@ -66,7 +68,6 @@ DialogRowButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: root.labelFor(item.modelData)
|
||||
}
|
||||
|
||||
@@ -77,7 +78,7 @@ DialogRowButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onTertiaryContainer
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
opacity: item.selected ? 1 : 0
|
||||
text: "check"
|
||||
|
||||
@@ -93,10 +94,13 @@ DialogRowButton {
|
||||
|
||||
onOpenChanged: {
|
||||
if (open) {
|
||||
root.rootParent.interactive = false;
|
||||
if (!initialSelect)
|
||||
selectedItem = null;
|
||||
else
|
||||
selectedItem = initialSelect;
|
||||
} else {
|
||||
root.rootParent.interactive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ ConnectedRect {
|
||||
|
||||
MaterialIcon {
|
||||
color: root.iconColor
|
||||
font.pointSize: Tokens.font.size.small
|
||||
fontStyle: Tokens.font.icon.small
|
||||
text: root.icon
|
||||
}
|
||||
}
|
||||
@@ -53,14 +53,14 @@ ConnectedRect {
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
text: root.subtext
|
||||
visible: root.subtext
|
||||
}
|
||||
@@ -72,7 +72,7 @@ ConnectedRect {
|
||||
Layout.maximumWidth: root.width / 2
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.body.small
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ ConnectedRect {
|
||||
implicitHeight: (showList && list.count > 0 ? list.contentHeight : placeholder.implicitHeight + Tokens.padding.extraLarge * 2) + extraHeight
|
||||
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -46,7 +45,7 @@ ConnectedRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.large
|
||||
fontStyle: Tokens.font.icon.large
|
||||
text: root.placeholderIcon
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ ConnectedRect {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font: Tokens.font.body.large
|
||||
text: root.placeholderText
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,14 +54,12 @@ ListView {
|
||||
}
|
||||
}
|
||||
Behavior on implicitHeight {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
model: DelegateModel {
|
||||
id: visualModel
|
||||
|
||||
delegate: ListRow {
|
||||
}
|
||||
delegate: ListRow {}
|
||||
model: ScriptModel {
|
||||
id: valuesModel
|
||||
}
|
||||
@@ -280,7 +278,7 @@ ListView {
|
||||
id: dragIcon
|
||||
|
||||
color: Qt.alpha(Colors.palette.m3onSurfaceVariant, enabledSwitch.checked ? 1 : 0.5)
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
text: "drag_indicator"
|
||||
}
|
||||
|
||||
@@ -290,7 +288,6 @@ ListView {
|
||||
Layout.fillWidth: true
|
||||
color: Qt.alpha(Colors.palette.m3onSurface, enabledSwitch.checked ? 1 : 0.5)
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
text: root.labelFor(item.modelData)
|
||||
}
|
||||
|
||||
@@ -298,13 +295,13 @@ ListView {
|
||||
id: enabledSwitch
|
||||
|
||||
checked: root.toggledFor(item.modelData)
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font: Tokens.font.icon.medium
|
||||
|
||||
onToggled: root.itemToggled(item.DelegateModel.itemsIndex, checked)
|
||||
}
|
||||
|
||||
IconButton {
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font: Tokens.font.icon.medium
|
||||
icon: "delete"
|
||||
inactiveOnColor: Colors.palette.m3error
|
||||
isRound: true
|
||||
|
||||
@@ -1,71 +1,8 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias icon: icon.text
|
||||
property alias status: status.text
|
||||
property alias text: label.text
|
||||
|
||||
signal clicked
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: navLayout.implicitHeight + navLayout.anchors.margins * 2
|
||||
|
||||
StateLayer {
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: navLayout
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.large
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: status
|
||||
|
||||
Layout.fillWidth: true
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
visible: text
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: "chevron_right"
|
||||
}
|
||||
}
|
||||
RowButton {
|
||||
trailingIcon: "chevron_right"
|
||||
subLabel.animate: true
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ ColumnLayout {
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
type: IconButton.Tonal
|
||||
font: Tokens.font.icon.medium
|
||||
|
||||
onClicked: root.sState.closeSubPage()
|
||||
}
|
||||
@@ -142,7 +143,7 @@ ColumnLayout {
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.large
|
||||
font: Tokens.font.title.large
|
||||
text: root.title
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ ConnectedRect {
|
||||
id: icon
|
||||
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.larger
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
@@ -52,7 +52,7 @@ ConnectedRect {
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
@@ -62,7 +62,7 @@ ConnectedRect {
|
||||
animate: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
visible: text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ ConnectedRect {
|
||||
property alias subtext: subLabel.text
|
||||
property alias text: label.text
|
||||
property string trailingIcon
|
||||
property string activeItem
|
||||
|
||||
signal clicked(event: MouseEvent)
|
||||
|
||||
@@ -40,16 +41,17 @@ ConnectedRect {
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: iconLabel
|
||||
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
visible: root.icon !== ""
|
||||
fill: 1
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
text: root.icon
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -64,7 +66,6 @@ ConnectedRect {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
@@ -74,11 +75,31 @@ ConnectedRect {
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
visible: text
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.activeItem && root.subtext
|
||||
asynchronous: true
|
||||
visible: active
|
||||
Layout.fillHeight: true
|
||||
|
||||
sourceComponent: CustomRect {
|
||||
color: Colors.palette.m3secondaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
implicitWidth: text.implicitWidth + Tokens.padding.medium * 2
|
||||
|
||||
CustomText {
|
||||
id: text
|
||||
anchors.centerIn: parent
|
||||
color: Colors.palette.m3onSecondaryContainer
|
||||
text: root.activeItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.trailingIcon
|
||||
asynchronous: true
|
||||
@@ -86,7 +107,7 @@ ConnectedRect {
|
||||
|
||||
sourceComponent: MaterialIcon {
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
text: root.trailingIcon
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
DialogRowButton {
|
||||
id: root
|
||||
|
||||
required property var model
|
||||
property var selectedItem
|
||||
property var initialSelect
|
||||
|
||||
acceptAllowed: !!selectedItem
|
||||
horizontalContentMargin: -Tokens.padding.extraSmall
|
||||
separateContent: true
|
||||
|
||||
content: Component {
|
||||
ColumnLayout {
|
||||
VerticalFadeListView {
|
||||
bottomMargin: Tokens.padding.large
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const s = searchField.text;
|
||||
var regex = new RegExp(s, "i");
|
||||
|
||||
return root.model.filter(n => regex.test(n));
|
||||
}
|
||||
}
|
||||
spacing: 0
|
||||
topMargin: Tokens.padding.large
|
||||
|
||||
delegate: CustomRect {
|
||||
id: item
|
||||
|
||||
required property var modelData
|
||||
readonly property bool selected: root.selectedItem === modelData
|
||||
|
||||
anchors.left: ListView.view.contentItem.left
|
||||
anchors.margins: 1 // Gets cut off for some reason without this
|
||||
anchors.right: ListView.view.contentItem.right
|
||||
color: Qt.alpha(Colors.palette.m3tertiaryContainer, selected ? 1 : 0)
|
||||
implicitHeight: label.implicitHeight + Tokens.padding.small * 2
|
||||
radius: stateLayer.pressed ? Tokens.rounding.extraSmall : selected ? Tokens.rounding.largeIncreased : Tokens.rounding.medium
|
||||
|
||||
Behavior on radius {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
id: stateLayer
|
||||
|
||||
onClicked: root.selectedItem = item.modelData
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: item.selected ? checkIcon.left : parent.right
|
||||
anchors.rightMargin: item.selected ? Tokens.spacing.medium : anchors.margins
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: item.selected ? Colors.palette.m3onTertiaryContainer : Colors.palette.m3onSurface
|
||||
elide: Text.ElideRight
|
||||
text: item.modelData
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: checkIcon
|
||||
|
||||
anchors.margins: Tokens.padding.large
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Colors.palette.m3onTertiaryContainer
|
||||
fontStyle: Tokens.font.icon.medium
|
||||
opacity: item.selected ? 1 : 0
|
||||
text: "check"
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SearchBar {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
bg.border.color: Colors.palette.m3outlineVariant
|
||||
bg.color: Colors.tPalette.m3surfaceContainerLowest
|
||||
clearIcon.font: Tokens.font.icon.medium
|
||||
clearIcon.padding: Tokens.padding.extraSmall
|
||||
font: Tokens.font.body.small
|
||||
placeholderText: qsTr("Search...")
|
||||
searchIcon.anchors.leftMargin: Tokens.padding.large
|
||||
searchIcon.fontStyle: Tokens.font.icon.medium
|
||||
|
||||
Behavior on bg.border.color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onOpenChanged: {
|
||||
if (open) {
|
||||
root.rootParent.interactive = false;
|
||||
if (!initialSelect)
|
||||
selectedItem = null;
|
||||
else
|
||||
selectedItem = initialSelect;
|
||||
} else {
|
||||
root.rootParent.interactive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,5 +13,5 @@ CustomText {
|
||||
Layout.topMargin: first ? 0 : Tokens.spacing.largeIncreased - ((parent as ColumnLayout).spacing ?? 0)
|
||||
color: Colors.palette.m3onSurfaceVariant
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
font: Tokens.font.label.medium
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ ConnectedRect {
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
text: root.subtext
|
||||
visible: root.subtext
|
||||
}
|
||||
|
||||
@@ -40,14 +40,14 @@ ConnectedRect {
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: valueLabel
|
||||
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,14 +39,14 @@ ConnectedRect {
|
||||
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
text: root.subtext
|
||||
visible: root.subtext
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Config
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
ConnectedRect {
|
||||
id: root
|
||||
|
||||
property alias label: label.text
|
||||
property string subtext
|
||||
property string value
|
||||
property alias placeholderText: input.placeholderText
|
||||
property string errorText
|
||||
property alias maximumLength: input.maximumLength
|
||||
property alias validate: input.validate
|
||||
property alias validator: input.validator
|
||||
property bool smallField
|
||||
readonly property alias field: input
|
||||
|
||||
signal valueEdited(value: string)
|
||||
signal editingFinished(value: string)
|
||||
|
||||
function clear(): void {
|
||||
input.clear();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
if (value !== input.text)
|
||||
editingFinished(input.text);
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: rowLayout.implicitHeight + Tokens.padding.medium + Math.max(0, Tokens.padding.large - input.verticalPadding) * 2
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: Tokens.padding.largeIncreased
|
||||
anchors.rightMargin: Tokens.padding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
id: label
|
||||
|
||||
Layout.fillWidth: true
|
||||
font: Tokens.font.body.small
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.subtext
|
||||
text: input.isError && root.errorText ? root.errorText : root.subtext
|
||||
color: input.isError && root.errorText ? Colors.palette.m3error : Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
animate: root.errorText
|
||||
}
|
||||
}
|
||||
|
||||
CustomTextField {
|
||||
id: input
|
||||
|
||||
Layout.preferredWidth: root.smallField ? Tokens.sizes.nexus.smallTextFieldWidth : Tokens.sizes.nexus.textFieldWidth
|
||||
Layout.maximumWidth: root.width / 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
verticalPadding: Tokens.padding.small
|
||||
|
||||
text: root.value
|
||||
|
||||
onTextEdited: root.valueEdited(text)
|
||||
onEditingFinished: root.editingFinished(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import ZShell.Blobs
|
||||
import qs.Components
|
||||
import ZShell.Config
|
||||
import qs.Services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool acceptAllowed: true
|
||||
required property string acceptLabel
|
||||
required property Component content
|
||||
required property string header
|
||||
property int horizontalContentMargin
|
||||
property string icon
|
||||
required property string label
|
||||
property alias iconLabel: openButton.iconText
|
||||
property alias rowButton: openButton
|
||||
property alias contentItem: dialogContent.item
|
||||
property string subtext: ""
|
||||
property bool open
|
||||
property real openHeight: Math.min(rootParent.height * 0.8, 400)
|
||||
property real openWidth: Math.min(rootParent.width * 0.8, 600)
|
||||
required property Item rootParent
|
||||
property bool first: false
|
||||
property bool last: false
|
||||
required property string settingAnchor
|
||||
property color openColor: Colors.palette.m3surfaceContainerHighest
|
||||
|
||||
signal accepted
|
||||
signal cancelled
|
||||
|
||||
function reparentWrapper(): void {
|
||||
const newParent = open ? rootParent : root;
|
||||
const pos = dialogWrapper.mapToItem(newParent, 0, 0);
|
||||
dialogWrapper.parent = newParent;
|
||||
dialogWrapper.x = pos.x;
|
||||
dialogWrapper.y = pos.y;
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: openButton.implicitHeight
|
||||
z: open || dialogTransition.running ? 2 : 0
|
||||
|
||||
BlobGroup {
|
||||
id: blobGroup
|
||||
|
||||
color: root.open ? root.openColor : Colors.tPalette.m3surfaceContainer
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: backdrop
|
||||
|
||||
anchors.fill: parent
|
||||
enabled: false
|
||||
hoverEnabled: enabled
|
||||
preventStealing: true
|
||||
parent: root.open ? root.rootParent : root
|
||||
|
||||
onClicked: root.open = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dialogWrapper
|
||||
|
||||
height: openButton.implicitHeight
|
||||
width: root.width
|
||||
z: 1
|
||||
|
||||
states: State {
|
||||
name: "open"
|
||||
when: root.open
|
||||
|
||||
PropertyChanges {
|
||||
backdrop.enabled: true
|
||||
dialogBg.bottomLeftRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.bottomRightRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.topRightRadius: Tokens.rounding.largeIncreased
|
||||
dialogBg.topLeftRadius: Tokens.rounding.largeIncreased
|
||||
dialogContent.opacity: 1
|
||||
dialogWrapper.height: root.openHeight
|
||||
dialogWrapper.width: root.openWidth
|
||||
dialogWrapper.x: (root.rootParent.width - root.openWidth) / 2
|
||||
dialogWrapper.y: (root.rootParent.height - root.openHeight) / 2
|
||||
elevation.opacity: 1
|
||||
openButton.opacity: 0
|
||||
}
|
||||
}
|
||||
transitions: Transition {
|
||||
id: dialogTransition
|
||||
|
||||
SequentialAnimation {
|
||||
ScriptAction {
|
||||
script: root.reparentWrapper()
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "x,y"
|
||||
}
|
||||
}
|
||||
|
||||
PropertyAction {
|
||||
property: "enabled"
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "opacity,topLeftRadius,topRightRadius,bottomLeftRadius,bottomRightRadius"
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
Anim {
|
||||
properties: "width,height"
|
||||
}
|
||||
}
|
||||
|
||||
Elevation {
|
||||
id: elevation
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: dialogBg.bottomLeftRadius
|
||||
bottomRightRadius: dialogBg.bottomRightRadius
|
||||
level: 4
|
||||
opacity: 0
|
||||
radius: dialogBg.radius
|
||||
|
||||
transform: Matrix4x4 {
|
||||
matrix: dialogBg.deformMatrix
|
||||
}
|
||||
}
|
||||
|
||||
BlobRect {
|
||||
id: dialogBg
|
||||
|
||||
anchors.fill: parent
|
||||
bottomLeftRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
|
||||
bottomRightRadius: root.last ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
|
||||
topRightRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
|
||||
topLeftRadius: root.first ? Tokens.rounding.largeIncreased : Tokens.rounding.extraSmall
|
||||
deformScale: 0
|
||||
group: blobGroup
|
||||
opacity: blobGroup.color.a * (root.enabled ? 1 : 0.5)
|
||||
}
|
||||
|
||||
TimeDialogToggle {
|
||||
id: openButton
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
color: "transparent"
|
||||
height: Math.min(implicitHeight, parent.height)
|
||||
subtext: root.subtext
|
||||
last: root.last
|
||||
first: root.first
|
||||
text: root.label
|
||||
|
||||
transform: Matrix4x4 {
|
||||
matrix: dialogBg.deformMatrix
|
||||
}
|
||||
|
||||
onClicked: root.open = true
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: dialogContent
|
||||
|
||||
active: opacity > 0
|
||||
anchors.fill: parent
|
||||
asynchronous: false
|
||||
opacity: 0
|
||||
|
||||
sourceComponent: MouseArea {
|
||||
onWheel: event => event.accepted = true
|
||||
preventStealing: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: undefined
|
||||
enabled: dialogContent.opacity > 0
|
||||
clip: true
|
||||
implicitWidth: innerLayout.implicitWidth + innerLayout.anchors.margins * 2
|
||||
implicitHeight: innerLayout.implicitHeight + innerLayout.anchors.margins + innerLayout.anchors.bottomMargin
|
||||
|
||||
ColumnLayout {
|
||||
id: innerLayout
|
||||
anchors.bottomMargin: Tokens.padding.largeIncreased
|
||||
anchors.fill: parent
|
||||
anchors.margins: Tokens.padding.extraLarge
|
||||
spacing: 0
|
||||
|
||||
CustomText {
|
||||
font: Tokens.font.title.builders.large.weight(Font.Normal).build()
|
||||
text: root.header
|
||||
}
|
||||
|
||||
Loader {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: root.horizontalContentMargin
|
||||
Layout.topMargin: Tokens.spacing.medium
|
||||
Layout.bottomMargin: Tokens.spacing.medium
|
||||
Layout.rightMargin: root.horizontalContentMargin
|
||||
sourceComponent: root.content
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
spacing: Tokens.spacing.extraSmall
|
||||
|
||||
TextButton {
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
isRound: true
|
||||
text: qsTr("Cancel")
|
||||
type: TextButton.Text
|
||||
verticalPadding: Tokens.padding.small
|
||||
stateLayer.preventStealing: true
|
||||
|
||||
onClicked: {
|
||||
root.cancelled();
|
||||
root.open = false;
|
||||
}
|
||||
}
|
||||
|
||||
TextButton {
|
||||
enabled: root.acceptAllowed
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
isRound: true
|
||||
text: root.acceptLabel
|
||||
type: TextButton.Text
|
||||
stateLayer.preventStealing: true
|
||||
verticalPadding: Tokens.padding.small
|
||||
|
||||
onClicked: {
|
||||
root.accepted();
|
||||
root.open = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
transform: Matrix4x4 {
|
||||
matrix: dialogBg.deformMatrix
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import ZShell.Config
|
||||
import qs.Helpers
|
||||
import qs.Components
|
||||
import qs.Services
|
||||
|
||||
TimeDialogRow {
|
||||
id: root
|
||||
|
||||
required property int start
|
||||
required property int end
|
||||
property int endCurrent
|
||||
property int startCurrent
|
||||
openColor: Colors.palette.m3surfaceContainer
|
||||
openHeight: Math.min(rootParent.height * 0.8, contentItem?.implicitHeight ?? 0)
|
||||
openWidth: Math.min(rootParent.width * 0.8, contentItem?.implicitWidth ?? 0)
|
||||
|
||||
function keyFor(item: var): string {
|
||||
return item.id;
|
||||
}
|
||||
|
||||
function labelFor(item: var): string {
|
||||
return item.label;
|
||||
}
|
||||
|
||||
horizontalContentMargin: -Tokens.padding.extraSmall
|
||||
|
||||
content: Component {
|
||||
TimeInput {
|
||||
id: timeContent
|
||||
start: root.start
|
||||
end: root.end
|
||||
|
||||
onEndEdit: value => root.endCurrent = value
|
||||
onStartEdit: value => root.startCurrent = value
|
||||
}
|
||||
}
|
||||
|
||||
onOpenChanged: {
|
||||
if (open) {
|
||||
root.rootParent.interactive = false;
|
||||
root.z = 5;
|
||||
} else {
|
||||
root.rootParent.interactive = true;
|
||||
root.z = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -10,12 +10,14 @@ ConnectedRect {
|
||||
|
||||
property alias checked: switchButton.checked
|
||||
property int horizontalPadding: Tokens.padding.largeIncreased
|
||||
required property Component popup
|
||||
property alias subtext: subtext.text
|
||||
property alias text: text.text
|
||||
property alias icon: icon
|
||||
property string iconText: "open_in_new"
|
||||
property int verticalPadding: Tokens.padding.small
|
||||
|
||||
signal clicked(checked: bool)
|
||||
signal clicked
|
||||
signal toggled(checked: bool)
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: layout.implicitHeight + verticalPadding * 2
|
||||
@@ -32,14 +34,14 @@ ConnectedRect {
|
||||
CustomText {
|
||||
id: text
|
||||
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
font: Tokens.font.body.small
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: subtext
|
||||
|
||||
color: Colors.palette.m3outline
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
@@ -50,11 +52,11 @@ ConnectedRect {
|
||||
anchors.right: switchButton.left
|
||||
anchors.rightMargin: Tokens.spacing.medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "open_in_new"
|
||||
text: root.iconText
|
||||
}
|
||||
|
||||
StateLayer {
|
||||
onClicked: PopupManager.requestOpen(root.popup)
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
|
||||
CustomSwitch {
|
||||
@@ -64,6 +66,6 @@ ConnectedRect {
|
||||
anchors.rightMargin: root.horizontalPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
onClicked: root.clicked(checked)
|
||||
onClicked: root.toggled(checked)
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,13 @@ import qs.Components
|
||||
import qs.Helpers
|
||||
import qs.Services
|
||||
|
||||
CustomClippingRect {
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property var object
|
||||
required property list<string> settings
|
||||
property bool shouldBeActive: true
|
||||
|
||||
signal applySettings(startTime: int, endTime: int)
|
||||
signal close
|
||||
required property int start
|
||||
required property int end
|
||||
signal endEdit(time: int)
|
||||
signal startEdit(time: int)
|
||||
|
||||
function convertHour(timeValue: int): int {
|
||||
return Math.floor(timeValue / 60);
|
||||
@@ -28,555 +26,459 @@ CustomClippingRect {
|
||||
return hour * 60 + minute;
|
||||
}
|
||||
|
||||
color: Colors.palette.m3surfaceContainer
|
||||
implicitHeight: column.implicitHeight + column.anchors.margins * 2 + buttonRow.implicitHeight + buttonRow.anchors.topMargin
|
||||
implicitWidth: column.implicitWidth + column.anchors.margins * 2
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
RowLayout {
|
||||
CustomRect {
|
||||
id: startHourRect
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.largeIncreased
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Tokens.spacing.medium
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: startHourField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: startHourField
|
||||
|
||||
function setConfigText(): string {
|
||||
var val = root.convertHour(root.start);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText()
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (startHourField.text.length >= 2) {
|
||||
startHourField.text = "0" + startHourField.text[0];
|
||||
} else if (startHourField.text.length === 1) {
|
||||
startHourField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
startHourField.text = setConfigText();
|
||||
startHourField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
startHourField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
startMinuteField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
endMinuteField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = startHourField.text.length;
|
||||
|
||||
if (textLen >= 2 && startHourField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(startHourField.text + digit);
|
||||
} else {
|
||||
val = parseInt(startHourField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
startHourField.text = "0" + val;
|
||||
} else {
|
||||
startHourField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextChanged: {
|
||||
var mins = parseInt(startMinuteField.text);
|
||||
const hours = root.convertToMinutes(parseInt(startHourField.text));
|
||||
root.startEdit(hours + mins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
text: qsTr("Select time")
|
||||
id: startSeparator
|
||||
|
||||
font: Tokens.font.body.builders.large.size(28).weight(Font.DemiBold).build()
|
||||
text: ":"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CustomRect {
|
||||
id: startHourRect
|
||||
CustomRect {
|
||||
id: startMinuteRect
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: startHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: startHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: startHourField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: startHourField
|
||||
|
||||
function setConfigText(setting: string): string {
|
||||
var val = root.convertHour(root.object[setting]);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText(root.settings[1])
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (startHourField.text.length >= 2) {
|
||||
startHourField.text = "0" + startHourField.text[0];
|
||||
} else if (startHourField.text.length === 1) {
|
||||
startHourField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
startHourField.text = setConfigText(root.settings[1]);
|
||||
startHourField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
startHourField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
startMinuteField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
endMinuteField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = startHourField.text.length;
|
||||
|
||||
if (textLen >= 2 && startHourField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(startHourField.text + digit);
|
||||
} else {
|
||||
val = parseInt(startHourField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
startHourField.text = "0" + val;
|
||||
} else {
|
||||
startHourField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextEdited: {
|
||||
if (startHourField.text === "")
|
||||
return;
|
||||
var val = parseInt(startHourField.text);
|
||||
if (isNaN(val))
|
||||
return;
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
var newText = val.toString();
|
||||
if (newText !== startHourField.text)
|
||||
startHourField.text = newText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: startSeparator
|
||||
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: ":"
|
||||
}
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
id: startMinuteRect
|
||||
anchors.fill: parent
|
||||
border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: startMinuteField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: startMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
Behavior on border.width {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: startMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: startMinuteField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
TextFieldBase {
|
||||
id: startMinuteField
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {
|
||||
}
|
||||
function setConfigText(): string {
|
||||
var val = root.convertMinute(root.start);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: startMinuteField
|
||||
|
||||
function setConfigText(setting: string): string {
|
||||
var val = root.convertMinute(root.object[setting]);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText(root.settings[1])
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (startMinuteField.text.length >= 2) {
|
||||
startMinuteField.text = "0" + startMinuteField.text[0];
|
||||
} else if (startMinuteField.text.length === 1) {
|
||||
startMinuteField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
startMinuteField.text = setConfigText(root.settings[1]);
|
||||
startMinuteField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
startMinuteField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
endHourField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
startHourField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = startMinuteField.text.length;
|
||||
|
||||
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(startMinuteField.text + digit);
|
||||
} else {
|
||||
val = parseInt(startMinuteField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(59, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
startMinuteField.text = "0" + val;
|
||||
} else {
|
||||
startMinuteField.text = val.toString();
|
||||
}
|
||||
}
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText()
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextEdited: {
|
||||
if (startMinuteField.text === "")
|
||||
return;
|
||||
var val = parseInt(startMinuteField.text);
|
||||
if (isNaN(val))
|
||||
return;
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
var newText = val.toString();
|
||||
if (newText !== startMinuteField.text)
|
||||
startMinuteField.text = newText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spacer
|
||||
|
||||
Layout.preferredWidth: Tokens.spacing.largeIncreased
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: endHourRect
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: endHourField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {
|
||||
if (startMinuteField.text.length >= 2) {
|
||||
startMinuteField.text = "0" + startMinuteField.text[0];
|
||||
} else if (startMinuteField.text.length === 1) {
|
||||
startMinuteField.text = "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: endHourField
|
||||
|
||||
function setConfigText(setting: string): string {
|
||||
var val = root.convertHour(root.object[setting]);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText(root.settings[2])
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (endHourField.text.length >= 2) {
|
||||
endHourField.text = "0" + endHourField.text[0];
|
||||
} else if (endHourField.text.length === 1) {
|
||||
endHourField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
endHourField.text = setConfigText(root.settings[2]);
|
||||
endHourField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
endHourField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
endMinuteField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
startMinuteField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = endHourField.text.length;
|
||||
|
||||
if (textLen >= 2 && endHourField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(endHourField.text + digit);
|
||||
} else {
|
||||
val = parseInt(endHourField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
endHourField.text = "0" + val;
|
||||
} else {
|
||||
endHourField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextEdited: {
|
||||
if (endHourField.text === "")
|
||||
return;
|
||||
var val = parseInt(endHourField.text);
|
||||
if (isNaN(val))
|
||||
return;
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
var newText = val.toString();
|
||||
if (newText !== endHourField.text)
|
||||
endHourField.text = newText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: endSeparator
|
||||
|
||||
font.pointSize: Tokens.font.size.extraLarge
|
||||
text: ":"
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: endMinuteRect
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: endMinuteField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: endMinuteField
|
||||
|
||||
function setConfigText(setting: string): string {
|
||||
var val = root.convertMinute(root.object[setting]);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
startMinuteField.text = setConfigText();
|
||||
startMinuteField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
startMinuteField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
endHourField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
startHourField.focus = true;
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText(root.settings[2])
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (endMinuteField.text.length >= 2) {
|
||||
endMinuteField.text = "0" + endMinuteField.text[0];
|
||||
} else if (endMinuteField.text.length === 1) {
|
||||
endMinuteField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
endMinuteField.text = setConfigText(root.settings[2]);
|
||||
endMinuteField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
endMinuteField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
startHourField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
endHourField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = endMinuteField.text.length;
|
||||
|
||||
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(endMinuteField.text + digit);
|
||||
} else {
|
||||
val = parseInt(endMinuteField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(59, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
endMinuteField.text = "0" + val;
|
||||
} else {
|
||||
endMinuteField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextEdited: {
|
||||
if (endMinuteField.text === "")
|
||||
var digit = event.text;
|
||||
var textLen = startMinuteField.text.length;
|
||||
|
||||
if (textLen >= 2 && startMinuteField.text[0] !== '0') {
|
||||
return;
|
||||
var val = parseInt(endMinuteField.text);
|
||||
if (isNaN(val))
|
||||
return;
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
var newText = val.toString();
|
||||
if (newText !== endMinuteField.text)
|
||||
endMinuteField.text = newText;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(startMinuteField.text + digit);
|
||||
} else {
|
||||
val = parseInt(startMinuteField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(59, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
startMinuteField.text = "0" + val;
|
||||
} else {
|
||||
startMinuteField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextChanged: {
|
||||
var mins = parseInt(startMinuteField.text);
|
||||
const hours = root.convertToMinutes(parseInt(startHourField.text));
|
||||
root.startEdit(hours + mins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
CustomText {
|
||||
Layout.preferredWidth: spacer.x + spacer.width
|
||||
text: qsTr("Start")
|
||||
Item {
|
||||
id: spacer
|
||||
|
||||
Layout.preferredWidth: Tokens.spacing.largeIncreased
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: endHourRect
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: endHourField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: endHourField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: endHourField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
|
||||
text: qsTr("End")
|
||||
TextFieldBase {
|
||||
id: endHourField
|
||||
|
||||
function setConfigText(): string {
|
||||
var val = root.convertHour(root.end);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText()
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (endHourField.text.length >= 2) {
|
||||
endHourField.text = "0" + endHourField.text[0];
|
||||
} else if (endHourField.text.length === 1) {
|
||||
endHourField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
endHourField.text = setConfigText();
|
||||
endHourField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
endHourField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
endMinuteField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
startMinuteField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = endHourField.text.length;
|
||||
|
||||
if (textLen >= 2 && endHourField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(endHourField.text + digit);
|
||||
} else {
|
||||
val = parseInt(endHourField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(23, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
endHourField.text = "0" + val;
|
||||
} else {
|
||||
endHourField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextChanged: {
|
||||
var mins = parseInt(endMinuteField.text);
|
||||
const hours = root.convertToMinutes(parseInt(endHourField.text));
|
||||
root.endEdit(hours + mins);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomText {
|
||||
id: endSeparator
|
||||
|
||||
font: Tokens.font.body.builders.large.size(28).weight(Font.DemiBold).build()
|
||||
text: ":"
|
||||
}
|
||||
|
||||
CustomRect {
|
||||
id: endMinuteRect
|
||||
|
||||
Layout.preferredHeight: 72
|
||||
Layout.preferredWidth: 96
|
||||
color: endMinuteField.focus ? Colors.palette.m3onPrimaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
implicitHeight: 72
|
||||
implicitWidth: 96
|
||||
radius: Tokens.rounding.small
|
||||
|
||||
CustomRect {
|
||||
anchors.fill: parent
|
||||
border.color: endMinuteField.focus ? Colors.palette.m3primaryContainer : Colors.palette.m3surfaceContainerHighest
|
||||
border.width: endMinuteField.focus ? 2 : 0
|
||||
radius: parent.radius - border.width
|
||||
|
||||
Behavior on border.width {
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldBase {
|
||||
id: endMinuteField
|
||||
|
||||
function setConfigText(): string {
|
||||
var val = root.convertMinute(root.end);
|
||||
if (val === 0) {
|
||||
return "00";
|
||||
}
|
||||
return String(val);
|
||||
}
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
clip: true
|
||||
color: focus ? Colors.palette.m3primaryContainer : Colors.palette.m3onSurface
|
||||
font.family: "Roboto"
|
||||
font.letterSpacing: -0.25
|
||||
font.pixelSize: 56
|
||||
font.weight: 400
|
||||
horizontalAlignment: TextInput.AlignHCenter
|
||||
text: setConfigText()
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Backspace) {
|
||||
event.accepted = true;
|
||||
if (endMinuteField.text.length >= 2) {
|
||||
endMinuteField.text = "0" + endMinuteField.text[0];
|
||||
} else if (endMinuteField.text.length === 1) {
|
||||
endMinuteField.text = "0";
|
||||
}
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Escape) {
|
||||
event.accepted = true;
|
||||
endMinuteField.text = setConfigText();
|
||||
endMinuteField.focus = false;
|
||||
} else if (event.key === Qt.Key_Return) {
|
||||
endMinuteField.focus = false;
|
||||
return;
|
||||
} else if (event.key === Qt.Key_Tab) {
|
||||
startHourField.focus = true;
|
||||
} else if (event.key === Qt.Key_Backtab) {
|
||||
endHourField.focus = true;
|
||||
}
|
||||
|
||||
if (event.text.length === 1 && event.text >= "0" && event.text <= "9") {
|
||||
event.accepted = true;
|
||||
var digit = event.text;
|
||||
var textLen = endMinuteField.text.length;
|
||||
|
||||
if (textLen >= 2 && endMinuteField.text[0] !== '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
var val = 0;
|
||||
if (textLen === 0) {
|
||||
val = parseInt(digit);
|
||||
} else if (textLen === 1) {
|
||||
val = parseInt(endMinuteField.text + digit);
|
||||
} else {
|
||||
val = parseInt(endMinuteField.text[1] + digit);
|
||||
}
|
||||
|
||||
val = Math.max(0, Math.min(59, val));
|
||||
|
||||
if (textLen >= 2 && val < 10) {
|
||||
endMinuteField.text = "0" + val;
|
||||
} else {
|
||||
endMinuteField.text = val.toString();
|
||||
}
|
||||
}
|
||||
|
||||
event.accepted = true;
|
||||
}
|
||||
onCursorPositionChanged: cursorPosition = 2
|
||||
onTextChanged: {
|
||||
var mins = parseInt(endMinuteField.text);
|
||||
const hours = root.convertToMinutes(parseInt(endHourField.text));
|
||||
root.endEdit(hours + mins);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: buttonRow
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Tokens.padding.largeIncreased
|
||||
anchors.right: parent.right
|
||||
anchors.top: column.bottom
|
||||
anchors.topMargin: Tokens.spacing.medium
|
||||
|
||||
Item {
|
||||
id: buttonSpacer
|
||||
|
||||
Layout.fillWidth: true
|
||||
CustomText {
|
||||
Layout.preferredWidth: spacer.x + spacer.width
|
||||
text: qsTr("Start")
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
IconTextButton {
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
icon: "close"
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
text: "Cancel"
|
||||
|
||||
onClicked: root.close()
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
font.pointSize: Tokens.font.size.normal
|
||||
icon: "check"
|
||||
isRound: true
|
||||
isToggle: false
|
||||
shapeMorph: true
|
||||
shapeMorphExpansion: pressed ? 12 : 0
|
||||
text: "Apply"
|
||||
|
||||
onClicked: {
|
||||
const start = root.convertToMinutes(parseInt(startHourField.text)) + parseInt(startMinuteField.text);
|
||||
const end = root.convertToMinutes(parseInt(endHourField.text)) + parseInt(endMinuteField.text);
|
||||
root.applySettings(start, end);
|
||||
}
|
||||
}
|
||||
CustomText {
|
||||
Layout.preferredWidth: endMinuteRect.width + endHourRect.width
|
||||
text: qsTr("End")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ CustomSwitch {
|
||||
property alias last: bg.last
|
||||
property string settingAnchor
|
||||
property string subtext
|
||||
property string iconText
|
||||
|
||||
function flashHighlight(): void {
|
||||
bg.flashHighlight();
|
||||
@@ -19,6 +20,7 @@ CustomSwitch {
|
||||
|
||||
Layout.fillWidth: true
|
||||
cLayer: 2
|
||||
font: Tokens.font.body.small
|
||||
horizontalPadding: Tokens.padding.largeIncreased
|
||||
implicitHeight: Math.max(implicitContentHeight, implicitIndicatorHeight) + verticalPadding * 2
|
||||
implicitWidth: implicitContentWidth + implicitIndicatorWidth + horizontalPadding * 2
|
||||
@@ -58,13 +60,12 @@ CustomSwitch {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.smaller
|
||||
font: root.font
|
||||
opacity: root.enabled ? 1 : 0.5
|
||||
text: root.text
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,16 +76,22 @@ CustomSwitch {
|
||||
anchors.right: parent.right
|
||||
color: Colors.palette.m3outline
|
||||
elide: Text.ElideRight
|
||||
font.pointSize: Tokens.font.size.small
|
||||
font: Tokens.font.label.small
|
||||
opacity: root.enabled ? 1 : 0.5
|
||||
text: root.subtext
|
||||
visible: root.subtext
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialIcon {
|
||||
id: icon
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.iconText ?? ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,25 +13,48 @@ import qs.Services
|
||||
Item {
|
||||
id: wrapper
|
||||
|
||||
property int buttonRowHeight: Tokens.padding.large * 3
|
||||
property bool changesMade: false
|
||||
readonly property var currentScreen: screens.length > selectedScreenIndex ? screens[selectedScreenIndex] : null
|
||||
readonly property real maxZoom: 5.0
|
||||
|
||||
readonly property bool changesMade: {
|
||||
cropRevision;
|
||||
|
||||
const r = cropRectLoader.item;
|
||||
if (!r || !currentScreen || scaledImg.status !== Image.Ready)
|
||||
return false;
|
||||
|
||||
const t = r.savedTarget();
|
||||
|
||||
return Math.abs(r.x - t.x) > 0.5 || Math.abs(r.y - t.y) > 0.5 || Math.abs(r.zoom - t.zoom) > 0.001;
|
||||
}
|
||||
|
||||
property int cropRevision: 0
|
||||
property int buttonRowHeight: Tokens.padding.large * 3
|
||||
property var screens: []
|
||||
property int selectedScreenIndex: 0
|
||||
property bool shouldBeActive: true
|
||||
|
||||
function zoomFromSlider(t: real): real {
|
||||
return 1 / (1 - t * (1 - 1 / maxZoom));
|
||||
}
|
||||
|
||||
function sliderFromZoom(zoom: real): real {
|
||||
return (1 - 1 / zoom) / (1 - 1 / maxZoom);
|
||||
}
|
||||
|
||||
function applyCrop(): void {
|
||||
if (!cropRectLoader.item || !currentScreen)
|
||||
return;
|
||||
|
||||
const cropRect = cropRectLoader.item;
|
||||
|
||||
const cropXPercent = (cropRect.x - cropRect.imageX) / scaledImg.paintedWidth;
|
||||
const cropYPercent = (cropRect.y - cropRect.imageY) / scaledImg.paintedHeight;
|
||||
const cropWidthPercent = cropRect.width / scaledImg.paintedWidth;
|
||||
const cropHeightPercent = cropRect.height / scaledImg.paintedHeight;
|
||||
const cropXPercent = (cropRect.x - cropRect.imageX) / cropRect.imageW;
|
||||
const cropYPercent = (cropRect.y - cropRect.imageY) / cropRect.imageH;
|
||||
const cropWidthPercent = cropRect.width / cropRect.imageW;
|
||||
const cropHeightPercent = cropRect.height / cropRect.imageH;
|
||||
|
||||
Wallpapers.setCrop(currentScreen.name, Qt.rect(cropXPercent, cropYPercent, cropWidthPercent, cropHeightPercent), cropRect.zoom);
|
||||
cropRevision++;
|
||||
}
|
||||
|
||||
function refreshScreens(): void {
|
||||
@@ -79,16 +102,13 @@ Item {
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
Behavior on y {
|
||||
Anim {
|
||||
}
|
||||
Anim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
@@ -99,34 +119,10 @@ Item {
|
||||
Qt.callLater(syncCropToScreen);
|
||||
}
|
||||
|
||||
IconButton {
|
||||
anchors.margins: Tokens.padding.small
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
icon: "check"
|
||||
opacity: wrapper.changesMade ? 1 : 0
|
||||
scale: wrapper.changesMade ? 1 : 0
|
||||
z: 2
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
Behavior on scale {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
wrapper.applyCrop();
|
||||
wrapper.changesMade = false;
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
id: screenSelector
|
||||
|
||||
anchors.bottom: scaledImg.top
|
||||
anchors.bottom: imageSlot.top
|
||||
anchors.bottomMargin: Tokens.spacing.medium
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Tokens.padding.extraLarge * 2
|
||||
@@ -149,6 +145,7 @@ Item {
|
||||
isRound: true
|
||||
shapeMorph: true
|
||||
text: modelData.name
|
||||
font: Tokens.font.body.builders.large.weight(Font.DemiBold).build()
|
||||
|
||||
onClicked: wrapper.selectScreen(index)
|
||||
}
|
||||
@@ -170,24 +167,54 @@ Item {
|
||||
Layout.leftMargin: Tokens.padding.small
|
||||
Layout.preferredHeight: Tokens.padding.medium * 3
|
||||
Layout.rightMargin: Tokens.padding.small
|
||||
from: 1.0
|
||||
implicitHeight: Tokens.padding.small * 3
|
||||
insetIcon: "crop"
|
||||
to: 5.0
|
||||
value: cropRectLoader.item ? cropRectLoader.item.zoom : 1.0
|
||||
value: cropRectLoader.item ? wrapper.sliderFromZoom(cropRectLoader.item.zoom) : 0
|
||||
|
||||
onInteraction: value => {
|
||||
wrapper.zoomClipRect(1 + (value * 4));
|
||||
wrapper.changesMade = true;
|
||||
wrapper.zoomClipRect(wrapper.zoomFromSlider(value));
|
||||
}
|
||||
}
|
||||
|
||||
ButtonRow {
|
||||
spacing: Tokens.spacing.medium
|
||||
|
||||
IconTextButton {
|
||||
isRound: true
|
||||
icon: "close"
|
||||
text: qsTr("Cancel")
|
||||
font: Tokens.font.body.large
|
||||
shapeMorph: true
|
||||
horizontalPadding: Tokens.padding.large
|
||||
inactiveColor: Colors.layer(Colors.palette.m3surfaceContainerHighest, 2)
|
||||
inactiveOnColor: Colors.palette.m3onSurfaceVariant
|
||||
enabled: wrapper.changesMade
|
||||
|
||||
onClicked: {
|
||||
wrapper.syncCropToScreen();
|
||||
}
|
||||
}
|
||||
|
||||
IconTextButton {
|
||||
isRound: true
|
||||
icon: "check"
|
||||
text: qsTr("Apply")
|
||||
font: Tokens.font.body.large
|
||||
shapeMorph: true
|
||||
horizontalPadding: Tokens.padding.large
|
||||
inactiveColor: Colors.palette.m3secondary
|
||||
inactiveOnColor: Colors.palette.m3onSecondary
|
||||
enabled: wrapper.changesMade
|
||||
|
||||
onClicked: {
|
||||
wrapper.applyCrop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: scaledImg
|
||||
|
||||
property var displayData
|
||||
property real monitorScale: 1.0
|
||||
Item {
|
||||
id: imageSlot
|
||||
|
||||
anchors.bottom: sliderLayout.top
|
||||
anchors.bottomMargin: Tokens.spacing.medium
|
||||
@@ -195,147 +222,238 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: wrapper.buttonRowHeight + Tokens.spacing.medium
|
||||
// anchors.top: screenSelector.bottom
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
retainWhileLoading: true
|
||||
source: Wallpapers.current
|
||||
sourceSize.height: parent.height
|
||||
sourceSize.width: parent.width
|
||||
|
||||
onPaintedWidthChanged: {
|
||||
if (paintedWidth > 0 && cropRectLoader.item) {
|
||||
if (!wrapper.screens.length)
|
||||
wrapper.refreshScreens();
|
||||
CustomClippingRect {
|
||||
id: fitBox
|
||||
|
||||
wrapper.syncCropToScreen();
|
||||
}
|
||||
}
|
||||
onSourceChanged: {
|
||||
if (cropRectLoader.item) {
|
||||
if (!wrapper.screens.length)
|
||||
wrapper.refreshScreens();
|
||||
readonly property real aspect: scaledImg.implicitHeight > 0 ? scaledImg.implicitWidth / scaledImg.implicitHeight : 1
|
||||
readonly property real fitW: Math.min(imageSlot.width, imageSlot.height * aspect)
|
||||
|
||||
wrapper.syncCropToScreen();
|
||||
}
|
||||
}
|
||||
onStatusChanged: {
|
||||
if (scaledImg.status == Image.Ready && cropRectLoader.item) {
|
||||
if (!wrapper.screens.length)
|
||||
wrapper.refreshScreens();
|
||||
implicitHeight: Math.round(fitW / aspect)
|
||||
implicitWidth: Math.round(fitW)
|
||||
x: Math.round((imageSlot.width - width) / 2)
|
||||
y: Math.round((imageSlot.height - height) / 2)
|
||||
radius: Tokens.rounding.largeIncreased
|
||||
|
||||
wrapper.syncCropToScreen();
|
||||
}
|
||||
}
|
||||
Loader {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Loader {
|
||||
id: cropRectLoader
|
||||
opacity: scaledImg.status === Image.Ready ? 0 : 1
|
||||
active: opacity > 0
|
||||
|
||||
active: scaledImg.status === Image.Ready
|
||||
sourceComponent: CustomRect {
|
||||
implicitWidth: loadingIndicator.implicitSize + Tokens.padding.large * 2
|
||||
implicitHeight: loadingIndicator.implicitSize + Tokens.padding.large * 2
|
||||
|
||||
sourceComponent: Component {
|
||||
CustomRect {
|
||||
id: cropRect
|
||||
color: Colors.palette.m3primaryContainer
|
||||
radius: Tokens.rounding.full
|
||||
|
||||
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1
|
||||
readonly property real baseHeight: baseWidth / aspectRatio
|
||||
readonly property real baseWidth: {
|
||||
let fittedHeight = scaledImg.paintedHeight;
|
||||
let fittedWidth = fittedHeight * aspectRatio;
|
||||
LoadingIndicator {
|
||||
id: loadingIndicator
|
||||
|
||||
if (fittedWidth > scaledImg.paintedWidth) {
|
||||
fittedWidth = scaledImg.paintedWidth;
|
||||
fittedHeight = fittedWidth / aspectRatio;
|
||||
}
|
||||
anchors.centerIn: parent
|
||||
|
||||
return fittedWidth;
|
||||
containsIcon: true
|
||||
implicitSize: Math.min(fitBox.width, fitBox.height) * 0.3
|
||||
}
|
||||
readonly property real imageX: (scaledImg.width - scaledImg.paintedWidth) / 2
|
||||
readonly property real imageY: (scaledImg.height - scaledImg.paintedHeight) / 2
|
||||
property real imgAspectRatio: scaledImg.paintedWidth / scaledImg.paintedHeight
|
||||
property real zoom: 1.0
|
||||
}
|
||||
|
||||
function centerInImage() {
|
||||
x = imageX + (scaledImg.paintedWidth - width) / 2;
|
||||
y = imageY + (scaledImg.paintedHeight - height) / 2;
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.DefaultEffects
|
||||
}
|
||||
|
||||
function clampToBounds() {
|
||||
x = Math.max(imageX, Math.min(x, imageX + scaledImg.paintedWidth - width));
|
||||
y = Math.max(imageY, Math.min(y, imageY + scaledImg.paintedHeight - height));
|
||||
}
|
||||
|
||||
function restoreFromData() {
|
||||
let data = Wallpapers.getCrop(wrapper.currentScreen.name);
|
||||
|
||||
if (data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001)) {
|
||||
zoom = data.zoom > 0 ? data.zoom : 1.0;
|
||||
x = imageX + (data.x * scaledImg.paintedWidth);
|
||||
y = imageY + (data.y * scaledImg.paintedHeight);
|
||||
|
||||
clampToBounds();
|
||||
} else {
|
||||
zoom = 1.0;
|
||||
centerInImage();
|
||||
}
|
||||
}
|
||||
|
||||
border.color: Colors.palette.m3primary
|
||||
border.width: 2
|
||||
height: baseHeight / zoom
|
||||
opacity: 1
|
||||
width: baseWidth / zoom
|
||||
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
restoreFromData();
|
||||
}
|
||||
onHeightChanged: clampToBounds()
|
||||
onWidthChanged: clampToBounds()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
Image {
|
||||
id: scaledImg
|
||||
|
||||
function updateCrop(mouseX, mouseY) {
|
||||
if (!cropRectLoader.item)
|
||||
return;
|
||||
anchors.fill: parent
|
||||
asynchronous: true
|
||||
fillMode: Image.Stretch
|
||||
source: Wallpapers.current
|
||||
sourceSize: {
|
||||
const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;
|
||||
return Qt.size(width * dpr, height * dpr);
|
||||
}
|
||||
retainWhileLoading: true
|
||||
opacity: status === Image.Ready ? 1 : 0
|
||||
|
||||
const cropRect = cropRectLoader.item;
|
||||
|
||||
let nx = mouseX - cropRect.width * 0.5;
|
||||
let ny = mouseY - cropRect.height * 0.5;
|
||||
|
||||
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + scaledImg.paintedWidth - cropRect.width));
|
||||
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + scaledImg.paintedHeight - cropRect.height));
|
||||
|
||||
cropRect.x = nx;
|
||||
cropRect.y = ny;
|
||||
Behavior on opacity {
|
||||
Anim {
|
||||
type: Anim.SlowEffects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
Loader {
|
||||
id: cropRectLoader
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
active: scaledImg.status === Image.Ready
|
||||
|
||||
sourceComponent: Component {
|
||||
CustomRect {
|
||||
id: cropRect
|
||||
|
||||
property real aspectRatio: wrapper.currentScreen ? wrapper.currentScreen.width / wrapper.currentScreen.height : 1
|
||||
readonly property real baseHeight: baseWidth / aspectRatio
|
||||
|
||||
readonly property real baseWidth: {
|
||||
let fittedHeight = imageH;
|
||||
let fittedWidth = fittedHeight * aspectRatio;
|
||||
|
||||
if (fittedWidth > imageW) {
|
||||
fittedWidth = imageW;
|
||||
fittedHeight = fittedWidth / aspectRatio;
|
||||
}
|
||||
|
||||
return fittedWidth;
|
||||
}
|
||||
|
||||
readonly property real imageH: Math.round(fitBox.height)
|
||||
readonly property real imageW: Math.round(fitBox.width)
|
||||
readonly property real imageX: 0
|
||||
readonly property real imageY: 0
|
||||
property real imgAspectRatio: imageH > 0 ? imageW / imageH : 1
|
||||
property real zoom: 1.0
|
||||
|
||||
function centerInImage() {
|
||||
x = Math.round(imageX + (imageW - width) / 2);
|
||||
y = Math.round(imageY + (imageH - height) / 2);
|
||||
}
|
||||
|
||||
function clampToBounds() {
|
||||
x = Math.max(imageX, Math.min(x, imageX + imageW - width));
|
||||
y = Math.max(imageY, Math.min(y, imageY + imageH - height));
|
||||
}
|
||||
|
||||
// Where the rect should be according to saved data (same rules as a restore).
|
||||
function savedTarget() {
|
||||
const data = Wallpapers.getCrop(wrapper.currentScreen.name);
|
||||
const hasCrop = data && (Math.abs(data.x) > 0.001 || Math.abs(data.y) > 0.001 || Math.abs(data.width - 1.0) > 0.001 || Math.abs(data.height - 1.0) > 0.001);
|
||||
|
||||
const z = hasCrop && data.zoom > 0 ? data.zoom : 1.0;
|
||||
const w = Math.round(baseWidth / z);
|
||||
const h = Math.round(baseHeight / z);
|
||||
|
||||
let tx = hasCrop ? Math.round(imageX + data.x * imageW) : Math.round(imageX + (imageW - w) / 2);
|
||||
let ty = hasCrop ? Math.round(imageY + data.y * imageH) : Math.round(imageY + (imageH - h) / 2);
|
||||
|
||||
tx = Math.max(imageX, Math.min(tx, imageX + imageW - w));
|
||||
ty = Math.max(imageY, Math.min(ty, imageY + imageH - h));
|
||||
|
||||
return {
|
||||
x: tx,
|
||||
y: ty,
|
||||
zoom: z
|
||||
};
|
||||
}
|
||||
|
||||
function restoreFromData() {
|
||||
const t = savedTarget();
|
||||
zoom = t.zoom;
|
||||
x = t.x;
|
||||
y = t.y;
|
||||
}
|
||||
|
||||
height: Math.round(baseHeight / zoom)
|
||||
width: Math.round(baseWidth / zoom)
|
||||
color: zoomSlider.dragging || mouse.pressed ? Qt.alpha(Colors.palette.m3tertiary, 0.3) : "transparent"
|
||||
|
||||
Behavior on color {
|
||||
CAnim {}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
restoreFromData();
|
||||
}
|
||||
onHeightChanged: clampToBounds()
|
||||
onWidthChanged: clampToBounds()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShaderEffect {
|
||||
anchors.fill: parent
|
||||
|
||||
readonly property real dpr: (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1
|
||||
|
||||
function snap(v: real): real {
|
||||
return Math.round(v * dpr) / dpr;
|
||||
}
|
||||
|
||||
property color borderColor: Colors.palette.m3tertiary
|
||||
property vector4d cropBox: cropRectLoader.item ? Qt.vector4d(snap(cropRectLoader.item.x), snap(cropRectLoader.item.y), snap(cropRectLoader.item.width), snap(cropRectLoader.item.height)) : Qt.vector4d(0, 0, 0, 0)
|
||||
property real borderWidth: 2
|
||||
property real cornerRadius: fitBox.radius
|
||||
property vector2d itemSize: Qt.vector2d(width, height)
|
||||
property real pixelScale: dpr
|
||||
|
||||
fragmentShader: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/shaders/crop_sdf.frag.qsb`)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouse
|
||||
|
||||
property real grabX: 0
|
||||
property real grabY: 0
|
||||
|
||||
function insideCropRect(mx, my): bool {
|
||||
const r = cropRectLoader.item;
|
||||
if (!r)
|
||||
return false;
|
||||
|
||||
return mx >= r.x && mx <= r.x + r.width && my >= r.y && my <= r.y + r.height;
|
||||
}
|
||||
|
||||
function beginGrab(mx, my): void {
|
||||
const r = cropRectLoader.item;
|
||||
if (!r)
|
||||
return;
|
||||
|
||||
const inside = insideCropRect(mx, my);
|
||||
|
||||
if (inside) {
|
||||
grabX = mx - r.x;
|
||||
grabY = my - r.y;
|
||||
} else {
|
||||
grabX = r.width * 0.5;
|
||||
grabY = r.height * 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCrop(mx, my): void {
|
||||
if (!cropRectLoader.item)
|
||||
return;
|
||||
|
||||
const cropRect = cropRectLoader.item;
|
||||
|
||||
let nx = mx - grabX;
|
||||
let ny = my - grabY;
|
||||
|
||||
nx = Math.max(cropRect.imageX, Math.min(nx, cropRect.imageX + cropRect.imageW - cropRect.width));
|
||||
ny = Math.max(cropRect.imageY, Math.min(ny, cropRect.imageY + cropRect.imageH - cropRect.height));
|
||||
|
||||
cropRect.x = nx;
|
||||
cropRect.y = ny;
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
preventStealing: true
|
||||
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : insideCropRect(mouseX, mouseY) ? Qt.OpenHandCursor : Qt.PointingHandCursor
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
updateCrop(mouse.x, mouse.y);
|
||||
}
|
||||
}
|
||||
onPressed: mouse => {
|
||||
beginGrab(mouse.x, mouse.y);
|
||||
updateCrop(mouse.x, mouse.y);
|
||||
wrapper.changesMade = true;
|
||||
}
|
||||
}
|
||||
onPressed: mouse => {
|
||||
updateCrop(mouse.x, mouse.y);
|
||||
wrapper.changesMade = true;
|
||||
}
|
||||
onReleased: {
|
||||
wrapper.changesMade = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user