formatter

This commit is contained in:
Zacharias-Brohn
2026-02-24 23:20:11 +01:00
parent 40cd984b6d
commit d56a0260fb
202 changed files with 15037 additions and 15352 deletions
+170 -139
View File
@@ -12,68 +12,65 @@ import qs.Daemons
import qs.Helpers
Item {
id: root
id: root
implicitWidth: layout.implicitWidth + 5 * 2
implicitHeight: layout.implicitHeight + 5 * 2
readonly property int topMargin: 0
readonly property int rounding: 6
readonly property int topMargin: 0
required property var wrapper
required property var wrapper
implicitHeight: layout.implicitHeight + 5 * 2
implicitWidth: layout.implicitWidth + 5 * 2
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
implicitWidth: stack.currentItem ? stack.currentItem.childrenRect.height : 0
spacing: 12
RowLayout {
id: tabBar
spacing: 6
Layout.fillWidth: true
property int tabHeight: 36
Layout.fillWidth: true
spacing: 6
CustomClippingRect {
radius: 6
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 0 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: 6
StateLayer {
function onClicked(): void {
stack.currentIndex = 0;
}
CustomText {
text: qsTr("Volumes")
anchors.centerIn: parent
color: stack.currentIndex === 0 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Volumes")
}
}
}
CustomClippingRect {
radius: 6
Layout.fillWidth: true
Layout.preferredHeight: tabBar.tabHeight
color: stack.currentIndex === 1 ? DynamicColors.palette.m3primary : DynamicColors.tPalette.m3surfaceContainer
radius: 6
StateLayer {
function onClicked(): void {
stack.currentIndex = 1;
}
CustomText {
text: qsTr("Devices")
anchors.centerIn: parent
color: stack.currentIndex === 1 ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3primary
text: qsTr("Devices")
}
}
}
@@ -81,175 +78,245 @@ Item {
StackLayout {
id: stack
Layout.fillWidth: true
Layout.preferredHeight: currentIndex === 0 ? vol.childrenRect.height : dev.childrenRect.height
currentIndex: 0
VolumesTab { id: vol }
DevicesTab { id: dev }
Behavior on currentIndex {
SequentialAnimation {
ParallelAnimation {
Anim {
target: stack
property: "opacity"
to: 0
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: stack
to: 0
}
Anim {
target: stack
property: "scale"
to: 0.9
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 0.9
}
}
PropertyAction {}
PropertyAction {
}
ParallelAnimation {
Anim {
target: stack
property: "opacity"
to: 1
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: stack
to: 1
}
Anim {
target: stack
property: "scale"
to: 1
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: stack
to: 1
}
}
}
}
}
}
VolumesTab {
id: vol
}
DevicesTab {
id: dev
}
}
}
component DevicesTab: ColumnLayout {
spacing: 12
ButtonGroup {
id: sinks
}
ButtonGroup {
id: sources
}
CustomText {
font.weight: 500
text: qsTr("Output device")
}
Repeater {
model: Audio.sinks
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: Audio.sink?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSink(modelData)
}
}
CustomText {
Layout.topMargin: 10
font.weight: 500
text: qsTr("Input device")
}
Repeater {
model: Audio.sources
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: Audio.source?.id === modelData.id
text: modelData.description
onClicked: Audio.setAudioSource(modelData)
}
}
}
component VolumesTab: ColumnLayout {
spacing: 12
CustomRect {
Layout.topMargin: root.topMargin
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
RowLayout {
id: outputVolume
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.spacing.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 15
CustomRect {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: DynamicColors.palette.m3primary
radius: 1000
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "speaker"
font.pointSize: 22
text: "speaker"
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
CustomText {
text: "Output Volume"
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Output Volume"
}
CustomText {
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`);
font.bold: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.muted ? qsTr("Muted") : `${Math.round(Audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.bottomMargin: 5
Layout.fillWidth: true
Layout.preferredHeight: 10
Layout.bottomMargin: 5
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 10
value: Audio.volume
onMoved: Audio.setVolume(value)
Behavior on value { Anim {} }
Behavior on value {
Anim {
}
}
onMoved: Audio.setVolume(value)
}
}
}
}
}
CustomRect {
Layout.topMargin: root.topMargin
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
RowLayout {
id: inputVolume
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.spacing.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 15
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: DynamicColors.palette.m3primary
radius: 1000
MaterialIcon {
anchors.centerIn: parent
anchors.alignWhenCentered: false
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
text: "mic"
font.pointSize: 22
text: "mic"
}
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
text: "Input Volume"
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillWidth: true
text: "Input Volume"
}
CustomText {
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`);
font.bold: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
font.bold: true
text: qsTr("%1").arg(Audio.sourceMuted ? qsTr("Muted") : `${Math.round(Audio.sourceVolume * 100)}%`)
}
}
CustomMouseArea {
Layout.fillWidth: true
Layout.bottomMargin: 5
Layout.fillWidth: true
implicitHeight: 10
CustomSlider {
@@ -257,9 +324,13 @@ Item {
anchors.right: parent.right
implicitHeight: 10
value: Audio.sourceVolume
onMoved: Audio.setSourceVolume(value)
Behavior on value { Anim {} }
Behavior on value {
Anim {
}
}
onMoved: Audio.setSourceVolume(value)
}
}
}
@@ -267,10 +338,9 @@ Item {
}
Rectangle {
Layout.topMargin: root.topMargin
Layout.fillWidth: true
Layout.preferredHeight: 1
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3outline
}
@@ -280,39 +350,40 @@ Item {
CustomRect {
id: appBox
Layout.topMargin: root.topMargin
required property int index
required property var modelData
Layout.fillWidth: true
Layout.preferredHeight: 42 + Appearance.spacing.smaller * 2
Layout.topMargin: root.topMargin
color: DynamicColors.tPalette.m3surfaceContainer
radius: root.rounding
required property var modelData
required property int index
RowLayout {
id: layoutVolume
anchors.fill: parent
anchors.margins: Appearance.spacing.smaller
spacing: 15
CustomRect {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: DynamicColors.palette.m3primary
radius: 1000
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "volume_up"
font.pointSize: 22
color: DynamicColors.palette.m3onPrimary
font.pointSize: 22
text: "volume_up"
StateLayer {
radius: 1000
onClicked: {
appBox.modelData.audio.muted = !appBox.modelData.audio.muted;
}
@@ -326,42 +397,46 @@ Item {
TextMetrics {
id: metrics
text: Audio.getStreamName(appBox.modelData)
elide: Text.ElideRight
elideWidth: root.width - 50
text: Audio.getStreamName(appBox.modelData)
}
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
text: metrics.elidedText
elide: Text.ElideRight
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
elide: Text.ElideRight
text: metrics.elidedText
}
CustomText {
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`);
font.bold: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true
font.bold: true
text: qsTr("%1").arg(appBox.modelData.audio.muted ? qsTr("Muted") : `${Math.round(appBox.modelData.audio.volume * 100)}%`)
}
}
CustomMouseArea {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.fillHeight: true
Layout.fillWidth: true
implicitHeight: 10
CustomSlider {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: 10
value: appBox.modelData.audio.volume
onMoved: {
Audio.setStreamVolume(appBox.modelData, value)
Audio.setStreamVolume(appBox.modelData, value);
}
}
}
@@ -370,48 +445,4 @@ Item {
}
}
}
component DevicesTab: ColumnLayout {
spacing: 12
ButtonGroup { id: sinks }
ButtonGroup { id: sources }
CustomText {
text: qsTr("Output device")
font.weight: 500
}
Repeater {
model: Audio.sinks
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
checked: Audio.sink?.id === modelData.id
onClicked: Audio.setAudioSink(modelData)
text: modelData.description
}
}
CustomText {
Layout.topMargin: 10
text: qsTr("Input device")
font.weight: 500
}
Repeater {
model: Audio.sources
CustomRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
checked: Audio.source?.id === modelData.id
onClicked: Audio.setAudioSource(modelData)
text: modelData.description
}
}
}
}
+96 -91
View File
@@ -8,113 +8,118 @@ import qs.Config
import qs.Components
Item {
id: root
implicitWidth: expanded ? 300 : 150
anchors.top: parent.top
anchors.bottom: parent.bottom
id: root
property bool expanded: false
property color textColor: DynamicColors.palette.m3onSurface
property color barColor: DynamicColors.palette.m3primary
property color barColor: DynamicColors.palette.m3primary
property bool expanded: false
property color textColor: DynamicColors.palette.m3onSurface
Behavior on implicitWidth {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: expanded ? 300 : 150
Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
height: 22
radius: height / 2
color: DynamicColors.tPalette.m3surfaceContainer
Behavior on implicitWidth {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
Behavior on color {
CAnim {}
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.tPalette.m3surfaceContainer
height: 22
radius: height / 2
Rectangle {
anchors.centerIn: parent
width: parent.width
height: parent.height
radius: width / 2
color: "transparent"
border.color: "#30ffffff"
border.width: 0
}
Behavior on color {
CAnim {
}
}
RowLayout {
anchors {
fill: parent
leftMargin: 10
rightMargin: 15
}
Rectangle {
anchors.centerIn: parent
border.color: "#30ffffff"
border.width: 0
color: "transparent"
height: parent.height
radius: width / 2
width: parent.width
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: Audio.muted ? "volume_off" : "volume_up"
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
}
RowLayout {
anchors {
fill: parent
leftMargin: 10
rightMargin: 15
}
Rectangle {
Layout.fillWidth: true
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: Audio.muted ? DynamicColors.palette.m3error : root.textColor
font.pixelSize: 18
text: Audio.muted ? "volume_off" : "volume_up"
}
implicitHeight: 4
radius: 20
color: "#50ffffff"
Rectangle {
Layout.fillWidth: true
color: "#50ffffff"
implicitHeight: 4
radius: 20
Rectangle {
id: sinkVolumeBar
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
Rectangle {
id: sinkVolumeBar
implicitWidth: parent.width * ( Audio.volume ?? 0 )
radius: parent.radius
color: Audio.muted ? DynamicColors.palette.m3error : root.barColor
Behavior on color {
CAnim {}
}
}
}
implicitWidth: parent.width * (Audio.volume ?? 0)
radius: parent.radius
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: Audio.sourceMuted ? "mic_off" : "mic"
color: ( Audio.sourceMuted ?? false ) ? DynamicColors.palette.m3error : root.textColor
}
Behavior on color {
CAnim {
}
}
Rectangle {
Layout.fillWidth: true
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
}
}
implicitHeight: 4
radius: 20
color: "#50ffffff"
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.textColor
font.pixelSize: 18
text: Audio.sourceMuted ? "mic_off" : "mic"
}
Rectangle {
id: sourceVolumeBar
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
Rectangle {
Layout.fillWidth: true
color: "#50ffffff"
implicitHeight: 4
radius: 20
implicitWidth: parent.width * ( Audio.sourceVolume ?? 0 )
radius: parent.radius
color: ( Audio.sourceMuted ?? false ) ? DynamicColors.palette.m3error : root.barColor
Rectangle {
id: sourceVolumeBar
Behavior on color {
CAnim {}
}
}
}
}
}
color: (Audio.sourceMuted ?? false) ? DynamicColors.palette.m3error : root.barColor
implicitWidth: parent.width * (Audio.sourceVolume ?? 0)
radius: parent.radius
Behavior on color {
CAnim {
}
}
anchors {
bottom: parent.bottom
left: parent.left
top: parent.top
}
}
}
}
}
}
+51 -50
View File
@@ -4,64 +4,65 @@ import qs.Components
import qs.Config
ShapePath {
id: root
id: root
required property Wrapper wrapper
required property bool invertBottomRounding
readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
property real ibr: invertBottomRounding ? -1 : 1
readonly property bool flatten: wrapper.height < rounding * 2
property real ibr: invertBottomRounding ? -1 : 1
required property bool invertBottomRounding
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ibr
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY * root.ibr
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: root.invertBottomRounding ? PathArc.Clockwise : PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY - root.roundingY * root.ibr
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: root.invertBottomRounding ? PathArc.Clockwise : PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY * root.ibr
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: -( root.wrapper.height - root.roundingY * 2 )
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
Behavior on fillColor {
CAnim {}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+60 -35
View File
@@ -13,24 +13,23 @@ import qs.Modules.Network
RowLayout {
id: root
anchors.fill: parent
readonly property int vPadding: 6
required property Wrapper popouts
required property PersistentProperties visibilities
required property PanelWindow bar
required property Wrapper popouts
required property ShellScreen screen
readonly property int vPadding: 6
required property PersistentProperties visibilities
function checkPopout(x: real): void {
const ch = childAt(x, 2) as WrappedLoader;
if (!ch) {
if ( !popouts.currentName.includes("traymenu") )
if (!popouts.currentName.includes("traymenu"))
popouts.hasCurrent = false;
return;
}
if ( visibilities.sidebar || visibilities.dashboard )
if (visibilities.sidebar || visibilities.dashboard)
return;
const id = ch.id;
@@ -38,49 +37,50 @@ RowLayout {
const item = ch.item;
const itemWidth = item.implicitWidth;
if (id === "audio" && Config.barConfig.popouts.audio) {
popouts.currentName = "audio";
popouts.currentCenter = Qt.binding( () => item.mapToItem(root, itemWidth / 2, 0 ).x );
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if ( id === "resources" && Config.barConfig.popouts.resources ) {
} else if (id === "resources" && Config.barConfig.popouts.resources) {
popouts.currentName = "resources";
popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if ( id === "tray" && Config.barConfig.popouts.tray ) {
const index = Math.floor((( x - top ) / item.implicitWidth ) * item.items.count );
const trayItem = item.items.itemAt( index );
if ( trayItem ) {
} else if (id === "tray" && Config.barConfig.popouts.tray) {
const index = Math.floor(((x - top) / item.implicitWidth) * item.items.count);
const trayItem = item.items.itemAt(index);
if (trayItem) {
// popouts.currentName = `traymenu${ index }`;
// popouts.currentCenter = Qt.binding( () => trayItem.mapToItem( root, trayItem.implicitWidth / 2, 0 ).x );
// popouts.hasCurrent = true;
} else {
// popouts.hasCurrent = false;
}
} else if ( id === "clock" && Config.barConfig.popouts.clock ) {
} else if (id === "clock" && Config.barConfig.popouts.clock) {
// Calendar.displayYear = new Date().getFullYear();
// Calendar.displayMonth = new Date().getMonth();
// popouts.currentName = "calendar";
// popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
// popouts.hasCurrent = true;
} else if ( id === "network" && Config.barConfig.popouts.network ) {
} else if (id === "network" && Config.barConfig.popouts.network) {
popouts.currentName = "network";
popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
} else if ( id === "upower" && Config.barConfig.popouts.upower ) {
} else if (id === "upower" && Config.barConfig.popouts.upower) {
popouts.currentName = "upower";
popouts.currentCenter = Qt.binding( () => item.mapToItem( root, itemWidth / 2, 0 ).x );
popouts.currentCenter = Qt.binding(() => item.mapToItem(root, itemWidth / 2, 0).x);
popouts.hasCurrent = true;
}
}
anchors.fill: parent
CustomShortcut {
name: "toggle-overview"
onPressed: {
Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors();
if ( root.popouts.hasCurrent && root.popouts.currentName === "overview" ) {
if (root.popouts.hasCurrent && root.popouts.currentName === "overview") {
root.popouts.hasCurrent = false;
} else {
root.popouts.currentName = "overview";
@@ -92,6 +92,7 @@ RowLayout {
Repeater {
id: repeater
model: Config.barConfig.entries
DelegateChooser {
@@ -99,67 +100,87 @@ RowLayout {
DelegateChoice {
roleValue: "spacer"
delegate: WrappedLoader {
Layout.fillWidth: true
}
}
DelegateChoice {
roleValue: "workspaces"
delegate: WrappedLoader {
sourceComponent: Workspaces {
bar: root.bar
}
}
}
DelegateChoice {
roleValue: "audio"
delegate: WrappedLoader {
sourceComponent: AudioWidget {}
sourceComponent: AudioWidget {
}
}
}
DelegateChoice {
roleValue: "tray"
delegate: WrappedLoader {
sourceComponent: TrayWidget {
bar: root.bar
popouts: root.popouts
loader: root
popouts: root.popouts
}
}
}
DelegateChoice {
roleValue: "resources"
delegate: WrappedLoader {
sourceComponent: Resources {}
sourceComponent: Resources {
}
}
}
DelegateChoice {
roleValue: "updates"
delegate: WrappedLoader {
sourceComponent: UpdatesWidget {}
sourceComponent: UpdatesWidget {
}
}
}
DelegateChoice {
roleValue: "notifBell"
delegate: WrappedLoader {
sourceComponent: NotifBell {
visibilities: root.visibilities
popouts: root.popouts
visibilities: root.visibilities
}
}
}
DelegateChoice {
roleValue: "clock"
delegate: WrappedLoader {
sourceComponent: Clock {
loader: root
popouts: root.popouts
visibilities: root.visibilities
loader: root
}
}
}
DelegateChoice {
roleValue: "activeWindow"
delegate: WrappedLoader {
sourceComponent: WindowTitle {
bar: root
@@ -167,16 +188,22 @@ RowLayout {
}
}
}
DelegateChoice {
roleValue: "upower"
delegate: WrappedLoader {
sourceComponent: UPowerWidget {}
sourceComponent: UPowerWidget {
}
}
}
DelegateChoice {
roleValue: "network"
delegate: WrappedLoader {
sourceComponent: NetworkWidget {}
sourceComponent: NetworkWidget {
}
}
}
// DelegateChoice {
@@ -195,15 +222,12 @@ RowLayout {
required property string id
required property int index
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
function findFirstEnabled(): Item {
const count = repeater.count;
for (let i = 0; i < count; i++) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
return item;
}
return null;
}
@@ -212,15 +236,16 @@ RowLayout {
for (let i = repeater.count - 1; i >= 0; i--) {
const item = repeater.itemAt(i);
if (item?.enabled)
return item;
return item;
}
return null;
}
Layout.alignment: Qt.AlignVCenter
Layout.fillHeight: true
Layout.leftMargin: findFirstEnabled() === this ? root.vPadding : 0
Layout.rightMargin: findLastEnabled() === this ? root.vPadding : 0
visible: enabled
active: enabled
visible: enabled
}
}
+30 -28
View File
@@ -8,42 +8,44 @@ import qs.Config
import qs.Components
Item {
id: root
id: root
required property Item bar
required property Item bar
required property PersistentProperties visibilities
anchors.fill: parent
anchors.fill: parent
CustomRect {
anchors.fill: parent
color: Config.barConfig.autoHide && !root.visibilities.bar ? "transparent" : DynamicColors.palette.m3surface
CustomRect {
anchors.fill: parent
color: Config.barConfig.autoHide && !root.visibilities.bar ? "transparent" : DynamicColors.palette.m3surface
layer.enabled: true
layer.enabled: true
layer.effect: MultiEffect {
maskEnabled: true
maskInverted: true
maskSource: mask
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
}
layer.effect: MultiEffect {
maskSource: mask
maskEnabled: true
maskInverted: true
maskThresholdMin: 0.5
maskSpreadAtMin: 1
}
}
Item {
id: mask
Item {
id: mask
anchors.fill: parent
layer.enabled: true
visible: false
anchors.fill: parent
layer.enabled: true
visible: false
Rectangle {
anchors.fill: parent
anchors.topMargin: Config.barConfig.autoHide && !root.visibilities.bar ? 4 : root.bar.implicitHeight
topLeftRadius: 8
topRightRadius: 8
Rectangle {
anchors.fill: parent
anchors.topMargin: Config.barConfig.autoHide && !root.visibilities.bar ? 4 : root.bar.implicitHeight
topLeftRadius: 8
topRightRadius: 8
Behavior on anchors.topMargin {
Anim {}
Anim {
}
}
}
}
}
}
}
+10 -13
View File
@@ -10,17 +10,17 @@ RowLayout {
spacing: 12
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: "transparent"
radius: 1000
MaterialIcon {
anchors.centerIn: parent
text: "arrow_back_2"
color: DynamicColors.palette.m3onSurface
fill: 1
font.pointSize: 24
color: DynamicColors.palette.m3onSurface
text: "arrow_back_2"
}
StateLayer {
@@ -36,30 +36,27 @@ RowLayout {
}
CustomText {
text: new Date(Calendar.displayYear, Calendar.displayMonth, 1).toLocaleDateString(
Qt.locale(),
"MMMM yyyy"
)
font.weight: 600
font.pointSize: 14
color: DynamicColors.palette.m3onSurface
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurface
font.pointSize: 14
font.weight: 600
horizontalAlignment: Text.AlignHCenter
text: new Date(Calendar.displayYear, Calendar.displayMonth, 1).toLocaleDateString(Qt.locale(), "MMMM yyyy")
}
Rectangle {
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Layout.preferredWidth: 40
color: "transparent"
radius: 1000
MaterialIcon {
anchors.centerIn: parent
text: "arrow_back_2"
color: DynamicColors.palette.m3onSurface
fill: 1
font.pointSize: 24
rotation: 180
color: DynamicColors.palette.m3onSurface
text: "arrow_back_2"
}
StateLayer {
+6 -6
View File
@@ -11,8 +11,8 @@ Item {
required property Item wrapper
implicitWidth: layout.childrenRect.width + layout.anchors.margins * 2
implicitHeight: layout.childrenRect.height + layout.anchors.margins * 2
implicitWidth: layout.childrenRect.width + layout.anchors.margins * 2
ColumnLayout {
id: layout
@@ -59,17 +59,17 @@ Item {
DayOfWeekRow {
id: dayOfWeekRow
locale: Qt.locale()
Layout.fillWidth: true
Layout.preferredHeight: 30
locale: Qt.locale()
}
MonthGrid {
locale: Qt.locale()
wrapper: root.wrapper
Layout.preferredWidth: childrenRect.width
Layout.preferredHeight: childrenRect.height
Layout.preferredWidth: childrenRect.width
locale: Qt.locale()
wrapper: root.wrapper
}
}
}
+26 -29
View File
@@ -7,39 +7,36 @@ import qs.Config
import qs.Helpers
RowLayout {
id: root
id: root
required property var locale
required property var locale
spacing: 4
spacing: 4
Repeater {
model: 7
Repeater {
model: 7
Item {
required property int index
Item {
readonly property string dayName: {
// Get the day name for this column
const dayIndex = (index + Calendar.weekStartDay) % 7;
return root.locale.dayName(dayIndex, Locale.ShortFormat);
}
required property int index
Layout.fillWidth: true
Layout.preferredHeight: 30
Layout.fillWidth: true
Layout.preferredHeight: 30
readonly property string dayName: {
// Get the day name for this column
const dayIndex = (index + Calendar.weekStartDay) % 7;
return root.locale.dayName(dayIndex, Locale.ShortFormat);
}
CustomText {
anchors.centerIn: parent
text: parent.dayName
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: 0.8
font.weight: 500
font.pointSize: 11
}
}
}
CustomText {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 11
font.weight: 500
horizontalAlignment: Text.AlignHCenter
opacity: 0.8
text: parent.dayName
verticalAlignment: Text.AlignVCenter
}
}
}
}
+37 -27
View File
@@ -13,48 +13,49 @@ GridLayout {
required property var locale
required property Item wrapper
columnSpacing: 4
columns: 7
rowSpacing: 4
columnSpacing: 4
uniformCellWidths: true
uniformCellHeights: true
component Anim: NumberAnimation {
target: root
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
uniformCellWidths: true
Repeater {
id: repeater
model: ScriptModel {
values: Calendar.getWeeksForMonth(Calendar.displayMonth, Calendar.displayYear)
Behavior on values {
SequentialAnimation {
id: switchAnim
ParallelAnimation {
Anim {
property: "opacity"
from: 1.0
property: "opacity"
to: 0.0
}
Anim {
property: "scale"
from: 1.0
property: "scale"
to: 0.8
}
}
PropertyAction {}
PropertyAction {
}
ParallelAnimation {
Anim {
property: "opacity"
from: 0.0
property: "opacity"
to: 1.0
}
Anim {
property: "scale"
from: 0.8
property: "scale"
to: 1.0
}
}
@@ -63,15 +64,11 @@ GridLayout {
}
Rectangle {
required property var modelData
required property int index
required property var modelData
Layout.preferredWidth: 40
Layout.preferredHeight: width
radius: 1000
Layout.preferredWidth: 40
color: {
if (modelData.isToday) {
console.log(width);
@@ -79,39 +76,52 @@ GridLayout {
}
return "transparent";
}
radius: 1000
Behavior on color {
ColorAnimation { duration: 200 }
ColorAnimation {
duration: 200
}
}
CustomText {
anchors.centerIn: parent
text: parent.modelData.day.toString()
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
opacity: parent.modelData.isCurrentMonth ? 1.0 : 0.4
color: {
if (parent.modelData.isToday) {
return DynamicColors.palette.m3onPrimaryContainer;
}
return DynamicColors.palette.m3onSurface;
}
horizontalAlignment: Text.AlignHCenter
opacity: parent.modelData.isCurrentMonth ? 1.0 : 0.4
text: parent.modelData.day.toString()
verticalAlignment: Text.AlignVCenter
Behavior on color {
ColorAnimation { duration: 200 }
ColorAnimation {
duration: 200
}
}
Behavior on opacity {
NumberAnimation { duration: 200 }
NumberAnimation {
duration: 200
}
}
}
StateLayer {
color: DynamicColors.palette.m3onSurface
onClicked: {
console.log(`Selected date: ${parent.modelData.day}/${parent.modelData.month + 1}/${parent.modelData.year}`);
}
}
}
}
component Anim: NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
target: root
}
}
+24 -24
View File
@@ -8,38 +8,38 @@ import qs.Config
import qs.Helpers
ColumnLayout {
id: root
id: root
spacing: 4
readonly property var weekNumbers: Calendar.getWeekNumbers(Calendar.displayMonth, Calendar.displayYear)
readonly property var weekNumbers: Calendar.getWeekNumbers(Calendar.displayMonth, Calendar.displayYear)
spacing: 4
Repeater {
model: ScriptModel {
Repeater {
model: ScriptModel {
values: root.weekNumbers
}
Item {
id: weekItem
Layout.preferredHeight: 40
Layout.preferredWidth: 20
Layout.alignment: Qt.AlignHCenter
Item {
id: weekItem
required property int index
required property var modelData
required property int index
required property var modelData
CustomText {
id: weekText
Layout.alignment: Qt.AlignHCenter
Layout.preferredHeight: 40
Layout.preferredWidth: 20
anchors.centerIn: parent
CustomText {
id: weekText
text: weekItem.modelData
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: 0.5
font.pointSize: 10
}
}
}
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
horizontalAlignment: Text.AlignHCenter
opacity: 0.5
text: weekItem.modelData
verticalAlignment: Text.AlignVCenter
}
}
}
}
+14 -11
View File
@@ -8,35 +8,38 @@ import qs.Components
Item {
id: root
required property PersistentProperties visibilities
required property Wrapper popouts
required property RowLayout loader
implicitWidth: timeText.contentWidth + 5 * 2
anchors.top: parent.top
anchors.bottom: parent.bottom
required property RowLayout loader
required property Wrapper popouts
required property PersistentProperties visibilities
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: timeText.contentWidth + 5 * 2
CustomRect {
anchors.bottomMargin: 3
anchors.fill: parent
anchors.topMargin: 3
anchors.bottomMargin: 3
radius: 4
color: "transparent"
radius: 4
CustomText {
id: timeText
anchors.centerIn: parent
text: Time.dateStr
color: DynamicColors.palette.m3onSurface
text: Time.dateStr
Behavior on color {
CAnim {}
CAnim {
}
}
}
StateLayer {
acceptedButtons: Qt.LeftButton
onClicked: {
root.visibilities.dashboard = !root.visibilities.dashboard;
}
+103 -100
View File
@@ -11,72 +11,75 @@ import qs.Modules.Network
import qs.Modules.UPower
Item {
id: root
id: root
required property Item wrapper
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
readonly property Item current: currentPopout?.item ?? null
readonly property Item current: currentPopout?.item ?? null
readonly property Popout currentPopout: content.children.find(c => c.shouldBeActive) ?? null
required property Item wrapper
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitHeight: (currentPopout?.implicitHeight ?? 0) + 5 * 2
implicitWidth: (currentPopout?.implicitWidth ?? 0) + 5 * 2
Item {
id: content
Item {
id: content
anchors.fill: parent
anchors.fill: parent
Popout {
name: "audio"
sourceComponent: AudioPopup {
wrapper: root.wrapper
}
}
Popout {
name: "audio"
Popout {
name: "resources"
sourceComponent: ResourcePopout {
wrapper: root.wrapper
}
}
sourceComponent: AudioPopup {
wrapper: root.wrapper
}
}
Repeater {
model: ScriptModel {
values: [ ...SystemTray.items.values ]
}
Popout {
name: "resources"
Popout {
id: trayMenu
sourceComponent: ResourcePopout {
wrapper: root.wrapper
}
}
required property SystemTrayItem modelData
required property int index
Repeater {
model: ScriptModel {
values: [...SystemTray.items.values]
}
name: `traymenu${index}`
sourceComponent: trayMenuComponent
Popout {
id: trayMenu
Connections {
target: root.wrapper
required property int index
required property SystemTrayItem modelData
function onHasCurrentChanged(): void {
if ( root.wrapper.hasCurrent && trayMenu.shouldBeActive ) {
trayMenu.sourceComponent = null;
trayMenu.sourceComponent = trayMenuComponent;
}
}
}
name: `traymenu${index}`
sourceComponent: trayMenuComponent
Component {
id: trayMenuComponent
Connections {
function onHasCurrentChanged(): void {
if (root.wrapper.hasCurrent && trayMenu.shouldBeActive) {
trayMenu.sourceComponent = null;
trayMenu.sourceComponent = trayMenuComponent;
}
}
TrayMenuPopout {
popouts: root.wrapper
trayItem: trayMenu.modelData.menu
}
}
}
}
target: root.wrapper
}
Component {
id: trayMenuComponent
TrayMenuPopout {
popouts: root.wrapper
trayItem: trayMenu.modelData.menu
}
}
}
}
Popout {
name: "calendar"
sourceComponent: CalendarPopup {
wrapper: root.wrapper
}
@@ -86,8 +89,8 @@ Item {
name: "overview"
sourceComponent: OverviewPopout {
wrapper: root.wrapper
screen: root.wrapper.screen
wrapper: root.wrapper
}
}
@@ -106,63 +109,63 @@ Item {
wrapper: root.wrapper
}
}
}
}
component Popout: Loader {
id: popout
component Popout: Loader {
id: popout
required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name
required property string name
readonly property bool shouldBeActive: root.wrapper.currentName === name
anchors.top: parent.top
anchors.topMargin: 5
anchors.horizontalCenter: parent.horizontalCenter
active: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 5
opacity: 0
scale: 0.8
opacity: 0
scale: 0.8
active: false
states: State {
name: "active"
when: popout.shouldBeActive
states: State {
name: "active"
when: popout.shouldBeActive
PropertyChanges {
popout.active: true
popout.opacity: 1
popout.scale: 1
}
}
transitions: [
Transition {
from: "active"
to: ""
PropertyChanges {
popout.active: true
popout.opacity: 1
popout.scale: 1
}
}
SequentialAnimation {
Anim {
duration: MaterialEasing.expressiveEffectsTime
properties: "opacity,scale"
}
transitions: [
Transition {
from: "active"
to: ""
PropertyAction {
property: "active"
target: popout
}
}
},
Transition {
from: ""
to: "active"
SequentialAnimation {
Anim {
properties: "opacity,scale"
duration: MaterialEasing.expressiveEffectsTime
}
PropertyAction {
target: popout
property: "active"
}
}
},
Transition {
from: ""
to: "active"
SequentialAnimation {
PropertyAction {
property: "active"
target: popout
}
SequentialAnimation {
PropertyAction {
target: popout
property: "active"
}
Anim {
properties: "opacity,scale"
}
}
}
]
}
Anim {
properties: "opacity,scale"
}
}
}
]
}
}
+4 -4
View File
@@ -9,12 +9,12 @@ CustomRect {
required property PersistentProperties visibilities
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.topMargin: 6
anchors.bottomMargin: 6
implicitWidth: 40
anchors.top: parent.top
anchors.topMargin: 6
color: DynamicColors.tPalette.m3surfaceContainer
implicitWidth: 40
radius: 1000
StateLayer {
@@ -25,7 +25,7 @@ CustomRect {
MaterialIcon {
anchors.centerIn: parent
text: "widgets"
color: DynamicColors.palette.m3onSurface
text: "widgets"
}
}
+49 -48
View File
@@ -4,62 +4,63 @@ import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
id: root
required property Wrapper wrapper
readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
Behavior on fillColor {
CAnim {
}
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
Behavior on fillColor {
CAnim {}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+58 -60
View File
@@ -8,81 +8,79 @@ import qs.Config
import qs.Components
Item {
id: root
id: root
required property PersistentProperties visibilities
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
required property PersistentProperties state
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
required property PersistentProperties visibilities
implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight
implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth
ClippingRectangle {
id: viewWrapper
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
ClippingRectangle {
id: viewWrapper
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.top: parent.top
color: "transparent"
radius: 6
radius: 6
color: "transparent"
Item {
id: view
Item {
id: view
readonly property int currentIndex: root.state.currentTab
readonly property Item currentItem: row.children[currentIndex]
readonly property int currentIndex: root.state.currentTab
readonly property Item currentItem: row.children[currentIndex]
anchors.fill: parent
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
anchors.fill: parent
RowLayout {
id: row
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
Pane {
index: 0
RowLayout {
id: row
Pane {
index: 0
sourceComponent: Dash {
state: root.state
sourceComponent: Dash {
state: root.state
visibilities: root.visibilities
}
}
}
}
}
}
}
}
}
}
Behavior on implicitWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
component Pane: Loader {
id: pane
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
required property int index
component Pane: Loader {
id: pane
Layout.alignment: Qt.AlignTop
required property int index
Layout.alignment: Qt.AlignTop
Component.onCompleted: active = Qt.binding(() => {
// Always keep current tab loaded
if (pane.index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
Component.onCompleted: active = Qt.binding(() => {
// Always keep current tab loaded
if (pane.index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
}
+86 -86
View File
@@ -9,18 +9,16 @@ import qs.Config
import qs.Modules.Dashboard.Dash
GridLayout {
id: root
id: root
required property PersistentProperties visibilities
required property PersistentProperties state
readonly property bool dashboardVisible: visibilities.dashboard
property int radius: 6
required property PersistentProperties state
required property PersistentProperties visibilities
rowSpacing: Appearance.spacing.smaller
columnSpacing: Appearance.spacing.smaller
columnSpacing: Appearance.spacing.smaller
opacity: 0
rowSpacing: Appearance.spacing.smaller
scale: 0.9
onDashboardVisibleChanged: {
@@ -33,115 +31,117 @@ GridLayout {
ParallelAnimation {
id: openAnim
Anim {
target: root
property: "opacity"
target: root
to: 1
}
Anim {
target: root
property: "scale"
target: root
to: 1
}
}
ParallelAnimation {
id: closeAnim
Anim {
target: root
property: "opacity"
target: root
to: 0
}
Anim {
target: root
property: "scale"
target: root
to: 0.9
}
}
Rect {
Layout.column: 2
Layout.columnSpan: 3
Layout.preferredWidth: user.implicitWidth
Layout.preferredHeight: user.implicitHeight
Rect {
Layout.column: 2
Layout.columnSpan: 3
Layout.preferredHeight: user.implicitHeight
Layout.preferredWidth: user.implicitWidth
radius: root.radius
radius: root.radius
User {
id: user
User {
id: user
state: root.state
}
}
state: root.state
}
}
Rect {
Layout.row: 0
Layout.columnSpan: 2
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth
Layout.fillHeight: true
radius: root.radius
Weather {}
}
// Rect {
// Layout.row: 1
// Layout.preferredWidth: dateTime.implicitWidth
// Layout.fillHeight: true
//
// radius: root.radius
//
// DateTime {
// id: dateTime
// }
// }
Rect {
Layout.row: 1
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.preferredHeight: calendar.implicitHeight
radius: root.radius
Calendar {
id: calendar
state: root.state
}
}
Rect {
Layout.row: 1
Layout.column: 3
Rect {
Layout.columnSpan: 2
Layout.preferredWidth: resources.implicitWidth
Layout.fillHeight: true
Layout.fillHeight: true
Layout.preferredWidth: Config.dashboard.sizes.weatherWidth
Layout.row: 0
radius: root.radius
radius: root.radius
Weather {
}
}
Resources {
id: resources
}
}
// Rect {
// Layout.row: 1
// Layout.preferredWidth: dateTime.implicitWidth
// Layout.fillHeight: true
//
// radius: root.radius
//
// DateTime {
// id: dateTime
// }
// }
Rect {
Layout.row: 0
Layout.column: 5
Layout.rowSpan: 2
Layout.preferredWidth: media.implicitWidth
Layout.fillHeight: true
Rect {
Layout.column: 0
Layout.columnSpan: 3
Layout.fillWidth: true
Layout.preferredHeight: calendar.implicitHeight
Layout.row: 1
radius: root.radius
radius: root.radius
Calendar {
id: calendar
Media {
id: media
}
}
state: root.state
}
}
component Rect: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
}
Rect {
Layout.column: 3
Layout.columnSpan: 2
Layout.fillHeight: true
Layout.preferredWidth: resources.implicitWidth
Layout.row: 1
radius: root.radius
Resources {
id: resources
}
}
Rect {
Layout.column: 5
Layout.fillHeight: true
Layout.preferredWidth: media.implicitWidth
Layout.row: 0
Layout.rowSpan: 2
radius: root.radius
Media {
id: media
}
}
component Rect: CustomRect {
color: DynamicColors.tPalette.m3surfaceContainer
}
}
+184 -196
View File
@@ -9,244 +9,232 @@ import qs.Config
import qs.Modules
CustomMouseArea {
id: root
id: root
required property var state
readonly property int currMonth: state.currentDate.getMonth()
readonly property int currYear: state.currentDate.getFullYear()
required property var state
readonly property int currMonth: state.currentDate.getMonth()
readonly property int currYear: state.currentDate.getFullYear()
function onWheel(event: WheelEvent): void {
if (event.angleDelta.y > 0)
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
else if (event.angleDelta.y < 0)
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: inner.implicitHeight + inner.anchors.margins * 2
acceptedButtons: Qt.MiddleButton
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: inner.implicitHeight + inner.anchors.margins * 2
acceptedButtons: Qt.MiddleButton
onClicked: root.state.currentDate = new Date()
onClicked: root.state.currentDate = new Date()
function onWheel(event: WheelEvent): void {
if (event.angleDelta.y > 0)
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
else if (event.angleDelta.y < 0)
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
ColumnLayout {
id: inner
ColumnLayout {
id: inner
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.small
RowLayout {
id: monthNavigationRow
RowLayout {
id: monthNavigationRow
Layout.fillWidth: true
spacing: Appearance.spacing.small
Layout.fillWidth: true
spacing: Appearance.spacing.small
Item {
implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
Item {
implicitWidth: implicitHeight
implicitHeight: prevMonthText.implicitHeight + Appearance.padding.small * 2
StateLayer {
id: prevMonthStateLayer
StateLayer {
id: prevMonthStateLayer
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
}
radius: Appearance.rounding.full
radius: Appearance.rounding.full
}
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth - 1, 1);
}
}
MaterialIcon {
id: prevMonthText
MaterialIcon {
id: prevMonthText
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_left"
}
}
anchors.centerIn: parent
text: "chevron_left"
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
}
}
Item {
Layout.fillWidth: true
implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2
implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2
Item {
Layout.fillWidth: true
StateLayer {
function onClicked(): void {
root.state.currentDate = new Date();
}
implicitWidth: monthYearDisplay.implicitWidth + Appearance.padding.small * 2
implicitHeight: monthYearDisplay.implicitHeight + Appearance.padding.small * 2
anchors.fill: monthYearDisplay
anchors.leftMargin: -Appearance.padding.normal
anchors.margins: -Appearance.padding.small
anchors.rightMargin: -Appearance.padding.normal
disabled: {
const now = new Date();
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
}
radius: Appearance.rounding.full
}
StateLayer {
anchors.fill: monthYearDisplay
anchors.margins: -Appearance.padding.small
anchors.leftMargin: -Appearance.padding.normal
anchors.rightMargin: -Appearance.padding.normal
CustomText {
id: monthYearDisplay
radius: Appearance.rounding.full
disabled: {
const now = new Date();
return root.currMonth === now.getMonth() && root.currYear === now.getFullYear();
}
anchors.centerIn: parent
color: DynamicColors.palette.m3primary
font.capitalization: Font.Capitalize
font.pointSize: Appearance.font.size.normal
font.weight: 500
text: grid.title
}
}
function onClicked(): void {
root.state.currentDate = new Date();
}
}
Item {
implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
CustomText {
id: monthYearDisplay
StateLayer {
id: nextMonthStateLayer
anchors.centerIn: parent
text: grid.title
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.normal
font.weight: 500
font.capitalization: Font.Capitalize
}
}
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
Item {
implicitWidth: implicitHeight
implicitHeight: nextMonthText.implicitHeight + Appearance.padding.small * 2
radius: Appearance.rounding.full
}
StateLayer {
id: nextMonthStateLayer
MaterialIcon {
id: nextMonthText
radius: Appearance.rounding.full
anchors.centerIn: parent
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
text: "chevron_right"
}
}
}
function onClicked(): void {
root.state.currentDate = new Date(root.currYear, root.currMonth + 1, 1);
}
}
DayOfWeekRow {
id: daysRow
MaterialIcon {
id: nextMonthText
Layout.fillWidth: true
locale: grid.locale
anchors.centerIn: parent
text: "chevron_right"
color: DynamicColors.palette.m3tertiary
font.pointSize: Appearance.font.size.normal
font.weight: 700
}
}
}
delegate: CustomText {
required property var model
DayOfWeekRow {
id: daysRow
color: (model.day === 0) ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurfaceVariant
font.weight: 500
horizontalAlignment: Text.AlignHCenter
text: model.shortName
}
}
Layout.fillWidth: true
locale: grid.locale
Item {
Layout.fillWidth: true
implicitHeight: grid.implicitHeight
delegate: CustomText {
required property var model
MonthGrid {
id: grid
horizontalAlignment: Text.AlignHCenter
text: model.shortName
font.weight: 500
color: (model.day === 0) ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurfaceVariant
}
}
anchors.fill: parent
locale: Qt.locale("en_SE")
month: root.currMonth
spacing: 3
year: root.currYear
Item {
Layout.fillWidth: true
implicitHeight: grid.implicitHeight
delegate: Item {
id: dayItem
MonthGrid {
id: grid
required property var model
month: root.currMonth
year: root.currYear
implicitHeight: text.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
anchors.fill: parent
CustomText {
id: text
spacing: 3
locale: Qt.locale("en_SE")
anchors.centerIn: parent
color: {
const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 6)
return DynamicColors.palette.m3secondary;
delegate: Item {
id: dayItem
return DynamicColors.palette.m3onSurfaceVariant;
}
font.pointSize: Appearance.font.size.normal
font.weight: 500
horizontalAlignment: Text.AlignHCenter
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
text: grid.locale.toString(dayItem.model.day)
}
}
}
required property var model
CustomRect {
id: todayIndicator
implicitWidth: implicitHeight
implicitHeight: text.implicitHeight + Appearance.padding.small * 2
property Item today
readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null
CustomText {
id: text
clip: true
color: DynamicColors.palette.m3primary
implicitHeight: today?.implicitHeight ?? 0
implicitWidth: today?.implicitWidth ?? 0
opacity: todayItem ? 1 : 0
radius: Appearance.rounding.full
scale: todayItem ? 1 : 0.7
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
anchors.centerIn: parent
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
horizontalAlignment: Text.AlignHCenter
text: grid.locale.toString(dayItem.model.day)
color: {
const dayOfWeek = dayItem.model.date.getUTCDay();
if (dayOfWeek === 6)
return DynamicColors.palette.m3secondary;
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
return DynamicColors.palette.m3onSurfaceVariant;
}
opacity: dayItem.model.today || dayItem.model.month === grid.month ? 1 : 0.4
font.pointSize: Appearance.font.size.normal
font.weight: 500
}
}
}
CustomRect {
id: todayIndicator
readonly property Item todayItem: grid.contentItem.children.find(c => c.model.today) ?? null
property Item today
onTodayItemChanged: {
if (todayItem)
today = todayItem;
}
x: today ? today.x + (today.width - implicitWidth) / 2 : 0
y: today?.y ?? 0
implicitWidth: today?.implicitWidth ?? 0
implicitHeight: today?.implicitHeight ?? 0
clip: true
radius: Appearance.rounding.full
color: DynamicColors.palette.m3primary
opacity: todayItem ? 1 : 0
scale: todayItem ? 1 : 0.7
Coloriser {
x: -todayIndicator.x
y: -todayIndicator.y
implicitWidth: grid.width
implicitHeight: grid.height
source: grid
sourceColor: DynamicColors.palette.m3onSurface
colorizationColor: DynamicColors.palette.m3onPrimary
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on x {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
}
}
Coloriser {
colorizationColor: DynamicColors.palette.m3onPrimary
implicitHeight: grid.height
implicitWidth: grid.width
source: grid
sourceColor: DynamicColors.palette.m3onSurface
x: -todayIndicator.x
y: -todayIndicator.y
}
}
}
}
}
+35 -35
View File
@@ -7,44 +7,44 @@ import qs.Config
import qs.Helpers
Item {
id: root
id: root
anchors.top: parent.top
anchors.bottom: parent.bottom
implicitWidth: 110
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: 110
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 0
CustomText {
Layout.bottomMargin: -(font.pointSize * 0.4)
Layout.alignment: Qt.AlignHCenter
text: Time.hourStr
color: DynamicColors.palette.m3secondary
font.pointSize: 18
font.family: "Rubik"
font.weight: 600
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -(font.pointSize * 0.4)
color: DynamicColors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: "•••"
color: DynamicColors.palette.m3primary
font.pointSize: 18 * 0.9
font.family: "Rubik"
}
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3primary
font.family: "Rubik"
font.pointSize: 18 * 0.9
text: "•••"
}
CustomText {
Layout.topMargin: -(font.pointSize * 0.4)
Layout.alignment: Qt.AlignHCenter
text: Time.minuteStr
color: DynamicColors.palette.m3secondary
font.pointSize: 18
font.family: "Rubik"
font.weight: 600
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -(font.pointSize * 0.4)
color: DynamicColors.palette.m3secondary
font.family: "Rubik"
font.pointSize: 18
font.weight: 600
text: Time.minuteStr
}
}
}
+179 -186
View File
@@ -9,230 +9,223 @@ import qs.Modules
import qs.Paths
Item {
id: root
id: root
property real playerProgress: {
const active = Players.active;
return active?.length ? active.position / active.length : 0;
}
property real playerProgress: {
const active = Players.active;
return active?.length ? active.position / active.length : 0;
}
anchors.top: parent.top
anchors.bottom: parent.bottom
implicitWidth: Config.dashboard.sizes.mediaWidth
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: Config.dashboard.sizes.mediaWidth
Behavior on playerProgress {
Anim {
duration: Appearance.anim.durations.large
}
}
Behavior on playerProgress {
Anim {
duration: Appearance.anim.durations.large
}
}
Timer {
running: Players.active?.isPlaying ?? false
interval: Config.dashboard.mediaUpdateInterval
triggeredOnStart: true
repeat: true
onTriggered: Players.active?.positionChanged()
}
Timer {
interval: Config.dashboard.mediaUpdateInterval
repeat: true
running: Players.active?.isPlaying ?? false
triggeredOnStart: true
ServiceRef {
service: Audio.beatTracker
}
onTriggered: Players.active?.positionChanged()
}
Shape {
preferredRendererType: Shape.CurveRenderer
ServiceRef {
service: Audio.beatTracker
}
ShapePath {
fillColor: "transparent"
strokeColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
Shape {
preferredRendererType: Shape.CurveRenderer
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep
}
ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
Behavior on strokeColor {
CAnim {}
}
}
Behavior on strokeColor {
CAnim {
}
}
ShapePath {
fillColor: "transparent"
strokeColor: DynamicColors.palette.m3primary
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep
}
}
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep * root.playerProgress
}
ShapePath {
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
fillColor: "transparent"
strokeColor: DynamicColors.palette.m3primary
strokeWidth: Config.dashboard.sizes.mediaProgressThickness
Behavior on strokeColor {
CAnim {}
}
}
}
Behavior on strokeColor {
CAnim {
}
}
CustomClippingRect {
id: cover
PathAngleArc {
centerX: cover.x + cover.width / 2
centerY: cover.y + cover.height / 2
radiusX: (cover.width + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
radiusY: (cover.height + Config.dashboard.sizes.mediaProgressThickness) / 2 + Appearance.spacing.small
startAngle: -90 - Config.dashboard.sizes.mediaProgressSweep / 2
sweepAngle: Config.dashboard.sizes.mediaProgressSweep * root.playerProgress
}
}
}
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large + Config.dashboard.sizes.mediaProgressThickness + Appearance.spacing.small
CustomClippingRect {
id: cover
implicitHeight: width
color: DynamicColors.tPalette.m3surfaceContainerHigh
radius: Infinity
anchors.left: parent.left
anchors.margins: Appearance.padding.large + Config.dashboard.sizes.mediaProgressThickness + Appearance.spacing.small
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainerHigh
implicitHeight: width
radius: Infinity
MaterialIcon {
anchors.centerIn: parent
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: (parent.width * 0.4) || 1
grade: 200
text: "art_track"
}
grade: 200
text: "art_track"
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: (parent.width * 0.4) || 1
}
Image {
id: image
Image {
id: image
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
source: Players.active?.trackArtUrl ?? ""
sourceSize.height: height
sourceSize.width: width
}
}
anchors.fill: parent
CustomText {
id: title
source: Players.active?.trackArtUrl ?? ""
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize.width: width
sourceSize.height: height
}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: cover.bottom
anchors.topMargin: Appearance.spacing.normal
animate: true
color: DynamicColors.palette.m3primary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
width: parent.implicitWidth - Appearance.padding.large * 2
}
CustomText {
id: title
CustomText {
id: album
anchors.top: cover.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Appearance.spacing.normal
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: title.bottom
anchors.topMargin: Appearance.spacing.small
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
width: parent.implicitWidth - Appearance.padding.large * 2
}
animate: true
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackTitle ?? qsTr("No media")) || qsTr("Unknown title")
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.normal
CustomText {
id: artist
width: parent.implicitWidth - Appearance.padding.large * 2
elide: Text.ElideRight
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: album.bottom
anchors.topMargin: Appearance.spacing.small
animate: true
color: DynamicColors.palette.m3secondary
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
width: parent.implicitWidth - Appearance.padding.large * 2
}
CustomText {
id: album
Row {
id: controls
anchors.top: title.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Appearance.spacing.small
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: artist.bottom
anchors.topMargin: Appearance.spacing.smaller
spacing: Appearance.spacing.small
animate: true
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackAlbum ?? qsTr("No media")) || qsTr("Unknown album")
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
Control {
function onClicked(): void {
Players.active?.previous();
}
width: parent.implicitWidth - Appearance.padding.large * 2
elide: Text.ElideRight
}
canUse: Players.active?.canGoPrevious ?? false
icon: "skip_previous"
}
CustomText {
id: artist
Control {
function onClicked(): void {
Players.active?.togglePlaying();
}
anchors.top: album.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Appearance.spacing.small
canUse: Players.active?.canTogglePlaying ?? false
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
}
animate: true
horizontalAlignment: Text.AlignHCenter
text: (Players.active?.trackArtist ?? qsTr("No media")) || qsTr("Unknown artist")
color: DynamicColors.palette.m3secondary
Control {
function onClicked(): void {
Players.active?.next();
}
width: parent.implicitWidth - Appearance.padding.large * 2
elide: Text.ElideRight
}
canUse: Players.active?.canGoNext ?? false
icon: "skip_next"
}
}
Row {
id: controls
component Control: CustomRect {
id: control
anchors.top: artist.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: Appearance.spacing.smaller
required property bool canUse
required property string icon
spacing: Appearance.spacing.small
function onClicked(): void {
}
Control {
icon: "skip_previous"
canUse: Players.active?.canGoPrevious ?? false
implicitHeight: implicitWidth
implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + Appearance.padding.small
function onClicked(): void {
Players.active?.previous();
}
}
StateLayer {
function onClicked(): void {
control.onClicked();
}
Control {
icon: Players.active?.isPlaying ? "pause" : "play_arrow"
canUse: Players.active?.canTogglePlaying ?? false
disabled: !control.canUse
radius: Appearance.rounding.full
}
function onClicked(): void {
Players.active?.togglePlaying();
}
}
MaterialIcon {
id: icon
Control {
icon: "skip_next"
canUse: Players.active?.canGoNext ?? false
function onClicked(): void {
Players.active?.next();
}
}
}
component Control: CustomRect {
id: control
required property string icon
required property bool canUse
function onClicked(): void {
}
implicitWidth: Math.max(icon.implicitHeight, icon.implicitHeight) + Appearance.padding.small
implicitHeight: implicitWidth
StateLayer {
disabled: !control.canUse
radius: Appearance.rounding.full
function onClicked(): void {
control.onClicked();
}
}
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.verticalCenterOffset: font.pointSize * 0.05
animate: true
text: control.icon
color: control.canUse ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
}
}
anchors.centerIn: parent
anchors.verticalCenterOffset: font.pointSize * 0.05
animate: true
color: control.canUse ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.large
text: control.icon
}
}
}
+70 -75
View File
@@ -4,95 +4,90 @@ import qs.Helpers
import qs.Config
Row {
id: root
id: root
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.top: parent.top
padding: Appearance.padding.large
spacing: Appearance.spacing.large
padding: Appearance.padding.large
spacing: Appearance.spacing.large
Ref {
service: SystemUsage
}
Ref {
service: SystemUsage
}
Resource {
color: DynamicColors.palette.m3primary
icon: "memory"
value: SystemUsage.cpuPerc
}
Resource {
icon: "memory"
value: SystemUsage.cpuPerc
color: DynamicColors.palette.m3primary
}
Resource {
color: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: SystemUsage.memPerc
}
Resource {
icon: "memory_alt"
value: SystemUsage.memPerc
color: DynamicColors.palette.m3secondary
}
Resource {
color: DynamicColors.palette.m3tertiary
icon: "gamepad"
value: SystemUsage.gpuPerc
}
Resource {
icon: "gamepad"
value: SystemUsage.gpuPerc
color: DynamicColors.palette.m3tertiary
}
Resource {
color: DynamicColors.palette.m3primary
icon: "host"
value: SystemUsage.gpuMemUsed
}
Resource {
icon: "host"
value: SystemUsage.gpuMemUsed
color: DynamicColors.palette.m3primary
}
Resource {
color: DynamicColors.palette.m3secondary
icon: "hard_disk"
value: SystemUsage.storagePerc
}
Resource {
icon: "hard_disk"
value: SystemUsage.storagePerc
color: DynamicColors.palette.m3secondary
}
component Resource: Item {
id: res
component Resource: Item {
id: res
required property color color
required property string icon
required property real value
required property string icon
required property real value
required property color color
anchors.bottom: parent.bottom
anchors.margins: Appearance.padding.large
anchors.top: parent.top
implicitWidth: icon.implicitWidth
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: Appearance.padding.large
implicitWidth: icon.implicitWidth
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
CustomRect {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.bottom: icon.top
anchors.bottomMargin: Appearance.spacing.small
CustomRect {
anchors.bottom: icon.top
anchors.bottomMargin: Appearance.spacing.small
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
radius: Appearance.rounding.full
implicitWidth: Config.dashboard.sizes.resourceProgessThickness
CustomRect {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
color: res.color
implicitHeight: res.value * parent.height
radius: Appearance.rounding.full
}
}
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
radius: Appearance.rounding.full
MaterialIcon {
id: icon
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
implicitHeight: res.value * parent.height
color: res.color
radius: Appearance.rounding.full
}
}
MaterialIcon {
id: icon
anchors.bottom: parent.bottom
text: res.icon
color: res.color
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
}
anchors.bottom: parent.bottom
color: res.color
text: res.icon
}
}
}
+88 -94
View File
@@ -7,122 +7,116 @@ import Quickshell
import QtQuick
Row {
id: root
id: root
required property PersistentProperties state
required property PersistentProperties state
padding: 20
spacing: 12
padding: 20
spacing: 12
CustomClippingRect {
implicitWidth: info.implicitHeight
implicitHeight: info.implicitHeight
CustomClippingRect {
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: info.implicitHeight
implicitWidth: info.implicitHeight
radius: 8
radius: 8
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
MaterialIcon {
anchors.centerIn: parent
fill: 1
font.pointSize: Math.floor(info.implicitHeight / 2) || 1
grade: 200
text: "person"
}
MaterialIcon {
anchors.centerIn: parent
CachingImage {
id: pfp
text: "person"
fill: 1
grade: 200
font.pointSize: Math.floor(info.implicitHeight / 2) || 1
}
anchors.fill: parent
path: `${Paths.home}/.face`
}
}
CachingImage {
id: pfp
Column {
id: info
anchors.fill: parent
path: `${Paths.home}/.face`
}
}
anchors.verticalCenter: parent.verticalCenter
spacing: 12
Column {
id: info
Item {
id: line
anchors.verticalCenter: parent.verticalCenter
spacing: 12
implicitHeight: Math.max(icon.implicitHeight, text.implicitHeight)
implicitWidth: icon.implicitWidth + text.width + text.anchors.leftMargin
Item {
id: line
ColoredIcon {
id: icon
implicitWidth: icon.implicitWidth + text.width + text.anchors.leftMargin
implicitHeight: Math.max(icon.implicitHeight, text.implicitHeight)
anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
color: DynamicColors.palette.m3primary
implicitSize: Math.floor(13 * 1.34)
source: SystemInfo.osLogo
}
ColoredIcon {
id: icon
CustomText {
id: text
anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
anchors.left: icon.right
anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideRight
font.pointSize: 13
text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}`
width: Config.dashboard.sizes.infoWidth
}
}
source: SystemInfo.osLogo
implicitSize: Math.floor(13 * 1.34)
color: DynamicColors.palette.m3primary
}
InfoLine {
colour: DynamicColors.palette.m3secondary
icon: "select_window_2"
text: SystemInfo.wm
}
CustomText {
id: text
InfoLine {
id: uptime
anchors.verticalCenter: icon.verticalCenter
anchors.left: icon.right
anchors.leftMargin: icon.anchors.leftMargin
text: `: ${SystemInfo.osPrettyName || SystemInfo.osName}`
font.pointSize: 13
colour: DynamicColors.palette.m3tertiary
icon: "timer"
text: qsTr("up %1").arg(SystemInfo.uptime)
}
}
width: Config.dashboard.sizes.infoWidth
elide: Text.ElideRight
}
}
component InfoLine: Item {
id: line
InfoLine {
icon: "select_window_2"
text: SystemInfo.wm
colour: DynamicColors.palette.m3secondary
}
required property color colour
required property string icon
required property string text
InfoLine {
id: uptime
implicitHeight: Math.max(icon.implicitHeight, text.implicitHeight)
implicitWidth: icon.implicitWidth + text.width + text.anchors.leftMargin
icon: "timer"
text: qsTr("up %1").arg(SystemInfo.uptime)
colour: DynamicColors.palette.m3tertiary
}
}
MaterialIcon {
id: icon
component InfoLine: Item {
id: line
anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
color: line.colour
fill: 1
font.pointSize: 13
text: line.icon
}
required property string icon
required property string text
required property color colour
CustomText {
id: text
implicitWidth: icon.implicitWidth + text.width + text.anchors.leftMargin
implicitHeight: Math.max(icon.implicitHeight, text.implicitHeight)
MaterialIcon {
id: icon
anchors.left: parent.left
anchors.leftMargin: (Config.dashboard.sizes.infoIconSize - implicitWidth) / 2
fill: 1
text: line.icon
color: line.colour
font.pointSize: 13
}
CustomText {
id: text
anchors.verticalCenter: icon.verticalCenter
anchors.left: icon.right
anchors.leftMargin: icon.anchors.leftMargin
text: `: ${line.text}`
font.pointSize: 13
width: Config.dashboard.sizes.infoWidth
elide: Text.ElideRight
}
}
anchors.left: icon.right
anchors.leftMargin: icon.anchors.leftMargin
anchors.verticalCenter: icon.verticalCenter
elide: Text.ElideRight
font.pointSize: 13
text: `: ${line.text}`
width: Config.dashboard.sizes.infoWidth
}
}
}
+34 -40
View File
@@ -4,53 +4,47 @@ import qs.Components
import qs.Config
Item {
id: root
id: root
anchors.centerIn: parent
anchors.centerIn: parent
implicitWidth: icon.implicitWidth + info.implicitWidth + info.anchors.leftMargin
implicitWidth: icon.implicitWidth + info.implicitWidth + info.anchors.leftMargin
Component.onCompleted: Weather.reload()
Component.onCompleted: Weather.reload()
MaterialIcon {
id: icon
MaterialIcon {
id: icon
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
animate: true
color: DynamicColors.palette.m3secondary
font.pointSize: 54
text: Weather.icon
}
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
Column {
id: info
animate: true
text: Weather.icon
color: DynamicColors.palette.m3secondary
font.pointSize: 54
}
Column {
id: info
anchors.verticalCenter: parent.verticalCenter
anchors.left: icon.right
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.large
anchors.verticalCenter: parent.verticalCenter
spacing: 8
spacing: 8
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
text: Weather.temp
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
text: Weather.temp
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
text: Weather.description
elide: Text.ElideRight
width: Math.min(implicitWidth, root.parent.width - icon.implicitWidth - info.anchors.leftMargin - 24 * 2)
}
}
CustomText {
anchors.horizontalCenter: parent.horizontalCenter
animate: true
elide: Text.ElideRight
text: Weather.description
width: Math.min(implicitWidth, root.parent.width - icon.implicitWidth - info.anchors.leftMargin - 24 * 2)
}
}
}
+182 -186
View File
@@ -10,237 +10,233 @@ import QtQuick
import QtQuick.Controls
Item {
id: root
id: root
required property real nonAnimWidth
required property PersistentProperties state
readonly property alias count: bar.count
readonly property alias count: bar.count
required property real nonAnimWidth
required property PersistentProperties state
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
TabBar {
id: bar
TabBar {
id: bar
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
background: null
currentIndex: root.state.currentTab
currentIndex: root.state.currentTab
background: null
onCurrentIndexChanged: root.state.currentTab = currentIndex
onCurrentIndexChanged: root.state.currentTab = currentIndex
Tab {
iconName: "dashboard"
text: qsTr("Dashboard")
}
Tab {
iconName: "dashboard"
text: qsTr("Dashboard")
}
Tab {
iconName: "queue_music"
text: qsTr("Media")
}
Tab {
iconName: "queue_music"
text: qsTr("Media")
}
Tab {
iconName: "speed"
text: qsTr("Performance")
}
Tab {
iconName: "speed"
text: qsTr("Performance")
}
Tab {
iconName: "cloud"
text: qsTr("Weather")
}
Tab {
iconName: "cloud"
text: qsTr("Weather")
}
// Tab {
// iconName: "workspaces"
// text: qsTr("Workspaces")
// }
}
// Tab {
// iconName: "workspaces"
// text: qsTr("Workspaces")
// }
}
Item {
id: indicator
Item {
id: indicator
anchors.top: bar.bottom
clip: true
implicitHeight: 40
implicitWidth: bar.currentItem.implicitWidth
x: {
const tab = bar.currentItem;
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
}
anchors.top: bar.bottom
Behavior on implicitWidth {
Anim {
}
}
Behavior on x {
Anim {
}
}
implicitWidth: bar.currentItem.implicitWidth
implicitHeight: 40
CustomRect {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.palette.m3primary
implicitHeight: parent.implicitHeight * 2
radius: 1000
}
}
x: {
const tab = bar.currentItem;
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
}
CustomRect {
id: separator
clip: true
anchors.left: parent.left
anchors.right: parent.right
anchors.top: indicator.bottom
color: DynamicColors.palette.m3outlineVariant
implicitHeight: 1
}
CustomRect {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: parent.implicitHeight * 2
component Tab: TabButton {
id: tab
color: DynamicColors.palette.m3primary
radius: 1000
}
readonly property bool current: TabBar.tabBar.currentItem === this
required property string iconName
Behavior on x {
Anim {}
}
background: null
Behavior on implicitWidth {
Anim {}
}
}
contentItem: CustomMouseArea {
id: mouse
CustomRect {
id: separator
function onWheel(event: WheelEvent): void {
if (event.angleDelta.y < 0)
root.state.currentTab = Math.min(root.state.currentTab + 1, bar.count - 1);
else if (event.angleDelta.y > 0)
root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
}
anchors.top: indicator.bottom
anchors.left: parent.left
anchors.right: parent.right
cursorShape: Qt.PointingHandCursor
implicitHeight: icon.height + label.height
implicitWidth: Math.max(icon.width, label.width)
implicitHeight: 1
color: DynamicColors.palette.m3outlineVariant
}
onPressed: event => {
root.state.currentTab = tab.TabBar.index;
component Tab: TabButton {
id: tab
const stateY = stateWrapper.y;
rippleAnim.x = event.x;
rippleAnim.y = event.y - stateY;
required property string iconName
readonly property bool current: TabBar.tabBar.currentItem === this
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y)));
background: null
rippleAnim.restart();
}
contentItem: CustomMouseArea {
id: mouse
SequentialAnimation {
id: rippleAnim
implicitWidth: Math.max(icon.width, label.width)
implicitHeight: icon.height + label.height
property real radius
property real x
property real y
cursorShape: Qt.PointingHandCursor
PropertyAction {
property: "x"
target: ripple
value: rippleAnim.x
}
onPressed: event => {
root.state.currentTab = tab.TabBar.index;
PropertyAction {
property: "y"
target: ripple
value: rippleAnim.y
}
const stateY = stateWrapper.y;
rippleAnim.x = event.x;
rippleAnim.y = event.y - stateY;
PropertyAction {
property: "opacity"
target: ripple
value: 0.08
}
const dist = (ox, oy) => ox * ox + oy * oy;
rippleAnim.radius = Math.sqrt(Math.max(dist(event.x, event.y + stateY), dist(event.x, stateWrapper.height - event.y), dist(width - event.x, event.y + stateY), dist(width - event.x, stateWrapper.height - event.y)));
rippleAnim.restart();
}
function onWheel(event: WheelEvent): void {
if (event.angleDelta.y < 0)
root.state.currentTab = Math.min(root.state.currentTab + 1, bar.count - 1);
else if (event.angleDelta.y > 0)
root.state.currentTab = Math.max(root.state.currentTab - 1, 0);
}
SequentialAnimation {
id: rippleAnim
property real x
property real y
property real radius
PropertyAction {
target: ripple
property: "x"
value: rippleAnim.x
}
PropertyAction {
target: ripple
property: "y"
value: rippleAnim.y
}
PropertyAction {
target: ripple
property: "opacity"
value: 0.08
}
Anim {
target: ripple
properties: "implicitWidth,implicitHeight"
from: 0
to: rippleAnim.radius * 2
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Anim {
target: ripple
property: "opacity"
to: 0
easing.type: Easing.BezierSpline
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
properties: "implicitWidth,implicitHeight"
target: ripple
to: rippleAnim.radius * 2
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
property: "opacity"
target: ripple
to: 0
}
}
ClippingRectangle {
id: stateWrapper
ClippingRectangle {
id: stateWrapper
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitHeight: parent.height + 8 * 2
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: "transparent"
implicitHeight: parent.height + 8 * 2
radius: 8
color: "transparent"
radius: 8
CustomRect {
id: stateLayer
CustomRect {
id: stateLayer
anchors.fill: parent
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
anchors.fill: parent
Behavior on opacity {
Anim {
}
}
}
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: mouse.pressed ? 0.1 : tab.hovered ? 0.08 : 0
CustomRect {
id: ripple
Behavior on opacity {
Anim {}
}
}
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: 0
radius: 1000
CustomRect {
id: ripple
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
}
radius: 1000
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurface
opacity: 0
MaterialIcon {
id: icon
transform: Translate {
x: -ripple.width / 2
y: -ripple.height / 2
}
}
}
anchors.bottom: label.top
anchors.horizontalCenter: parent.horizontalCenter
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
fill: tab.current ? 1 : 0
font.pointSize: 18
text: tab.iconName
MaterialIcon {
id: icon
Behavior on fill {
Anim {
}
}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: label.top
CustomText {
id: label
text: tab.iconName
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
fill: tab.current ? 1 : 0
font.pointSize: 18
Behavior on fill {
Anim {}
}
}
CustomText {
id: label
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: tab.text
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
}
}
}
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
color: tab.current ? DynamicColors.palette.m3primary : DynamicColors.palette.m3onSurfaceVariant
text: tab.text
}
}
}
}
+65 -67
View File
@@ -6,85 +6,83 @@ import qs.Components
import qs.Config
Item {
id: root
id: root
required property PersistentProperties visibilities
readonly property PersistentProperties dashState: PersistentProperties {
property int currentTab
property date currentDate: new Date()
readonly property PersistentProperties dashState: PersistentProperties {
property date currentDate: new Date()
property int currentTab
reloadableId: "dashboardState"
}
reloadableId: "dashboardState"
}
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
required property PersistentProperties visibilities
readonly property real nonAnimHeight: state === "visible" ? (content.item?.nonAnimHeight ?? 0) : 0
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
visible: height > 0
implicitHeight: 0
implicitWidth: content.implicitWidth
states: State {
name: "visible"
when: root.visibilities.dashboard && Config.dashboard.enabled
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
states: State {
name: "visible"
when: root.visibilities.dashboard && Config.dashboard.enabled
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
]
transitions: [
Transition {
from: ""
to: "visible"
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
Anim {
target: root
property: "implicitHeight"
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
},
Transition {
from: "visible"
to: ""
Timer {
id: timer
Anim {
target: root
property: "implicitHeight"
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
]
interval: Appearance.anim.durations.extraLarge
running: true
Timer {
id: timer
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.dashboard && Config.dashboard.enabled) || root.visible);
content.visible = true;
}
}
running: true
interval: Appearance.anim.durations.extraLarge
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.dashboard && Config.dashboard.enabled) || root.visible);
content.visible = true;
}
}
Loader {
id: content
Loader {
id: content
active: true
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
visible: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
visible: false
active: true
sourceComponent: Content {
visibilities: root.visibilities
state: root.dashState
}
}
sourceComponent: Content {
state: root.dashState
visibilities: root.visibilities
}
}
}
+176 -180
View File
@@ -8,211 +8,207 @@ import qs.Components
import qs.Config
CustomListView {
id: root
id: root
required property CustomTextField search
required property PersistentProperties visibilities
required property CustomTextField search
required property PersistentProperties visibilities
model: ScriptModel {
id: model
highlightFollowsCurrentItem: false
highlightRangeMode: ListView.ApplyRange
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
orientation: Qt.Vertical
preferredHighlightBegin: 0
preferredHighlightEnd: height
spacing: Appearance.spacing.small
state: {
const text = search.text;
const prefix = Config.launcher.actionPrefix;
if (text.startsWith(prefix)) {
for (const action of ["calc", "scheme", "variant"])
if (text.startsWith(`${prefix}${action} `))
return action;
onValuesChanged: root.currentIndex = 0
}
return "actions";
}
return "apps";
}
verticalLayoutDirection: ListView.BottomToTop
spacing: Appearance.spacing.small
orientation: Qt.Vertical
implicitHeight: (Config.launcher.sizes.itemHeight + spacing) * Math.min(Config.launcher.maxAppsShown, count) - spacing
preferredHighlightBegin: 0
preferredHighlightEnd: height
highlightRangeMode: ListView.ApplyRange
CustomScrollBar.vertical: CustomScrollBar {
flickable: root
}
add: Transition {
enabled: !root.state
highlightFollowsCurrentItem: false
highlight: CustomRect {
radius: 8
color: DynamicColors.palette.m3onSurface
opacity: 0.08
Anim {
from: 0
properties: "opacity,scale"
to: 1
}
}
addDisplaced: Transition {
Anim {
duration: Appearance.anim.durations.small
property: "y"
}
y: root.currentItem?.y ?? 0
implicitWidth: root.width
implicitHeight: root.currentItem?.implicitHeight ?? 0
Anim {
properties: "opacity,scale"
to: 1
}
}
displaced: Transition {
Anim {
property: "y"
}
Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
Anim {
properties: "opacity,scale"
to: 1
}
}
highlight: CustomRect {
color: DynamicColors.palette.m3onSurface
implicitHeight: root.currentItem?.implicitHeight ?? 0
implicitWidth: root.width
opacity: 0.08
radius: 8
y: root.currentItem?.y ?? 0
state: {
const text = search.text;
const prefix = Config.launcher.actionPrefix;
if (text.startsWith(prefix)) {
for (const action of ["calc", "scheme", "variant"])
if (text.startsWith(`${prefix}${action} `))
return action;
Behavior on y {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
model: ScriptModel {
id: model
return "actions";
}
onValuesChanged: root.currentIndex = 0
}
move: Transition {
Anim {
property: "y"
}
return "apps";
}
Anim {
properties: "opacity,scale"
to: 1
}
}
remove: Transition {
enabled: !root.state
states: [
State {
name: "apps"
Anim {
from: 1
properties: "opacity,scale"
to: 0
}
}
states: [
State {
name: "apps"
PropertyChanges {
model.values: Apps.search(search.text)
root.delegate: appItem
}
},
State {
name: "actions"
PropertyChanges {
model.values: Apps.search(search.text)
root.delegate: appItem
}
},
State {
name: "actions"
PropertyChanges {
model.values: Actions.query(search.text)
root.delegate: actionItem
}
},
State {
name: "calc"
PropertyChanges {
model.values: Actions.query(search.text)
root.delegate: actionItem
}
},
State {
name: "calc"
PropertyChanges {
model.values: [0]
root.delegate: calcItem
}
},
]
PropertyChanges {
model.values: [0]
root.delegate: calcItem
}
}
]
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
from: 1
property: "opacity"
target: root
to: 0
}
transitions: Transition {
SequentialAnimation {
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
Anim {
target: root
property: "scale"
from: 1
to: 0.9
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
PropertyAction {
targets: [model, root]
properties: "values,delegate"
}
ParallelAnimation {
Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
Anim {
target: root
property: "scale"
from: 0.9
to: 1
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
PropertyAction {
targets: [root.add, root.remove]
property: "enabled"
value: true
}
}
}
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
from: 1
property: "scale"
target: root
to: 0.9
}
}
CustomScrollBar.vertical: CustomScrollBar {
flickable: root
}
PropertyAction {
properties: "values,delegate"
targets: [model, root]
}
add: Transition {
enabled: !root.state
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
from: 0
property: "opacity"
target: root
to: 1
}
Anim {
properties: "opacity,scale"
from: 0
to: 1
}
}
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
from: 0.9
property: "scale"
target: root
to: 1
}
}
remove: Transition {
enabled: !root.state
PropertyAction {
property: "enabled"
targets: [root.add, root.remove]
value: true
}
}
}
Anim {
properties: "opacity,scale"
from: 1
to: 0
}
}
Component {
id: appItem
move: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
AppItem {
visibilities: root.visibilities
}
}
addDisplaced: Transition {
Anim {
property: "y"
duration: Appearance.anim.durations.small
}
Anim {
properties: "opacity,scale"
to: 1
}
}
Component {
id: actionItem
displaced: Transition {
Anim {
property: "y"
}
Anim {
properties: "opacity,scale"
to: 1
}
}
ActionItem {
list: root
}
}
Component {
id: appItem
Component {
id: calcItem
AppItem {
visibilities: root.visibilities
}
}
Component {
id: actionItem
ActionItem {
list: root
}
}
Component {
id: calcItem
CalcItem {
list: root
}
}
CalcItem {
list: root
}
}
}
+55 -48
View File
@@ -4,56 +4,63 @@ import qs.Components
import qs.Config
ShapePath {
id: root
id: root
required property Wrapper wrapper
readonly property real rounding: Config.barConfig.rounding + 5
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: Config.barConfig.rounding + 5
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
Behavior on fillColor {
CAnim {
}
}
Behavior on fillColor {
CAnim {}
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.width - root.rounding * 2
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
}
+142 -156
View File
@@ -8,183 +8,169 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
required property real maxHeight
readonly property int padding: Appearance.padding.small
required property var panels
readonly property int rounding: Appearance.rounding.large
required property PersistentProperties visibilities
readonly property int padding: Appearance.padding.small
readonly property int rounding: Appearance.rounding.large
implicitHeight: searchWrapper.height + listWrapper.height + padding * 2
implicitWidth: listWrapper.width + padding * 2
implicitWidth: listWrapper.width + padding * 2
implicitHeight: searchWrapper.height + listWrapper.height + padding * 2
Item {
id: listWrapper
Item {
id: listWrapper
anchors.bottom: searchWrapper.top
anchors.bottomMargin: root.padding
anchors.horizontalCenter: parent.horizontalCenter
implicitHeight: list.height + root.padding
implicitWidth: list.width
implicitWidth: list.width
implicitHeight: list.height + root.padding
ContentList {
id: list
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: searchWrapper.top
anchors.bottomMargin: root.padding
content: root
maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3
padding: root.padding
panels: root.panels
rounding: root.rounding
search: search
visibilities: root.visibilities
}
}
ContentList {
id: list
CustomRect {
id: searchWrapper
content: root
visibilities: root.visibilities
panels: root.panels
maxHeight: root.maxHeight - searchWrapper.implicitHeight - root.padding * 3
search: search
padding: root.padding
rounding: root.rounding
}
}
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: root.padding
anchors.right: parent.right
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
radius: 8
CustomRect {
id: searchWrapper
MaterialIcon {
id: searchIcon
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
radius: 8
anchors.left: parent.left
anchors.leftMargin: root.padding + 10
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
text: "search"
}
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: root.padding
CustomTextField {
id: search
implicitHeight: Math.max(searchIcon.implicitHeight, search.implicitHeight, clearIcon.implicitHeight)
anchors.left: searchIcon.right
anchors.leftMargin: Appearance.spacing.small
anchors.right: clearIcon.left
anchors.rightMargin: Appearance.spacing.small
bottomPadding: Appearance.padding.larger
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
topPadding: Appearance.padding.larger
MaterialIcon {
id: searchIcon
Component.onCompleted: forceActiveFocus()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
Keys.onEscapePressed: root.visibilities.launcher = false
Keys.onPressed: event => {
if (!Config.launcher.vimKeybinds)
return;
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.padding + 10
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
}
Keys.onUpPressed: list.currentList?.incrementCurrentIndex()
onAccepted: {
const currentItem = list.currentList?.currentItem;
if (currentItem) {
if (list.showWallpapers) {
if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.previewColourLock = true;
Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false;
} else if (text.startsWith(Config.launcher.actionPrefix)) {
if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
currentItem.onClicked();
else
currentItem.modelData.onClicked(list.currentList);
} else {
Apps.launch(currentItem.modelData);
root.visibilities.launcher = false;
}
}
}
text: "search"
color: DynamicColors.palette.m3onSurfaceVariant
}
Connections {
function onLauncherChanged(): void {
if (!root.visibilities.launcher)
search.text = "";
}
CustomTextField {
id: search
function onSessionChanged(): void {
if (!root.visibilities.session)
search.forceActiveFocus();
}
anchors.left: searchIcon.right
anchors.right: clearIcon.left
anchors.leftMargin: Appearance.spacing.small
anchors.rightMargin: Appearance.spacing.small
target: root.visibilities
}
}
topPadding: Appearance.padding.larger
bottomPadding: Appearance.padding.larger
MaterialIcon {
id: clearIcon
placeholderText: qsTr("Type \"%1\" for commands").arg(Config.launcher.actionPrefix)
anchors.right: parent.right
anchors.rightMargin: root.padding + 10
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
opacity: {
if (!search.text)
return 0;
if (mouse.pressed)
return 0.7;
if (mouse.containsMouse)
return 0.8;
return 1;
}
text: "close"
width: search.text ? implicitWidth : implicitWidth / 2
onAccepted: {
const currentItem = list.currentList?.currentItem;
if (currentItem) {
if (list.showWallpapers) {
if (DynamicColors.scheme === "dynamic" && currentItem.modelData.path !== Wallpapers.actualCurrent)
Wallpapers.previewColourLock = true;
Wallpapers.setWallpaper(currentItem.modelData.path);
root.visibilities.launcher = false;
} else if (text.startsWith(Config.launcher.actionPrefix)) {
if (text.startsWith(`${Config.launcher.actionPrefix}calc `))
currentItem.onClicked();
else
currentItem.modelData.onClicked(list.currentList);
} else {
Apps.launch(currentItem.modelData);
root.visibilities.launcher = false;
}
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on width {
Anim {
duration: Appearance.anim.durations.small
}
}
Keys.onUpPressed: list.currentList?.incrementCurrentIndex()
Keys.onDownPressed: list.currentList?.decrementCurrentIndex()
MouseArea {
id: mouse
Keys.onEscapePressed: root.visibilities.launcher = false
anchors.fill: parent
cursorShape: search.text ? Qt.PointingHandCursor : undefined
hoverEnabled: true
Keys.onPressed: event => {
if (!Config.launcher.vimKeybinds)
return;
if (event.modifiers & Qt.ControlModifier) {
if (event.key === Qt.Key_J) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_K) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
} else if (event.key === Qt.Key_Tab) {
list.currentList?.incrementCurrentIndex();
event.accepted = true;
} else if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
list.currentList?.decrementCurrentIndex();
event.accepted = true;
}
}
Component.onCompleted: forceActiveFocus()
Connections {
target: root.visibilities
function onLauncherChanged(): void {
if (!root.visibilities.launcher)
search.text = "";
}
function onSessionChanged(): void {
if (!root.visibilities.session)
search.forceActiveFocus();
}
}
}
MaterialIcon {
id: clearIcon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: root.padding + 10
width: search.text ? implicitWidth : implicitWidth / 2
opacity: {
if (!search.text)
return 0;
if (mouse.pressed)
return 0.7;
if (mouse.containsMouse)
return 0.8;
return 1;
}
text: "close"
color: DynamicColors.palette.m3onSurfaceVariant
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: search.text ? Qt.PointingHandCursor : undefined
onClicked: search.text = ""
}
Behavior on width {
Anim {
duration: Appearance.anim.durations.small
}
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.small
}
}
}
}
onClicked: search.text = ""
}
}
}
}
+129 -135
View File
@@ -9,162 +9,156 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property var content
required property PersistentProperties visibilities
required property var panels
required property real maxHeight
required property CustomTextField search
required property int padding
required property int rounding
required property var content
readonly property Item currentList: showWallpapers ? wallpaperList.item : appList.item
required property real maxHeight
required property int padding
required property var panels
required property int rounding
required property CustomTextField search
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
required property PersistentProperties visibilities
readonly property bool showWallpapers: search.text.startsWith(`${Config.launcher.actionPrefix}wallpaper `)
readonly property Item currentList: showWallpapers ? wallpaperList.item : appList.item
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
clip: true
state: showWallpapers ? "wallpapers" : "apps"
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
Behavior on implicitHeight {
enabled: root.visibilities.launcher
clip: true
state: showWallpapers ? "wallpapers" : "apps"
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Behavior on implicitWidth {
enabled: root.visibilities.launcher
states: [
State {
name: "apps"
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Behavior on state {
SequentialAnimation {
Anim {
duration: Appearance.anim.durations.small
from: 1
property: "opacity"
target: root
to: 0
}
PropertyChanges {
root.implicitWidth: Config.launcher.sizes.itemWidth
root.implicitHeight: Math.min(root.maxHeight, appList.implicitHeight > 0 ? appList.implicitHeight : empty.implicitHeight)
appList.active: true
}
PropertyAction {
}
AnchorChanges {
anchors.left: root.parent.left
anchors.right: root.parent.right
}
},
State {
name: "wallpapers"
Anim {
duration: Appearance.anim.durations.small
from: 0
property: "opacity"
target: root
to: 1
}
}
}
states: [
State {
name: "apps"
PropertyChanges {
root.implicitWidth: Math.max(Config.launcher.sizes.itemWidth * 1.2, wallpaperList.implicitWidth)
root.implicitHeight: Config.launcher.sizes.wallpaperHeight
wallpaperList.active: true
}
}
]
PropertyChanges {
appList.active: true
root.implicitHeight: Math.min(root.maxHeight, appList.implicitHeight > 0 ? appList.implicitHeight : empty.implicitHeight)
root.implicitWidth: Config.launcher.sizes.itemWidth
}
Behavior on state {
SequentialAnimation {
Anim {
target: root
property: "opacity"
from: 1
to: 0
duration: Appearance.anim.durations.small
}
PropertyAction {}
Anim {
target: root
property: "opacity"
from: 0
to: 1
duration: Appearance.anim.durations.small
}
}
}
AnchorChanges {
anchors.left: root.parent.left
anchors.right: root.parent.right
}
},
State {
name: "wallpapers"
Loader {
id: appList
PropertyChanges {
root.implicitHeight: Config.launcher.sizes.wallpaperHeight
root.implicitWidth: Math.max(Config.launcher.sizes.itemWidth * 1.2, wallpaperList.implicitWidth)
wallpaperList.active: true
}
}
]
active: false
Loader {
id: appList
anchors.fill: parent
active: false
anchors.fill: parent
sourceComponent: AppList {
search: root.search
visibilities: root.visibilities
}
}
sourceComponent: AppList {
search: root.search
visibilities: root.visibilities
}
}
Loader {
id: wallpaperList
Loader {
id: wallpaperList
active: false
active: false
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
sourceComponent: WallpaperList {
content: root.content
panels: root.panels
search: root.search
visibilities: root.visibilities
}
}
sourceComponent: WallpaperList {
search: root.search
visibilities: root.visibilities
panels: root.panels
content: root.content
}
}
Row {
id: empty
Row {
id: empty
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
opacity: root.currentList?.count === 0 ? 1 : 0
padding: Appearance.padding.large
scale: root.currentList?.count === 0 ? 1 : 0.5
spacing: Appearance.spacing.normal
opacity: root.currentList?.count === 0 ? 1 : 0
scale: root.currentList?.count === 0 ? 1 : 0.5
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
spacing: Appearance.spacing.normal
padding: Appearance.padding.large
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Column {
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
text: root.state === "wallpapers" ? "wallpaper_slideshow" : "manage_search"
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.extraLarge
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
font.weight: 500
text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results")
}
anchors.verticalCenter: parent.verticalCenter
}
Column {
anchors.verticalCenter: parent.verticalCenter
CustomText {
text: root.state === "wallpapers" ? qsTr("No wallpapers found") : qsTr("No results")
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.larger
font.weight: 500
}
CustomText {
text: root.state === "wallpapers" && Wallpapers.list.length === 0 ? qsTr("Try putting some wallpapers in %1").arg(Paths.shortenHome(Paths.wallsdir)) : qsTr("Try searching for something else")
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
}
Behavior on implicitWidth {
enabled: root.visibilities.launcher
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Behavior on implicitHeight {
enabled: root.visibilities.launcher
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
text: root.state === "wallpapers" && Wallpapers.list.length === 0 ? qsTr("Try putting some wallpapers in %1").arg(Paths.shortenHome(Paths.wallsdir)) : qsTr("Try searching for something else")
}
}
}
}
+45 -50
View File
@@ -5,66 +5,61 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property var modelData
required property var list
required property var list
required property var modelData
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
function onClicked(): void {
root.modelData?.onClicked(root.list);
}
StateLayer {
radius: Appearance.rounding.normal
radius: Appearance.rounding.normal
}
function onClicked(): void {
root.modelData?.onClicked(root.list);
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.larger
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.rightMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
MaterialIcon {
id: icon
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
font.pointSize: Appearance.font.size.extraLarge
text: root.modelData?.icon ?? ""
}
text: root.modelData?.icon ?? ""
font.pointSize: Appearance.font.size.extraLarge
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
implicitHeight: name.implicitHeight + desc.implicitHeight
implicitWidth: parent.width - icon.width
anchors.verticalCenter: parent.verticalCenter
}
CustomText {
id: name
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
font.pointSize: Appearance.font.size.normal
text: root.modelData?.name ?? ""
}
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + desc.implicitHeight
CustomText {
id: desc
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
CustomText {
id: desc
text: root.modelData?.desc ?? ""
font.pointSize: Appearance.font.size.small
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}
anchors.top: name.bottom
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.modelData?.desc ?? ""
width: root.width - icon.width - Appearance.rounding.normal * 2
}
}
}
}
+46 -51
View File
@@ -8,67 +8,62 @@ import qs.Config
import qs.Modules
Item {
id: root
id: root
required property DesktopEntry modelData
required property PersistentProperties visibilities
required property DesktopEntry modelData
required property PersistentProperties visibilities
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
function onClicked(): void {
Apps.launch(root.modelData);
root.visibilities.launcher = false;
}
StateLayer {
radius: 8
radius: 8
}
function onClicked(): void {
Apps.launch(root.modelData);
root.visibilities.launcher = false;
}
}
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
anchors.rightMargin: Appearance.padding.larger
Item {
anchors.fill: parent
anchors.leftMargin: Appearance.padding.larger
anchors.rightMargin: Appearance.padding.larger
anchors.margins: Appearance.padding.smaller
IconImage {
id: icon
IconImage {
id: icon
anchors.verticalCenter: parent.verticalCenter
implicitSize: parent.height
source: Quickshell.iconPath(root.modelData?.icon, "image-missing")
}
source: Quickshell.iconPath(root.modelData?.icon, "image-missing")
implicitSize: parent.height
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
implicitHeight: name.implicitHeight + comment.implicitHeight
implicitWidth: parent.width - icon.width
anchors.verticalCenter: parent.verticalCenter
}
CustomText {
id: name
Item {
anchors.left: icon.right
anchors.leftMargin: Appearance.spacing.normal
anchors.verticalCenter: icon.verticalCenter
font.pointSize: Appearance.font.size.normal
text: root.modelData?.name ?? ""
}
implicitWidth: parent.width - icon.width
implicitHeight: name.implicitHeight + comment.implicitHeight
CustomText {
id: comment
CustomText {
id: name
text: root.modelData?.name ?? ""
font.pointSize: Appearance.font.size.normal
}
CustomText {
id: comment
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
font.pointSize: Appearance.font.size.small
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
width: root.width - icon.width - Appearance.rounding.normal * 2
anchors.top: name.bottom
}
}
}
anchors.top: name.bottom
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: (root.modelData?.comment || root.modelData?.genericName || root.modelData?.name) ?? ""
width: root.width - icon.width - Appearance.rounding.normal * 2
}
}
}
}
+86 -94
View File
@@ -8,117 +8,109 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property var list
readonly property string math: list.search.text.slice(`${Config.launcher.actionPrefix}calc `.length)
required property var list
readonly property string math: list.search.text.slice(`${Config.launcher.actionPrefix}calc `.length)
function onClicked(): void {
Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]);
root.list.visibilities.launcher = false;
}
function onClicked(): void {
Quickshell.execDetached(["wl-copy", Qalculator.eval(math, false)]);
root.list.visibilities.launcher = false;
}
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: Config.launcher.sizes.itemHeight
anchors.left: parent?.left
anchors.right: parent?.right
StateLayer {
function onClicked(): void {
root.onClicked();
}
StateLayer {
radius: Appearance.rounding.normal
radius: Appearance.rounding.normal
}
function onClicked(): void {
root.onClicked();
}
}
RowLayout {
anchors.left: parent.left
anchors.margins: Appearance.padding.larger
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Appearance.spacing.normal
RowLayout {
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.larger
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pointSize: Appearance.font.size.extraLarge
text: "function"
}
spacing: Appearance.spacing.normal
CustomText {
id: result
MaterialIcon {
text: "function"
font.pointSize: Appearance.font.size.extraLarge
Layout.alignment: Qt.AlignVCenter
}
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
color: {
if (text.includes("error: ") || text.includes("warning: "))
return DynamicColors.palette.m3error;
if (!root.math)
return DynamicColors.palette.m3onSurfaceVariant;
return DynamicColors.palette.m3onSurface;
}
elide: Text.ElideLeft
text: root.math.length > 0 ? Qalculator.eval(root.math) : qsTr("Type an expression to calculate")
}
CustomText {
id: result
CustomRect {
Layout.alignment: Qt.AlignVCenter
clip: true
color: DynamicColors.palette.m3tertiary
implicitHeight: Math.max(label.implicitHeight, icon.implicitHeight) + Appearance.padding.small * 2
implicitWidth: (stateLayer.containsMouse ? label.implicitWidth + label.anchors.rightMargin : 0) + icon.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.normal
color: {
if (text.includes("error: ") || text.includes("warning: "))
return DynamicColors.palette.m3error;
if (!root.math)
return DynamicColors.palette.m3onSurfaceVariant;
return DynamicColors.palette.m3onSurface;
}
Behavior on implicitWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
text: root.math.length > 0 ? Qalculator.eval(root.math) : qsTr("Type an expression to calculate")
elide: Text.ElideLeft
StateLayer {
id: stateLayer
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
function onClicked(): void {
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
root.list.visibilities.launcher = false;
}
CustomRect {
color: DynamicColors.palette.m3tertiary
radius: Appearance.rounding.normal
clip: true
color: DynamicColors.palette.m3onTertiary
}
implicitWidth: (stateLayer.containsMouse ? label.implicitWidth + label.anchors.rightMargin : 0) + icon.implicitWidth + Appearance.padding.normal * 2
implicitHeight: Math.max(label.implicitHeight, icon.implicitHeight) + Appearance.padding.small * 2
CustomText {
id: label
Layout.alignment: Qt.AlignVCenter
anchors.right: icon.left
anchors.rightMargin: Appearance.spacing.small
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.normal
opacity: stateLayer.containsMouse ? 1 : 0
text: qsTr("Open in calculator")
StateLayer {
id: stateLayer
Behavior on opacity {
Anim {
}
}
}
color: DynamicColors.palette.m3onTertiary
MaterialIcon {
id: icon
function onClicked(): void {
Quickshell.execDetached(["app2unit", "--", ...Config.general.apps.terminal, "fish", "-C", `exec qalc -i '${root.math}'`]);
root.list.visibilities.launcher = false;
}
}
CustomText {
id: label
anchors.verticalCenter: parent.verticalCenter
anchors.right: icon.left
anchors.rightMargin: Appearance.spacing.small
text: qsTr("Open in calculator")
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.normal
opacity: stateLayer.containsMouse ? 1 : 0
Behavior on opacity {
Anim {}
}
}
MaterialIcon {
id: icon
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
text: "open_in_new"
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.large
}
Behavior on implicitWidth {
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
}
}
anchors.right: parent.right
anchors.rightMargin: Appearance.padding.normal
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onTertiary
font.pointSize: Appearance.font.size.large
text: "open_in_new"
}
}
}
}
+71 -73
View File
@@ -7,92 +7,90 @@ import qs.Config
import qs.Modules
Item {
id: root
id: root
required property FileSystemEntry modelData
required property PersistentProperties visibilities
required property FileSystemEntry modelData
required property PersistentProperties visibilities
scale: 0.5
opacity: 0
z: PathView.z ?? 0
implicitHeight: image.height + label.height + Appearance.spacing.small / 2 + Appearance.padding.large + Appearance.padding.normal
implicitWidth: image.width + Appearance.padding.larger * 2
opacity: 0
scale: 0.5
z: PathView.z ?? 0
Component.onCompleted: {
scale = Qt.binding(() => PathView.isCurrentItem ? 1 : PathView.onPath ? 0.8 : 0);
opacity = Qt.binding(() => PathView.onPath ? 1 : 0);
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
implicitWidth: image.width + Appearance.padding.larger * 2
implicitHeight: image.height + label.height + Appearance.spacing.small / 2 + Appearance.padding.large + Appearance.padding.normal
Component.onCompleted: {
scale = Qt.binding(() => PathView.isCurrentItem ? 1 : PathView.onPath ? 0.8 : 0);
opacity = Qt.binding(() => PathView.onPath ? 1 : 0);
}
StateLayer {
radius: Appearance.rounding.normal
function onClicked(): void {
StateLayer {
function onClicked(): void {
console.log(root.modelData.path);
Wallpapers.setWallpaper(root.modelData.path);
root.visibilities.launcher = false;
}
}
Wallpapers.setWallpaper(root.modelData.path);
root.visibilities.launcher = false;
}
Elevation {
anchors.fill: image
radius: image.radius
opacity: root.PathView.isCurrentItem ? 1 : 0
level: 4
radius: Appearance.rounding.normal
}
Behavior on opacity {
Anim {}
}
}
Elevation {
anchors.fill: image
level: 4
opacity: root.PathView.isCurrentItem ? 1 : 0
radius: image.radius
CustomClippingRect {
id: image
Behavior on opacity {
Anim {
}
}
}
anchors.horizontalCenter: parent.horizontalCenter
y: Appearance.padding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.normal
CustomClippingRect {
id: image
implicitWidth: Config.launcher.sizes.wallpaperWidth
implicitHeight: implicitWidth / 16 * 9
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: implicitWidth / 16 * 9
implicitWidth: Config.launcher.sizes.wallpaperWidth
radius: Appearance.rounding.normal
y: Appearance.padding.large
MaterialIcon {
anchors.centerIn: parent
text: "image"
color: DynamicColors.tPalette.m3outline
font.pointSize: Appearance.font.size.extraLarge * 2
font.weight: 600
}
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.tPalette.m3outline
font.pointSize: Appearance.font.size.extraLarge * 2
font.weight: 600
text: "image"
}
CachingImage {
path: root.modelData.path
smooth: !root.PathView.view.moving
cache: true
CachingImage {
anchors.fill: parent
cache: true
path: root.modelData.path
smooth: !root.PathView.view.moving
}
}
anchors.fill: parent
}
}
CustomText {
id: label
CustomText {
id: label
anchors.top: image.bottom
anchors.topMargin: Appearance.spacing.small / 2
anchors.horizontalCenter: parent.horizontalCenter
width: image.width - Appearance.padding.normal * 2
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
renderType: Text.QtRendering
text: root.modelData.relativePath
font.pointSize: Appearance.font.size.normal
}
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {}
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: image.bottom
anchors.topMargin: Appearance.spacing.small / 2
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
horizontalAlignment: Text.AlignHCenter
renderType: Text.QtRendering
text: root.modelData.relativePath
width: image.width - Appearance.padding.normal * 2
}
}
+34 -33
View File
@@ -7,45 +7,46 @@ import Quickshell
import QtQuick
Searcher {
id: root
id: root
function transformSearch(search: string): string {
return search.slice(Config.launcher.actionPrefix.length);
}
function transformSearch(search: string): string {
return search.slice(Config.launcher.actionPrefix.length);
}
list: variants.instances
useFuzzy: Config.launcher.useFuzzy.actions
list: variants.instances
useFuzzy: Config.launcher.useFuzzy.actions
Variants {
id: variants
Variants {
id: variants
model: Config.launcher.actions.filter(a => (a.enabled ?? true))
model: Config.launcher.actions.filter(a => (a.enabled ?? true))
Action {}
}
Action {
}
}
component Action: QtObject {
required property var modelData
readonly property string name: modelData.name ?? qsTr("Unnamed")
readonly property string desc: modelData.description ?? qsTr("No description")
readonly property string icon: modelData.icon ?? "help_outline"
readonly property list<string> command: modelData.command ?? []
readonly property bool enabled: modelData.enabled ?? true
readonly property bool dangerous: modelData.dangerous ?? false
component Action: QtObject {
readonly property list<string> command: modelData.command ?? []
readonly property bool dangerous: modelData.dangerous ?? false
readonly property string desc: modelData.description ?? qsTr("No description")
readonly property bool enabled: modelData.enabled ?? true
readonly property string icon: modelData.icon ?? "help_outline"
required property var modelData
readonly property string name: modelData.name ?? qsTr("Unnamed")
function onClicked(list: AppList): void {
if (command.length === 0)
return;
function onClicked(list: AppList): void {
if (command.length === 0)
return;
if (command[0] === "autocomplete" && command.length > 1) {
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
} else if (command[0] === "setMode" && command.length > 1) {
list.visibilities.launcher = false;
Colours.setMode(command[1]);
} else {
list.visibilities.launcher = false;
Quickshell.execDetached(command);
}
}
}
if (command[0] === "autocomplete" && command.length > 1) {
list.search.text = `${Config.launcher.actionPrefix}${command[1]} `;
} else if (command[0] === "setMode" && command.length > 1) {
list.visibilities.launcher = false;
Colours.setMode(command[1]);
} else {
list.visibilities.launcher = false;
Quickshell.execDetached(command);
}
}
}
}
+58 -58
View File
@@ -7,72 +7,72 @@ import qs.Helpers
import qs.Paths
Searcher {
id: root
id: root
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
function launch(entry: DesktopEntry): void {
appDb.incrementFrequency(entry.id);
if (entry.runInTerminal)
Quickshell.execDetached({
command: ["app2unit", "--", ...Config.general.apps.terminal, `${Quickshell.shellDir}/assets/wrap_term_launch.sh`, ...entry.command],
workingDirectory: entry.workingDirectory
});
else
Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command],
workingDirectory: entry.workingDirectory
});
}
if (entry.runInTerminal)
Quickshell.execDetached({
command: ["app2unit", "--", ...Config.general.apps.terminal, `${Quickshell.shellDir}/assets/wrap_term_launch.sh`, ...entry.command],
workingDirectory: entry.workingDirectory
});
else
Quickshell.execDetached({
command: ["app2unit", "--", ...entry.command],
workingDirectory: entry.workingDirectory
});
}
function search(search: string): list<var> {
const prefix = Config.launcher.specialPrefix;
function search(search: string): list<var> {
const prefix = Config.launcher.specialPrefix;
if (search.startsWith(`${prefix}i `)) {
keys = ["id", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}c `)) {
keys = ["categories", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}d `)) {
keys = ["comment", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}e `)) {
keys = ["execString", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}w `)) {
keys = ["startupClass", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}g `)) {
keys = ["genericName", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}k `)) {
keys = ["keywords", "name"];
weights = [0.9, 0.1];
} else {
keys = ["name"];
weights = [1];
if (search.startsWith(`${prefix}i `)) {
keys = ["id", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}c `)) {
keys = ["categories", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}d `)) {
keys = ["comment", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}e `)) {
keys = ["execString", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}w `)) {
keys = ["startupClass", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}g `)) {
keys = ["genericName", "name"];
weights = [0.9, 0.1];
} else if (search.startsWith(`${prefix}k `)) {
keys = ["keywords", "name"];
weights = [0.9, 0.1];
} else {
keys = ["name"];
weights = [1];
if (!search.startsWith(`${prefix}t `))
return query(search).map(e => e.entry);
}
if (!search.startsWith(`${prefix}t `))
return query(search).map(e => e.entry);
}
const results = query(search.slice(prefix.length + 2)).map(e => e.entry);
if (search.startsWith(`${prefix}t `))
return results.filter(a => a.runInTerminal);
return results;
}
const results = query(search.slice(prefix.length + 2)).map(e => e.entry);
if (search.startsWith(`${prefix}t `))
return results.filter(a => a.runInTerminal);
return results;
}
function selector(item: var): string {
return keys.map(k => item[k]).join(" ");
}
function selector(item: var): string {
return keys.map(k => item[k]).join(" ");
}
list: appDb.apps
useFuzzy: Config.launcher.useFuzzy.apps
list: appDb.apps
useFuzzy: Config.launcher.useFuzzy.apps
AppDb {
id: appDb
AppDb {
id: appDb
path: `${Paths.state}/apps.sqlite`
entries: DesktopEntries.applications.values
}
entries: DesktopEntries.applications.values
path: `${Paths.state}/apps.sqlite`
}
}
+69 -71
View File
@@ -8,90 +8,88 @@ import qs.Config
import qs.Modules.Launcher.Items
PathView {
id: root
id: root
required property CustomTextField search
required property var visibilities
required property var panels
required property var content
required property var content
readonly property int itemWidth: Config.launcher.sizes.wallpaperWidth * 0.8 + Appearance.padding.larger * 2
readonly property int numItems: {
const screen = QsWindow.window?.screen;
if (!screen)
return 0;
readonly property int itemWidth: Config.launcher.sizes.wallpaperWidth * 0.8 + Appearance.padding.larger * 2
// Screen width - 4x outer rounding - 2x max side thickness (cause centered)
const barMargins = panels.bar.implicitWidth;
let outerMargins = 0;
if (panels.popouts.hasCurrent && panels.popouts.currentCenter + panels.popouts.nonAnimHeight / 2 > screen.height - content.implicitHeight)
outerMargins = panels.popouts.nonAnimWidth;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2;
readonly property int numItems: {
const screen = QsWindow.window?.screen;
if (!screen)
return 0;
if (maxWidth <= 0)
return 0;
// Screen width - 4x outer rounding - 2x max side thickness (cause centered)
const barMargins = panels.bar.implicitWidth;
let outerMargins = 0;
if (panels.popouts.hasCurrent && panels.popouts.currentCenter + panels.popouts.nonAnimHeight / 2 > screen.height - content.implicitHeight)
outerMargins = panels.popouts.nonAnimWidth;
if ((visibilities.utilities || visibilities.sidebar) && panels.utilities.implicitWidth > outerMargins)
outerMargins = panels.utilities.implicitWidth;
const maxWidth = screen.width - Config.barConfig.rounding * 4 - (barMargins + outerMargins) * 2;
const maxItemsOnScreen = Math.floor(maxWidth / itemWidth);
const visible = Math.min(maxItemsOnScreen, Config.launcher.maxWallpapers, scriptModel.values.length);
if (maxWidth <= 0)
return 0;
if (visible === 2)
return 1;
if (visible > 1 && visible % 2 === 0)
return visible - 1;
return visible;
}
required property var panels
required property CustomTextField search
required property var visibilities
const maxItemsOnScreen = Math.floor(maxWidth / itemWidth);
const visible = Math.min(maxItemsOnScreen, Config.launcher.maxWallpapers, scriptModel.values.length);
cacheItemCount: 4
highlightRangeMode: PathView.StrictlyEnforceRange
implicitWidth: Math.min(numItems, count) * itemWidth
pathItemCount: numItems
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
snapMode: PathView.SnapToItem
if (visible === 2)
return 1;
if (visible > 1 && visible % 2 === 0)
return visible - 1;
return visible;
}
delegate: WallpaperItem {
visibilities: root.visibilities
}
model: ScriptModel {
id: scriptModel
model: ScriptModel {
id: scriptModel
readonly property string search: root.search.text.split(" ").slice(1).join(" ")
readonly property string search: root.search.text.split(" ").slice(1).join(" ")
values: Wallpapers.query(search)
values: Wallpapers.query(search)
onValuesChanged: root.currentIndex = search ? 0 : values.findIndex(w => w.path === Wallpapers.actualCurrent)
}
onValuesChanged: root.currentIndex = search ? 0 : values.findIndex(w => w.path === Wallpapers.actualCurrent)
}
path: Path {
startY: root.height / 2
Component.onCompleted: currentIndex = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent)
Component.onDestruction: Wallpapers.stopPreview()
PathAttribute {
name: "z"
value: 0
}
onCurrentItemChanged: {
if (currentItem)
Wallpapers.preview(currentItem.modelData.path);
}
PathLine {
relativeY: 0
x: root.width / 2
}
implicitWidth: Math.min(numItems, count) * itemWidth
pathItemCount: numItems
cacheItemCount: 4
PathAttribute {
name: "z"
value: 1
}
snapMode: PathView.SnapToItem
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
highlightRangeMode: PathView.StrictlyEnforceRange
PathLine {
relativeY: 0
x: root.width
}
}
delegate: WallpaperItem {
visibilities: root.visibilities
}
path: Path {
startY: root.height / 2
PathAttribute {
name: "z"
value: 0
}
PathLine {
x: root.width / 2
relativeY: 0
}
PathAttribute {
name: "z"
value: 1
}
PathLine {
x: root.width
relativeY: 0
}
}
Component.onCompleted: currentIndex = Wallpapers.list.findIndex(w => w.path === Wallpapers.actualCurrent)
Component.onDestruction: Wallpapers.stopPreview()
onCurrentItemChanged: {
if (currentItem)
Wallpapers.preview(currentItem.modelData.path);
}
}
+99 -99
View File
@@ -6,125 +6,125 @@ import qs.Components
import qs.Config
Item {
id: root
id: root
required property ShellScreen screen
required property PersistentProperties visibilities
required property var panels
property int contentHeight
readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large;
if (visibilities.dashboard)
max -= panels.dashboard.nonAnimHeight;
return max;
}
required property var panels
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.launcher
required property PersistentProperties visibilities
readonly property bool shouldBeActive: visibilities.launcher
property int contentHeight
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
readonly property real maxHeight: {
let max = screen.height - Appearance.spacing.large;
if (visibilities.dashboard)
max -= panels.dashboard.nonAnimHeight;
return max;
}
onMaxHeightChanged: timer.start()
onShouldBeActiveChanged: {
if (shouldBeActive) {
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
onMaxHeightChanged: timer.start()
SequentialAnimation {
id: showAnim
visible: height > 0
implicitHeight: 0
implicitWidth: content.implicitWidth
Anim {
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: root.contentHeight
}
onShouldBeActiveChanged: {
if (shouldBeActive) {
timer.stop();
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
SequentialAnimation {
id: showAnim
SequentialAnimation {
id: hideAnim
Anim {
target: root
property: "implicitHeight"
to: root.contentHeight
duration: Appearance.anim.durations.small
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
ScriptAction {
script: root.implicitHeight = Qt.binding(() => content.implicitHeight)
}
}
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
SequentialAnimation {
id: hideAnim
Anim {
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
property: "implicitHeight"
target: root
to: 0
}
}
ScriptAction {
script: root.implicitHeight = root.implicitHeight
}
Anim {
target: root
property: "implicitHeight"
to: 0
easing.bezierCurve: Appearance.anim.curves.expressiveEffects
}
}
Connections {
function onEnabledChanged(): void {
timer.start();
}
Connections {
target: Config.launcher
function onMaxShownChanged(): void {
timer.start();
}
function onEnabledChanged(): void {
timer.start();
}
target: Config.launcher
}
function onMaxShownChanged(): void {
timer.start();
}
}
Connections {
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
Connections {
target: DesktopEntries.applications
target: DesktopEntries.applications
}
function onValuesChanged(): void {
if (DesktopEntries.applications.values.length < Config.launcher.maxAppsShown)
timer.start();
}
}
Timer {
id: timer
Timer {
id: timer
interval: Appearance.anim.durations.small
interval: Appearance.anim.durations.small
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
}
}
onRunningChanged: {
if (running && !root.shouldBeActive) {
content.visible = false;
content.active = true;
} else {
root.contentHeight = Math.min(root.maxHeight, content.implicitHeight);
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
if (showAnim.running) {
showAnim.stop();
showAnim.start();
}
}
}
}
Loader {
id: content
Loader {
id: content
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
active: false
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
visible: false
visible: false
active: false
Component.onCompleted: timer.start()
sourceComponent: Content {
maxHeight: root.maxHeight
panels: root.panels
visibilities: root.visibilities
sourceComponent: Content {
visibilities: root.visibilities
panels: root.panels
maxHeight: root.maxHeight
Component.onCompleted: root.contentHeight = implicitHeight
}
Component.onCompleted: root.contentHeight = implicitHeight
}
}
Component.onCompleted: timer.start()
}
}
+378 -387
View File
@@ -9,391 +9,382 @@ import qs.Config
import qs.Modules
ColumnLayout {
id: root
required property var lock
readonly property real centerScale: Math.min(1, (lock.screen?.height ?? 1440) / 1440)
readonly property int centerWidth: Config.lock.sizes.centerWidth * centerScale
Layout.preferredWidth: centerWidth
Layout.fillWidth: false
Layout.fillHeight: true
spacing: Appearance.spacing.large * 2
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Appearance.spacing.small
CustomText {
Layout.alignment: Qt.AlignVCenter
text: Time.hourStr
color: DynamicColors.palette.m3secondary
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
font.family: Appearance.font.family.clock
font.bold: true
}
CustomText {
Layout.alignment: Qt.AlignVCenter
text: ":"
color: DynamicColors.palette.m3primary
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
font.family: Appearance.font.family.clock
font.bold: true
}
CustomText {
Layout.alignment: Qt.AlignVCenter
text: Time.minuteStr
color: DynamicColors.palette.m3secondary
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
font.family: Appearance.font.family.clock
font.bold: true
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -Appearance.padding.large * 2
text: Time.format("dddd, d MMMM yyyy")
color: DynamicColors.palette.m3tertiary
font.pointSize: Math.floor(Appearance.font.size.extraLarge * root.centerScale)
font.family: Appearance.font.family.mono
font.bold: true
}
CustomClippingRect {
Layout.topMargin: Appearance.spacing.large * 2
Layout.alignment: Qt.AlignHCenter
implicitWidth: root.centerWidth / 2
implicitHeight: root.centerWidth / 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.full
MaterialIcon {
anchors.centerIn: parent
text: "person"
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Math.floor(root.centerWidth / 4)
}
CachingImage {
id: pfp
anchors.fill: parent
path: `${Paths.home}/.face`
}
}
CustomRect {
Layout.alignment: Qt.AlignHCenter
implicitWidth: root.centerWidth * 0.8
implicitHeight: input.implicitHeight + Appearance.padding.small * 2
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.full
focus: true
onActiveFocusChanged: {
if (!activeFocus)
forceActiveFocus();
}
Keys.onPressed: event => {
if (root.lock.unlocking)
return;
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
inputField.placeholder.animate = false;
root.lock.pam.handleKey(event);
}
StateLayer {
hoverEnabled: false
cursorShape: Qt.IBeamCursor
function onClicked(): void {
parent.forceActiveFocus();
}
}
RowLayout {
id: input
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
Item {
implicitWidth: implicitHeight
implicitHeight: fprintIcon.implicitHeight + Appearance.padding.small * 2
MaterialIcon {
id: fprintIcon
anchors.centerIn: parent
animate: true
text: {
if (root.lock.pam.fprint.tries >= Config.lock.maxFprintTries)
return "fingerprint_off";
if (root.lock.pam.fprint.active)
return "fingerprint";
return "lock";
}
color: root.lock.pam.fprint.tries >= Config.lock.maxFprintTries ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
opacity: root.lock.pam.passwd.active ? 0 : 1
Behavior on opacity {
Anim {}
}
}
CircularIndicator {
anchors.fill: parent
running: root.lock.pam.passwd.active
}
}
InputField {
id: inputField
pam: root.lock.pam
}
CustomRect {
implicitWidth: implicitHeight
implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2
color: root.lock.pam.buffer ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
radius: Appearance.rounding.full
StateLayer {
color: root.lock.pam.buffer ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
function onClicked(): void {
root.lock.pam.passwd.start();
}
}
MaterialIcon {
id: enterIcon
anchors.centerIn: parent
text: "arrow_forward"
color: root.lock.pam.buffer ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.weight: 500
}
}
}
}
Item {
Layout.fillWidth: true
Layout.topMargin: -Appearance.spacing.large
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
Behavior on implicitHeight {
Anim {}
}
CustomText {
id: stateMessage
readonly property string msg: {
if (Hypr.kbLayout !== Hypr.defaultKbLayout) {
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.capsLock)
return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.numLock)
return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull);
}
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.");
if (Hypr.capsLock)
return qsTr("Caps lock is ON.");
if (Hypr.numLock)
return qsTr("Num lock is ON.");
return "";
}
property bool shouldBeVisible
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
} else {
text = msg;
}
shouldBeVisible = true;
} else {
shouldBeVisible = false;
}
}
anchors.left: parent.left
anchors.right: parent.right
scale: shouldBeVisible && !message.msg ? 1 : 0.7
opacity: shouldBeVisible && !message.msg ? 1 : 0
color: DynamicColors.palette.m3onSurfaceVariant
animateProp: "opacity"
font.family: Appearance.font.family.mono
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
lineHeight: 1.2
Behavior on scale {
Anim {}
}
Behavior on opacity {
Anim {}
}
}
CustomText {
id: message
readonly property Pam pam: root.lock.pam
readonly property string msg: {
if (pam.fprintState === "error")
return qsTr("FP ERROR: %1").arg(pam.fprint.message);
if (pam.state === "error")
return qsTr("PW ERROR: %1").arg(pam.passwd.message);
if (pam.lockMessage)
return pam.lockMessage;
if (pam.state === "max" && pam.fprintState === "max")
return qsTr("Maximum password and fingerprint attempts reached.");
if (pam.state === "max") {
if (pam.fprint.available)
return qsTr("Maximum password attempts reached. Please use fingerprint.");
return qsTr("Maximum password attempts reached.");
}
if (pam.fprintState === "max")
return qsTr("Maximum fingerprint attempts reached. Please use password.");
if (pam.state === "fail") {
if (pam.fprint.available)
return qsTr("Incorrect password. Please try again or use fingerprint.");
return qsTr("Incorrect password. Please try again.");
}
if (pam.fprintState === "fail")
return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries);
return "";
}
anchors.left: parent.left
anchors.right: parent.right
scale: 0.7
opacity: 0
color: DynamicColors.palette.m3error
font.pointSize: Appearance.font.size.small
font.family: Appearance.font.family.mono
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
exitAnim.stop();
if (scale < 1)
appearAnim.restart();
else
flashAnim.restart();
} else {
text = msg;
exitAnim.stop();
appearAnim.restart();
}
} else {
appearAnim.stop();
flashAnim.stop();
exitAnim.start();
}
}
Connections {
target: root.lock.pam
function onFlashMsg(): void {
exitAnim.stop();
if (message.scale < 1)
appearAnim.restart();
else
flashAnim.restart();
}
}
Anim {
id: appearAnim
target: message
properties: "scale,opacity"
to: 1
onFinished: flashAnim.restart()
}
SequentialAnimation {
id: flashAnim
loops: 2
FlashAnim {
to: 0.3
}
FlashAnim {
to: 1
}
}
ParallelAnimation {
id: exitAnim
Anim {
target: message
property: "scale"
to: 0.7
duration: Appearance.anim.durations.large
}
Anim {
target: message
property: "opacity"
to: 0
duration: Appearance.anim.durations.large
}
}
}
}
component FlashAnim: NumberAnimation {
target: message
property: "opacity"
duration: Appearance.anim.durations.small
easing.type: Easing.Linear
}
id: root
readonly property real centerScale: Math.min(1, (lock.screen?.height ?? 1440) / 1440)
readonly property int centerWidth: Config.lock.sizes.centerWidth * centerScale
required property var lock
Layout.fillHeight: true
Layout.fillWidth: false
Layout.preferredWidth: centerWidth
spacing: Appearance.spacing.large * 2
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Appearance.spacing.small
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3secondary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: Time.hourStr
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3primary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: ":"
}
CustomText {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3secondary
font.bold: true
font.family: Appearance.font.family.clock
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 3 * root.centerScale)
text: Time.minuteStr
}
}
CustomText {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: -Appearance.padding.large * 2
color: DynamicColors.palette.m3tertiary
font.bold: true
font.family: Appearance.font.family.mono
font.pointSize: Math.floor(Appearance.font.size.extraLarge * root.centerScale)
text: Time.format("dddd, d MMMM yyyy")
}
CustomClippingRect {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large * 2
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: root.centerWidth / 2
implicitWidth: root.centerWidth / 2
radius: Appearance.rounding.full
MaterialIcon {
anchors.centerIn: parent
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Math.floor(root.centerWidth / 4)
text: "person"
}
CachingImage {
id: pfp
anchors.fill: parent
path: `${Paths.home}/.face`
}
}
CustomRect {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.tPalette.m3surfaceContainer
focus: true
implicitHeight: input.implicitHeight + Appearance.padding.small * 2
implicitWidth: root.centerWidth * 0.8
radius: Appearance.rounding.full
Keys.onPressed: event => {
if (root.lock.unlocking)
return;
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)
inputField.placeholder.animate = false;
root.lock.pam.handleKey(event);
}
onActiveFocusChanged: {
if (!activeFocus)
forceActiveFocus();
}
StateLayer {
function onClicked(): void {
parent.forceActiveFocus();
}
cursorShape: Qt.IBeamCursor
hoverEnabled: false
}
RowLayout {
id: input
anchors.fill: parent
anchors.margins: Appearance.padding.small
spacing: Appearance.spacing.normal
Item {
implicitHeight: fprintIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
MaterialIcon {
id: fprintIcon
anchors.centerIn: parent
animate: true
color: root.lock.pam.fprint.tries >= Config.lock.maxFprintTries ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurface
opacity: root.lock.pam.passwd.active ? 0 : 1
text: {
if (root.lock.pam.fprint.tries >= Config.lock.maxFprintTries)
return "fingerprint_off";
if (root.lock.pam.fprint.active)
return "fingerprint";
return "lock";
}
Behavior on opacity {
Anim {
}
}
}
CircularIndicator {
anchors.fill: parent
running: root.lock.pam.passwd.active
}
}
InputField {
id: inputField
pam: root.lock.pam
}
CustomRect {
color: root.lock.pam.buffer ? DynamicColors.palette.m3primary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: enterIcon.implicitHeight + Appearance.padding.small * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.full
StateLayer {
function onClicked(): void {
root.lock.pam.passwd.start();
}
color: root.lock.pam.buffer ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
}
MaterialIcon {
id: enterIcon
anchors.centerIn: parent
color: root.lock.pam.buffer ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.weight: 500
text: "arrow_forward"
}
}
}
}
Item {
Layout.fillWidth: true
Layout.topMargin: -Appearance.spacing.large
implicitHeight: Math.max(message.implicitHeight, stateMessage.implicitHeight)
Behavior on implicitHeight {
Anim {
}
}
CustomText {
id: stateMessage
readonly property string msg: {
if (Hypr.kbLayout !== Hypr.defaultKbLayout) {
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.\nKeyboard layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.capsLock)
return qsTr("Caps lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
if (Hypr.numLock)
return qsTr("Num lock is ON. Kb layout: %1").arg(Hypr.kbLayoutFull);
return qsTr("Keyboard layout: %1").arg(Hypr.kbLayoutFull);
}
if (Hypr.capsLock && Hypr.numLock)
return qsTr("Caps lock and Num lock are ON.");
if (Hypr.capsLock)
return qsTr("Caps lock is ON.");
if (Hypr.numLock)
return qsTr("Num lock is ON.");
return "";
}
property bool shouldBeVisible
anchors.left: parent.left
anchors.right: parent.right
animateProp: "opacity"
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
horizontalAlignment: Qt.AlignHCenter
lineHeight: 1.2
opacity: shouldBeVisible && !message.msg ? 1 : 0
scale: shouldBeVisible && !message.msg ? 1 : 0.7
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
} else {
text = msg;
}
shouldBeVisible = true;
} else {
shouldBeVisible = false;
}
}
}
CustomText {
id: message
readonly property string msg: {
if (pam.fprintState === "error")
return qsTr("FP ERROR: %1").arg(pam.fprint.message);
if (pam.state === "error")
return qsTr("PW ERROR: %1").arg(pam.passwd.message);
if (pam.lockMessage)
return pam.lockMessage;
if (pam.state === "max" && pam.fprintState === "max")
return qsTr("Maximum password and fingerprint attempts reached.");
if (pam.state === "max") {
if (pam.fprint.available)
return qsTr("Maximum password attempts reached. Please use fingerprint.");
return qsTr("Maximum password attempts reached.");
}
if (pam.fprintState === "max")
return qsTr("Maximum fingerprint attempts reached. Please use password.");
if (pam.state === "fail") {
if (pam.fprint.available)
return qsTr("Incorrect password. Please try again or use fingerprint.");
return qsTr("Incorrect password. Please try again.");
}
if (pam.fprintState === "fail")
return qsTr("Fingerprint not recognized (%1/%2). Please try again or use password.").arg(pam.fprint.tries).arg(Config.lock.maxFprintTries);
return "";
}
readonly property Pam pam: root.lock.pam
anchors.left: parent.left
anchors.right: parent.right
color: DynamicColors.palette.m3error
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.small
horizontalAlignment: Qt.AlignHCenter
opacity: 0
scale: 0.7
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
onMsgChanged: {
if (msg) {
if (opacity > 0) {
animate = true;
text = msg;
animate = false;
exitAnim.stop();
if (scale < 1)
appearAnim.restart();
else
flashAnim.restart();
} else {
text = msg;
exitAnim.stop();
appearAnim.restart();
}
} else {
appearAnim.stop();
flashAnim.stop();
exitAnim.start();
}
}
Connections {
function onFlashMsg(): void {
exitAnim.stop();
if (message.scale < 1)
appearAnim.restart();
else
flashAnim.restart();
}
target: root.lock.pam
}
Anim {
id: appearAnim
properties: "scale,opacity"
target: message
to: 1
onFinished: flashAnim.restart()
}
SequentialAnimation {
id: flashAnim
loops: 2
FlashAnim {
to: 0.3
}
FlashAnim {
to: 1
}
}
ParallelAnimation {
id: exitAnim
Anim {
duration: Appearance.anim.durations.large
property: "scale"
target: message
to: 0.7
}
Anim {
duration: Appearance.anim.durations.large
property: "opacity"
target: message
to: 0
}
}
}
}
component FlashAnim: NumberAnimation {
duration: Appearance.anim.durations.small
easing.type: Easing.Linear
property: "opacity"
target: message
}
}
+57 -59
View File
@@ -5,78 +5,76 @@ import qs.Helpers
import qs.Config
RowLayout {
id: root
id: root
required property var lock
required property var lock
spacing: Appearance.spacing.large * 2
spacing: Appearance.spacing.large * 2
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
CustomRect {
Layout.fillWidth: true
implicitHeight: weather.implicitHeight
CustomRect {
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: weather.implicitHeight
radius: Appearance.rounding.small
topLeftRadius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large
radius: Appearance.rounding.small
color: DynamicColors.tPalette.m3surfaceContainer
WeatherInfo {
id: weather
WeatherInfo {
id: weather
rootHeight: root.height
}
}
CustomRect {
Layout.fillWidth: true
implicitHeight: resources.implicitHeight
radius: Appearance.rounding.small
color: DynamicColors.tPalette.m3surfaceContainer
Resources {
id: resources
rootHeight: root.height
}
}
}
CustomClippingRect {
Layout.fillWidth: true
Layout.fillHeight: true
CustomRect {
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: resources.implicitHeight
radius: Appearance.rounding.small
bottomLeftRadius: Appearance.rounding.large
radius: Appearance.rounding.small
color: DynamicColors.tPalette.m3surfaceContainer
Resources {
id: resources
Media {
id: media
}
}
lock: root.lock
}
}
}
CustomClippingRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomLeftRadius: Appearance.rounding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.small
Center {
lock: root.lock
}
Media {
id: media
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
CustomRect {
Layout.fillWidth: true
Layout.fillHeight: true
lock: root.lock
}
}
}
topRightRadius: Appearance.rounding.large
bottomRightRadius: Appearance.rounding.large
radius: Appearance.rounding.small
color: DynamicColors.tPalette.m3surfaceContainer
Center {
lock: root.lock
}
NotifDock {
lock: root.lock
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
CustomRect {
Layout.fillHeight: true
Layout.fillWidth: true
bottomRightRadius: Appearance.rounding.large
color: DynamicColors.tPalette.m3surfaceContainer
radius: Appearance.rounding.small
topRightRadius: Appearance.rounding.large
NotifDock {
lock: root.lock
}
}
}
}
+120 -123
View File
@@ -9,156 +9,153 @@ import qs.Helpers
import qs.Config
ColumnLayout {
id: root
id: root
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
anchors.topMargin: Appearance.padding.large
anchors.fill: parent
anchors.margins: Appearance.padding.large * 2
anchors.topMargin: Appearance.padding.large
spacing: Appearance.spacing.small
spacing: Appearance.spacing.small
RowLayout {
Layout.fillHeight: false
Layout.fillWidth: true
spacing: Appearance.spacing.normal
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
spacing: Appearance.spacing.normal
CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: prompt.implicitHeight + Appearance.padding.normal * 2
implicitWidth: prompt.implicitWidth + Appearance.padding.normal * 2
radius: Appearance.rounding.small
CustomRect {
implicitWidth: prompt.implicitWidth + Appearance.padding.normal * 2
implicitHeight: prompt.implicitHeight + Appearance.padding.normal * 2
MonoText {
id: prompt
color: DynamicColors.palette.m3primary
radius: Appearance.rounding.small
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
text: ">"
}
}
MonoText {
id: prompt
MonoText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
text: "caelestiafetch.sh"
}
anchors.centerIn: parent
text: ">"
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
color: DynamicColors.palette.m3onPrimary
}
}
WrappedLoader {
Layout.fillHeight: true
active: !iconLoader.active
MonoText {
Layout.fillWidth: true
text: "caelestiafetch.sh"
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
elide: Text.ElideRight
}
sourceComponent: OsLogo {
}
}
}
WrappedLoader {
Layout.fillHeight: true
active: !iconLoader.active
RowLayout {
Layout.fillHeight: false
Layout.fillWidth: true
spacing: height * 0.15
sourceComponent: OsLogo {}
}
}
WrappedLoader {
id: iconLoader
RowLayout {
Layout.fillWidth: true
Layout.fillHeight: false
spacing: height * 0.15
Layout.fillHeight: true
active: root.width > 320
WrappedLoader {
id: iconLoader
sourceComponent: OsLogo {
}
}
Layout.fillHeight: true
active: root.width > 320
ColumnLayout {
Layout.bottomMargin: Appearance.padding.normal
Layout.fillWidth: true
Layout.leftMargin: iconLoader.active ? 0 : width * 0.1
Layout.topMargin: Appearance.padding.normal
spacing: Appearance.spacing.normal
sourceComponent: OsLogo {}
}
WrappedLoader {
Layout.fillWidth: true
active: !batLoader.active && root.height > 200
ColumnLayout {
Layout.fillWidth: true
Layout.topMargin: Appearance.padding.normal
Layout.bottomMargin: Appearance.padding.normal
Layout.leftMargin: iconLoader.active ? 0 : width * 0.1
spacing: Appearance.spacing.normal
sourceComponent: FetchText {
text: `OS : ${SystemInfo.osPrettyName || SysInfo.osName}`
}
}
WrappedLoader {
Layout.fillWidth: true
active: !batLoader.active && root.height > 200
WrappedLoader {
Layout.fillWidth: true
active: root.height > (batLoader.active ? 200 : 110)
sourceComponent: FetchText {
text: `OS : ${SystemInfo.osPrettyName || SysInfo.osName}`
}
}
sourceComponent: FetchText {
text: `WM : ${SystemInfo.wm}`
}
}
WrappedLoader {
Layout.fillWidth: true
active: root.height > (batLoader.active ? 200 : 110)
WrappedLoader {
Layout.fillWidth: true
active: !batLoader.active || root.height > 110
sourceComponent: FetchText {
text: `WM : ${SystemInfo.wm}`
}
}
sourceComponent: FetchText {
text: `USER: ${SystemInfo.user}`
}
}
WrappedLoader {
Layout.fillWidth: true
active: !batLoader.active || root.height > 110
FetchText {
text: `UP : ${SystemInfo.uptime}`
}
sourceComponent: FetchText {
text: `USER: ${SystemInfo.user}`
}
}
WrappedLoader {
id: batLoader
FetchText {
text: `UP : ${SystemInfo.uptime}`
}
Layout.fillWidth: true
active: UPower.displayDevice.isLaptopBattery
WrappedLoader {
id: batLoader
sourceComponent: FetchText {
text: `BATT: ${[UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state) ? "(+) " : ""}${Math.round(UPower.displayDevice.percentage * 100)}%`
}
}
}
}
Layout.fillWidth: true
active: UPower.displayDevice.isLaptopBattery
WrappedLoader {
Layout.alignment: Qt.AlignHCenter
active: root.height > 180
sourceComponent: FetchText {
text: `BATT: ${[UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state) ? "(+) " : ""}${Math.round(UPower.displayDevice.percentage * 100)}%`
}
}
}
}
sourceComponent: RowLayout {
spacing: Appearance.spacing.large
WrappedLoader {
Layout.alignment: Qt.AlignHCenter
active: root.height > 180
Repeater {
model: Math.max(0, Math.min(8, root.width / (Appearance.font.size.larger * 2 + Appearance.spacing.large)))
sourceComponent: RowLayout {
spacing: Appearance.spacing.large
CustomRect {
required property int index
Repeater {
model: Math.max(0, Math.min(8, root.width / (Appearance.font.size.larger * 2 + Appearance.spacing.large)))
color: DynamicColors.palette[`term${index}`]
implicitHeight: Appearance.font.size.larger * 2
implicitWidth: implicitHeight
radius: Appearance.rounding.small
}
}
}
}
CustomRect {
required property int index
implicitWidth: implicitHeight
implicitHeight: Appearance.font.size.larger * 2
color: DynamicColors.palette[`term${index}`]
radius: Appearance.rounding.small
}
}
}
}
component WrappedLoader: Loader {
visible: active
}
component OsLogo: ColoredIcon {
source: SystemInfo.osLogo
implicitSize: height
color: DynamicColors.palette.m3primary
layer.enabled: Config.lock.recolorLogo || SystemInfo.isDefaultLogo
}
component FetchText: MonoText {
Layout.fillWidth: true
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
elide: Text.ElideRight
}
component MonoText: CustomText {
font.family: Appearance.font.family.mono
}
component FetchText: MonoText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
}
component MonoText: CustomText {
font.family: Appearance.font.family.mono
}
component OsLogo: ColoredIcon {
color: DynamicColors.palette.m3primary
implicitSize: height
layer.enabled: Config.lock.recolorLogo || SystemInfo.isDefaultLogo
source: SystemInfo.osLogo
}
component WrappedLoader: Loader {
visible: active
}
}
-39
View File
@@ -1,39 +0,0 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import qs.Config
import qs.Helpers
Scope {
id: root
required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction( action: var ): void {
if ( !action )
return;
if ( action === "lock" )
lock.lock.locked = true;
else if ( action === "unlock" )
lock.lock.locked = false;
else if ( typeof action === "string" )
Hypr.dispatch( action );
else
Quickshell.execDetached( action );
}
Variants {
model: Config.general.idle.timeouts
IdleMonitor {
required property var modelData
enabled: root.enabled && modelData.timeout > 0 ? true : false
timeout: modelData.timeout
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
}
}
}
+39
View File
@@ -0,0 +1,39 @@
pragma ComponentBehavior: Bound
import Quickshell
import Quickshell.Wayland
import qs.Config
import qs.Helpers
Scope {
id: root
required property Lock lock
readonly property bool enabled: !Players.list.some( p => p.isPlaying )
function handleIdleAction( action: var ): void {
if ( !action )
return;
if ( action === "lock" )
lock.lock.locked = true;
else if ( action === "unlock" )
lock.lock.locked = false;
else if ( typeof action === "string" )
Hypr.dispatch( action );
else
Quickshell.execDetached( action );
}
Variants {
model: Config.general.idle.timeouts
IdleMonitor {
required property var modelData
enabled: root.enabled && modelData.timeout > 0 ? true : false
timeout: modelData.timeout
onIsIdleChanged: root.handleIdleAction( isIdle ? modelData.idleAction : modelData.activeAction )
}
}
}
+112 -116
View File
@@ -9,142 +9,138 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property Pam pam
readonly property alias placeholder: placeholder
property string buffer
property string buffer
required property Pam pam
readonly property alias placeholder: placeholder
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillHeight: true
Layout.fillWidth: true
clip: true
clip: true
Connections {
function onBufferChanged(): void {
if (root.pam.buffer.length > root.buffer.length) {
charList.bindImWidth();
} else if (root.pam.buffer.length === 0) {
charList.implicitWidth = charList.implicitWidth;
placeholder.animate = true;
}
Connections {
target: root.pam
root.buffer = root.pam.buffer;
}
function onBufferChanged(): void {
if (root.pam.buffer.length > root.buffer.length) {
charList.bindImWidth();
} else if (root.pam.buffer.length === 0) {
charList.implicitWidth = charList.implicitWidth;
placeholder.animate = true;
}
target: root.pam
}
root.buffer = root.pam.buffer;
}
}
CustomText {
id: placeholder
CustomText {
id: placeholder
anchors.centerIn: parent
animate: true
color: root.pam.passwd.active ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.normal
opacity: root.buffer ? 0 : 1
text: {
if (root.pam.passwd.active)
return qsTr("Loading...");
if (root.pam.state === "max")
return qsTr("You have reached the maximum number of tries");
return qsTr("Enter your password");
}
anchors.centerIn: parent
Behavior on opacity {
Anim {
}
}
}
text: {
if (root.pam.passwd.active)
return qsTr("Loading...");
if (root.pam.state === "max")
return qsTr("You have reached the maximum number of tries");
return qsTr("Enter your password");
}
ListView {
id: charList
animate: true
color: root.pam.passwd.active ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.normal
font.family: Appearance.font.family.mono
readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
opacity: root.buffer ? 0 : 1
function bindImWidth(): void {
imWidthBehavior.enabled = false;
implicitWidth = Qt.binding(() => fullWidth);
imWidthBehavior.enabled = true;
}
Behavior on opacity {
Anim {}
}
}
anchors.centerIn: parent
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
implicitHeight: Appearance.font.size.normal
implicitWidth: fullWidth
interactive: false
orientation: Qt.Horizontal
spacing: Appearance.spacing.small / 2
ListView {
id: charList
delegate: CustomRect {
id: ch
readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
color: DynamicColors.palette.m3onSurface
implicitHeight: charList.implicitHeight
implicitWidth: implicitHeight
opacity: 0
radius: Appearance.rounding.small / 2
scale: 0
function bindImWidth(): void {
imWidthBehavior.enabled = false;
implicitWidth = Qt.binding(() => fullWidth);
imWidthBehavior.enabled = true;
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
anchors.centerIn: parent
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
Component.onCompleted: {
opacity = 1;
scale = 1;
}
ListView.onRemove: removeAnim.start()
implicitWidth: fullWidth
implicitHeight: Appearance.font.size.normal
SequentialAnimation {
id: removeAnim
orientation: Qt.Horizontal
spacing: Appearance.spacing.small / 2
interactive: false
PropertyAction {
property: "ListView.delayRemove"
target: ch
value: true
}
model: ScriptModel {
values: root.buffer.split("")
}
ParallelAnimation {
Anim {
property: "opacity"
target: ch
to: 0
}
delegate: CustomRect {
id: ch
Anim {
property: "scale"
target: ch
to: 0.5
}
}
implicitWidth: implicitHeight
implicitHeight: charList.implicitHeight
PropertyAction {
property: "ListView.delayRemove"
target: ch
value: false
}
}
}
Behavior on implicitWidth {
id: imWidthBehavior
color: DynamicColors.palette.m3onSurface
radius: Appearance.rounding.small / 2
opacity: 0
scale: 0
Component.onCompleted: {
opacity = 1;
scale = 1;
}
ListView.onRemove: removeAnim.start()
SequentialAnimation {
id: removeAnim
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: true
}
ParallelAnimation {
Anim {
target: ch
property: "opacity"
to: 0
}
Anim {
target: ch
property: "scale"
to: 0.5
}
}
PropertyAction {
target: ch
property: "ListView.delayRemove"
value: false
}
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
}
Behavior on implicitWidth {
id: imWidthBehavior
Anim {}
}
}
Anim {
}
}
model: ScriptModel {
values: root.buffer.split("")
}
}
}
+6 -4
View File
@@ -18,11 +18,12 @@ Scope {
WlSessionLock {
id: lock
signal unlock
signal requestLock
signal unlock
LockSurface {
id: lockSurface
lock: lock
pam: pam
}
@@ -35,16 +36,17 @@ Scope {
}
IpcHandler {
target: "lock"
function lock() {
return lock.locked = true;
}
target: "lock"
}
CustomShortcut {
name: "lock"
description: "Lock the current session"
name: "lock"
onPressed: {
lock.locked = true;
}
+165 -154
View File
@@ -8,185 +8,196 @@ import qs.Helpers
import qs.Components
WlSessionLockSurface {
id: root
id: root
required property WlSessionLock lock
required property Pam pam
required property WlSessionLock lock
required property Pam pam
readonly property alias unlocking: unlockAnim.running
readonly property alias unlocking: unlockAnim.running
color: "transparent"
color: "transparent"
Connections {
function onUnlock(): void {
unlockAnim.start();
}
Connections {
target: root.lock
target: root.lock
}
function onUnlock(): void {
unlockAnim.start();
}
}
SequentialAnimation {
id: unlockAnim
SequentialAnimation {
id: unlockAnim
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
properties: "implicitWidth,implicitHeight"
target: lockContent
to: lockContent.size
}
ParallelAnimation {
Anim {
target: lockContent
properties: "implicitWidth,implicitHeight"
to: lockContent.size
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
Anim {
target: lockBg
property: "radius"
to: lockContent.radius
}
Anim {
target: content
property: "scale"
to: 0
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
Anim {
target: content
property: "opacity"
to: 0
duration: Appearance.anim.durations.small
}
Anim {
target: lockIcon
property: "opacity"
to: 1
duration: Appearance.anim.durations.large
}
SequentialAnimation {
PauseAnimation {
duration: Appearance.anim.durations.small
}
Anim {
target: lockContent
property: "opacity"
to: 0
}
}
}
PropertyAction {
target: root.lock
property: "locked"
value: false
}
}
Anim {
property: "radius"
target: lockBg
to: lockContent.radius
}
ParallelAnimation {
id: initAnim
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "scale"
target: content
to: 0
}
running: true
Anim {
duration: Appearance.anim.durations.small
property: "opacity"
target: content
to: 0
}
SequentialAnimation {
ParallelAnimation {
Anim {
target: lockContent
property: "scale"
to: 1
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
ParallelAnimation {
Anim {
target: lockIcon
property: "opacity"
to: 0
}
Anim {
target: content
property: "opacity"
to: 1
}
Anim {
target: content
property: "scale"
to: 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
Anim {
target: lockBg
property: "radius"
to: Appearance.rounding.large * 1.5
}
Anim {
target: lockContent
property: "implicitWidth"
to: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult * Config.lock.sizes.ratio
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
Anim {
target: lockContent
property: "implicitHeight"
to: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
}
Anim {
duration: Appearance.anim.durations.large
property: "opacity"
target: lockIcon
to: 1
}
SequentialAnimation {
PauseAnimation {
duration: Appearance.anim.durations.small
}
Anim {
property: "opacity"
target: lockContent
to: 0
}
}
}
PropertyAction {
property: "locked"
target: root.lock
value: false
}
}
ParallelAnimation {
id: initAnim
running: true
SequentialAnimation {
ParallelAnimation {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
property: "scale"
target: lockContent
to: 1
}
}
ParallelAnimation {
Anim {
property: "opacity"
target: lockIcon
to: 0
}
Anim {
property: "opacity"
target: content
to: 1
}
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "scale"
target: content
to: 1
}
Anim {
property: "radius"
target: lockBg
to: Appearance.rounding.large * 1.5
}
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitWidth"
target: lockContent
to: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult * Config.lock.sizes.ratio
}
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "implicitHeight"
target: lockContent
to: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult
}
}
}
}
Image {
id: background
anchors.fill: parent
source: WallpaperPath.lockscreenBg
}
Item {
id: lockContent
Item {
id: lockContent
readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
readonly property int radius: size / 4 * Appearance.rounding.scale
readonly property int radius: size / 4 * Appearance.rounding.scale
readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
anchors.centerIn: parent
implicitWidth: size
implicitHeight: size
anchors.centerIn: parent
implicitHeight: size
implicitWidth: size
scale: 0
scale: 0
CustomRect {
id: lockBg
CustomRect {
id: lockBg
anchors.fill: parent
color: DynamicColors.palette.m3surface
anchors.fill: parent
color: DynamicColors.palette.m3surface
layer.enabled: true
opacity: DynamicColors.transparency.enabled ? DynamicColors.transparency.base : 1
radius: lockContent.radius
opacity: DynamicColors.transparency.enabled ? DynamicColors.transparency.base : 1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
blurMax: 15
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 0.7)
}
}
layer.effect: MultiEffect {
blurMax: 15
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 0.7)
shadowEnabled: true
}
}
MaterialIcon {
id: lockIcon
MaterialIcon {
id: lockIcon
anchors.centerIn: parent
text: "lock"
font.pointSize: Appearance.font.size.extraLarge * 4
font.bold: true
}
anchors.centerIn: parent
font.bold: true
font.pointSize: Appearance.font.size.extraLarge * 4
text: "lock"
}
Content {
id: content
Content {
id: content
anchors.centerIn: parent
width: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult * Config.lock.sizes.ratio - Appearance.padding.large * 2
height: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult - Appearance.padding.large * 2
lock: root
opacity: 0
scale: 0
}
}
anchors.centerIn: parent
height: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult - Appearance.padding.large * 2
lock: root
opacity: 0
scale: 0
width: (root.screen?.height ?? 0) * Config.lock.sizes.heightMult * Config.lock.sizes.ratio - Appearance.padding.large * 2
}
}
}
+156 -159
View File
@@ -8,198 +8,195 @@ import qs.Helpers
import qs.Config
Item {
id: root
id: root
required property var lock
required property var lock
anchors.fill: parent
Image {
anchors.fill: parent
source: Players.active?.trackArtUrl ?? ""
Image {
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
layer.enabled: true
opacity: status === Image.Ready ? 1 : 0
source: Players.active?.trackArtUrl ?? ""
sourceSize.height: height
sourceSize.width: width
asynchronous: true
fillMode: Image.PreserveAspectCrop
sourceSize.width: width
sourceSize.height: height
layer.effect: OpacityMask {
maskSource: mask
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.extraLarge
}
}
}
layer.enabled: true
layer.effect: OpacityMask {
maskSource: mask
}
Rectangle {
id: mask
opacity: status === Image.Ready ? 1 : 0
anchors.fill: parent
layer.enabled: true
visible: false
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.extraLarge
}
}
}
gradient: Gradient {
orientation: Gradient.Horizontal
Rectangle {
id: mask
GradientStop {
color: Qt.rgba(0, 0, 0, 0.5)
position: 0
}
anchors.fill: parent
layer.enabled: true
visible: false
GradientStop {
color: Qt.rgba(0, 0, 0, 0.2)
position: 0.4
}
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
color: Qt.rgba(0, 0, 0, 0)
position: 0.8
}
}
}
GradientStop {
position: 0
color: Qt.rgba(0, 0, 0, 0.5)
}
GradientStop {
position: 0.4
color: Qt.rgba(0, 0, 0, 0.2)
}
GradientStop {
position: 0.8
color: Qt.rgba(0, 0, 0, 0)
}
}
}
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: Appearance.padding.large
anchors.fill: parent
anchors.margins: Appearance.padding.large
CustomText {
Layout.bottomMargin: Appearance.spacing.larger
Layout.topMargin: Appearance.padding.large
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
font.weight: 500
text: qsTr("Now playing")
}
CustomText {
Layout.topMargin: Appearance.padding.large
Layout.bottomMargin: Appearance.spacing.larger
text: qsTr("Now playing")
color: DynamicColors.palette.m3onSurfaceVariant
font.family: Appearance.font.family.mono
font.weight: 500
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3primary
elide: Text.ElideRight
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.large
font.weight: 600
horizontalAlignment: Text.AlignHCenter
text: Players.active?.trackArtist ?? qsTr("No media")
}
CustomText {
Layout.fillWidth: true
animate: true
text: Players.active?.trackArtist ?? qsTr("No media")
color: DynamicColors.palette.m3primary
horizontalAlignment: Text.AlignHCenter
font.pointSize: Appearance.font.size.large
font.family: Appearance.font.family.mono
font.weight: 600
elide: Text.ElideRight
}
CustomText {
Layout.fillWidth: true
animate: true
elide: Text.ElideRight
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.larger
horizontalAlignment: Text.AlignHCenter
text: Players.active?.trackTitle ?? qsTr("No media")
}
CustomText {
Layout.fillWidth: true
animate: true
text: Players.active?.trackTitle ?? qsTr("No media")
horizontalAlignment: Text.AlignHCenter
font.pointSize: Appearance.font.size.larger
font.family: Appearance.font.family.mono
elide: Text.ElideRight
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.spacing.large * 1.2
spacing: Appearance.spacing.large
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Appearance.spacing.large * 1.2
Layout.bottomMargin: Appearance.padding.large
PlayerControl {
function onClicked(): void {
if (Players.active?.canGoPrevious)
Players.active.previous();
}
spacing: Appearance.spacing.large
icon: "skip_previous"
}
PlayerControl {
icon: "skip_previous"
PlayerControl {
function onClicked(): void {
if (Players.active?.canTogglePlaying)
Players.active.togglePlaying();
}
function onClicked(): void {
if (Players.active?.canGoPrevious)
Players.active.previous();
}
}
active: Players.active?.isPlaying ?? false
animate: true
colour: "Primary"
icon: active ? "pause" : "play_arrow"
level: active ? 2 : 1
}
PlayerControl {
animate: true
icon: active ? "pause" : "play_arrow"
colour: "Primary"
level: active ? 2 : 1
active: Players.active?.isPlaying ?? false
PlayerControl {
function onClicked(): void {
if (Players.active?.canGoNext)
Players.active.next();
}
function onClicked(): void {
if (Players.active?.canTogglePlaying)
Players.active.togglePlaying();
}
}
icon: "skip_next"
}
}
}
PlayerControl {
icon: "skip_next"
component PlayerControl: CustomRect {
id: control
function onClicked(): void {
if (Players.active?.canGoNext)
Players.active.next();
}
}
}
}
property bool active
property alias animate: controlIcon.animate
property string colour: "Secondary"
property alias icon: controlIcon.text
property int level: 1
component PlayerControl: CustomRect {
id: control
function onClicked(): void {
}
property alias animate: controlIcon.animate
property alias icon: controlIcon.text
property bool active
property string colour: "Secondary"
property int level: 1
Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : active ? Appearance.padding.small * 2 : 0)
color: active ? DynamicColors.palette[`m3${colour.toLowerCase()}`] : DynamicColors.palette[`m3${colour.toLowerCase()}Container`]
implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2
implicitWidth: controlIcon.implicitWidth + Appearance.padding.large * 2
radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
function onClicked(): void {
}
Behavior on Layout.preferredWidth {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
Behavior on radius {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
Layout.preferredWidth: implicitWidth + (controlState.pressed ? Appearance.padding.normal * 2 : active ? Appearance.padding.small * 2 : 0)
implicitWidth: controlIcon.implicitWidth + Appearance.padding.large * 2
implicitHeight: controlIcon.implicitHeight + Appearance.padding.normal * 2
Elevation {
anchors.fill: parent
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
radius: parent.radius
z: -1
}
color: active ? DynamicColors.palette[`m3${colour.toLowerCase()}`] : DynamicColors.palette[`m3${colour.toLowerCase()}Container`]
radius: active || controlState.pressed ? Appearance.rounding.small : Appearance.rounding.normal
StateLayer {
id: controlState
Elevation {
anchors.fill: parent
radius: parent.radius
z: -1
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
}
function onClicked(): void {
control.onClicked();
}
StateLayer {
id: controlState
color: control.active ? DynamicColors.palette[`m3on${control.colour}`] : DynamicColors.palette[`m3on${control.colour}Container`]
}
color: control.active ? DynamicColors.palette[`m3on${control.colour}`] : DynamicColors.palette[`m3on${control.colour}Container`]
MaterialIcon {
id: controlIcon
function onClicked(): void {
control.onClicked();
}
}
anchors.centerIn: parent
color: control.active ? DynamicColors.palette[`m3on${control.colour}`] : DynamicColors.palette[`m3on${control.colour}Container`]
fill: control.active ? 1 : 0
font.pointSize: Appearance.font.size.large
MaterialIcon {
id: controlIcon
anchors.centerIn: parent
color: control.active ? DynamicColors.palette[`m3on${control.colour}`] : DynamicColors.palette[`m3on${control.colour}Container`]
font.pointSize: Appearance.font.size.large
fill: control.active ? 1 : 0
Behavior on fill {
Anim {}
}
}
Behavior on Layout.preferredWidth {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
Behavior on radius {
Anim {
duration: Appearance.anim.durations.expressiveFastSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
}
}
}
Behavior on fill {
Anim {
}
}
}
}
}
+111 -115
View File
@@ -11,135 +11,131 @@ import qs.Config
import qs.Daemons
ColumnLayout {
id: root
id: root
required property var lock
required property var lock
anchors.fill: parent
anchors.margins: Appearance.padding.large
anchors.fill: parent
anchors.margins: Appearance.padding.large
spacing: Appearance.spacing.smaller
spacing: Appearance.spacing.smaller
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.family: Appearance.font.family.mono
font.weight: 500
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
}
CustomText {
Layout.fillWidth: true
text: NotifServer.list.length > 0 ? qsTr("%1 notification%2").arg(NotifServer.list.length).arg(NotifServer.list.length === 1 ? "" : "s") : qsTr("Notifications")
color: DynamicColors.palette.m3outline
font.family: Appearance.font.family.mono
font.weight: 500
elide: Text.ElideRight
}
ClippingRectangle {
id: clipRect
ClippingRectangle {
id: clipRect
Layout.fillHeight: true
Layout.fillWidth: true
color: "transparent"
radius: Appearance.rounding.small
Layout.fillWidth: true
Layout.fillHeight: true
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: NotifServer.list.length > 0 ? 0 : 1
radius: Appearance.rounding.small
color: "transparent"
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.extraLarge
}
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.large
Loader {
anchors.centerIn: parent
active: opacity > 0
opacity: NotifServer.list.length > 0 ? 0 : 1
Image {
asynchronous: true
fillMode: Image.PreserveAspectFit
layer.enabled: true
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
sourceSize.width: clipRect.width * 0.8
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.large
layer.effect: Coloriser {
brightness: 1
colorizationColor: DynamicColors.palette.m3outlineVariant
}
}
Image {
asynchronous: true
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
fillMode: Image.PreserveAspectFit
sourceSize.width: clipRect.width * 0.8
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
font.family: Appearance.font.family.mono
font.pointSize: Appearance.font.size.large
font.weight: 500
text: qsTr("No Notifications")
}
}
}
layer.enabled: true
layer.effect: Coloriser {
colorizationColor: DynamicColors.palette.m3outlineVariant
brightness: 1
}
}
CustomListView {
anchors.fill: parent
clip: true
spacing: Appearance.spacing.small
CustomText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("No Notifications")
color: DynamicColors.palette.m3outlineVariant
font.pointSize: Appearance.font.size.large
font.family: Appearance.font.family.mono
font.weight: 500
}
}
add: Transition {
Anim {
from: 0
property: "opacity"
to: 1
}
Behavior on opacity {
Anim {
duration: Appearance.anim.durations.extraLarge
}
}
}
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
from: 0
property: "scale"
to: 1
}
}
delegate: NotifGroup {
}
displaced: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
CustomListView {
anchors.fill: parent
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "y"
}
}
model: ScriptModel {
values: {
const list = NotifServer.notClosed.map(n => [n.appName, null]);
return [...new Map(list).keys()];
}
}
move: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
spacing: Appearance.spacing.small
clip: true
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
property: "y"
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
model: ScriptModel {
values: {
const list = NotifServer.notClosed.map(n => [n.appName, null]);
return [...new Map(list).keys()];
}
}
delegate: NotifGroup {}
add: Transition {
Anim {
property: "opacity"
from: 0
to: 1
}
Anim {
property: "scale"
from: 0
to: 1
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
remove: Transition {
Anim {
property: "opacity"
to: 0
}
Anim {
property: "scale"
to: 0.6
}
}
move: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
Anim {
property: "y"
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
displaced: Transition {
Anim {
properties: "opacity,scale"
to: 1
}
Anim {
property: "y"
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
}
}
Anim {
property: "scale"
to: 0.6
}
}
}
}
}
+247 -248
View File
@@ -12,305 +12,304 @@ import qs.Config
import qs.Daemons
CustomRect {
id: root
id: root
required property string modelData
readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
property bool expanded
readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? ""
required property string modelData
readonly property list<var> notifs: NotifServer.list.filter(notif => notif.appName === modelData)
readonly property string urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? "critical" : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? "normal" : "low"
readonly property list<var> notifs: NotifServer.list.filter(notif => notif.appName === modelData)
readonly property string image: notifs.find(n => n.image.length > 0)?.image ?? ""
readonly property string appIcon: notifs.find(n => n.appIcon.length > 0)?.appIcon ?? ""
readonly property string urgency: notifs.some(n => n.urgency === NotificationUrgency.Critical) ? "critical" : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? "normal" : "low"
anchors.left: parent?.left
anchors.right: parent?.right
clip: true
color: root.urgency === "critical" ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
radius: Appearance.rounding.normal
property bool expanded
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
RowLayout {
id: content
clip: true
radius: Appearance.rounding.normal
color: root.urgency === "critical" ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
anchors.left: parent.left
anchors.margins: Appearance.padding.normal
anchors.right: parent.right
anchors.top: parent.top
spacing: Appearance.spacing.normal
RowLayout {
id: content
Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Appearance.padding.normal
Component {
id: imageComp
spacing: Appearance.spacing.normal
Image {
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
height: Config.notifs.sizes.image
source: Qt.resolvedUrl(root.image)
width: Config.notifs.sizes.image
}
}
Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
Component {
id: appIconComp
Component {
id: imageComp
ColoredIcon {
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
layer.enabled: root.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.appIcon)
}
}
Image {
source: Qt.resolvedUrl(root.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
}
}
Component {
id: materialIconComp
Component {
id: appIconComp
MaterialIcon {
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: Appearance.font.size.large
text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
}
}
ColoredIcon {
implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
source: Quickshell.iconPath(root.appIcon)
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
ClippingRectangle {
anchors.fill: parent
color: root.urgency === "critical" ? DynamicColors.palette.m3error : root.urgency === "low" ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3) : DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.full
Component {
id: materialIconComp
Loader {
anchors.centerIn: parent
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
}
}
MaterialIcon {
text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: Appearance.font.size.large
}
}
Loader {
active: root.appIcon && root.image
anchors.bottom: parent.bottom
anchors.right: parent.right
ClippingRectangle {
anchors.fill: parent
color: root.urgency === "critical" ? DynamicColors.palette.m3error : root.urgency === "low" ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3) : DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.full
sourceComponent: CustomRect {
color: root.urgency === "critical" ? DynamicColors.palette.m3error : root.urgency === "low" ? DynamicColors.palette.m3surfaceContainerHighest : DynamicColors.palette.m3secondaryContainer
implicitHeight: Config.notifs.sizes.badge
implicitWidth: Config.notifs.sizes.badge
radius: Appearance.rounding.full
Loader {
anchors.centerIn: parent
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
}
}
ColoredIcon {
anchors.centerIn: parent
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
layer.enabled: root.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.appIcon)
}
}
}
}
Loader {
anchors.right: parent.right
anchors.bottom: parent.bottom
active: root.appIcon && root.image
ColumnLayout {
Layout.bottomMargin: -Appearance.padding.small / 2 - (root.expanded ? 0 : spacing)
Layout.fillWidth: true
Layout.topMargin: -Appearance.padding.small
spacing: Math.round(Appearance.spacing.small / 2)
sourceComponent: CustomRect {
implicitWidth: Config.notifs.sizes.badge
implicitHeight: Config.notifs.sizes.badge
RowLayout {
Layout.bottomMargin: -parent.spacing
Layout.fillWidth: true
spacing: Appearance.spacing.smaller
color: root.urgency === "critical" ? DynamicColors.palette.m3error : root.urgency === "low" ? DynamicColors.palette.m3surfaceContainerHighest : DynamicColors.palette.m3secondaryContainer
radius: Appearance.rounding.full
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.small
text: root.modelData
}
ColoredIcon {
anchors.centerIn: parent
implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
source: Quickshell.iconPath(root.appIcon)
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : root.urgency === "low" ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
}
}
CustomText {
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
text: root.notifs[0]?.timeStr ?? ""
}
ColumnLayout {
Layout.topMargin: -Appearance.padding.small
Layout.bottomMargin: -Appearance.padding.small / 2 - (root.expanded ? 0 : spacing)
Layout.fillWidth: true
spacing: Math.round(Appearance.spacing.small / 2)
CustomRect {
Layout.preferredWidth: root.notifs.length > Config.notifs.groupPreviewNum ? implicitWidth : 0
color: root.urgency === "critical" ? DynamicColors.palette.m3error : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
implicitHeight: groupCount.implicitHeight + Appearance.padding.small
implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2
opacity: root.notifs.length > Config.notifs.groupPreviewNum ? 1 : 0
radius: Appearance.rounding.full
RowLayout {
Layout.bottomMargin: -parent.spacing
Layout.fillWidth: true
spacing: Appearance.spacing.smaller
Behavior on Layout.preferredWidth {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
CustomText {
Layout.fillWidth: true
text: root.modelData
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.small
elide: Text.ElideRight
}
StateLayer {
function onClicked(): void {
root.expanded = !root.expanded;
}
CustomText {
animate: true
text: root.notifs[0]?.timeStr ?? ""
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.small
}
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
}
CustomRect {
implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2
implicitHeight: groupCount.implicitHeight + Appearance.padding.small
RowLayout {
id: expandBtn
color: root.urgency === "critical" ? DynamicColors.palette.m3error : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
radius: Appearance.rounding.full
anchors.centerIn: parent
spacing: Appearance.spacing.small / 2
opacity: root.notifs.length > Config.notifs.groupPreviewNum ? 1 : 0
Layout.preferredWidth: root.notifs.length > Config.notifs.groupPreviewNum ? implicitWidth : 0
CustomText {
id: groupCount
StateLayer {
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
Layout.leftMargin: Appearance.padding.small / 2
animate: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.small
text: root.notifs.length
}
function onClicked(): void {
root.expanded = !root.expanded;
}
}
MaterialIcon {
Layout.rightMargin: -Appearance.padding.small / 2
animate: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
text: root.expanded ? "expand_less" : "expand_more"
}
}
}
}
RowLayout {
id: expandBtn
Repeater {
model: ScriptModel {
values: root.notifs.slice(0, Config.notifs.groupPreviewNum)
}
anchors.centerIn: parent
spacing: Appearance.spacing.small / 2
NotifLine {
id: notif
CustomText {
id: groupCount
ParallelAnimation {
running: true
Layout.leftMargin: Appearance.padding.small / 2
animate: true
text: root.notifs.length
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
font.pointSize: Appearance.font.size.small
}
Anim {
from: 0
property: "opacity"
target: notif
to: 1
}
MaterialIcon {
Layout.rightMargin: -Appearance.padding.small / 2
animate: true
text: root.expanded ? "expand_less" : "expand_more"
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
}
}
Anim {
from: 0.7
property: "scale"
target: notif
to: 1
}
Behavior on opacity {
Anim {}
}
Anim {
from: 0
property: "preferredHeight"
target: notif.Layout
to: notif.implicitHeight
}
}
Behavior on Layout.preferredWidth {
Anim {}
}
}
}
ParallelAnimation {
running: notif.modelData.closed
Repeater {
model: ScriptModel {
values: root.notifs.slice(0, Config.notifs.groupPreviewNum)
}
onFinished: notif.modelData.unlock(notif)
NotifLine {
id: notif
Anim {
property: "opacity"
target: notif
to: 0
}
ParallelAnimation {
running: true
Anim {
property: "scale"
target: notif
to: 0.7
}
Anim {
target: notif
property: "opacity"
from: 0
to: 1
}
Anim {
target: notif
property: "scale"
from: 0.7
to: 1
}
Anim {
target: notif.Layout
property: "preferredHeight"
from: 0
to: notif.implicitHeight
}
}
Anim {
property: "preferredHeight"
target: notif.Layout
to: 0
}
}
}
}
ParallelAnimation {
running: notif.modelData.closed
onFinished: notif.modelData.unlock(notif)
Loader {
Layout.fillWidth: true
Layout.preferredHeight: root.expanded ? implicitHeight : 0
active: opacity > 0
opacity: root.expanded ? 1 : 0
Anim {
target: notif
property: "opacity"
to: 0
}
Anim {
target: notif
property: "scale"
to: 0.7
}
Anim {
target: notif.Layout
property: "preferredHeight"
to: 0
}
}
}
}
Behavior on opacity {
Anim {
}
}
sourceComponent: ColumnLayout {
Repeater {
model: ScriptModel {
values: root.notifs.slice(Config.notifs.groupPreviewNum)
}
Loader {
Layout.fillWidth: true
NotifLine {
}
}
}
}
}
}
opacity: root.expanded ? 1 : 0
Layout.preferredHeight: root.expanded ? implicitHeight : 0
active: opacity > 0
component NotifLine: CustomText {
id: notifLine
sourceComponent: ColumnLayout {
Repeater {
model: ScriptModel {
values: root.notifs.slice(Config.notifs.groupPreviewNum)
}
required property NotifServer.Notif modelData
NotifLine {}
}
}
Layout.fillWidth: true
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
text: {
const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
Behavior on opacity {
Anim {}
}
}
}
}
if (metrics.text === metrics.elidedText)
return `${summary} <span style='color:${color}'>${body}</span>`;
Behavior on implicitHeight {
Anim {
duration: Appearance.anim.durations.expressiveDefaultSpatial
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
}
}
const t = metrics.elidedText.length - 3;
if (t < summary.length)
return `${summary.slice(0, t)}...`;
component NotifLine: CustomText {
id: notifLine
return `${summary} <span style='color:${color}'>${body.slice(0, t - summary.length)}...</span>`;
}
textFormat: Text.MarkdownText
required property NotifServer.Notif modelData
Component.onCompleted: modelData.lock(this)
Component.onDestruction: modelData.unlock(this)
Layout.fillWidth: true
textFormat: Text.MarkdownText
text: {
const summary = modelData.summary.replace(/\n/g, " ");
const body = modelData.body.replace(/\n/g, " ");
const color = root.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline;
TextMetrics {
id: metrics
if (metrics.text === metrics.elidedText)
return `${summary} <span style='color:${color}'>${body}</span>`;
const t = metrics.elidedText.length - 3;
if (t < summary.length)
return `${summary.slice(0, t)}...`;
return `${summary} <span style='color:${color}'>${body.slice(0, t - summary.length)}...</span>`;
}
color: root.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
Component.onCompleted: modelData.lock(this)
Component.onDestruction: modelData.unlock(this)
TextMetrics {
id: metrics
text: `${notifLine.modelData.summary} ${notifLine.modelData.body}`.replace(/\n/g, " ")
font.pointSize: notifLine.font.pointSize
font.family: notifLine.font.family
elideWidth: notifLine.width
elide: Text.ElideRight
}
}
elide: Text.ElideRight
elideWidth: notifLine.width
font.family: notifLine.font.family
font.pointSize: notifLine.font.pointSize
text: `${notifLine.modelData.summary} ${notifLine.modelData.body}`.replace(/\n/g, " ")
}
}
}
+150 -149
View File
@@ -6,188 +6,189 @@ import QtQuick
import qs.Config
Scope {
id: root
id: root
required property WlSessionLock lock
property string buffer
readonly property alias fprint: fprint
property string fprintState
required property WlSessionLock lock
property string lockMessage
readonly property alias passwd: passwd
property string state
readonly property alias passwd: passwd
readonly property alias fprint: fprint
property string lockMessage
property string state
property string fprintState
property string buffer
signal flashMsg
signal flashMsg
function handleKey(event: KeyEvent): void {
if (passwd.active || state === "max")
return;
function handleKey(event: KeyEvent): void {
if (passwd.active || state === "max")
return;
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
passwd.start();
} else if (event.key === Qt.Key_Backspace) {
if (event.modifiers & Qt.ControlModifier) {
buffer = "";
} else {
buffer = buffer.slice(0, -1);
}
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
// No illegal characters (you are insane if you use unicode in your password)
buffer += event.text;
}
}
if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) {
passwd.start();
} else if (event.key === Qt.Key_Backspace) {
if (event.modifiers & Qt.ControlModifier) {
buffer = "";
} else {
buffer = buffer.slice(0, -1);
}
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
// No illegal characters (you are insane if you use unicode in your password)
buffer += event.text;
}
}
PamContext {
id: passwd
PamContext {
id: passwd
config: "passwd"
configDirectory: Quickshell.shellDir + "/assets/pam.d"
config: "passwd"
configDirectory: Quickshell.shellDir + "/assets/pam.d"
onCompleted: res => {
if (res === PamResult.Success)
return root.lock.unlock();
onMessageChanged: {
if (message.startsWith("The account is locked"))
root.lockMessage = message;
else if (root.lockMessage && message.endsWith(" left to unlock)"))
root.lockMessage += "\n" + message;
}
if (res === PamResult.Error)
root.state = "error";
else if (res === PamResult.MaxTries)
root.state = "max";
else if (res === PamResult.Failed)
root.state = "fail";
onResponseRequiredChanged: {
if (!responseRequired)
return;
root.flashMsg();
stateReset.restart();
}
onMessageChanged: {
if (message.startsWith("The account is locked"))
root.lockMessage = message;
else if (root.lockMessage && message.endsWith(" left to unlock)"))
root.lockMessage += "\n" + message;
}
onResponseRequiredChanged: {
if (!responseRequired)
return;
respond(root.buffer);
root.buffer = "";
}
respond(root.buffer);
root.buffer = "";
}
}
onCompleted: res => {
if (res === PamResult.Success)
return root.lock.unlock();
PamContext {
id: fprint
if (res === PamResult.Error)
root.state = "error";
else if (res === PamResult.MaxTries)
root.state = "max";
else if (res === PamResult.Failed)
root.state = "fail";
property bool available
property int errorTries
property int tries
root.flashMsg();
stateReset.restart();
}
}
function checkAvail(): void {
if (!available || !Config.lock.enableFprint || !root.lock.secure) {
abort();
return;
}
PamContext {
id: fprint
tries = 0;
errorTries = 0;
start();
}
property bool available
property int tries
property int errorTries
config: "fprint"
configDirectory: Quickshell.shellDir + "/assets/pam.d"
function checkAvail(): void {
if (!available || !Config.lock.enableFprint || !root.lock.secure) {
abort();
return;
}
onCompleted: res => {
if (!available)
return;
tries = 0;
errorTries = 0;
start();
}
if (res === PamResult.Success)
return root.lock.unlock();
config: "fprint"
configDirectory: Quickshell.shellDir + "/assets/pam.d"
if (res === PamResult.Error) {
root.fprintState = "error";
errorTries++;
if (errorTries < 5) {
abort();
errorRetry.restart();
}
} else if (res === PamResult.MaxTries) {
// Isn't actually the real max tries as pam only reports completed
// when max tries is reached.
tries++;
if (tries < Config.lock.maxFprintTries) {
// Restart if not actually real max tries
root.fprintState = "fail";
start();
} else {
root.fprintState = "max";
abort();
}
}
onCompleted: res => {
if (!available)
return;
root.flashMsg();
fprintStateReset.start();
}
}
if (res === PamResult.Success)
return root.lock.unlock();
Process {
id: availProc
if (res === PamResult.Error) {
root.fprintState = "error";
errorTries++;
if (errorTries < 5) {
abort();
errorRetry.restart();
}
} else if (res === PamResult.MaxTries) {
// Isn't actually the real max tries as pam only reports completed
// when max tries is reached.
tries++;
if (tries < Config.lock.maxFprintTries) {
// Restart if not actually real max tries
root.fprintState = "fail";
start();
} else {
root.fprintState = "max";
abort();
}
}
command: ["sh", "-c", "fprintd-list $USER"]
root.flashMsg();
fprintStateReset.start();
}
}
onExited: code => {
fprint.available = code === 0;
fprint.checkAvail();
}
}
Process {
id: availProc
Timer {
id: errorRetry
command: ["sh", "-c", "fprintd-list $USER"]
onExited: code => {
fprint.available = code === 0;
fprint.checkAvail();
}
}
interval: 800
Timer {
id: errorRetry
onTriggered: fprint.start()
}
interval: 800
onTriggered: fprint.start()
}
Timer {
id: stateReset
Timer {
id: stateReset
interval: 4000
interval: 4000
onTriggered: {
if (root.state !== "max")
root.state = "";
}
}
onTriggered: {
if (root.state !== "max")
root.state = "";
}
}
Timer {
id: fprintStateReset
Timer {
id: fprintStateReset
interval: 4000
onTriggered: {
root.fprintState = "";
fprint.errorTries = 0;
}
}
interval: 4000
Connections {
target: root.lock
onTriggered: {
root.fprintState = "";
fprint.errorTries = 0;
}
}
function onSecureChanged(): void {
if (root.lock.secure) {
availProc.running = true;
root.buffer = "";
root.state = "";
root.fprintState = "";
root.lockMessage = "";
}
}
Connections {
function onSecureChanged(): void {
if (root.lock.secure) {
availProc.running = true;
root.buffer = "";
root.state = "";
root.fprintState = "";
root.lockMessage = "";
}
}
function onUnlock(): void {
fprint.abort();
}
}
function onUnlock(): void {
fprint.abort();
}
Connections {
target: Config.lock
target: root.lock
}
function onEnableFprintChanged(): void {
fprint.checkAvail();
}
}
Connections {
function onEnableFprintChanged(): void {
fprint.checkAvail();
}
target: Config.lock
}
}
+57 -59
View File
@@ -6,75 +6,73 @@ import qs.Helpers
import qs.Config
GridLayout {
id: root
id: root
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large
anchors.left: parent.left
anchors.margins: Appearance.padding.large
anchors.right: parent.right
columnSpacing: Appearance.spacing.large
columns: 2
rowSpacing: Appearance.spacing.large
rows: 1
rowSpacing: Appearance.spacing.large
columnSpacing: Appearance.spacing.large
rows: 1
columns: 2
Ref {
service: SystemUsage
}
Ref {
service: SystemUsage
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3primary
icon: "memory"
value: SystemUsage.cpuPerc
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
icon: "memory"
value: SystemUsage.cpuPerc
colour: DynamicColors.palette.m3primary
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
colour: DynamicColors.palette.m3secondary
icon: "memory_alt"
value: SystemUsage.memPerc
}
Resource {
Layout.bottomMargin: Appearance.padding.large
Layout.topMargin: Appearance.padding.large
icon: "memory_alt"
value: SystemUsage.memPerc
colour: DynamicColors.palette.m3secondary
}
component Resource: CustomRect {
id: res
component Resource: CustomRect {
id: res
required property color colour
required property string icon
required property real value
required property string icon
required property real value
required property color colour
Layout.fillWidth: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: width
radius: Appearance.rounding.large
Layout.fillWidth: true
implicitHeight: width
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
radius: Appearance.rounding.large
CircularProgress {
id: circ
CircularProgress {
id: circ
anchors.fill: parent
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
fgColour: res.colour
padding: Appearance.padding.large * 3
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
value: res.value
}
anchors.fill: parent
value: res.value
padding: Appearance.padding.large * 3
fgColour: res.colour
bgColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 3)
strokeWidth: width < 200 ? Appearance.padding.smaller : Appearance.padding.normal
}
MaterialIcon {
id: icon
MaterialIcon {
id: icon
anchors.centerIn: parent
text: res.icon
color: res.colour
font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600
}
Behavior on value {
Anim {
duration: Appearance.anim.durations.large
}
}
}
anchors.centerIn: parent
color: res.colour
font.pointSize: (circ.arcRadius * 0.7) || 1
font.weight: 600
text: res.icon
}
}
}
+4 -4
View File
@@ -7,18 +7,18 @@ Item {
id: root
ClippingRectangle {
radius: 1000
anchors.fill: parent
radius: 1000
Image {
id: userImage
anchors.fill: parent
sourceSize.width: parent.width
sourceSize.height: parent.height
asynchronous: true
fillMode: Image.PreserveAspectCrop
source: `${Paths.home}/.face`
sourceSize.height: parent.height
sourceSize.width: parent.width
}
}
}
+133 -139
View File
@@ -7,169 +7,163 @@ import qs.Helpers
import qs.Config
ColumnLayout {
id: root
id: root
required property int rootHeight
required property int rootHeight
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large * 2
anchors.left: parent.left
anchors.margins: Appearance.padding.large * 2
anchors.right: parent.right
spacing: Appearance.spacing.small
spacing: Appearance.spacing.small
Loader {
Layout.alignment: Qt.AlignHCenter
Layout.bottomMargin: -Appearance.padding.large
Layout.topMargin: Appearance.padding.large * 2
active: root.rootHeight > 610
visible: active
Loader {
Layout.topMargin: Appearance.padding.large * 2
Layout.bottomMargin: -Appearance.padding.large
Layout.alignment: Qt.AlignHCenter
sourceComponent: CustomText {
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
text: qsTr("Weather")
}
}
active: root.rootHeight > 610
visible: active
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.large
sourceComponent: CustomText {
text: qsTr("Weather")
color: DynamicColors.palette.m3primary
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
}
}
MaterialIcon {
animate: true
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge * 2.5
text: Weather.icon
}
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.large
ColumnLayout {
spacing: Appearance.spacing.small
MaterialIcon {
animate: true
text: Weather.icon
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.extraLarge * 2.5
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3secondary
elide: Text.ElideRight
font.pointSize: Appearance.font.size.large
font.weight: 500
text: Weather.description
}
ColumnLayout {
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: Appearance.font.size.normal
text: qsTr("Humidity: %1%").arg(Weather.humidity)
}
}
CustomText {
Layout.fillWidth: true
Loader {
Layout.rightMargin: Appearance.padding.smaller
active: root.width > 400
visible: active
animate: true
text: Weather.description
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.large
font.weight: 500
elide: Text.ElideRight
}
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3primary
elide: Text.ElideLeft
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
horizontalAlignment: Text.AlignRight
text: Weather.temp
}
animate: true
text: qsTr("Humidity: %1%").arg(Weather.humidity)
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: Appearance.font.size.normal
elide: Text.ElideRight
}
}
CustomText {
Layout.fillWidth: true
animate: true
color: DynamicColors.palette.m3outline
elide: Text.ElideLeft
font.pointSize: Appearance.font.size.smaller
horizontalAlignment: Text.AlignRight
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
}
}
}
}
Loader {
Layout.rightMargin: Appearance.padding.smaller
active: root.width > 400
visible: active
Loader {
id: forecastLoader
sourceComponent: ColumnLayout {
spacing: Appearance.spacing.small
Layout.bottomMargin: Appearance.padding.large * 2
Layout.fillWidth: true
Layout.topMargin: Appearance.spacing.smaller
active: root.rootHeight > 820
visible: active
CustomText {
Layout.fillWidth: true
sourceComponent: RowLayout {
spacing: Appearance.spacing.large
animate: true
text: Weather.temp
color: DynamicColors.palette.m3primary
horizontalAlignment: Text.AlignRight
font.pointSize: Appearance.font.size.extraLarge
font.weight: 500
elide: Text.ElideLeft
}
Repeater {
model: {
const forecast = Weather.hourlyForecast;
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
if (!forecast)
return Array.from({
length: count
}, () => null);
CustomText {
Layout.fillWidth: true
return forecast.slice(0, count);
}
animate: true
text: qsTr("Feels like: %1").arg(Weather.feelsLike)
color: DynamicColors.palette.m3outline
horizontalAlignment: Text.AlignRight
font.pointSize: Appearance.font.size.smaller
elide: Text.ElideLeft
}
}
}
}
ColumnLayout {
id: forecastHour
Loader {
id: forecastLoader
required property var modelData
Layout.topMargin: Appearance.spacing.smaller
Layout.bottomMargin: Appearance.padding.large * 2
Layout.fillWidth: true
Layout.fillWidth: true
spacing: Appearance.spacing.small
active: root.rootHeight > 820
visible: active
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3outline
font.pointSize: Appearance.font.size.larger
horizontalAlignment: Text.AlignHCenter
text: {
const hour = forecastHour.modelData?.hour ?? 0;
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
}
}
sourceComponent: RowLayout {
spacing: Appearance.spacing.large
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
font.pointSize: Appearance.font.size.extraLarge * 1.5
font.weight: 500
text: forecastHour.modelData?.icon ?? "cloud_alert"
}
Repeater {
model: {
const forecast = Weather.hourlyForecast;
const count = root.width < 320 ? 3 : root.width < 400 ? 4 : 5;
if (!forecast)
return Array.from({
length: count
}, () => null);
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.larger
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
}
}
}
}
}
return forecast.slice(0, count);
}
Timer {
interval: 900000 // 15 minutes
repeat: true
running: true
triggeredOnStart: true
ColumnLayout {
id: forecastHour
required property var modelData
Layout.fillWidth: true
spacing: Appearance.spacing.small
CustomText {
Layout.fillWidth: true
text: {
const hour = forecastHour.modelData?.hour ?? 0;
return hour > 12 ? `${(hour - 12).toString().padStart(2, "0")} PM` : `${hour.toString().padStart(2, "0")} AM`;
}
color: DynamicColors.palette.m3outline
horizontalAlignment: Text.AlignHCenter
font.pointSize: Appearance.font.size.larger
}
MaterialIcon {
Layout.alignment: Qt.AlignHCenter
text: forecastHour.modelData?.icon ?? "cloud_alert"
font.pointSize: Appearance.font.size.extraLarge * 1.5
font.weight: 500
}
CustomText {
Layout.alignment: Qt.AlignHCenter
text: Config.services.useFahrenheit ? `${forecastHour.modelData?.tempF ?? 0}°F` : `${forecastHour.modelData?.tempC ?? 0}°C`
color: DynamicColors.palette.m3secondary
font.pointSize: Appearance.font.size.larger
}
}
}
}
}
Timer {
running: true
triggeredOnStart: true
repeat: true
interval: 900000 // 15 minutes
onTriggered: Weather.reload()
}
onTriggered: Weather.reload()
}
}
+1 -1
View File
@@ -26,12 +26,12 @@ Item {
CustomRadioButton {
id: network
visible: modelData.name !== "lo"
required property NetworkDevice modelData
checked: Helpers.Network.activeDevice?.name === modelData.name
text: modelData.description
visible: modelData.name !== "lo"
}
}
}
+4 -4
View File
@@ -7,19 +7,19 @@ import qs.Modules
Item {
id: root
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: layout.implicitWidth
RowLayout {
id: layout
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.top: parent.top
MaterialIcon {
text: "android_wifi_4_bar"
Layout.alignment: Qt.AlignVCenter
text: "android_wifi_4_bar"
}
}
}
+13 -11
View File
@@ -6,40 +6,42 @@ import qs.Helpers
import qs.Components
Item {
id: root
id: root
required property PersistentProperties visibilities
required property Wrapper popouts
required property PersistentProperties visibilities
implicitWidth: 25
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: 25
CustomRect {
anchors.bottomMargin: 3
anchors.fill: parent
anchors.topMargin: 3
anchors.bottomMargin: 3
color: "transparent"
radius: 4
MaterialIcon {
id: notificationCenterIcon
anchors.centerIn: parent
property color iconColor: DynamicColors.palette.m3onSurface
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
anchors.centerIn: parent
color: iconColor
font.family: "Material Symbols Rounded"
font.pixelSize: 20
color: iconColor
text: HasNotifications.hasNotifications ? "\uf4fe" : "\ue7f4"
Behavior on color {
CAnim {}
CAnim {
}
}
}
StateLayer {
cursorShape: Qt.PointingHandCursor
onClicked: {
root.visibilities.sidebar = !root.visibilities.sidebar;
}
+48 -42
View File
@@ -4,50 +4,56 @@ import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
id: root
required property Wrapper wrapper
required property var sidebar
readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
relativeX: root.sidebar.notifsRoundingX
relativeY: root.roundingY
radiusX: root.sidebar.notifsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: root.rounding
radiusX: root.rounding
radiusY: root.rounding
}
Behavior on fillColor {
CAnim {
}
}
Behavior on fillColor {
CAnim {}
}
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.roundingY * 2
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.sidebar.notifsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.sidebar.notifsRoundingX
relativeY: root.roundingY
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.notifsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
radiusX: root.rounding
radiusY: root.rounding
relativeX: root.rounding
relativeY: root.rounding
}
}
+153 -155
View File
@@ -6,198 +6,196 @@ import Quickshell.Widgets
import QtQuick
Item {
id: root
id: root
required property PersistentProperties visibilities
required property Item panels
readonly property int padding: 6
readonly property int padding: 6
required property Item panels
required property PersistentProperties visibilities
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: {
const count = list.count;
if (count === 0)
return 0;
implicitWidth: Config.notifs.sizes.width + padding * 2
implicitHeight: {
const count = list.count;
if (count === 0)
return 0;
let height = (count - 1) * 8;
for (let i = 0; i < count; i++)
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0;
let height = (count - 1) * 8;
for (let i = 0; i < count; i++)
height += list.itemAtIndex(i)?.nonAnimHeight ?? 0;
if (visibilities && panels) {
if (visibilities.osd) {
const h = panels.osd.y - 8 * 2 - padding * 2;
if (height > h)
height = h;
}
if (visibilities && panels) {
if (visibilities.osd) {
const h = panels.osd.y - 8 * 2 - padding * 2;
if (height > h)
height = h;
}
if (visibilities.session) {
const h = panels.session.y - 8 * 2 - padding * 2;
if (height > h)
height = h;
}
}
if (visibilities.session) {
const h = panels.session.y - 8 * 2 - padding * 2;
if (height > h)
height = h;
}
}
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
}
implicitWidth: Config.notifs.sizes.width + padding * 2
return Math.min((QsWindow.window?.screen?.height ?? 0) - 1 * 2, height + padding * 2);
}
Behavior on implicitHeight {
Anim {
}
}
ClippingWrapperRectangle {
anchors.fill: parent
anchors.margins: root.padding
ClippingWrapperRectangle {
anchors.fill: parent
anchors.margins: root.padding
color: "transparent"
radius: 4
color: "transparent"
radius: 4
CustomListView {
id: list
CustomListView {
id: list
anchors.fill: parent
cacheBuffer: QsWindow.window?.screen.height ?? 0
orientation: Qt.Vertical
spacing: 0
model: ScriptModel {
values: NotifServer.popups.filter(n => !n.closed)
}
delegate: Item {
id: wrapper
anchors.fill: parent
property int idx
required property int index
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notif.nonAnimHeight
orientation: Qt.Vertical
spacing: 0
cacheBuffer: QsWindow.window?.screen.height ?? 0
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
implicitWidth: notif.implicitWidth
delegate: Item {
id: wrapper
ListView.onRemove: removeAnim.start()
onIndexChanged: {
if (index !== -1)
idx = index;
}
required property NotifServer.Notif modelData
required property int index
readonly property alias nonAnimHeight: notif.nonAnimHeight
property int idx
SequentialAnimation {
id: removeAnim
onIndexChanged: {
if (index !== -1)
idx = index;
}
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: true
}
implicitWidth: notif.implicitWidth
implicitHeight: notif.implicitHeight + (idx === 0 ? 0 : 8)
PropertyAction {
property: "enabled"
target: wrapper
value: false
}
ListView.onRemove: removeAnim.start()
PropertyAction {
property: "implicitHeight"
target: wrapper
value: 0
}
SequentialAnimation {
id: removeAnim
PropertyAction {
property: "z"
target: wrapper
value: 1
}
PropertyAction {
target: wrapper
property: "ListView.delayRemove"
value: true
}
PropertyAction {
target: wrapper
property: "enabled"
value: false
}
PropertyAction {
target: wrapper
property: "implicitHeight"
value: 0
}
PropertyAction {
target: wrapper
property: "z"
value: 1
}
Anim {
target: notif
property: "x"
to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
PropertyAction {
target: wrapper
property: "ListView.delayRemove"
value: false
}
}
property: "x"
target: notif
to: (notif.x >= 0 ? Config.notifs.sizes.width : -Config.notifs.sizes.width) * 2
}
ClippingRectangle {
anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
PropertyAction {
property: "ListView.delayRemove"
target: wrapper
value: false
}
}
color: "transparent"
radius: 4
implicitWidth: notif.implicitWidth
implicitHeight: notif.implicitHeight
ClippingRectangle {
anchors.top: parent.top
anchors.topMargin: wrapper.idx === 0 ? 0 : 8
color: "transparent"
implicitHeight: notif.implicitHeight
implicitWidth: notif.implicitWidth
radius: 4
Notification {
id: notif
Notification {
id: notif
modelData: wrapper.modelData
}
}
}
modelData: wrapper.modelData
}
}
}
displaced: Transition {
Anim {
property: "y"
}
}
model: ScriptModel {
values: NotifServer.popups.filter(n => !n.closed)
}
move: Transition {
Anim {
property: "y"
}
}
move: Transition {
Anim {
property: "y"
}
}
ExtraIndicator {
anchors.top: parent.top
extra: {
const count = list.count;
if (count === 0)
return 0;
displaced: Transition {
Anim {
property: "y"
}
}
const scrollY = list.contentY;
ExtraIndicator {
anchors.top: parent.top
extra: {
const count = list.count;
if (count === 0)
return 0;
let height = 0;
for (let i = 0; i < count; i++) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
const scrollY = list.contentY;
if (height - 8 >= scrollY)
return i;
}
let height = 0;
for (let i = 0; i < count; i++) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
return count;
}
}
if (height - 8 >= scrollY)
return i;
}
ExtraIndicator {
anchors.bottom: parent.bottom
extra: {
const count = list.count;
if (count === 0)
return 0;
return count;
}
}
const scrollY = list.contentHeight - (list.contentY + list.height);
ExtraIndicator {
anchors.bottom: parent.bottom
extra: {
const count = list.count;
if (count === 0)
return 0;
let height = 0;
for (let i = count - 1; i >= 0; i--) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
const scrollY = list.contentHeight - (list.contentY + list.height);
if (height - 8 >= scrollY)
return count - i - 1;
}
let height = 0;
for (let i = count - 1; i >= 0; i--) {
height += (list.itemAtIndex(i)?.nonAnimHeight ?? 0) + 8;
return 0;
}
}
}
}
if (height - 8 >= scrollY)
return count - i - 1;
}
return 0;
}
}
}
}
Behavior on implicitHeight {
Anim {}
}
component Anim: NumberAnimation {
easing.type: Easing.BezierSpline
component Anim: NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
easing.type: Easing.BezierSpline
}
}
+372 -389
View File
@@ -12,473 +12,456 @@ import QtQuick
import QtQuick.Layouts
CustomRect {
id: root
id: root
required property NotifServer.Notif modelData
readonly property bool hasImage: modelData.image.length > 0
readonly property bool hasAppIcon: modelData.appIcon.length > 0
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
property bool expanded: Config.notifs.openExpanded
property bool expanded: Config.notifs.openExpanded
readonly property bool hasAppIcon: modelData.appIcon.length > 0
readonly property bool hasImage: modelData.image.length > 0
required property NotifServer.Notif modelData
readonly property int nonAnimHeight: summary.implicitHeight + (root.expanded ? appName.height + body.height + actions.height + actions.anchors.topMargin : bodyPreview.height) + inner.anchors.margins * 2
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
radius: 6
implicitWidth: Config.notifs.sizes.width
implicitHeight: inner.implicitHeight
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondaryContainer : DynamicColors.tPalette.m3surfaceContainer
implicitHeight: inner.implicitHeight
implicitWidth: Config.notifs.sizes.width
radius: 6
x: Config.notifs.sizes.width
x: Config.notifs.sizes.width
Component.onCompleted: {
x = 0;
modelData.lock(this);
}
Component.onDestruction: modelData.unlock(this)
Behavior on x {
Anim {
Behavior on x {
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
MouseArea {
property int startY
Component.onCompleted: {
x = 0;
modelData.lock(this);
}
Component.onDestruction: modelData.unlock(this)
anchors.fill: parent
hoverEnabled: true
cursorShape: root.expanded && body.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
preventStealing: true
MouseArea {
property int startY
onEntered: root.modelData.timer.stop()
onExited: {
if (!pressed)
root.modelData.timer.start();
}
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
anchors.fill: parent
cursorShape: root.expanded && body.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
drag.axis: Drag.XAxis
drag.target: parent
hoverEnabled: true
preventStealing: true
drag.target: parent
drag.axis: Drag.XAxis
onClicked: event => {
if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton)
return;
onPressed: event => {
root.modelData.timer.stop();
startY = event.y;
if (event.button === Qt.MiddleButton)
root.modelData.close();
}
onReleased: event => {
if (!containsMouse)
root.modelData.timer.start();
const actions = root.modelData.actions;
if (actions?.length === 1)
actions[0].invoke();
}
onEntered: root.modelData.timer.stop()
onExited: {
if (!pressed)
root.modelData.timer.start();
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
root.expanded = diffY > 0;
}
}
onPressed: event => {
root.modelData.timer.stop();
startY = event.y;
if (event.button === Qt.MiddleButton)
root.modelData.close();
}
onReleased: event => {
if (!containsMouse)
root.modelData.timer.start();
if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold)
root.x = 0;
else
root.modelData.popup = false;
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
root.expanded = diffY > 0;
}
}
onClicked: event => {
if (!Config.notifs.actionOnClick || event.button !== Qt.LeftButton)
return;
if (Math.abs(root.x) < Config.notifs.sizes.width * Config.notifs.clearThreshold)
root.x = 0;
else
root.modelData.popup = false;
}
const actions = root.modelData.actions;
if (actions?.length === 1)
actions[0].invoke();
}
Item {
id: inner
Item {
id: inner
anchors.left: parent.left
anchors.margins: 8
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: root.nonAnimHeight
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 8
implicitHeight: root.nonAnimHeight
Behavior on implicitHeight {
Anim {
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
Loader {
id: image
Loader {
id: image
active: root.hasImage
anchors.left: parent.left
anchors.top: parent.top
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
visible: root.hasImage || root.hasAppIcon
active: root.hasImage
anchors.left: parent.left
anchors.top: parent.top
asynchronous: true
height: Config.notifs.sizes.image
visible: root.hasImage || root.hasAppIcon
width: Config.notifs.sizes.image
sourceComponent: ClippingRectangle {
radius: 1000
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
sourceComponent: ClippingRectangle {
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
radius: 1000
Image {
anchors.fill: parent
source: Qt.resolvedUrl(root.modelData.image)
fillMode: Image.PreserveAspectCrop
Image {
anchors.fill: parent
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
mipmap: true
cache: false
asynchronous: true
}
}
}
source: Qt.resolvedUrl(root.modelData.image)
}
}
}
Loader {
id: appIcon
Loader {
id: appIcon
active: root.hasAppIcon || !root.hasImage
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
anchors.right: root.hasImage ? image.right : undefined
anchors.bottom: root.hasImage ? image.bottom : undefined
active: root.hasAppIcon || !root.hasImage
anchors.bottom: root.hasImage ? image.bottom : undefined
anchors.horizontalCenter: root.hasImage ? undefined : image.horizontalCenter
anchors.right: root.hasImage ? image.right : undefined
anchors.verticalCenter: root.hasImage ? undefined : image.verticalCenter
asynchronous: true
sourceComponent: CustomRect {
radius: 1000
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) : DynamicColors.palette.m3secondaryContainer
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
sourceComponent: CustomRect {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2) : DynamicColors.palette.m3secondaryContainer
implicitHeight: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
implicitWidth: root.hasImage ? Config.notifs.sizes.badge : Config.notifs.sizes.image
radius: 1000
Loader {
id: icon
Loader {
id: icon
active: root.hasAppIcon
active: root.hasAppIcon
anchors.centerIn: parent
asynchronous: true
height: Math.round(parent.width * 0.6)
width: Math.round(parent.width * 0.6)
anchors.centerIn: parent
sourceComponent: CustomIcon {
anchors.fill: parent
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.modelData.appIcon)
}
}
Loader {
active: !root.hasAppIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -18 * 0.02
anchors.verticalCenterOffset: 18 * 0.02
asynchronous: true
width: Math.round(parent.width * 0.6)
height: Math.round(parent.width * 0.6)
sourceComponent: MaterialIcon {
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
}
}
}
}
sourceComponent: CustomIcon {
anchors.fill: parent
source: Quickshell.iconPath(root.modelData.appIcon)
layer.enabled: root.modelData.appIcon.endsWith("symbolic")
}
}
CustomText {
id: appName
Loader {
active: !root.hasAppIcon
anchors.centerIn: parent
anchors.horizontalCenterOffset: -18 * 0.02
anchors.verticalCenterOffset: 18 * 0.02
asynchronous: true
anchors.left: image.right
anchors.leftMargin: 10
anchors.top: parent.top
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
maximumLineCount: 1
opacity: root.expanded ? 1 : 0
text: appNameMetrics.elidedText
sourceComponent: MaterialIcon {
text: Icons.getNotifIcon(root.modelData.summary, root.modelData.urgency)
Behavior on opacity {
Anim {
}
}
}
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.modelData.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
}
}
}
}
TextMetrics {
id: appNameMetrics
CustomText {
id: appName
elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
font.family: appName.font.family
font.pointSize: appName.font.pointSize
text: root.modelData.appName
}
anchors.top: parent.top
anchors.left: image.right
anchors.leftMargin: 10
CustomText {
id: summary
animate: true
text: appNameMetrics.elidedText
maximumLineCount: 1
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
anchors.left: image.right
anchors.leftMargin: 10
anchors.top: parent.top
animate: true
height: implicitHeight
maximumLineCount: 1
text: summaryMetrics.elidedText
opacity: root.expanded ? 1 : 0
Behavior on height {
Anim {
}
}
states: State {
name: "expanded"
when: root.expanded
Behavior on opacity {
Anim {}
}
}
PropertyChanges {
summary.maximumLineCount: undefined
}
TextMetrics {
id: appNameMetrics
AnchorChanges {
anchors.top: appName.bottom
target: summary
}
}
transitions: Transition {
PropertyAction {
property: "maximumLineCount"
target: summary
}
text: root.modelData.appName
font.family: appName.font.family
font.pointSize: appName.font.pointSize
elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
}
CustomText {
id: summary
anchors.top: parent.top
anchors.left: image.right
anchors.leftMargin: 10
animate: true
text: summaryMetrics.elidedText
maximumLineCount: 1
height: implicitHeight
states: State {
name: "expanded"
when: root.expanded
PropertyChanges {
summary.maximumLineCount: undefined
}
AnchorChanges {
target: summary
anchors.top: appName.bottom
}
}
transitions: Transition {
PropertyAction {
target: summary
property: "maximumLineCount"
}
AnchorAnimation {
easing.type: Easing.BezierSpline
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on height {
Anim {}
}
}
TextMetrics {
id: summaryMetrics
text: root.modelData.summary
font.family: summary.font.family
font.pointSize: summary.font.pointSize
elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
}
CustomText {
id: timeSep
anchors.top: parent.top
anchors.left: summary.right
anchors.leftMargin: 7
text: "•"
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
states: State {
name: "expanded"
when: root.expanded
AnchorChanges {
target: timeSep
anchors.left: appName.right
}
}
transitions: Transition {
AnchorAnimation {
easing.type: Easing.BezierSpline
}
}
}
TextMetrics {
id: summaryMetrics
elide: Text.ElideRight
elideWidth: expandBtn.x - time.width - timeSep.width - summary.x - 7 * 3
font.family: summary.font.family
font.pointSize: summary.font.pointSize
text: root.modelData.summary
}
CustomText {
id: timeSep
anchors.left: summary.right
anchors.leftMargin: 7
anchors.top: parent.top
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
text: "•"
states: State {
name: "expanded"
when: root.expanded
AnchorChanges {
anchors.left: appName.right
target: timeSep
}
}
transitions: Transition {
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
easing.type: Easing.BezierSpline
}
}
}
CustomText {
id: time
CustomText {
id: time
anchors.top: parent.top
anchors.left: timeSep.right
anchors.leftMargin: 7
anchors.left: timeSep.right
anchors.leftMargin: 7
anchors.top: parent.top
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
horizontalAlignment: Text.AlignLeft
text: root.modelData.timeStr
}
animate: true
horizontalAlignment: Text.AlignLeft
text: root.modelData.timeStr
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
}
Item {
id: expandBtn
Item {
id: expandBtn
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: expandIcon.height
implicitWidth: expandIcon.height
anchors.right: parent.right
anchors.top: parent.top
StateLayer {
function onClicked() {
root.expanded = !root.expanded;
}
implicitWidth: expandIcon.height
implicitHeight: expandIcon.height
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
radius: 1000
}
StateLayer {
radius: 1000
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
MaterialIcon {
id: expandIcon
function onClicked() {
root.expanded = !root.expanded;
}
}
anchors.centerIn: parent
animate: true
font.pointSize: 13
text: root.expanded ? "expand_less" : "expand_more"
}
}
MaterialIcon {
id: expandIcon
CustomText {
id: bodyPreview
anchors.centerIn: parent
anchors.left: summary.left
anchors.right: expandBtn.left
anchors.rightMargin: 7
anchors.top: summary.bottom
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
opacity: root.expanded ? 0 : 1
text: bodyPreviewMetrics.elidedText
textFormat: Text.MarkdownText
animate: true
text: root.expanded ? "expand_less" : "expand_more"
font.pointSize: 13
}
}
Behavior on opacity {
Anim {
}
}
}
CustomText {
id: bodyPreview
TextMetrics {
id: bodyPreviewMetrics
anchors.left: summary.left
anchors.right: expandBtn.left
anchors.top: summary.bottom
anchors.rightMargin: 7
elide: Text.ElideRight
elideWidth: bodyPreview.width
font.family: bodyPreview.font.family
font.pointSize: bodyPreview.font.pointSize
text: root.modelData.body
}
animate: true
textFormat: Text.MarkdownText
text: bodyPreviewMetrics.elidedText
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
CustomText {
id: body
opacity: root.expanded ? 0 : 1
anchors.left: summary.left
anchors.right: expandBtn.left
anchors.rightMargin: 7
anchors.top: summary.bottom
animate: true
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
height: text ? implicitHeight : 0
opacity: root.expanded ? 1 : 0
text: root.modelData.body
textFormat: Text.MarkdownText
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
Behavior on opacity {
Anim {}
}
}
Behavior on opacity {
Anim {
}
}
TextMetrics {
id: bodyPreviewMetrics
onLinkActivated: link => {
if (!root.expanded)
return;
text: root.modelData.body
font.family: bodyPreview.font.family
font.pointSize: bodyPreview.font.pointSize
elide: Text.ElideRight
elideWidth: bodyPreview.width
}
Quickshell.execDetached(["app2unit", "-O", "--", link]);
root.modelData.popup = false;
}
}
CustomText {
id: body
RowLayout {
id: actions
anchors.left: summary.left
anchors.right: expandBtn.left
anchors.top: summary.bottom
anchors.rightMargin: 7
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: body.bottom
anchors.topMargin: 7
opacity: root.expanded ? 1 : 0
spacing: 10
animate: true
textFormat: Text.MarkdownText
text: root.modelData.body
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
height: text ? implicitHeight : 0
Behavior on opacity {
Anim {
}
}
onLinkActivated: link => {
if (!root.expanded)
return;
Action {
modelData: QtObject {
readonly property string text: qsTr("Close")
Quickshell.execDetached(["app2unit", "-O", "--", link]);
root.modelData.popup = false;
}
function invoke(): void {
root.modelData.close();
}
}
}
opacity: root.expanded ? 1 : 0
Repeater {
model: root.modelData.actions
Behavior on opacity {
Anim {}
}
}
delegate: Component {
Action {
}
}
}
}
}
}
RowLayout {
id: actions
component Action: CustomRect {
id: action
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: body.bottom
anchors.topMargin: 7
required property var modelData
spacing: 10
Layout.preferredHeight: actionText.height + 4 * 2
Layout.preferredWidth: actionText.width + 8 * 2
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
implicitHeight: actionText.height + 4 * 2
implicitWidth: actionText.width + 8 * 2
radius: 1000
opacity: root.expanded ? 1 : 0
StateLayer {
function onClicked(): void {
action.modelData.invoke();
}
Behavior on opacity {
Anim {}
}
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurface
radius: 1000
}
Action {
modelData: QtObject {
readonly property string text: qsTr("Close")
function invoke(): void {
root.modelData.close();
}
}
}
CustomText {
id: actionText
Repeater {
model: root.modelData.actions
anchors.centerIn: parent
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
text: actionTextMetrics.elidedText
}
delegate: Component {
Action {}
}
}
}
}
}
TextMetrics {
id: actionTextMetrics
component Action: CustomRect {
id: action
required property var modelData
radius: 1000
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3secondary : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
Layout.preferredWidth: actionText.width + 8 * 2
Layout.preferredHeight: actionText.height + 4 * 2
implicitWidth: actionText.width + 8 * 2
implicitHeight: actionText.height + 4 * 2
StateLayer {
radius: 1000
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurface
function onClicked(): void {
action.modelData.invoke();
}
}
CustomText {
id: actionText
anchors.centerIn: parent
text: actionTextMetrics.elidedText
color: root.modelData.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 10
}
TextMetrics {
id: actionTextMetrics
text: action.modelData.text
font.family: actionText.font.family
font.pointSize: actionText.font.pointSize
elide: Text.ElideRight
elideWidth: {
const numActions = root.modelData.actions.length + 1;
return (inner.width - actions.spacing * (numActions - 1)) / numActions - 8 * 2;
}
}
}
elide: Text.ElideRight
elideWidth: {
const numActions = root.modelData.actions.length + 1;
return (inner.width - actions.spacing * (numActions - 1)) / numActions - 8 * 2;
}
font.family: actionText.font.family
font.pointSize: actionText.font.pointSize
text: action.modelData.text
}
}
}
+41 -40
View File
@@ -4,50 +4,51 @@ import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
required property Wrapper wrapper
required property var panels
readonly property real rounding: 8
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding
id: root
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real notifsRoundingX: panels.notifications.height > 0 && notifsWidthDiff < rounding * 2 ? notifsWidthDiff / 2 : rounding
readonly property real notifsWidthDiff: panels.notifications.width - wrapper.width
required property var panels
readonly property real rounding: 8
readonly property real utilsRoundingX: utilsWidthDiff < rounding * 2 ? utilsWidthDiff / 2 : rounding
readonly property real utilsWidthDiff: panels.utilities.width - wrapper.width
required property Wrapper wrapper
strokeWidth: -1
fillColor: flatten ? "transparent" : DynamicColors.palette.m3surface
strokeWidth: -1
PathLine {
relativeX: -root.wrapper.width - root.notifsRoundingX
relativeY: 0
}
PathArc {
relativeX: root.notifsRoundingX
relativeY: root.rounding
radiusX: root.notifsRoundingX
radiusY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
relativeX: -root.utilsRoundingX
relativeY: root.rounding
radiusX: root.utilsRoundingX
radiusY: root.rounding
}
PathLine {
relativeX: root.wrapper.width + root.utilsRoundingX
relativeY: 0
}
Behavior on fillColor {
CAnim {
}
}
Behavior on fillColor {
CAnim {}
}
PathLine {
relativeX: -root.wrapper.width - root.notifsRoundingX
relativeY: 0
}
PathArc {
radiusX: root.notifsRoundingX
radiusY: root.rounding
relativeX: root.notifsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 2
}
PathArc {
radiusX: root.utilsRoundingX
radiusY: root.rounding
relativeX: -root.utilsRoundingX
relativeY: root.rounding
}
PathLine {
relativeX: root.wrapper.width + root.utilsRoundingX
relativeY: 0
}
}
+24 -26
View File
@@ -4,36 +4,34 @@ import QtQuick
import QtQuick.Layouts
Item {
id: root
id: root
required property Props props
required property var visibilities
required property Props props
required property var visibilities
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.fill: parent
spacing: 8
anchors.fill: parent
spacing: 8
CustomRect {
Layout.fillWidth: true
Layout.fillHeight: true
CustomRect {
Layout.fillHeight: true
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainerLow
radius: 8
radius: 8
color: DynamicColors.tPalette.m3surfaceContainerLow
NotifDock {
props: root.props
visibilities: root.visibilities
}
}
NotifDock {
props: root.props
visibilities: root.visibilities
}
}
CustomRect {
Layout.topMargin: 8 - layout.spacing
Layout.fillWidth: true
implicitHeight: 1
color: DynamicColors.tPalette.m3outlineVariant
}
}
CustomRect {
Layout.fillWidth: true
Layout.topMargin: 8 - layout.spacing
color: DynamicColors.tPalette.m3outlineVariant
implicitHeight: 1
}
}
}
+141 -146
View File
@@ -9,158 +9,153 @@ import QtQuick
import QtQuick.Layouts
CustomRect {
id: root
id: root
required property NotifServer.Notif modelData
required property Props props
required property bool expanded
required property var visibilities
readonly property CustomText body: expandedContent.item?.body ?? null
required property bool expanded
required property NotifServer.Notif modelData
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + 10 * 2 : summaryHeightMetrics.height
required property Props props
required property var visibilities
readonly property CustomText body: expandedContent.item?.body ?? null
readonly property real nonAnimHeight: expanded ? summary.implicitHeight + expandedContent.implicitHeight + expandedContent.anchors.topMargin + 10 * 2 : summaryHeightMetrics.height
color: {
const c = root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2);
return expanded ? c : Qt.alpha(c, 0);
}
implicitHeight: nonAnimHeight
radius: 6
implicitHeight: nonAnimHeight
radius: 6
color: {
const c = root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2);
return expanded ? c : Qt.alpha(c, 0);
}
states: State {
name: "expanded"
when: root.expanded
PropertyChanges {
summary.anchors.margins: 10
dummySummary.anchors.margins: 10
compactBody.anchors.margins: 10
timeStr.anchors.margins: 10
expandedContent.anchors.margins: 10
summary.width: root.width - 10 * 2 - timeStr.implicitWidth - 7
summary.maximumLineCount: Number.MAX_SAFE_INTEGER
}
}
transitions: Transition {
Anim {
properties: "margins,width,maximumLineCount"
}
}
TextMetrics {
id: summaryHeightMetrics
font: summary.font
text: " " // Use this height to prevent weird characters from changing the line height
}
CustomText {
id: summary
anchors.top: parent.top
anchors.left: parent.left
width: parent.width
text: root.modelData.summary
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 1
}
CustomText {
id: dummySummary
anchors.top: parent.top
anchors.left: parent.left
visible: false
text: root.modelData.summary
}
WrappedLoader {
id: compactBody
shouldBeActive: !root.expanded
anchors.top: parent.top
anchors.left: dummySummary.right
anchors.right: parent.right
anchors.leftMargin: 7
sourceComponent: CustomText {
textFormat: Text.StyledText
text: root.modelData.body.replace(/\n/g, " ")
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline
elide: Text.ElideRight
}
}
WrappedLoader {
id: timeStr
shouldBeActive: root.expanded
anchors.top: parent.top
anchors.right: parent.right
sourceComponent: CustomText {
animate: true
text: root.modelData.timeStr
color: DynamicColors.palette.m3outline
font.pointSize: 11
}
}
WrappedLoader {
id: expandedContent
shouldBeActive: root.expanded
anchors.top: summary.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 7 / 2
sourceComponent: ColumnLayout {
readonly property alias body: body
spacing: 10
CustomText {
id: body
Layout.fillWidth: true
textFormat: Text.MarkdownText
text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body given")
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurface
wrapMode: Text.WordWrap
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
root.visibilities.sidebar = false;
}
}
NotifActionList {
notif: root.modelData
}
}
}
Behavior on implicitHeight {
Anim {
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
states: State {
name: "expanded"
when: root.expanded
component WrappedLoader: Loader {
required property bool shouldBeActive
PropertyChanges {
compactBody.anchors.margins: 10
dummySummary.anchors.margins: 10
expandedContent.anchors.margins: 10
summary.anchors.margins: 10
summary.maximumLineCount: Number.MAX_SAFE_INTEGER
summary.width: root.width - 10 * 2 - timeStr.implicitWidth - 7
timeStr.anchors.margins: 10
}
}
transitions: Transition {
Anim {
properties: "margins,width,maximumLineCount"
}
}
opacity: shouldBeActive ? 1 : 0
active: opacity > 0
TextMetrics {
id: summaryHeightMetrics
Behavior on opacity {
Anim {}
}
}
font: summary.font
text: " " // Use this height to prevent weird characters from changing the line height
}
CustomText {
id: summary
anchors.left: parent.left
anchors.top: parent.top
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3onSecondaryContainer : DynamicColors.palette.m3onSurface
elide: Text.ElideRight
maximumLineCount: 1
text: root.modelData.summary
width: parent.width
wrapMode: Text.WordWrap
}
CustomText {
id: dummySummary
anchors.left: parent.left
anchors.top: parent.top
text: root.modelData.summary
visible: false
}
WrappedLoader {
id: compactBody
anchors.left: dummySummary.right
anchors.leftMargin: 7
anchors.right: parent.right
anchors.top: parent.top
shouldBeActive: !root.expanded
sourceComponent: CustomText {
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3outline
elide: Text.ElideRight
text: root.modelData.body.replace(/\n/g, " ")
textFormat: Text.StyledText
}
}
WrappedLoader {
id: timeStr
anchors.right: parent.right
anchors.top: parent.top
shouldBeActive: root.expanded
sourceComponent: CustomText {
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: 11
text: root.modelData.timeStr
}
}
WrappedLoader {
id: expandedContent
anchors.left: parent.left
anchors.right: parent.right
anchors.top: summary.bottom
anchors.topMargin: 7 / 2
shouldBeActive: root.expanded
sourceComponent: ColumnLayout {
readonly property alias body: body
spacing: 10
CustomText {
id: body
Layout.fillWidth: true
color: root.modelData.urgency === "critical" ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3onSurface
text: root.modelData.body.replace(/(.)\n(?!\n)/g, "$1\n\n") || qsTr("No body given")
textFormat: Text.MarkdownText
wrapMode: Text.WordWrap
onLinkActivated: link => {
Quickshell.execDetached(["app2unit", "-O", "--", link]);
root.visibilities.sidebar = false;
}
}
NotifActionList {
notif: root.modelData
}
}
}
component WrappedLoader: Loader {
required property bool shouldBeActive
active: opacity > 0
opacity: shouldBeActive ? 1 : 0
Behavior on opacity {
Anim {
}
}
}
}
+105 -106
View File
@@ -10,125 +10,124 @@ import QtQuick
import QtQuick.Layouts
Item {
id: root
id: root
required property NotifServer.Notif notif
required property NotifServer.Notif notif
Layout.fillWidth: true
implicitHeight: flickable.contentHeight
Layout.fillWidth: true
implicitHeight: flickable.contentHeight
CustomFlickable {
id: flickable
CustomFlickable {
id: flickable
anchors.fill: parent
contentWidth: Math.max(width, actionList.implicitWidth)
contentHeight: actionList.implicitHeight
anchors.fill: parent
contentHeight: actionList.implicitHeight
contentWidth: Math.max(width, actionList.implicitWidth)
RowLayout {
id: actionList
RowLayout {
id: actionList
anchors.fill: parent
spacing: 7
anchors.fill: parent
spacing: 7
Repeater {
model: [
{
isClose: true
},
...root.notif.actions,
{
isCopy: true
}
]
Repeater {
model: [
{
isClose: true
},
...root.notif.actions,
{
isCopy: true
}
]
CustomRect {
id: action
CustomRect {
id: action
required property var modelData
required property var modelData
Layout.fillWidth: true
Layout.fillHeight: true
implicitWidth: actionInner.implicitWidth + 5 * 2
implicitHeight: actionInner.implicitHeight + 5 * 2
Layout.fillHeight: true
Layout.fillWidth: true
Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? 18 : 0)
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 4)
implicitHeight: actionInner.implicitHeight + 5 * 2
implicitWidth: actionInner.implicitWidth + 5 * 2
radius: actionStateLayer.pressed ? 6 / 2 : 6
Layout.preferredWidth: implicitWidth + (actionStateLayer.pressed ? 18 : 0)
radius: actionStateLayer.pressed ? 6 / 2 : 6
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 4)
Timer {
id: copyTimer
interval: 1000
onTriggered: actionInner.item.text = "content_copy"
}
StateLayer {
id: actionStateLayer
function onClicked(): void {
if (action.modelData.isClose) {
root.notif.close();
} else if (action.modelData.isCopy) {
Quickshell.clipboardText = root.notif.body;
actionInner.item.text = "inventory";
copyTimer.start();
} else if (action.modelData.invoke) {
action.modelData.invoke();
} else if (!root.notif.resident) {
root.notif.close();
}
}
}
Loader {
id: actionInner
anchors.centerIn: parent
sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif.hasActionIcons ? iconComp : textComp
}
Component {
id: iconBtn
MaterialIcon {
animate: action.modelData.isCopy ?? false
text: action.modelData.isCopy ? "content_copy" : "close"
color: DynamicColors.palette.m3onSurfaceVariant
}
}
Component {
id: iconComp
IconImage {
source: Quickshell.iconPath(action.modelData.identifier)
}
}
Component {
id: textComp
CustomText {
text: action.modelData.text
color: DynamicColors.palette.m3onSurfaceVariant
}
}
Behavior on Layout.preferredWidth {
Anim {
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on radius {
Anim {
}
}
Behavior on radius {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
}
}
}
}
Timer {
id: copyTimer
interval: 1000
onTriggered: actionInner.item.text = "content_copy"
}
StateLayer {
id: actionStateLayer
function onClicked(): void {
if (action.modelData.isClose) {
root.notif.close();
} else if (action.modelData.isCopy) {
Quickshell.clipboardText = root.notif.body;
actionInner.item.text = "inventory";
copyTimer.start();
} else if (action.modelData.invoke) {
action.modelData.invoke();
} else if (!root.notif.resident) {
root.notif.close();
}
}
}
Loader {
id: actionInner
anchors.centerIn: parent
sourceComponent: action.modelData.isClose || action.modelData.isCopy ? iconBtn : root.notif.hasActionIcons ? iconComp : textComp
}
Component {
id: iconBtn
MaterialIcon {
animate: action.modelData.isCopy ?? false
color: DynamicColors.palette.m3onSurfaceVariant
text: action.modelData.isCopy ? "content_copy" : "close"
}
}
Component {
id: iconComp
IconImage {
source: Quickshell.iconPath(action.modelData.identifier)
}
}
Component {
id: textComp
CustomText {
color: DynamicColors.palette.m3onSurfaceVariant
text: action.modelData.text
}
}
}
}
}
}
}
+144 -150
View File
@@ -10,183 +10,177 @@ import QtQuick
import QtQuick.Layouts
Item {
id: root
id: root
required property Props props
required property var visibilities
readonly property int notifCount: NotifServer.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
readonly property int notifCount: NotifServer.list.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
required property Props props
required property var visibilities
anchors.fill: parent
anchors.margins: 8
anchors.fill: parent
anchors.margins: 8
Component.onCompleted: NotifServer.list.forEach(n => n.popup = false)
Component.onCompleted: NotifServer.list.forEach(n => n.popup = false)
Item {
id: title
Item {
id: title
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 4
anchors.left: parent.left
anchors.margins: 4
anchors.right: parent.right
anchors.top: parent.top
implicitHeight: Math.max(count.implicitHeight, titleText.implicitHeight)
implicitHeight: Math.max(count.implicitHeight, titleText.implicitHeight)
CustomText {
id: count
CustomText {
id: count
anchors.left: parent.left
anchors.leftMargin: root.notifCount > 0 ? 0 : -width - titleText.anchors.leftMargin
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3outline
font.family: "CaskaydiaCove NF"
font.pointSize: 13
font.weight: 500
opacity: root.notifCount > 0 ? 1 : 0
text: root.notifCount
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: root.notifCount > 0 ? 0 : -width - titleText.anchors.leftMargin
opacity: root.notifCount > 0 ? 1 : 0
Behavior on anchors.leftMargin {
Anim {
}
}
Behavior on opacity {
Anim {
}
}
}
text: root.notifCount
color: DynamicColors.palette.m3outline
font.pointSize: 13
font.family: "CaskaydiaCove NF"
font.weight: 500
CustomText {
id: titleText
Behavior on anchors.leftMargin {
Anim {}
}
anchors.left: count.right
anchors.leftMargin: 7
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3outline
elide: Text.ElideRight
font.family: "CaskaydiaCove NF"
font.pointSize: 13
font.weight: 500
text: root.notifCount > 0 ? qsTr("notification%1").arg(root.notifCount === 1 ? "" : "s") : qsTr("Notifications")
}
}
Behavior on opacity {
Anim {}
}
}
ClippingRectangle {
id: clipRect
CustomText {
id: titleText
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.top: title.bottom
anchors.topMargin: 10
color: "transparent"
radius: 6
anchors.verticalCenter: parent.verticalCenter
anchors.left: count.right
anchors.right: parent.right
anchors.leftMargin: 7
Loader {
active: opacity > 0
anchors.centerIn: parent
opacity: root.notifCount > 0 ? 0 : 1
text: root.notifCount > 0 ? qsTr("notification%1").arg(root.notifCount === 1 ? "" : "s") : qsTr("Notifications")
color: DynamicColors.palette.m3outline
font.pointSize: 13
font.family: "CaskaydiaCove NF"
font.weight: 500
elide: Text.ElideRight
}
}
ClippingRectangle {
id: clipRect
anchors.left: parent.left
anchors.right: parent.right
anchors.top: title.bottom
anchors.bottom: parent.bottom
anchors.topMargin: 10
radius: 6
color: "transparent"
Loader {
anchors.centerIn: parent
active: opacity > 0
opacity: root.notifCount > 0 ? 0 : 1
sourceComponent: ColumnLayout {
spacing: 20
CustomText {
Layout.alignment: Qt.AlignHCenter
text: qsTr("No Notifications")
color: DynamicColors.palette.m3outlineVariant
font.pointSize: 18
font.family: "CaskaydiaCove NF"
font.weight: 500
}
}
Behavior on opacity {
Anim {
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
}
}
}
sourceComponent: ColumnLayout {
spacing: 20
CustomFlickable {
id: view
CustomText {
Layout.alignment: Qt.AlignHCenter
color: DynamicColors.palette.m3outlineVariant
font.family: "CaskaydiaCove NF"
font.pointSize: 18
font.weight: 500
text: qsTr("No Notifications")
}
}
}
anchors.fill: parent
CustomFlickable {
id: view
flickableDirection: Flickable.VerticalFlick
contentWidth: width
contentHeight: notifList.implicitHeight
anchors.fill: parent
contentHeight: notifList.implicitHeight
contentWidth: width
flickableDirection: Flickable.VerticalFlick
CustomScrollBar.vertical: CustomScrollBar {
flickable: view
}
CustomScrollBar.vertical: CustomScrollBar {
flickable: view
}
NotifDockList {
id: notifList
NotifDockList {
id: notifList
props: root.props
visibilities: root.visibilities
container: view
}
}
}
container: view
props: root.props
visibilities: root.visibilities
}
}
}
Timer {
id: clearTimer
Timer {
id: clearTimer
repeat: true
interval: 50
onTriggered: {
let next = null;
for (let i = 0; i < notifList.repeater.count; i++) {
next = notifList.repeater.itemAt(i);
if (!next?.closed)
break;
}
if (next)
next.closeAll();
else
stop();
}
}
interval: 50
repeat: true
Loader {
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 8
onTriggered: {
let next = null;
for (let i = 0; i < notifList.repeater.count; i++) {
next = notifList.repeater.itemAt(i);
if (!next?.closed)
break;
}
if (next)
next.closeAll();
else
stop();
}
}
scale: root.notifCount > 0 ? 1 : 0.5
opacity: root.notifCount > 0 ? 1 : 0
active: opacity > 0
Loader {
active: opacity > 0
anchors.bottom: parent.bottom
anchors.margins: 8
anchors.right: parent.right
opacity: root.notifCount > 0 ? 1 : 0
scale: root.notifCount > 0 ? 1 : 0.5
sourceComponent: IconButton {
id: clearBtn
icon: "clear_all"
radius: 8
padding: 8
font.pointSize: Math.round(18 * 1.2)
onClicked: clearTimer.start()
Elevation {
anchors.fill: parent
radius: parent.radius
z: -1
level: clearBtn.stateLayer.containsMouse ? 4 : 3
}
}
Behavior on scale {
Anim {
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
Behavior on scale {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
sourceComponent: IconButton {
id: clearBtn
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
}
font.pointSize: Math.round(18 * 1.2)
icon: "clear_all"
padding: 8
radius: 8
onClicked: clearTimer.start()
Elevation {
anchors.fill: parent
level: clearBtn.stateLayer.containsMouse ? 4 : 3
radius: parent.radius
z: -1
}
}
}
}
+136 -140
View File
@@ -8,162 +8,158 @@ import Quickshell
import QtQuick
Item {
id: root
id: root
required property Props props
required property Flickable container
required property var visibilities
required property Flickable container
property bool flag
required property Props props
readonly property alias repeater: repeater
readonly property int spacing: 8
required property var visibilities
readonly property alias repeater: repeater
readonly property int spacing: 8
property bool flag
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: {
const item = repeater.itemAt(repeater.count - 1);
return item ? item.y + item.implicitHeight : 0;
}
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: {
const item = repeater.itemAt(repeater.count - 1);
return item ? item.y + item.implicitHeight : 0;
}
Repeater {
id: repeater
Repeater {
id: repeater
model: ScriptModel {
values: {
const map = new Map();
for (const n of NotifServer.notClosed)
map.set(n.appName, null);
for (const n of NotifServer.list)
map.set(n.appName, null);
return [...map.keys()];
}
model: ScriptModel {
values: {
const map = new Map();
for (const n of NotifServer.notClosed)
map.set(n.appName, null);
for (const n of NotifServer.list)
map.set(n.appName, null);
return [...map.keys()];
}
onValuesChanged: root.flagChanged()
}
onValuesChanged: root.flagChanged()
}
MouseArea {
id: notif
MouseArea {
id: notif
required property int index
required property string modelData
readonly property bool closed: notifInner.notifCount === 0
required property int index
required property string modelData
readonly property alias nonAnimHeight: notifInner.nonAnimHeight
property int startY
readonly property bool closed: notifInner.notifCount === 0
readonly property alias nonAnimHeight: notifInner.nonAnimHeight
property int startY
function closeAll(): void {
for (const n of NotifServer.notClosed.filter(n => n.appName === modelData)) {
n.close();
function closeAll(): void {
for (const n of NotifServer.notClosed.filter(n => n.appName === modelData)) {
n.close();
}
}
}
y: {
root.flag; // Force update
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (!item.closed)
y += item.nonAnimHeight + root.spacing;
}
return y;
}
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
drag.axis: Drag.XAxis
drag.target: this
enabled: !closed
hoverEnabled: true
implicitHeight: notifInner.implicitHeight
implicitWidth: root.width
preventStealing: true
y: {
root.flag; // Force update
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (!item.closed)
y += item.nonAnimHeight + root.spacing;
}
return y;
}
containmentMask: QtObject {
function contains(p: point): bool {
if (!root.container.contains(notif.mapToItem(root.container, p)))
return false;
return notifInner.contains(p);
}
}
implicitWidth: root.width
implicitHeight: notifInner.implicitHeight
hoverEnabled: true
cursorShape: pressed ? Qt.ClosedHandCursor : undefined
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
preventStealing: true
enabled: !closed
drag.target: this
drag.axis: Drag.XAxis
onPressed: event => {
startY = event.y;
if (event.button === Qt.RightButton)
notifInner.toggleExpand(!notifInner.expanded);
else if (event.button === Qt.MiddleButton)
closeAll();
}
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
notifInner.toggleExpand(diffY > 0);
}
}
onReleased: event => {
if (Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
closeAll();
}
ParallelAnimation {
running: true
Anim {
target: notif
property: "opacity"
from: 0
to: 1
}
Anim {
target: notif
property: "scale"
from: 0
to: 1
containmentMask: QtObject {
function contains(p: point): bool {
if (!root.container.contains(notif.mapToItem(root.container, p)))
return false;
return notifInner.contains(p);
}
}
Behavior on x {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ParallelAnimation {
running: notif.closed
Anim {
target: notif
property: "opacity"
to: 0
}
Anim {
target: notif
property: "scale"
to: 0.6
}
}
NotifGroup {
id: notifInner
modelData: notif.modelData
props: root.props
container: root.container
visibilities: root.visibilities
}
Behavior on x {
Anim {
}
}
Behavior on y {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
Behavior on y {
Anim {
onPositionChanged: event => {
if (pressed) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
notifInner.toggleExpand(diffY > 0);
}
}
onPressed: event => {
startY = event.y;
if (event.button === Qt.RightButton)
notifInner.toggleExpand(!notifInner.expanded);
else if (event.button === Qt.MiddleButton)
closeAll();
}
onReleased: event => {
if (Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
closeAll();
}
ParallelAnimation {
running: true
Anim {
from: 0
property: "opacity"
target: notif
to: 1
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
from: 0
property: "scale"
target: notif
to: 1
}
}
ParallelAnimation {
running: notif.closed
Anim {
property: "opacity"
target: notif
to: 0
}
Anim {
property: "scale"
target: notif
to: 0.6
}
}
NotifGroup {
id: notifInner
container: root.container
modelData: notif.modelData
props: root.props
visibilities: root.visibilities
}
}
}
}
+179 -184
View File
@@ -11,229 +11,224 @@ import QtQuick
import QtQuick.Layouts
CustomRect {
id: root
id: root
required property string modelData
required property Props props
required property Flickable container
required property var visibilities
readonly property string appIcon: notifs.find(n => !n.closed && n.appIcon.length > 0)?.appIcon ?? ""
required property Flickable container
readonly property bool expanded: props.expandedNotifs.includes(modelData)
readonly property string image: notifs.find(n => !n.closed && n.image.length > 0)?.image ?? ""
required property string modelData
readonly property int nonAnimHeight: {
const headerHeight = header.implicitHeight + (root.expanded ? Math.round(7 / 2) : 0);
const columnHeight = headerHeight + notifList.nonAnimHeight + column.Layout.topMargin + column.Layout.bottomMargin;
return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + 10 * 2);
}
readonly property int notifCount: notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
readonly property list<var> notifs: NotifServer.list.filter(n => n.appName === modelData)
required property Props props
readonly property int urgency: notifs.some(n => !n.closed && n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low
required property var visibilities
readonly property list<var> notifs: NotifServer.list.filter(n => n.appName === modelData)
readonly property int notifCount: notifs.reduce((acc, n) => n.closed ? acc : acc + 1, 0)
readonly property string image: notifs.find(n => !n.closed && n.image.length > 0)?.image ?? ""
readonly property string appIcon: notifs.find(n => !n.closed && n.appIcon.length > 0)?.appIcon ?? ""
readonly property int urgency: notifs.some(n => !n.closed && n.urgency === NotificationUrgency.Critical) ? NotificationUrgency.Critical : notifs.some(n => n.urgency === NotificationUrgency.Normal) ? NotificationUrgency.Normal : NotificationUrgency.Low
function toggleExpand(expand: bool): void {
if (expand) {
if (!expanded)
props.expandedNotifs.push(modelData);
} else if (expanded) {
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
}
}
readonly property int nonAnimHeight: {
const headerHeight = header.implicitHeight + (root.expanded ? Math.round(7 / 2) : 0);
const columnHeight = headerHeight + notifList.nonAnimHeight + column.Layout.topMargin + column.Layout.bottomMargin;
return Math.round(Math.max(Config.notifs.sizes.image, columnHeight) + 10 * 2);
}
readonly property bool expanded: props.expandedNotifs.includes(modelData)
anchors.left: parent?.left
anchors.right: parent?.right
clip: true
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
implicitHeight: content.implicitHeight + 10 * 2
radius: 8
function toggleExpand(expand: bool): void {
if (expand) {
if (!expanded)
props.expandedNotifs.push(modelData);
} else if (expanded) {
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
}
}
Component.onDestruction: {
if (notifCount === 0 && expanded)
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
}
Component.onDestruction: {
if (notifCount === 0 && expanded)
props.expandedNotifs.splice(props.expandedNotifs.indexOf(modelData), 1);
}
RowLayout {
id: content
anchors.left: parent?.left
anchors.right: parent?.right
implicitHeight: content.implicitHeight + 10 * 2
anchors.left: parent.left
anchors.margins: 10
anchors.right: parent.right
anchors.top: parent.top
spacing: 10
clip: true
radius: 8
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainer, 2)
Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
implicitHeight: Config.notifs.sizes.image
implicitWidth: Config.notifs.sizes.image
RowLayout {
id: content
Component {
id: imageComp
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
Image {
asynchronous: true
cache: false
fillMode: Image.PreserveAspectCrop
height: Config.notifs.sizes.image
source: Qt.resolvedUrl(root.image)
width: Config.notifs.sizes.image
}
}
spacing: 10
Component {
id: appIconComp
Item {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
implicitWidth: Config.notifs.sizes.image
implicitHeight: Config.notifs.sizes.image
CustomIcon {
implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
layer.enabled: root.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.appIcon)
}
}
Component {
id: imageComp
Component {
id: materialIconComp
Image {
source: Qt.resolvedUrl(root.image)
fillMode: Image.PreserveAspectCrop
cache: false
asynchronous: true
width: Config.notifs.sizes.image
height: Config.notifs.sizes.image
}
}
MaterialIcon {
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
}
}
Component {
id: appIconComp
CustomClippingRect {
anchors.fill: parent
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 3) : DynamicColors.palette.m3secondaryContainer
radius: 1000
CustomIcon {
implicitSize: Math.round(Config.notifs.sizes.image * 0.6)
source: Quickshell.iconPath(root.appIcon)
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
Loader {
anchors.centerIn: parent
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
}
}
Component {
id: materialIconComp
Loader {
active: root.appIcon && root.image
anchors.bottom: parent.bottom
anchors.right: parent.right
MaterialIcon {
text: Icons.getNotifIcon(root.notifs[0]?.summary, root.urgency)
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : root.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
}
}
sourceComponent: CustomRect {
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3secondaryContainer
implicitHeight: Config.notifs.sizes.badge
implicitWidth: Config.notifs.sizes.badge
radius: 1000
CustomClippingRect {
anchors.fill: parent
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.urgency === NotificationUrgency.Low ? DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 3) : DynamicColors.palette.m3secondaryContainer
radius: 1000
CustomIcon {
anchors.centerIn: parent
implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
layer.enabled: root.appIcon.endsWith("symbolic")
source: Quickshell.iconPath(root.appIcon)
}
}
}
}
Loader {
anchors.centerIn: parent
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
}
}
ColumnLayout {
id: column
Loader {
anchors.right: parent.right
anchors.bottom: parent.bottom
active: root.appIcon && root.image
Layout.bottomMargin: -10 / 2
Layout.fillWidth: true
Layout.topMargin: -10
spacing: 0
sourceComponent: CustomRect {
implicitWidth: Config.notifs.sizes.badge
implicitHeight: Config.notifs.sizes.badge
RowLayout {
id: header
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : root.urgency === NotificationUrgency.Low ? DynamicColors.palette.m3surfaceContainerHigh : DynamicColors.palette.m3secondaryContainer
radius: 1000
Layout.bottomMargin: root.expanded ? Math.round(7 / 2) : 0
Layout.fillWidth: true
spacing: 5
CustomIcon {
anchors.centerIn: parent
implicitSize: Math.round(Config.notifs.sizes.badge * 0.6)
source: Quickshell.iconPath(root.appIcon)
layer.enabled: root.appIcon.endsWith("symbolic")
}
}
}
}
Behavior on Layout.bottomMargin {
Anim {
}
}
ColumnLayout {
id: column
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: 11
text: root.modelData
}
Layout.topMargin: -10
Layout.bottomMargin: -10 / 2
Layout.fillWidth: true
spacing: 0
CustomText {
animate: true
color: DynamicColors.palette.m3outline
font.pointSize: 11
text: root.notifs.find(n => !n.closed)?.timeStr ?? ""
}
RowLayout {
id: header
CustomRect {
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 3)
implicitHeight: groupCount.implicitHeight + 10
implicitWidth: expandBtn.implicitWidth + 7 * 2
radius: 1000
Layout.bottomMargin: root.expanded ? Math.round(7 / 2) : 0
Layout.fillWidth: true
spacing: 5
StateLayer {
function onClicked(): void {
root.toggleExpand(!root.expanded);
}
CustomText {
Layout.fillWidth: true
text: root.modelData
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 11
elide: Text.ElideRight
}
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
}
CustomText {
animate: true
text: root.notifs.find(n => !n.closed)?.timeStr ?? ""
color: DynamicColors.palette.m3outline
font.pointSize: 11
}
RowLayout {
id: expandBtn
CustomRect {
implicitWidth: expandBtn.implicitWidth + 7 * 2
implicitHeight: groupCount.implicitHeight + 10
anchors.centerIn: parent
spacing: 7 / 2
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3error : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 3)
radius: 1000
CustomText {
id: groupCount
StateLayer {
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
Layout.leftMargin: 10 / 2
animate: true
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
font.pointSize: 11
text: root.notifCount
}
function onClicked(): void {
root.toggleExpand(!root.expanded);
}
}
MaterialIcon {
Layout.rightMargin: -10 / 2
Layout.topMargin: root.expanded ? -Math.floor(7 / 2) : 0
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
rotation: root.expanded ? 180 : 0
text: "expand_more"
RowLayout {
id: expandBtn
anchors.centerIn: parent
spacing: 7 / 2
CustomText {
id: groupCount
Layout.leftMargin: 10 / 2
animate: true
text: root.notifCount
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
font.pointSize: 11
}
MaterialIcon {
Layout.rightMargin: -10 / 2
text: "expand_more"
color: root.urgency === NotificationUrgency.Critical ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
rotation: root.expanded ? 180 : 0
Layout.topMargin: root.expanded ? -Math.floor(7 / 2) : 0
Behavior on rotation {
Anim {
Behavior on Layout.topMargin {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on Layout.topMargin {
Anim {
}
}
Behavior on rotation {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
}
}
}
}
}
}
}
Behavior on Layout.bottomMargin {
Anim {}
}
}
NotifGroupList {
id: notifList
NotifGroupList {
id: notifList
container: root.container
expanded: root.expanded
notifs: root.notifs
props: root.props
visibilities: root.visibilities
props: root.props
notifs: root.notifs
expanded: root.expanded
container: root.container
visibilities: root.visibilities
onRequestToggleExpand: expand => root.toggleExpand(expand)
}
}
}
onRequestToggleExpand: expand => root.toggleExpand(expand)
}
}
}
}
+191 -196
View File
@@ -9,206 +9,201 @@ import QtQuick
import QtQuick.Layouts
Item {
id: root
id: root
required property Props props
required property list<var> notifs
required property bool expanded
required property Flickable container
required property var visibilities
required property Flickable container
required property bool expanded
property bool flag
readonly property real nonAnimHeight: {
let h = -root.spacing;
for (let i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i);
if (!item.modelData.closed && !item.previewHidden)
h += item.nonAnimHeight + root.spacing;
}
return h;
}
required property list<var> notifs
required property Props props
property bool showAllNotifs
readonly property int spacing: Math.round(7 / 2)
required property var visibilities
readonly property real nonAnimHeight: {
let h = -root.spacing;
for (let i = 0; i < repeater.count; i++) {
const item = repeater.itemAt(i);
if (!item.modelData.closed && !item.previewHidden)
h += item.nonAnimHeight + root.spacing;
}
return h;
}
signal requestToggleExpand(expand: bool)
readonly property int spacing: Math.round(7 / 2)
property bool showAllNotifs
property bool flag
Layout.fillWidth: true
implicitHeight: nonAnimHeight
signal requestToggleExpand(expand: bool)
onExpandedChanged: {
if (expanded) {
clearTimer.stop();
showAllNotifs = true;
} else {
clearTimer.start();
}
}
Layout.fillWidth: true
implicitHeight: nonAnimHeight
Timer {
id: clearTimer
interval: MaterialEasing.standardTime
onTriggered: root.showAllNotifs = false
}
Repeater {
id: repeater
model: ScriptModel {
values: root.showAllNotifs ? root.notifs : root.notifs.slice(0, Config.notifs.groupPreviewNum + 1)
onValuesChanged: root.flagChanged()
}
MouseArea {
id: notif
required property int index
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notifInner.nonAnimHeight
readonly property bool previewHidden: {
if (root.expanded)
return false;
let extraHidden = 0;
for (let i = 0; i < index; i++)
if (root.notifs[i].closed)
extraHidden++;
return index >= Config.notifs.groupPreviewNum + extraHidden;
}
property int startY
y: {
root.flag; // Force update
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (!item.modelData.closed && !item.previewHidden)
y += item.nonAnimHeight + root.spacing;
}
return y;
}
containmentMask: QtObject {
function contains(p: point): bool {
if (!root.container.contains(notif.mapToItem(root.container, p)))
return false;
return notifInner.contains(p);
}
}
opacity: previewHidden ? 0 : 1
scale: previewHidden ? 0.7 : 1
implicitWidth: root.width
implicitHeight: notifInner.implicitHeight
hoverEnabled: true
cursorShape: notifInner.body?.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
preventStealing: !root.expanded
enabled: !modelData.closed
drag.target: this
drag.axis: Drag.XAxis
onPressed: event => {
startY = event.y;
if (event.button === Qt.RightButton)
root.requestToggleExpand(!root.expanded);
else if (event.button === Qt.MiddleButton)
modelData.close();
}
onPositionChanged: event => {
if (pressed && !root.expanded) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
root.requestToggleExpand(diffY > 0);
}
}
onReleased: event => {
if (Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
modelData.close();
}
Component.onCompleted: modelData.lock(this)
Component.onDestruction: modelData.unlock(this)
ParallelAnimation {
Component.onCompleted: running = !notif.previewHidden
Anim {
target: notif
property: "opacity"
from: 0
to: 1
}
Anim {
target: notif
property: "scale"
from: 0.7
to: 1
}
}
ParallelAnimation {
running: notif.modelData.closed
onFinished: notif.modelData.unlock(notif)
Anim {
target: notif
property: "opacity"
to: 0
}
Anim {
target: notif
property: "x"
to: notif.x >= 0 ? notif.width : -notif.width
}
}
Notif {
id: notifInner
anchors.fill: parent
modelData: notif.modelData
props: root.props
expanded: root.expanded
visibilities: root.visibilities
}
Behavior on opacity {
Anim {}
}
Behavior on scale {
Anim {}
}
Behavior on x {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on y {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
Behavior on implicitHeight {
Anim {
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
onExpandedChanged: {
if (expanded) {
clearTimer.stop();
showAllNotifs = true;
} else {
clearTimer.start();
}
}
Timer {
id: clearTimer
interval: MaterialEasing.standardTime
onTriggered: root.showAllNotifs = false
}
Repeater {
id: repeater
model: ScriptModel {
values: root.showAllNotifs ? root.notifs : root.notifs.slice(0, Config.notifs.groupPreviewNum + 1)
onValuesChanged: root.flagChanged()
}
MouseArea {
id: notif
required property int index
required property NotifServer.Notif modelData
readonly property alias nonAnimHeight: notifInner.nonAnimHeight
readonly property bool previewHidden: {
if (root.expanded)
return false;
let extraHidden = 0;
for (let i = 0; i < index; i++)
if (root.notifs[i].closed)
extraHidden++;
return index >= Config.notifs.groupPreviewNum + extraHidden;
}
property int startY
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
cursorShape: notifInner.body?.hoveredLink ? Qt.PointingHandCursor : pressed ? Qt.ClosedHandCursor : undefined
drag.axis: Drag.XAxis
drag.target: this
enabled: !modelData.closed
hoverEnabled: true
implicitHeight: notifInner.implicitHeight
implicitWidth: root.width
opacity: previewHidden ? 0 : 1
preventStealing: !root.expanded
scale: previewHidden ? 0.7 : 1
y: {
root.flag; // Force update
let y = 0;
for (let i = 0; i < index; i++) {
const item = repeater.itemAt(i);
if (!item.modelData.closed && !item.previewHidden)
y += item.nonAnimHeight + root.spacing;
}
return y;
}
containmentMask: QtObject {
function contains(p: point): bool {
if (!root.container.contains(notif.mapToItem(root.container, p)))
return false;
return notifInner.contains(p);
}
}
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
Behavior on x {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on y {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Component.onCompleted: modelData.lock(this)
Component.onDestruction: modelData.unlock(this)
onPositionChanged: event => {
if (pressed && !root.expanded) {
const diffY = event.y - startY;
if (Math.abs(diffY) > Config.notifs.expandThreshold)
root.requestToggleExpand(diffY > 0);
}
}
onPressed: event => {
startY = event.y;
if (event.button === Qt.RightButton)
root.requestToggleExpand(!root.expanded);
else if (event.button === Qt.MiddleButton)
modelData.close();
}
onReleased: event => {
if (Math.abs(x) < width * Config.notifs.clearThreshold)
x = 0;
else
modelData.close();
}
ParallelAnimation {
Component.onCompleted: running = !notif.previewHidden
Anim {
from: 0
property: "opacity"
target: notif
to: 1
}
Anim {
from: 0.7
property: "scale"
target: notif
to: 1
}
}
ParallelAnimation {
running: notif.modelData.closed
onFinished: notif.modelData.unlock(notif)
Anim {
property: "opacity"
target: notif
to: 0
}
Anim {
property: "x"
target: notif
to: notif.x >= 0 ? notif.width : -notif.width
}
}
Notif {
id: notifInner
anchors.fill: parent
expanded: root.expanded
modelData: notif.modelData
props: root.props
visibilities: root.visibilities
}
}
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
import Quickshell
PersistentProperties {
property list<string> expandedNotifs: []
property list<string> expandedNotifs: []
reloadableId: "sidebar"
reloadableId: "sidebar"
}
@@ -4,51 +4,57 @@ import QtQuick
import QtQuick.Shapes
ShapePath {
id: root
id: root
required property Wrapper wrapper
required property var sidebar
readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property var sidebar
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: -root.roundingY
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
relativeX: root.sidebar.utilsRoundingX
relativeY: -root.roundingY
radiusX: root.sidebar.utilsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.utilsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
relativeX: root.rounding
relativeY: -root.rounding
radiusX: root.rounding
radiusY: root.rounding
direction: PathArc.Counterclockwise
}
Behavior on fillColor {
CAnim {
}
}
Behavior on fillColor {
CAnim {}
}
PathLine {
relativeX: -(root.wrapper.width + root.rounding)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
radiusX: root.sidebar.utilsRoundingX
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.sidebar.utilsRoundingX
relativeY: -root.roundingY
}
PathLine {
relativeX: root.wrapper.height > 0 ? root.wrapper.width - root.rounding - root.sidebar.utilsRoundingX : root.wrapper.width
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: root.rounding
relativeX: root.rounding
relativeY: -root.rounding
}
}
@@ -8,75 +8,80 @@ import qs.Modules
import qs.Daemons
CustomRect {
id: root
id: root
required property var visibilities
required property Item popouts
required property Item popouts
required property var visibilities
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + 18 * 2
Layout.fillWidth: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + 18 * 2
radius: 8
radius: 8
color: DynamicColors.tPalette.m3surfaceContainer
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.fill: parent
anchors.margins: 18
spacing: 10
anchors.fill: parent
anchors.margins: 18
spacing: 10
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 7
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: 7
Toggle {
visible: QSNetwork.Networking.devices.values.length > 0
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
checked: Network.wifiEnabled
icon: Network.wifiEnabled ? "wifi" : "wifi_off"
visible: QSNetwork.Networking.devices.values.length > 0
onClicked: Network.toggleWifi()
}
Toggle {
Toggle {
id: toggle
icon: NotifServer.dnd ? "notifications_off" : "notifications"
checked: !NotifServer.dnd
onClicked: NotifServer.dnd = !NotifServer.dnd
}
checked: !NotifServer.dnd
icon: NotifServer.dnd ? "notifications_off" : "notifications"
onClicked: NotifServer.dnd = !NotifServer.dnd
}
Toggle {
icon: Audio.sourceMuted ? "mic_off" : "mic"
checked: !Audio.sourceMuted
icon: Audio.sourceMuted ? "mic_off" : "mic"
onClicked: {
const audio = Audio.source?.audio;
if ( audio )
if (audio)
audio.muted = !audio.muted;
}
}
Toggle {
icon: Audio.muted ? "volume_off" : "volume_up"
checked: !Audio.muted
icon: Audio.muted ? "volume_off" : "volume_up"
onClicked: {
const audio = Audio.sink?.audio;
if ( audio )
if (audio)
audio.muted = !audio.muted;
}
}
Toggle {
visible: Bluetooth.defaultAdapter ?? false
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled"
checked: Bluetooth.defaultAdapter?.enabled ?? false
icon: Bluetooth.defaultAdapter?.enabled ? "bluetooth" : "bluetooth_disabled"
visible: Bluetooth.defaultAdapter ?? false
onClicked: {
// console.log(Bluetooth.defaultAdapter)
const adapter = Bluetooth.defaultAdapter
if ( adapter )
const adapter = Bluetooth.defaultAdapter;
if (adapter)
adapter.enabled = !adapter.enabled;
}
}
}
}
}
}
CustomShortcut {
name: "toggle-dnd"
@@ -86,20 +91,20 @@ CustomRect {
}
}
component Toggle: IconButton {
Layout.fillWidth: true
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
toggle: true
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
component Toggle: IconButton {
Layout.fillWidth: true
Layout.preferredWidth: implicitWidth + (stateLayer.pressed ? 18 : internalChecked ? 7 : 0)
inactiveColour: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
radius: stateLayer.pressed ? 6 / 2 : internalChecked ? 6 : 8
radiusAnim.duration: MaterialEasing.expressiveEffectsTime
radiusAnim.easing.bezierCurve: MaterialEasing.expressiveEffects
toggle: true
Behavior on Layout.preferredWidth {
Anim {
Behavior on Layout.preferredWidth {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
}
}
}
+17 -16
View File
@@ -4,26 +4,27 @@ import QtQuick
import QtQuick.Layouts
Item {
id: root
id: root
required property var props
required property var visibilities
required property Item popouts
required property Item popouts
required property var props
required property var visibilities
implicitWidth: layout.implicitWidth
implicitHeight: layout.implicitHeight
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.fill: parent
spacing: 8
anchors.fill: parent
spacing: 8
IdleInhibit {}
IdleInhibit {
}
Toggles {
visibilities: root.visibilities
popouts: root.popouts
}
}
Toggles {
popouts: root.popouts
visibilities: root.visibilities
}
}
}
@@ -5,120 +5,115 @@ import QtQuick
import QtQuick.Layouts
CustomRect {
id: root
id: root
Layout.fillWidth: true
implicitHeight: layout.implicitHeight + (IdleInhibitor.enabled ? activeChip.implicitHeight + activeChip.anchors.topMargin : 0) + 18 * 2
Layout.fillWidth: true
clip: true
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: layout.implicitHeight + (IdleInhibitor.enabled ? activeChip.implicitHeight + activeChip.anchors.topMargin : 0) + 18 * 2
radius: 8
radius: 8
color: DynamicColors.tPalette.m3surfaceContainer
clip: true
RowLayout {
id: layout
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 18
spacing: 10
CustomRect {
implicitWidth: implicitHeight
implicitHeight: icon.implicitHeight + 7 * 2
radius: 1000
color: IdleInhibitor.enabled ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3secondaryContainer
MaterialIcon {
id: icon
anchors.centerIn: parent
text: "coffee"
color: IdleInhibitor.enabled ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
text: qsTr("Keep Awake")
font.pointSize: 13
elide: Text.ElideRight
}
CustomText {
Layout.fillWidth: true
text: IdleInhibitor.enabled ? qsTr("Preventing sleep mode") : qsTr("Normal power management")
color: DynamicColors.palette.m3onSurfaceVariant
font.pointSize: 11
elide: Text.ElideRight
}
}
CustomSwitch {
checked: IdleInhibitor.enabled
onToggled: IdleInhibitor.enabled = checked
}
}
Loader {
id: activeChip
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.topMargin: 20
anchors.bottomMargin: IdleInhibitor.enabled ? 18 : -implicitHeight
anchors.leftMargin: 18
opacity: IdleInhibitor.enabled ? 1 : 0
scale: IdleInhibitor.enabled ? 1 : 0.5
Component.onCompleted: active = Qt.binding(() => opacity > 0)
sourceComponent: CustomRect {
implicitWidth: activeText.implicitWidth + 10 * 2
implicitHeight: activeText.implicitHeight + 10 * 2
radius: 1000
color: DynamicColors.palette.m3primary
CustomText {
id: activeText
anchors.centerIn: parent
text: qsTr("Active since %1").arg(Qt.formatTime(IdleInhibitor.enabledSince, Config.services.useTwelveHourClock ? "hh:mm a" : "hh:mm"))
color: DynamicColors.palette.m3onPrimary
font.pointSize: Math.round(11 * 0.9)
}
}
Behavior on anchors.bottomMargin {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
Behavior on scale {
Anim {}
}
}
Behavior on implicitHeight {
Anim {
Behavior on implicitHeight {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: 18
anchors.right: parent.right
anchors.top: parent.top
spacing: 10
CustomRect {
color: IdleInhibitor.enabled ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3secondaryContainer
implicitHeight: icon.implicitHeight + 7 * 2
implicitWidth: implicitHeight
radius: 1000
MaterialIcon {
id: icon
anchors.centerIn: parent
color: IdleInhibitor.enabled ? DynamicColors.palette.m3onSecondary : DynamicColors.palette.m3onSecondaryContainer
font.pointSize: 18
text: "coffee"
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
CustomText {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: 13
text: qsTr("Keep Awake")
}
CustomText {
Layout.fillWidth: true
color: DynamicColors.palette.m3onSurfaceVariant
elide: Text.ElideRight
font.pointSize: 11
text: IdleInhibitor.enabled ? qsTr("Preventing sleep mode") : qsTr("Normal power management")
}
}
CustomSwitch {
checked: IdleInhibitor.enabled
onToggled: IdleInhibitor.enabled = checked
}
}
Loader {
id: activeChip
anchors.bottom: parent.bottom
anchors.bottomMargin: IdleInhibitor.enabled ? 18 : -implicitHeight
anchors.left: parent.left
anchors.leftMargin: 18
anchors.topMargin: 20
opacity: IdleInhibitor.enabled ? 1 : 0
scale: IdleInhibitor.enabled ? 1 : 0.5
Behavior on anchors.bottomMargin {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on opacity {
Anim {
duration: MaterialEasing.expressiveEffectsTime
}
}
Behavior on scale {
Anim {
}
}
sourceComponent: CustomRect {
color: DynamicColors.palette.m3primary
implicitHeight: activeText.implicitHeight + 10 * 2
implicitWidth: activeText.implicitWidth + 10 * 2
radius: 1000
CustomText {
id: activeText
anchors.centerIn: parent
color: DynamicColors.palette.m3onPrimary
font.pointSize: Math.round(11 * 0.9)
text: qsTr("Active since %1").arg(Qt.formatTime(IdleInhibitor.enabledSince, Config.services.useTwelveHourClock ? "hh:mm a" : "hh:mm"))
}
}
Component.onCompleted: active = Qt.binding(() => opacity > 0)
}
}
+69 -71
View File
@@ -6,91 +6,89 @@ import Quickshell
import QtQuick
Item {
id: root
id: root
required property var visibilities
required property Item sidebar
required property Item popouts
required property Item popouts
readonly property PersistentProperties props: PersistentProperties {
property string recordingConfirmDelete
property bool recordingListExpanded: false
property string recordingMode
readonly property PersistentProperties props: PersistentProperties {
property bool recordingListExpanded: false
property string recordingConfirmDelete
property string recordingMode
reloadableId: "utilities"
}
readonly property bool shouldBeActive: visibilities.sidebar
required property Item sidebar
required property var visibilities
reloadableId: "utilities"
}
readonly property bool shouldBeActive: visibilities.sidebar
implicitHeight: 0
implicitWidth: sidebar.visible ? sidebar.width : Config.utilities.sizes.width
visible: height > 0
visible: height > 0
implicitHeight: 0
implicitWidth: sidebar.visible ? sidebar.width : Config.utilities.sizes.width
states: State {
name: "visible"
when: root.shouldBeActive
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
PropertyChanges {
root.implicitHeight: content.implicitHeight + 8 * 2
}
}
transitions: [
Transition {
from: ""
to: "visible"
states: State {
name: "visible"
when: root.shouldBeActive
PropertyChanges {
root.implicitHeight: content.implicitHeight + 8 * 2
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitHeight"
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
},
Transition {
from: "visible"
to: ""
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitHeight"
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
]
property: "implicitHeight"
target: root
}
}
]
Timer {
id: timer
onStateChanged: {
if (state === "visible" && timer.running) {
timer.triggered();
timer.stop();
}
}
running: true
interval: 1000
onTriggered: {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
}
}
Timer {
id: timer
Loader {
id: content
interval: 1000
running: true
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 8
onTriggered: {
content.active = Qt.binding(() => root.shouldBeActive || root.visible);
content.visible = true;
}
}
visible: false
active: true
Loader {
id: content
sourceComponent: Content {
implicitWidth: root.implicitWidth - 8 * 2
props: root.props
visibilities: root.visibilities
popouts: root.popouts
}
}
active: true
anchors.left: parent.left
anchors.margins: 8
anchors.top: parent.top
visible: false
sourceComponent: Content {
implicitWidth: root.implicitWidth - 8 * 2
popouts: root.popouts
props: root.props
visibilities: root.visibilities
}
}
}
+47 -47
View File
@@ -5,64 +5,64 @@ import qs.Config
import QtQuick
Item {
id: root
id: root
required property var visibilities
required property var panels
readonly property Props props: Props {}
required property var panels
readonly property Props props: Props {
}
required property var visibilities
visible: width > 0
implicitWidth: 0
implicitWidth: 0
visible: width > 0
states: State {
name: "visible"
when: root.visibilities.sidebar
states: State {
name: "visible"
when: root.visibilities.sidebar
PropertyChanges {
root.implicitWidth: Config.sidebar.sizes.width
}
}
PropertyChanges {
root.implicitWidth: Config.sidebar.sizes.width
}
}
transitions: [
Transition {
from: ""
to: "visible"
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitWidth"
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
},
Transition {
from: "visible"
to: ""
property: "implicitWidth"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitWidth"
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
]
property: "implicitWidth"
target: root
}
}
]
Loader {
id: content
Loader {
id: content
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.margins: 8
anchors.bottomMargin: 0
active: true
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.left: parent.left
anchors.margins: 8
anchors.top: parent.top
active: true
Component.onCompleted: active = Qt.binding(() => (root.visibilities.sidebar && Config.sidebar.enabled) || root.visible)
sourceComponent: Content {
implicitWidth: Config.sidebar.sizes.width - 8 * 2
props: root.props
visibilities: root.visibilities
}
sourceComponent: Content {
implicitWidth: Config.sidebar.sizes.width - 8 * 2
props: root.props
visibilities: root.visibilities
}
}
Component.onCompleted: active = Qt.binding(() => (root.visibilities.sidebar && Config.sidebar.enabled) || root.visible)
}
}
+26 -27
View File
@@ -3,37 +3,36 @@ import qs.Components
import qs.Config
Item {
id: root
id: root
required property var visibilities
required property Item panels
required property Item panels
required property var visibilities
visible: height > 0
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
implicitHeight: content.implicitHeight
implicitHeight: content.implicitHeight
implicitWidth: Math.max(panels.sidebar.width, content.implicitWidth)
visible: height > 0
states: State {
name: "hidden"
when: root.visibilities.sidebar
states: State {
name: "hidden"
when: root.visibilities.sidebar
PropertyChanges {
root.implicitHeight: 0
}
}
PropertyChanges {
root.implicitHeight: 0
}
}
transitions: Transition {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
transitions: Transition {
Anim {
target: root
property: "implicitHeight"
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Content {
id: content
Content {
id: content
visibilities: root.visibilities
panels: root.panels
}
panels: root.panels
visibilities: root.visibilities
}
}
+55 -48
View File
@@ -4,56 +4,63 @@ import qs.Components
import qs.Config
ShapePath {
id: root
id: root
required property Wrapper wrapper
readonly property real rounding: 10
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
readonly property bool flatten: wrapper.width < rounding * 2
readonly property real rounding: 10
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
required property Wrapper wrapper
strokeWidth: -1
fillColor: DynamicColors.palette.m3surface
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
PathArc {
relativeX: -root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 3)
relativeY: 0
}
PathArc {
relativeX: -root.roundingX * 2
relativeY: root.rounding * 2
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 4
}
PathArc {
relativeX: root.roundingX * 2
relativeY: root.rounding * 2
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 3
relativeY: 0
}
PathArc {
relativeX: root.roundingX
relativeY: root.rounding
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
}
Behavior on fillColor {
CAnim {
}
}
Behavior on fillColor {
CAnim {}
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: -root.roundingX
relativeY: root.rounding
}
PathLine {
relativeX: -(root.wrapper.width - root.roundingX * 3)
relativeY: 0
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: -root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: 0
relativeY: root.wrapper.height - root.rounding * 4
}
PathArc {
direction: PathArc.Counterclockwise
radiusX: Math.min(root.rounding * 2, root.wrapper.width)
radiusY: root.rounding * 2
relativeX: root.roundingX * 2
relativeY: root.rounding * 2
}
PathLine {
relativeX: root.wrapper.width - root.roundingX * 3
relativeY: 0
}
PathArc {
radiusX: Math.min(root.rounding, root.wrapper.width)
radiusY: root.rounding
relativeX: root.roundingX
relativeY: root.rounding
}
}
+97 -98
View File
@@ -8,129 +8,128 @@ import qs.Config
import qs.Daemons
Item {
id: root
id: root
required property Brightness.Monitor monitor
required property var visibilities
required property real brightness
required property Brightness.Monitor monitor
required property bool muted
required property bool sourceMuted
required property real sourceVolume
required property var visibilities
required property real volume
required property real volume
required property bool muted
required property real sourceVolume
required property bool sourceMuted
required property real brightness
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2
implicitWidth: layout.implicitWidth + Appearance.padding.small * 2
implicitHeight: layout.implicitHeight + Appearance.padding.small * 2
ColumnLayout {
id: layout
ColumnLayout {
id: layout
anchors.centerIn: parent
spacing: Appearance.spacing.normal
anchors.centerIn: parent
spacing: Appearance.spacing.normal
// Speaker volume
CustomMouseArea {
function onWheel(event: WheelEvent) {
if (event.angleDelta.y > 0)
Audio.incrementVolume();
else if (event.angleDelta.y < 0)
Audio.decrementVolume();
}
// Speaker volume
CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
implicitHeight: Config.osd.sizes.sliderHeight
implicitWidth: Config.osd.sizes.sliderWidth
function onWheel(event: WheelEvent) {
if (event.angleDelta.y > 0)
Audio.incrementVolume();
else if (event.angleDelta.y < 0)
Audio.decrementVolume();
}
FilledSlider {
anchors.fill: parent
icon: Icons.getVolumeIcon(value, root.muted)
value: root.volume
to: Config.services.maxVolume
FilledSlider {
anchors.fill: parent
color: Audio.muted ? DynamicColors.palette.m3error : DynamicColors.palette.m3secondary
onMoved: Audio.setVolume(value)
}
}
icon: Icons.getVolumeIcon(value, root.muted)
to: Config.services.maxVolume
value: root.volume
// Microphone volume
WrappedLoader {
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session)
onMoved: Audio.setVolume(value)
}
}
sourceComponent: CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
// Microphone volume
WrappedLoader {
shouldBeActive: Config.osd.enableMicrophone && (!Config.osd.enableBrightness || !root.visibilities.session)
function onWheel(event: WheelEvent) {
if (event.angleDelta.y > 0)
Audio.incrementSourceVolume();
else if (event.angleDelta.y < 0)
Audio.decrementSourceVolume();
}
sourceComponent: CustomMouseArea {
function onWheel(event: WheelEvent) {
if (event.angleDelta.y > 0)
Audio.incrementSourceVolume();
else if (event.angleDelta.y < 0)
Audio.decrementSourceVolume();
}
FilledSlider {
anchors.fill: parent
implicitHeight: Config.osd.sizes.sliderHeight
implicitWidth: Config.osd.sizes.sliderWidth
icon: Icons.getMicVolumeIcon(value, root.sourceMuted)
value: root.sourceVolume
to: Config.services.maxVolume
FilledSlider {
anchors.fill: parent
color: Audio.sourceMuted ? DynamicColors.palette.m3error : DynamicColors.palette.m3secondary
onMoved: Audio.setSourceVolume(value)
}
}
}
icon: Icons.getMicVolumeIcon(value, root.sourceMuted)
to: Config.services.maxVolume
value: root.sourceVolume
// Brightness
WrappedLoader {
shouldBeActive: Config.osd.enableBrightness
onMoved: Audio.setSourceVolume(value)
}
}
}
sourceComponent: CustomMouseArea {
implicitWidth: Config.osd.sizes.sliderWidth
implicitHeight: Config.osd.sizes.sliderHeight
// Brightness
WrappedLoader {
shouldBeActive: Config.osd.enableBrightness
function onWheel(event: WheelEvent) {
const monitor = root.monitor;
if (!monitor)
return;
if (event.angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement);
else if (event.angleDelta.y < 0)
monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement);
}
sourceComponent: CustomMouseArea {
function onWheel(event: WheelEvent) {
const monitor = root.monitor;
if (!monitor)
return;
if (event.angleDelta.y > 0)
monitor.setBrightness(monitor.brightness + Config.services.brightnessIncrement);
else if (event.angleDelta.y < 0)
monitor.setBrightness(monitor.brightness - Config.services.brightnessIncrement);
}
FilledSlider {
anchors.fill: parent
implicitHeight: Config.osd.sizes.sliderHeight
implicitWidth: Config.osd.sizes.sliderWidth
icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.brightness
onMoved: {
if ( Config.osd.allMonBrightness ) {
root.monitor?.setBrightness(value)
FilledSlider {
anchors.fill: parent
icon: `brightness_${(Math.round(value * 6) + 1)}`
value: root.brightness
onMoved: {
if (Config.osd.allMonBrightness) {
root.monitor?.setBrightness(value);
} else {
for (const mon of Brightness.monitors) {
mon.setBrightness(value)
mon.setBrightness(value);
}
}
}
}
}
}
}
}
}
}
}
component WrappedLoader: Loader {
required property bool shouldBeActive
component WrappedLoader: Loader {
required property bool shouldBeActive
Layout.preferredHeight: shouldBeActive ? Config.osd.sizes.sliderHeight : 0
opacity: shouldBeActive ? 1 : 0
active: opacity > 0
visible: active
Layout.preferredHeight: shouldBeActive ? Config.osd.sizes.sliderHeight : 0
active: opacity > 0
opacity: shouldBeActive ? 1 : 0
visible: active
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
Behavior on opacity {
Anim {}
}
}
Behavior on Layout.preferredHeight {
Anim {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
Behavior on opacity {
Anim {
}
}
}
}
+101 -102
View File
@@ -8,128 +8,127 @@ import qs.Config
import qs.Daemons
Item {
id: root
id: root
required property ShellScreen screen
required property var visibilities
property bool hovered
readonly property Brightness.Monitor monitor: Brightness.getMonitorForScreen(root.screen)
readonly property bool shouldBeActive: visibilities.osd && Config.osd.enabled && !(visibilities.utilities && Config.utilities.enabled)
property real brightness
property bool hovered
readonly property Brightness.Monitor monitor: Brightness.getMonitorForScreen(root.screen)
property bool muted
required property ShellScreen screen
readonly property bool shouldBeActive: visibilities.osd && Config.osd.enabled && !(visibilities.utilities && Config.utilities.enabled)
property bool sourceMuted
property real sourceVolume
required property var visibilities
property real volume
property real volume
property bool muted
property real sourceVolume
property bool sourceMuted
property real brightness
function show(): void {
visibilities.osd = true;
timer.restart();
}
function show(): void {
visibilities.osd = true;
timer.restart();
}
implicitHeight: content.implicitHeight
implicitWidth: 0
visible: width > 0
Component.onCompleted: {
volume = Audio.volume;
muted = Audio.muted;
sourceVolume = Audio.sourceVolume;
sourceMuted = Audio.sourceMuted;
brightness = root.monitor?.brightness ?? 0;
}
states: State {
name: "visible"
when: root.shouldBeActive
visible: width > 0
implicitWidth: 0
implicitHeight: content.implicitHeight
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
}
transitions: [
Transition {
from: ""
to: "visible"
states: State {
name: "visible"
when: root.shouldBeActive
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
},
Transition {
from: "visible"
to: ""
PropertyChanges {
root.implicitWidth: content.implicitWidth
}
}
Anim {
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitWidth"
target: root
}
}
]
transitions: [
Transition {
from: ""
to: "visible"
Component.onCompleted: {
volume = Audio.volume;
muted = Audio.muted;
sourceVolume = Audio.sourceVolume;
sourceMuted = Audio.sourceMuted;
brightness = root.monitor?.brightness ?? 0;
}
Anim {
target: root
property: "implicitWidth"
easing.bezierCurve: MaterialEasing.expressiveEffects
}
},
Transition {
from: "visible"
to: ""
Connections {
function onMutedChanged(): void {
root.show();
root.muted = Audio.muted;
}
Anim {
target: root
property: "implicitWidth"
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
]
function onSourceMutedChanged(): void {
root.show();
root.sourceMuted = Audio.sourceMuted;
}
Connections {
target: Audio
function onSourceVolumeChanged(): void {
root.show();
root.sourceVolume = Audio.sourceVolume;
}
function onMutedChanged(): void {
root.show();
root.muted = Audio.muted;
}
function onVolumeChanged(): void {
root.show();
root.volume = Audio.volume;
}
function onVolumeChanged(): void {
root.show();
root.volume = Audio.volume;
}
target: Audio
}
function onSourceMutedChanged(): void {
root.show();
root.sourceMuted = Audio.sourceMuted;
}
Connections {
function onBrightnessChanged(): void {
root.show();
root.brightness = root.monitor?.brightness ?? 0;
}
function onSourceVolumeChanged(): void {
root.show();
root.sourceVolume = Audio.sourceVolume;
}
}
target: root.monitor
}
Connections {
target: root.monitor
Timer {
id: timer
function onBrightnessChanged(): void {
root.show();
root.brightness = root.monitor?.brightness ?? 0;
}
}
interval: Config.osd.hideDelay
Timer {
id: timer
onTriggered: {
if (!root.hovered)
root.visibilities.osd = false;
}
}
interval: Config.osd.hideDelay
onTriggered: {
if (!root.hovered)
root.visibilities.osd = false;
}
}
Loader {
id: content
Loader {
id: content
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
sourceComponent: Content {
brightness: root.brightness
monitor: root.monitor
muted: root.muted
sourceMuted: root.sourceMuted
sourceVolume: root.sourceVolume
visibilities: root.visibilities
volume: root.volume
}
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
sourceComponent: Content {
monitor: root.monitor
visibilities: root.visibilities
volume: root.volume
muted: root.muted
sourceVolume: root.sourceVolume
sourceMuted: root.sourceMuted
brightness: root.brightness
}
}
Component.onCompleted: active = Qt.binding(() => root.shouldBeActive || root.visible)
}
}
+123 -116
View File
@@ -19,53 +19,57 @@ Scope {
PanelWindow {
id: panelWindow
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.namespace: "ZShell-Auth"
WlrLayershell.layer: WlrLayer.Overlay
visible: false
color: "transparent"
property bool detailsOpen: false
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.namespace: "ZShell-Auth"
color: "transparent"
visible: false
Connections {
target: root
onShouldShowChanged: {
if ( root.shouldShow ) {
panelWindow.visible = true
openAnim.start()
if (root.shouldShow) {
panelWindow.visible = true;
openAnim.start();
} else {
closeAnim.start()
closeAnim.start();
}
}
}
Anim {
id: openAnim
target: inputPanel
property: "opacity"
to: 1
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: inputPanel
to: 1
}
Anim {
id: closeAnim
target: inputPanel
property: "opacity"
to: 0
duration: MaterialEasing.expressiveEffectsTime
onStarted: {
panelWindow.detailsOpen = false
}
property: "opacity"
target: inputPanel
to: 0
onFinished: {
panelWindow.visible = false
panelWindow.visible = false;
}
onStarted: {
panelWindow.detailsOpen = false;
}
}
anchors {
bottom: true
left: true
right: true
top: true
bottom: true
}
// mask: Region { item: inputPanel }
@@ -73,106 +77,105 @@ Scope {
Rectangle {
id: inputPanel
color: DynamicColors.tPalette.m3surface
opacity: 0
anchors.centerIn: parent
color: DynamicColors.tPalette.m3surface
implicitHeight: layout.childrenRect.height + 28
implicitWidth: layout.childrenRect.width + 32
opacity: 0
radius: 24
implicitWidth: layout.childrenRect.width + 32
implicitHeight: layout.childrenRect.height + 28
ColumnLayout {
id: layout
anchors.centerIn: parent
RowLayout {
id: contentRow
spacing: 24
Item {
Layout.preferredWidth: icon.implicitSize
Layout.preferredHeight: icon.implicitSize
Layout.leftMargin: 16
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.leftMargin: 16
Layout.preferredHeight: icon.implicitSize
Layout.preferredWidth: icon.implicitSize
IconImage {
id: icon
anchors.fill: parent
visible: `${source}`.includes("://")
source: Quickshell.iconPath(polkitAgent.flow?.iconName, true) ?? ""
implicitSize: 64
mipmap: true
source: Quickshell.iconPath(polkitAgent.flow?.iconName, true) ?? ""
visible: `${source}`.includes("://")
}
MaterialIcon {
visible: !icon.visible
text: "security"
anchors.fill: parent
font.pointSize: 64
horizontalAlignment: Text.AlignHCenter
text: "security"
verticalAlignment: Text.AlignVCenter
visible: !icon.visible
}
}
ColumnLayout {
id: contentColumn
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
CustomText {
Layout.preferredWidth: Math.min(600, contentWidth)
Layout.alignment: Qt.AlignLeft
Layout.preferredWidth: Math.min(600, contentWidth)
font.bold: true
font.pointSize: 16
text: polkitAgent.flow?.message
wrapMode: Text.WordWrap
font.pointSize: 16
font.bold: true
}
CustomText {
Layout.preferredWidth: Math.min(600, contentWidth)
Layout.alignment: Qt.AlignLeft
text: polkitAgent.flow?.supplementaryMessage || "No Additional Information"
Layout.preferredWidth: Math.min(600, contentWidth)
color: DynamicColors.tPalette.m3onSurfaceVariant
wrapMode: Text.WordWrap
font.pointSize: 12
font.bold: true
font.pointSize: 12
text: polkitAgent.flow?.supplementaryMessage || "No Additional Information"
wrapMode: Text.WordWrap
}
TextField {
id: passInput
Layout.preferredHeight: 40
Layout.preferredWidth: contentColumn.implicitWidth
color: DynamicColors.palette.m3onSurfaceVariant
echoMode: polkitAgent.flow?.responseVisible ? TextInput.Normal : TextInput.Password
placeholderText: polkitAgent.flow?.failed ? " Incorrect Password" : " Input Password"
selectByMouse: true
onAccepted: okButton.clicked()
color: DynamicColors.palette.m3onSurfaceVariant
placeholderTextColor: polkitAgent.flow?.failed ? DynamicColors.palette.m3onError : DynamicColors.tPalette.m3onSurfaceVariant
Layout.preferredWidth: contentColumn.implicitWidth
Layout.preferredHeight: 40
selectByMouse: true
background: CustomRect {
radius: 8
color: (polkitAgent.flow?.failed && passInput.text === "") ? DynamicColors.palette.m3error : DynamicColors.tPalette.m3surfaceVariant
implicitHeight: 40
color: ( polkitAgent.flow?.failed && passInput.text === "" ) ? DynamicColors.palette.m3error : DynamicColors.tPalette.m3surfaceVariant
radius: 8
}
onAccepted: okButton.clicked()
}
CustomCheckbox {
id: showPassCheckbox
text: "Show Password"
checked: polkitAgent.flow?.responseVisible
onCheckedChanged: {
passInput.echoMode = checked ? TextInput.Normal : TextInput.Password
passInput.forceActiveFocus()
}
Layout.alignment: Qt.AlignLeft
checked: polkitAgent.flow?.responseVisible
text: "Show Password"
onCheckedChanged: {
passInput.echoMode = checked ? TextInput.Normal : TextInput.Password;
passInput.forceActiveFocus();
}
}
}
}
@@ -180,51 +183,49 @@ Scope {
CustomRect {
id: detailsPanel
visible: true
color: DynamicColors.tPalette.m3surfaceContainerLow
radius: 16
clip: true
implicitHeight: 0
property bool open: panelWindow.detailsOpen
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
property bool open: panelWindow.detailsOpen
clip: true
color: DynamicColors.tPalette.m3surfaceContainerLow
implicitHeight: 0
radius: 16
visible: true
Behavior on open {
ParallelAnimation {
Anim {
target: detailsPanel
duration: MaterialEasing.expressiveEffectsTime
property: "implicitHeight"
target: detailsPanel
to: !detailsPanel.open ? textDetailsColumn.childrenRect.height + 16 : 0
duration: MaterialEasing.expressiveEffectsTime
}
Anim {
target: textDetailsColumn
duration: MaterialEasing.expressiveEffectsTime
property: "opacity"
target: textDetailsColumn
to: !detailsPanel.open ? 1 : 0
duration: MaterialEasing.expressiveEffectsTime
}
Anim {
target: textDetailsColumn
property: "scale"
to: !detailsPanel.open ? 1 : 0.9
duration: MaterialEasing.expressiveEffectsTime
property: "scale"
target: textDetailsColumn
to: !detailsPanel.open ? 1 : 0.9
}
}
}
ColumnLayout {
id: textDetailsColumn
spacing: 8
anchors.fill: parent
anchors.margins: 8
opacity: 0
scale: 0.9
spacing: 8
CustomText {
text: `actionId: ${polkitAgent.flow?.actionId}`
@@ -239,64 +240,69 @@ Scope {
}
RowLayout {
spacing: 8
Layout.preferredWidth: contentRow.implicitWidth
spacing: 8
CustomButton {
id: detailsButton
text: "Details"
textColor: DynamicColors.palette.m3onSurface
Layout.alignment: Qt.AlignLeft
Layout.preferredHeight: 40
Layout.preferredWidth: 92
bgColor: DynamicColors.palette.m3surfaceContainer
enabled: true
radius: 1000
Layout.preferredWidth: 92
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignLeft
text: "Details"
textColor: DynamicColors.palette.m3onSurface
onClicked: {
panelWindow.detailsOpen = !panelWindow.detailsOpen
console.log(panelWindow.detailsOpen)
panelWindow.detailsOpen = !panelWindow.detailsOpen;
console.log(panelWindow.detailsOpen);
}
}
Item {
id: spacer
Layout.fillWidth: true
}
CustomButton {
id: okButton
text: "OK"
textColor: DynamicColors.palette.m3onPrimary
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 76
bgColor: DynamicColors.palette.m3primary
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
radius: 1000
Layout.preferredWidth: 76
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignRight
text: "OK"
textColor: DynamicColors.palette.m3onPrimary
onClicked: {
polkitAgent.flow.submit(passInput.text)
passInput.text = ""
passInput.forceActiveFocus()
polkitAgent.flow.submit(passInput.text);
passInput.text = "";
passInput.forceActiveFocus();
}
}
CustomButton {
id: cancelButton
text: "Cancel"
textColor: DynamicColors.palette.m3onSurface
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 40
Layout.preferredWidth: 76
bgColor: DynamicColors.palette.m3surfaceContainer
enabled: passInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired
radius: 1000
Layout.preferredWidth: 76
Layout.preferredHeight: 40
Layout.alignment: Qt.AlignRight
text: "Cancel"
textColor: DynamicColors.palette.m3onSurface
onClicked: {
root.shouldShow = false
console.log(icon.source, icon.visible)
polkitAgent.flow.cancelAuthenticationRequest()
passInput.text = ""
root.shouldShow = false;
console.log(icon.source, icon.visible);
polkitAgent.flow.cancelAuthenticationRequest();
passInput.text = "";
}
}
}
@@ -304,48 +310,49 @@ Scope {
}
Connections {
target: polkitAgent.flow
function onIsResponseRequiredChanged() {
passInput.text = ""
if ( polkitAgent.flow?.isResponseRequired )
root.shouldShow = true
passInput.forceActiveFocus()
passInput.text = "";
if (polkitAgent.flow?.isResponseRequired)
root.shouldShow = true;
passInput.forceActiveFocus();
}
function onIsSuccessfulChanged() {
if ( polkitAgent.flow?.isSuccessful )
root.shouldShow = false
passInput.text = ""
if (polkitAgent.flow?.isSuccessful)
root.shouldShow = false;
passInput.text = "";
}
target: polkitAgent.flow
}
}
PolkitAgent {
id: polkitAgent
}
Variants {
model: Quickshell.screens
PanelWindow {
required property var modelData
color: root.shouldShow ? "#80000000" : "transparent"
screen: modelData
exclusionMode: ExclusionMode.Ignore
screen: modelData
visible: panelWindow.visible
Behavior on color {
CAnim {}
CAnim {
}
}
anchors {
bottom: true
left: true
right: true
top: true
bottom: true
}
}
}
+93 -87
View File
@@ -5,106 +5,112 @@ import qs.Components
import qs.Config
Item {
id: root
required property double percentage
property int warningThreshold: 100
property bool shown: true
clip: true
visible: width > 0 && height > 0
implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth
implicitHeight: 22
property bool warning: percentage * 100 >= warningThreshold
id: root
property color borderColor: warning ? DynamicColors.palette.m3onError : mainColor
required property color mainColor
property color usageColor: warning ? DynamicColors.palette.m3error : mainColor
property color borderColor: warning ? DynamicColors.palette.m3onError : mainColor
required property double percentage
property bool shown: true
property color usageColor: warning ? DynamicColors.palette.m3error : mainColor
property bool warning: percentage * 100 >= warningThreshold
property int warningThreshold: 100
Behavior on percentage {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
clip: true
implicitHeight: 22
implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth
visible: width > 0 && height > 0
RowLayout {
id: resourceRowLayout
spacing: 2
x: shown ? 0 : -resourceRowLayout.width
anchors {
verticalCenter: parent.verticalCenter
}
Behavior on percentage {
NumberAnimation {
duration: 300
easing.type: Easing.InOutQuad
}
}
Item {
Layout.alignment: Qt.AlignVCenter
implicitWidth: 14
implicitHeight: root.implicitHeight
RowLayout {
id: resourceRowLayout
Rectangle {
id: backgroundCircle
anchors.centerIn: parent
width: 14
height: 14
radius: height / 2
color: "#40000000"
border.color: "#404040"
border.width: 1
}
spacing: 2
x: shown ? 0 : -resourceRowLayout.width
Shape {
anchors.fill: backgroundCircle
anchors {
verticalCenter: parent.verticalCenter
}
smooth: true
preferredRendererType: Shape.CurveRenderer
Item {
Layout.alignment: Qt.AlignVCenter
implicitHeight: root.implicitHeight
implicitWidth: 14
ShapePath {
strokeWidth: 0
fillColor: root.usageColor
startX: backgroundCircle.width / 2
startY: backgroundCircle.height / 2
Rectangle {
id: backgroundCircle
Behavior on fillColor {
CAnim {}
}
anchors.centerIn: parent
border.color: "#404040"
border.width: 1
color: "#40000000"
height: 14
radius: height / 2
width: 14
}
PathLine {
x: backgroundCircle.width / 2
y: 0 + ( 1 / 2 )
}
Shape {
anchors.fill: backgroundCircle
preferredRendererType: Shape.CurveRenderer
smooth: true
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - ( 1 / 2 )
radiusY: backgroundCircle.height / 2 - ( 1 / 2 )
startAngle: -90
sweepAngle: 360 * root.percentage
}
ShapePath {
fillColor: root.usageColor
startX: backgroundCircle.width / 2
startY: backgroundCircle.height / 2
strokeWidth: 0
PathLine {
x: backgroundCircle.width / 2
y: backgroundCircle.height / 2
}
}
Behavior on fillColor {
CAnim {
}
}
ShapePath {
strokeWidth: 1
strokeColor: root.borderColor
fillColor: "transparent"
capStyle: ShapePath.FlatCap
PathLine {
x: backgroundCircle.width / 2
y: 0 + (1 / 2)
}
Behavior on strokeColor {
CAnim {}
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - (1 / 2)
radiusY: backgroundCircle.height / 2 - (1 / 2)
startAngle: -90
sweepAngle: 360 * root.percentage
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - ( 1 / 2 )
radiusY: backgroundCircle.height / 2 - ( 1 / 2 )
startAngle: -90
sweepAngle: 360 * root.percentage
}
}
}
}
}
PathLine {
x: backgroundCircle.width / 2
y: backgroundCircle.height / 2
}
}
ShapePath {
capStyle: ShapePath.FlatCap
fillColor: "transparent"
strokeColor: root.borderColor
strokeWidth: 1
Behavior on strokeColor {
CAnim {
}
}
PathAngleArc {
centerX: backgroundCircle.width / 2
centerY: backgroundCircle.height / 2
radiusX: backgroundCircle.width / 2 - (1 / 2)
radiusY: backgroundCircle.height / 2 - (1 / 2)
startAngle: -90
sweepAngle: 360 * root.percentage
}
}
}
}
}
}
+60 -58
View File
@@ -5,71 +5,73 @@ import qs.Config
import qs.Components
Item {
id: root
required property string resourceName
required property double percentage
required property int warningThreshold
required property string details
required property string iconString
property color barColor: DynamicColors.palette.m3primary
property color warningBarColor: DynamicColors.palette.m3error
property color textColor: DynamicColors.palette.m3onSurface
id: root
Layout.preferredWidth: 158
Layout.preferredHeight: columnLayout.implicitHeight
property color barColor: DynamicColors.palette.m3primary
required property string details
required property string iconString
required property double percentage
required property string resourceName
property color textColor: DynamicColors.palette.m3onSurface
property color warningBarColor: DynamicColors.palette.m3error
required property int warningThreshold
ColumnLayout {
id: columnLayout
anchors.fill: parent
spacing: 4
Layout.preferredHeight: columnLayout.implicitHeight
Layout.preferredWidth: 158
Row {
spacing: 6
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
ColumnLayout {
id: columnLayout
MaterialIcon {
font.family: "Material Symbols Rounded"
font.pointSize: 28
text: root.iconString
color: root.textColor
}
anchors.fill: parent
spacing: 4
CustomText {
anchors.verticalCenter: parent.verticalCenter
text: root.resourceName
font.pointSize: 12
color: root.textColor
}
}
Row {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
spacing: 6
Rectangle {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.preferredHeight: 6
radius: height / 2
color: "#40000000"
MaterialIcon {
color: root.textColor
font.family: "Material Symbols Rounded"
font.pointSize: 28
text: root.iconString
}
Rectangle {
width: parent.width * Math.min(root.percentage, 1)
height: parent.height
radius: height / 2
color: root.percentage * 100 >= root.warningThreshold ? root.warningBarColor : root.barColor
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: root.textColor
font.pointSize: 12
text: root.resourceName
}
}
Behavior on width {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
Rectangle {
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
Layout.preferredHeight: 6
color: "#40000000"
radius: height / 2
CustomText {
Layout.alignment: Qt.AlignLeft
text: root.details
font.pointSize: 10
color: root.textColor
}
}
Rectangle {
color: root.percentage * 100 >= root.warningThreshold ? root.warningBarColor : root.barColor
height: parent.height
radius: height / 2
width: parent.width * Math.min(root.percentage, 1)
Behavior on width {
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
}
}
CustomText {
Layout.alignment: Qt.AlignLeft
color: root.textColor
font.pointSize: 10
text: root.details
}
}
}
+45 -47
View File
@@ -5,57 +5,55 @@ import QtQuick.Layouts
import qs.Config
Item {
id: popoutWindow
implicitWidth: contentColumn.implicitWidth + 10 * 2
implicitHeight: contentColumn.implicitHeight + 10
required property var wrapper
id: popoutWindow
// ShadowRect {
// anchors.fill: contentRect
// radius: 8
// }
required property var wrapper
ColumnLayout {
id: contentColumn
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
implicitHeight: contentColumn.implicitHeight + 10
implicitWidth: contentColumn.implicitWidth + 10 * 2
ResourceDetail {
resourceName: qsTr( "Memory Usage" )
iconString: "\uf7a3"
percentage: ResourceUsage.memoryUsedPercentage
warningThreshold: 95
details: qsTr( "%1 of %2 MB used" )
.arg( Math.round( ResourceUsage.memoryUsed * 0.001 ))
.arg( Math.round( ResourceUsage.memoryTotal * 0.001 ))
}
// ShadowRect {
// anchors.fill: contentRect
// radius: 8
// }
ResourceDetail {
resourceName: qsTr( "CPU Usage" )
iconString: "\ue322"
percentage: ResourceUsage.cpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.cpuUsage * 100 ))
}
ColumnLayout {
id: contentColumn
ResourceDetail {
resourceName: qsTr( "GPU Usage" )
iconString: "\ue30f"
percentage: ResourceUsage.gpuUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuUsage * 100 ))
}
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
spacing: 10
ResourceDetail {
resourceName: qsTr( "VRAM Usage" )
iconString: "\ue30d"
percentage: ResourceUsage.gpuMemUsage
warningThreshold: 95
details: qsTr( "%1% used" )
.arg( Math.round( ResourceUsage.gpuMemUsage * 100 ))
}
}
ResourceDetail {
details: qsTr("%1 of %2 MB used").arg(Math.round(ResourceUsage.memoryUsed * 0.001)).arg(Math.round(ResourceUsage.memoryTotal * 0.001))
iconString: "\uf7a3"
percentage: ResourceUsage.memoryUsedPercentage
resourceName: qsTr("Memory Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.cpuUsage * 100))
iconString: "\ue322"
percentage: ResourceUsage.cpuUsage
resourceName: qsTr("CPU Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.gpuUsage * 100))
iconString: "\ue30f"
percentage: ResourceUsage.gpuUsage
resourceName: qsTr("GPU Usage")
warningThreshold: 95
}
ResourceDetail {
details: qsTr("%1% used").arg(Math.round(ResourceUsage.gpuMemUsage * 100))
iconString: "\ue30d"
percentage: ResourceUsage.gpuMemUsage
resourceName: qsTr("VRAM Usage")
warningThreshold: 95
}
}
}
+94 -75
View File
@@ -7,98 +7,117 @@ import Quickshell.Io
import qs.Config
Singleton {
id: root
property double memoryTotal: 1
id: root
property string autoGpuType: "NONE"
property double cpuUsage: 0
property double gpuMemUsage: 0
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property double gpuUsage: 0
property double memoryFree: 1
property double memoryTotal: 1
property double memoryUsed: memoryTotal - memoryFree
property double memoryUsedPercentage: memoryUsed / memoryTotal
property double swapTotal: 1
property double memoryUsedPercentage: memoryUsed / memoryTotal
property var previousCpuStats
property double swapFree: 1
property double swapTotal: 1
property double swapUsed: swapTotal - swapFree
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
property double cpuUsage: 0
property var previousCpuStats
property double gpuUsage: 0
property double gpuMemUsage: 0
property double totalMem: 0
readonly property string gpuType: Config.services.gpuType.toUpperCase() || autoGpuType
property string autoGpuType: "NONE"
property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0
property double totalMem: 0
Timer {
interval: 1
running: true
repeat: true
repeat: true
running: true
onTriggered: {
// Reload files
fileMeminfo.reload()
fileStat.reload()
// Reload files
fileMeminfo.reload();
fileStat.reload();
// Parse memory and swap usage
const textMeminfo = fileMeminfo.text()
memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1)
memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0)
swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1)
swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0)
// Parse memory and swap usage
const textMeminfo = fileMeminfo.text();
memoryTotal = Number(textMeminfo.match(/MemTotal: *(\d+)/)?.[1] ?? 1);
memoryFree = Number(textMeminfo.match(/MemAvailable: *(\d+)/)?.[1] ?? 0);
swapTotal = Number(textMeminfo.match(/SwapTotal: *(\d+)/)?.[1] ?? 1);
swapFree = Number(textMeminfo.match(/SwapFree: *(\d+)/)?.[1] ?? 0);
// Parse CPU usage
const textStat = fileStat.text()
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/)
if (cpuLine) {
const stats = cpuLine.slice(1).map(Number)
const total = stats.reduce((a, b) => a + b, 0)
const idle = stats[3]
// Parse CPU usage
const textStat = fileStat.text();
const cpuLine = textStat.match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
if (cpuLine) {
const stats = cpuLine.slice(1).map(Number);
const total = stats.reduce((a, b) => a + b, 0);
const idle = stats[3];
if (previousCpuStats) {
const totalDiff = total - previousCpuStats.total
const idleDiff = idle - previousCpuStats.idle
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0
}
if (previousCpuStats) {
const totalDiff = total - previousCpuStats.total;
const idleDiff = idle - previousCpuStats.idle;
cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
}
previousCpuStats = { total, idle }
}
if ( root.gpuType === "NVIDIA" ) {
processGpu.running = true
}
previousCpuStats = {
total,
idle
};
}
if (root.gpuType === "NVIDIA") {
processGpu.running = true;
}
interval = 3000
}
interval = 3000;
}
}
FileView { id: fileMeminfo; path: "/proc/meminfo" }
FileView { id: fileStat; path: "/proc/stat" }
FileView {
id: fileMeminfo
Process {
id: gpuTypeCheck
path: "/proc/meminfo"
}
running: !Config.services.gpuType
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
FileView {
id: fileStat
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && totalMem === 0
stdout: StdioCollector {
onStreamFinished: {
totalMem = Number(this.text.trim())
oneshotMem.running = false
}
}
}
path: "/proc/stat"
}
Process {
id: processGpu
command: ["nvidia-smi", "--query-gpu=utilization.gpu,memory.used", "--format=csv,noheader,nounits"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const parts = this.text.trim().split(", ")
gpuUsage = Number(parts[0]) / 100
gpuMemUsage = Number(parts[1]) / totalMem
}
}
}
Process {
id: gpuTypeCheck
command: ["sh", "-c", "if command -v nvidia-smi &>/dev/null && nvidia-smi -L &>/dev/null; then echo NVIDIA; elif ls /sys/class/drm/card*/device/gpu_busy_percent 2>/dev/null | grep -q .; then echo GENERIC; else echo NONE; fi"]
running: !Config.services.gpuType
stdout: StdioCollector {
onStreamFinished: root.autoGpuType = text.trim()
}
}
Process {
id: oneshotMem
command: ["nvidia-smi", "--query-gpu=memory.total", "--format=csv,noheader,nounits"]
running: root.gpuType === "NVIDIA" && totalMem === 0
stdout: StdioCollector {
onStreamFinished: {
totalMem = Number(this.text.trim());
oneshotMem.running = false;
}
}
}
Process {
id: processGpu
command: ["nvidia-smi", "--query-gpu=utilization.gpu,memory.used", "--format=csv,noheader,nounits"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const parts = this.text.trim().split(", ");
gpuUsage = Number(parts[0]) / 100;
gpuMemUsage = Number(parts[1]) / totalMem;
}
}
}
}
+73 -67
View File
@@ -10,81 +10,87 @@ import qs.Effects
import qs.Components
Item {
id: root
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
implicitHeight: 34
clip: true
id: root
Rectangle {
id: backgroundRect
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
implicitHeight: 22
color: DynamicColors.tPalette.m3surfaceContainer
radius: height / 2
Behavior on color {
CAnim {}
}
RowLayout {
id: rowLayout
clip: true
implicitHeight: 34
implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin
spacing: 6
anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
Rectangle {
id: backgroundRect
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: "memory_alt"
color: DynamicColors.palette.m3onSurface
}
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: 22
radius: height / 2
Resource {
percentage: ResourceUsage.memoryUsedPercentage
warningThreshold: 95
mainColor: DynamicColors.palette.m3primary
}
Behavior on color {
CAnim {
}
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: "memory"
color: DynamicColors.palette.m3onSurface
}
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
Resource {
percentage: ResourceUsage.cpuUsage
warningThreshold: 80
mainColor: DynamicColors.palette.m3secondary
}
RowLayout {
id: rowLayout
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: "gamepad"
color: DynamicColors.palette.m3onSurface
}
anchors.fill: parent
anchors.leftMargin: 5
anchors.rightMargin: 5
spacing: 6
Resource {
percentage: ResourceUsage.gpuUsage
mainColor: DynamicColors.palette.m3tertiary
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pixelSize: 18
text: "memory_alt"
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
font.pixelSize: 18
text: "developer_board"
color: DynamicColors.palette.m3onSurface
}
Resource {
mainColor: DynamicColors.palette.m3primary
percentage: ResourceUsage.memoryUsedPercentage
warningThreshold: 95
}
Resource {
percentage: ResourceUsage.gpuMemUsage
mainColor: DynamicColors.palette.m3primary
}
}
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pixelSize: 18
text: "memory"
}
Resource {
mainColor: DynamicColors.palette.m3secondary
percentage: ResourceUsage.cpuUsage
warningThreshold: 80
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pixelSize: 18
text: "gamepad"
}
Resource {
mainColor: DynamicColors.palette.m3tertiary
percentage: ResourceUsage.gpuUsage
}
MaterialIcon {
Layout.alignment: Qt.AlignVCenter
color: DynamicColors.palette.m3onSurface
font.pixelSize: 18
text: "developer_board"
}
Resource {
mainColor: DynamicColors.palette.m3primary
percentage: ResourceUsage.gpuMemUsage
}
}
}
}
+25 -25
View File
@@ -2,24 +2,28 @@ import QtQuick
import QtQuick.Shapes
import qs.Components
import qs.Config
import qs.Helpers
ShapePath {
id: root
required property Wrapper wrapper
readonly property real rounding: 8
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
strokeWidth: -1
readonly property bool flatten: wrapper.height < rounding * 2
readonly property real rounding: 8
readonly property real roundingY: flatten ? wrapper.height / 2 : rounding
required property Wrapper wrapper
fillColor: DynamicColors.palette.m3surface
strokeWidth: -1
Behavior on fillColor {
CAnim {
}
}
PathArc {
radiusX: root.rounding
radiusY: Math.min(root.roundingY, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min( root.roundingY, root.wrapper.height )
}
PathLine {
@@ -28,11 +32,11 @@ ShapePath {
}
PathArc {
relativeX: root.rounding
relativeY: root.roundingY
radiusX: root.rounding
radiusY: Math.min( root.rounding, root.wrapper.height )
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: root.roundingY
}
PathLine {
@@ -41,26 +45,22 @@ ShapePath {
}
PathArc {
relativeX: root.rounding
relativeY: - root.roundingY
radiusX: root.rounding
radiusY: Math.min( root.rounding, root.wrapper.height )
direction: PathArc.Counterclockwise
radiusX: root.rounding
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
PathLine {
relativeX: 0
relativeY: - ( root.wrapper.height - root.roundingY * 2 )
relativeY: -(root.wrapper.height - root.roundingY * 2)
}
PathArc {
relativeX: root.rounding
relativeY: - root.roundingY
radiusX: root.rounding
radiusY: Math.min( root.rounding, root.wrapper.height )
}
Behavior on fillColor {
CAnim {}
radiusY: Math.min(root.rounding, root.wrapper.height)
relativeX: root.rounding
relativeY: -root.roundingY
}
}
+42 -46
View File
@@ -12,105 +12,105 @@ import qs.Helpers
Item {
id: root
required property Item content
implicitHeight: clayout.contentHeight + Appearance.padding.smaller * 2
implicitWidth: clayout.contentWidth + Appearance.padding.smaller * 2
ListModel {
id: listModel
ListElement {
name: "General"
icon: "settings"
name: "General"
}
ListElement {
name: "Wallpaper"
icon: "wallpaper"
name: "Wallpaper"
}
ListElement {
name: "Bar"
icon: "settop_component"
name: "Bar"
}
ListElement {
name: "Lockscreen"
icon: "lock"
name: "Lockscreen"
}
ListElement {
name: "Services"
icon: "build_circle"
name: "Services"
}
ListElement {
name: "Notifications"
icon: "notifications"
name: "Notifications"
}
ListElement {
name: "Sidebar"
icon: "view_sidebar"
name: "Sidebar"
}
ListElement {
name: "Utilities"
icon: "handyman"
name: "Utilities"
}
ListElement {
name: "Dashboard"
icon: "dashboard"
name: "Dashboard"
}
ListElement {
icon: "colors"
name: "Appearance"
icon: "colors"
}
ListElement {
name: "On screen display"
icon: "display_settings"
name: "On screen display"
}
ListElement {
name: "Launcher"
icon: "rocket_launch"
name: "Launcher"
}
ListElement {
name: "Colors"
icon: "colors"
name: "Colors"
}
}
required property Item content
implicitWidth: clayout.contentWidth + Appearance.padding.smaller * 2
implicitHeight: clayout.contentHeight + Appearance.padding.smaller * 2
CustomRect {
anchors.fill: parent
color: DynamicColors.tPalette.m3surfaceContainer
radius: 4
CustomListView {
id: clayout
anchors.centerIn: parent
model: listModel
contentWidth: contentItem.childrenRect.width
contentHeight: contentItem.childrenRect.height
implicitWidth: contentItem.childrenRect.width
contentWidth: contentItem.childrenRect.width
highlightFollowsCurrentItem: false
implicitHeight: contentItem.childrenRect.height
implicitWidth: contentItem.childrenRect.width
model: listModel
spacing: 5
delegate: Category {
}
highlight: CustomRect {
color: DynamicColors.palette.m3primary
radius: 4
y: clayout.currentItem?.y ?? 0
implicitWidth: clayout.width
implicitHeight: clayout.currentItem?.implicitHeight ?? 0
implicitWidth: clayout.width
radius: 4
y: clayout.currentItem?.y ?? 0
Behavior on y {
Anim {
@@ -119,53 +119,49 @@ Item {
}
}
}
highlightFollowsCurrentItem: false
delegate: Category {}
}
}
component Category: CustomRect {
id: categoryItem
required property string name
required property string icon
required property int index
required property string name
implicitWidth: 200
implicitHeight: 42
implicitWidth: 200
radius: 4
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.smaller
MaterialIcon {
id: icon
text: categoryItem.icon
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillHeight: true
Layout.preferredWidth: icon.contentWidth
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
font.pointSize: 22
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.preferredWidth: icon.contentWidth
Layout.fillHeight: true
text: categoryItem.icon
verticalAlignment: Text.AlignVCenter
}
CustomText {
id: text
text: categoryItem.name
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
color: categoryItem.index === clayout.currentIndex ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
text: categoryItem.name
verticalAlignment: Text.AlignVCenter
}
}
+8 -11
View File
@@ -13,23 +13,21 @@ CustomRect {
ColumnLayout {
id: clayout
anchors.left: parent.left
anchors.right: parent.right
CustomRect {
Layout.preferredHeight: colorLayout.implicitHeight
Layout.fillWidth: true
Layout.preferredHeight: colorLayout.implicitHeight
color: DynamicColors.tPalette.m3surfaceContainer
ColumnLayout {
id: colorLayout
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.large
anchors.right: parent.right
Settings {
name: "smth"
@@ -49,20 +47,19 @@ CustomRect {
required property string name
Layout.preferredWidth: 200
Layout.preferredHeight: 42
Layout.preferredWidth: 200
radius: 4
CustomText {
id: text
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: Appearance.padding.smaller
text: settingsItem.name
font.pointSize: 32
anchors.right: parent.right
font.bold: true
font.pointSize: 32
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
+1 -1
View File
@@ -9,5 +9,5 @@ import qs.Helpers
CustomRect {
id: root
}
+4 -5
View File
@@ -20,7 +20,6 @@ CustomRect {
}
Item {
}
}
@@ -29,26 +28,26 @@ CustomRect {
required property string name
implicitWidth: 200
implicitHeight: 42
implicitWidth: 200
radius: 4
RowLayout {
id: layout
anchors.left: parent.left
anchors.margins: Appearance.padding.smaller
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.margins: Appearance.padding.smaller
CustomText {
id: text
text: settingsItem.name
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: Appearance.spacing.normal
text: settingsItem.name
verticalAlignment: Text.AlignVCenter
}
}
+25 -24
View File
@@ -11,71 +11,69 @@ import qs.Helpers
Item {
id: root
required property PersistentProperties visibilities
readonly property real nonAnimWidth: view.implicitWidth + 500 + viewWrapper.anchors.margins * 2
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
property string currentCategory: "general"
readonly property real nonAnimHeight: view.implicitHeight + viewWrapper.anchors.margins * 2
readonly property real nonAnimWidth: view.implicitWidth + 500 + viewWrapper.anchors.margins * 2
required property PersistentProperties visibilities
implicitWidth: nonAnimWidth
implicitHeight: nonAnimHeight
implicitWidth: nonAnimWidth
Connections {
target: root
function onCurrentCategoryChanged() {
stack.pop();
if ( currentCategory === "general" ) {
if (currentCategory === "general") {
stack.push(general);
} else if ( currentCategory === "wallpaper" ) {
} else if (currentCategory === "wallpaper") {
stack.push(background);
} else if ( currentCategory === "appearance" ) {
} else if (currentCategory === "appearance") {
stack.push(appearance);
}
}
target: root
}
ClippingRectangle {
id: viewWrapper
anchors.fill: parent
anchors.margins: Appearance.padding.smaller
color: "transparent"
Item {
id: view
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
implicitWidth: layout.implicitWidth
anchors.top: parent.top
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
Categories {
id: layout
content: root
anchors.fill: parent
content: root
}
}
CustomClippingRect {
id: categoryContent
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: view.right
anchors.leftMargin: Appearance.spacing.smaller
radius: 4
anchors.right: parent.right
anchors.top: parent.top
color: DynamicColors.tPalette.m3surfaceContainer
radius: 4
StackView {
id: stack
anchors.fill: parent
anchors.margins: Appearance.padding.smaller
initialItem: general
}
}
@@ -84,18 +82,21 @@ Item {
Component {
id: general
Cat.General {}
Cat.General {
}
}
Component {
id: background
Cat.Background {}
Cat.Background {
}
}
Component {
id: appearance
Cat.Appearance {}
Cat.Appearance {
}
}
}
+9 -13
View File
@@ -1,37 +1,33 @@
import Quickshell
import Quickshell.Widgets
import QtQuick
import QtQuick.Layouts
import qs.Components
import qs.Modules as Modules
import qs.Config
import qs.Helpers
RowLayout {
id: root
required property string name
required property var object
required property string setting
required property string name
Layout.preferredHeight: 42
Layout.fillWidth: true
Layout.preferredHeight: 42
CustomText {
id: text
text: root.name
font.pointSize: 16
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
Layout.fillWidth: true
font.pointSize: 16
text: root.name
}
CustomSwitch {
id: cswitch
Layout.alignment: Qt.AlignRight
checked: root.object[root.setting]
onToggled: {
root.object[root.setting] = checked;
Config.save();
-1
View File
@@ -8,5 +8,4 @@ import qs.Config
import qs.Helpers
Item {
}
+9 -13
View File
@@ -7,56 +7,52 @@ import qs.Helpers
Item {
id: root
required property PersistentProperties visibilities
required property var panels
required property PersistentProperties visibilities
implicitWidth: content.implicitWidth
implicitHeight: 0
implicitWidth: content.implicitWidth
visible: height > 0
states: State {
name: "visible"
when: root.visibilities.settings
PropertyChanges {
root.implicitHeight: content.implicitHeight
}
}
transitions: [
Transition {
from: ""
to: "visible"
Anim {
target: root
property: "implicitHeight"
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
},
Transition {
from: "visible"
to: ""
Anim {
target: root
property: "implicitHeight"
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: root
}
}
]
Loader {
id: content
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
visible: true
active: true
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
visible: true
sourceComponent: Content {
visibilities: root.visibilities
+19 -19
View File
@@ -7,29 +7,29 @@ import qs.Helpers
Scope {
id: root
property bool launcherInterrupted
readonly property bool hasFullscreen: Hypr.focusedWorkspace?.toplevels.values.some(t => t.lastIpcObject.fullscreen === 2) ?? false
property bool launcherInterrupted
CustomShortcut {
description: "Toggle launcher"
name: "toggle-launcher"
CustomShortcut {
name: "toggle-launcher"
description: "Toggle launcher"
onPressed: root.launcherInterrupted = false
onReleased: {
if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher;
}
root.launcherInterrupted = false;
}
}
onPressed: root.launcherInterrupted = false
onReleased: {
if (!root.launcherInterrupted && !root.hasFullscreen) {
const visibilities = Visibilities.getForActive();
visibilities.launcher = !visibilities.launcher;
}
root.launcherInterrupted = false;
}
}
CustomShortcut {
name: "toggle-nc"
onPressed: {
const visibilities = Visibilities.getForActive()
visibilities.sidebar = !visibilities.sidebar
const visibilities = Visibilities.getForActive();
visibilities.sidebar = !visibilities.sidebar;
}
}
@@ -37,8 +37,8 @@ Scope {
name: "show-osd"
onPressed: {
const visibilities = Visibilities.getForActive()
visibilities.osd = !visibilities.osd
const visibilities = Visibilities.getForActive();
visibilities.osd = !visibilities.osd;
}
}
@@ -46,8 +46,8 @@ Scope {
name: "toggle-settings"
onPressed: {
const visibilities = Visibilities.getForActive()
visibilities.settings = !visibilities.settings
const visibilities = Visibilities.getForActive();
visibilities.settings = !visibilities.settings;
}
}
}
+14 -13
View File
@@ -5,25 +5,26 @@ import QtQuick
Singleton {
id: root
property alias enabled: clock.enabled
readonly property date date: clock.date
readonly property int hours: clock.hours
readonly property int minutes: clock.minutes
readonly property int seconds: clock.seconds
readonly property string timeStr: format("hh:mm:ss")
readonly property date date: clock.date
readonly property string dateStr: format("ddd d MMM - hh:mm:ss")
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string hourStr: timeComponents[0] ?? ""
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property string secondStr: timeComponents[2] ?? ""
property alias enabled: clock.enabled
readonly property string hourStr: timeComponents[0] ?? ""
readonly property int hours: clock.hours
readonly property string minuteStr: timeComponents[1] ?? ""
readonly property int minutes: clock.minutes
readonly property string secondStr: timeComponents[2] ?? ""
readonly property int seconds: clock.seconds
readonly property list<string> timeComponents: timeStr.split(":")
readonly property string timeStr: format("hh:mm:ss")
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
function format(fmt: string): string {
return Qt.formatDateTime(clock.date, fmt);
}
SystemClock {
id: clock
precision: SystemClock.Seconds
}
}
+31 -30
View File
@@ -7,28 +7,29 @@ import qs.Components
import qs.Config
Item {
id: root
id: root
required property SystemTrayItem item
required property PanelWindow bar
required property PanelWindow bar
property bool hasLoaded: false
required property int ind
required property Wrapper popouts
required property SystemTrayItem item
required property RowLayout loader
property bool hasLoaded: false
required property Wrapper popouts
StateLayer {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
anchors.margins: 3
radius: 6
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if ( mouse.button === Qt.LeftButton ) {
if (mouse.button === Qt.LeftButton) {
root.item.activate();
} else if ( mouse.button === Qt.RightButton ) {
root.popouts.currentName = `traymenu${ root.ind }`;
root.popouts.currentCenter = Qt.binding( () => root.mapToItem( root.loader, root.implicitWidth / 2, 0 ).x );
} else if (mouse.button === Qt.RightButton) {
root.popouts.currentName = `traymenu${root.ind}`;
root.popouts.currentCenter = Qt.binding(() => root.mapToItem(root.loader, root.implicitWidth / 2, 0).x);
root.popouts.hasCurrent = true;
if ( visibilities.sidebar || visibilities.dashboard ) {
if (visibilities.sidebar || visibilities.dashboard) {
visibilities.sidebar = false;
visibilities.dashboard = false;
}
@@ -40,27 +41,27 @@ Item {
id: icon
anchors.centerIn: parent
source: root.item.icon
implicitSize: 22
color: DynamicColors.palette.m3onSurface
implicitSize: 22
layer.enabled: DynamicColors.light
source: root.item.icon
}
// Image {
// id: icon
//
// property bool batteryHDPI: root.bar.screen.x < 0 && root.item.icon.includes("battery")
// property bool nmHDPI: root.bar.screen.x < 0 && root.item.icon.includes("nm-")
//
// anchors.centerIn: parent
// width: batteryHDPI ? 26 : ( nmHDPI ? 25 : 22 )
// height: batteryHDPI ? 26 : ( nmHDPI ? 25 : 22 )
// source: root.item.icon
// mipmap: true
// smooth: ( batteryHDPI || nmHDPI ) ? false : true
// asynchronous: true
// sourceSize.width: ( batteryHDPI || nmHDPI ) ? 16 : 22
// sourceSize.height: ( batteryHDPI || nmHDPI ) ? 16 : 22
// fillMode: Image.PreserveAspectFit
// }
// Image {
// id: icon
//
// property bool batteryHDPI: root.bar.screen.x < 0 && root.item.icon.includes("battery")
// property bool nmHDPI: root.bar.screen.x < 0 && root.item.icon.includes("nm-")
//
// anchors.centerIn: parent
// width: batteryHDPI ? 26 : ( nmHDPI ? 25 : 22 )
// height: batteryHDPI ? 26 : ( nmHDPI ? 25 : 22 )
// source: root.item.icon
// mipmap: true
// smooth: ( batteryHDPI || nmHDPI ) ? false : true
// asynchronous: true
// sourceSize.width: ( batteryHDPI || nmHDPI ) ? 16 : 22
// sourceSize.height: ( batteryHDPI || nmHDPI ) ? 16 : 22
// fillMode: Image.PreserveAspectFit
// }
}
+347 -311
View File
@@ -10,347 +10,383 @@ import qs.Effects
import qs.Config
PanelWindow {
id: root
id: root
signal menuActionTriggered()
signal finishedLoading()
required property QsMenuHandle trayMenu
required property point trayItemRect
required property PanelWindow bar
property var menuStack: []
property real scaleValue: 0
property alias focusGrab: grab.active
property int entryHeight: 30
property int biggestWidth: 0
property int menuItemCount: menuOpener.children.values.length
property color backgroundColor: DynamicColors.tPalette.m3surface
required property PanelWindow bar
property int biggestWidth: 0
property color disabledHighlightColor: DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0)
property color disabledTextColor: DynamicColors.layer(DynamicColors.palette.m3onSurface, 0)
property int entryHeight: 30
property alias focusGrab: grab.active
property color highlightColor: DynamicColors.tPalette.m3primaryContainer
property int menuItemCount: menuOpener.children.values.length
property var menuStack: []
property real scaleValue: 0
property color textColor: DynamicColors.palette.m3onSurface
required property point trayItemRect
required property QsMenuHandle trayMenu
property color backgroundColor: DynamicColors.tPalette.m3surface
property color highlightColor: DynamicColors.tPalette.m3primaryContainer
property color textColor: DynamicColors.palette.m3onSurface
property color disabledHighlightColor: DynamicColors.layer(DynamicColors.palette.m3primaryContainer, 0)
property color disabledTextColor: DynamicColors.layer(DynamicColors.palette.m3onSurface, 0)
signal finishedLoading
signal menuActionTriggered
QsMenuOpener {
id: menuOpener
menu: root.trayMenu
}
function goBack() {
if (root.menuStack.length > 0) {
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = root.menuStack.pop();
listLayout.positionViewAtBeginning();
backEntry.visible = false;
}
}
// onTrayMenuChanged: {
// listLayout.forceLayout();
// }
function updateMask() {
root.mask.changed();
}
visible: false
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
color: "transparent"
mask: Region { id: mask; item: menuRect }
// onTrayMenuChanged: {
// listLayout.forceLayout();
// }
function goBack() {
if ( root.menuStack.length > 0 ) {
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = root.menuStack.pop();
listLayout.positionViewAtBeginning();
backEntry.visible = false;
}
}
visible: false
function updateMask() {
root.mask.changed();
}
mask: Region {
id: mask
onVisibleChanged: {
if ( !visible )
root.menuStack.pop();
backEntry.visible = false;
item: menuRect
}
openAnim.start();
}
onMenuActionTriggered: {
if (root.menuStack.length > 0) {
backEntry.visible = true;
}
}
onVisibleChanged: {
if (!visible)
root.menuStack.pop();
backEntry.visible = false;
HyprlandFocusGrab {
id: grab
windows: [ root ]
active: false
onCleared: {
closeAnim.start();
}
}
openAnim.start();
}
SequentialAnimation {
id: menuChangeAnimation
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "x"
target: translateAnim
to: -listLayout.width / 2
}
QsMenuOpener {
id: menuOpener
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 1
property: "opacity"
target: columnLayout
to: 0
}
}
menu: root.trayMenu
}
PropertyAction {
property: "menu"
target: columnLayout
}
anchors {
bottom: true
left: true
right: true
top: true
}
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 0
property: "opacity"
target: columnLayout
to: 1
}
HyprlandFocusGrab {
id: grab
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: listLayout.width / 2
property: "x"
target: translateAnim
to: 0
}
}
}
active: false
windows: [root]
onMenuActionTriggered: {
if ( root.menuStack.length > 0 ) {
backEntry.visible = true;
}
}
onCleared: {
closeAnim.start();
}
}
ParallelAnimation {
id: closeAnim
Anim {
target: menuRect
property: "implicitHeight"
to: 0
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Anim {
targets: [ menuRect, shadowRect ]
property: "opacity"
from: 1
to: 0
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
onFinished: {
root.visible = false;
}
}
SequentialAnimation {
id: menuChangeAnimation
ParallelAnimation {
id: openAnim
Anim {
target: menuRect
property: "implicitHeight"
from: 0
to: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 )
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Anim {
targets: [ menuRect, shadowRect ]
property: "opacity"
from: 0
to: 1
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "x"
target: translateAnim
to: -listLayout.width / 2
}
ShadowRect {
id: shadowRect
anchors.fill: menuRect
radius: menuRect.radius
}
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 1
property: "opacity"
target: columnLayout
to: 0
}
}
Rectangle {
id: menuRect
x: Math.round( root.trayItemRect.x - ( menuRect.implicitWidth / 2 ) + 11 )
y: Math.round( root.trayItemRect.y - 5 )
implicitWidth: listLayout.contentWidth + 10
implicitHeight: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 )
color: root.backgroundColor
radius: 8
clip: true
PropertyAction {
property: "menu"
target: columnLayout
}
Behavior on implicitWidth {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ParallelAnimation {
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.standard
from: 0
property: "opacity"
target: columnLayout
to: 1
}
Behavior on implicitHeight {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
NumberAnimation {
duration: MaterialEasing.standardTime / 2
easing.bezierCurve: MaterialEasing.expressiveEffects
from: listLayout.width / 2
property: "x"
target: translateAnim
to: 0
}
}
}
ColumnLayout {
id: columnLayout
anchors.fill: parent
anchors.margins: 5
spacing: 0
transform: [
Translate {
id: translateAnim
x: 0
y: 0
}
]
ListView {
id: listLayout
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
spacing: 0
contentWidth: root.biggestWidth
contentHeight: contentItem.childrenRect.height
model: menuOpener.children
ParallelAnimation {
id: closeAnim
delegate: Rectangle {
id: menuItem
required property int index
required property QsMenuEntry modelData
property var child: QsMenuOpener {
menu: menuItem.modelData
}
property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled
property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled
width: widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20
anchors.left: parent.left
anchors.right: parent.right
height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? root.highlightColor : containsMouseAndNotEnabled ? root.disabledHighlightColor : "transparent"
radius: 4
visible: true
onFinished: {
root.visible = false;
}
Behavior on color {
CAnim {
duration: 150
}
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
property: "implicitHeight"
target: menuRect
to: 0
}
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20;
if ( currentWidth > biggestWidth ) {
root.biggestWidth = currentWidth;
}
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 1
property: "opacity"
targets: [menuRect, shadowRect]
to: 0
}
}
TextMetrics {
id: widthMetrics
text: menuItem.modelData.text
}
ParallelAnimation {
id: openAnim
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
acceptedButtons: Qt.LeftButton
onClicked: {
if ( !menuItem.modelData.hasChildren ) {
if ( menuItem.modelData.enabled ) {
menuItem.modelData.triggered();
closeAnim.start();
}
} else {
root.menuStack.push(root.trayMenu);
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = menuItem.modelData;
listLayout.positionViewAtBeginning();
root.menuActionTriggered();
}
}
}
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "implicitHeight"
target: menuRect
to: listLayout.contentHeight + (root.menuStack.length > 0 ? root.entryHeight + 10 : 10)
}
RowLayout {
anchors.fill: parent
Text {
id: menuText
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 10
text: menuItem.modelData.text
color: menuItem.modelData.enabled ? root.textColor : root.disabledTextColor
}
Image {
id: iconImage
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: 10
Layout.maximumWidth: 20
Layout.maximumHeight: 20
source: menuItem.modelData.icon
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
layer.enabled: true
layer.effect: ColorOverlay {
color: menuItem.modelData.enabled ? "white" : "gray"
}
}
Text {
id: textArrow
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.rightMargin: 10
Layout.bottomMargin: 5
Layout.maximumWidth: 20
Layout.maximumHeight: 20
text: ""
color: menuItem.modelData.enabled ? "white" : "gray"
visible: menuItem.modelData.hasChildren ?? false
}
}
}
}
Rectangle {
id: backEntry
visible: false
Layout.fillWidth: true
Layout.preferredHeight: root.entryHeight
color: mouseAreaBack.containsMouse ? "#15FFFFFF" : "transparent"
radius: 4
Anim {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
from: 0
property: "opacity"
targets: [menuRect, shadowRect]
to: 1
}
}
MouseArea {
id: mouseAreaBack
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.goBack();
}
}
ShadowRect {
id: shadowRect
Text {
anchors.fill: parent
anchors.leftMargin: 10
verticalAlignment: Text.AlignVCenter
text: "Back "
color: "white"
}
}
}
}
anchors.fill: menuRect
radius: menuRect.radius
}
Rectangle {
id: menuRect
clip: true
color: root.backgroundColor
implicitHeight: listLayout.contentHeight + (root.menuStack.length > 0 ? root.entryHeight + 10 : 10)
implicitWidth: listLayout.contentWidth + 10
radius: 8
x: Math.round(root.trayItemRect.x - (menuRect.implicitWidth / 2) + 11)
y: Math.round(root.trayItemRect.y - 5)
Behavior on implicitHeight {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
Behavior on implicitWidth {
NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ColumnLayout {
id: columnLayout
anchors.fill: parent
anchors.margins: 5
spacing: 0
transform: [
Translate {
id: translateAnim
x: 0
y: 0
}
]
ListView {
id: listLayout
Layout.fillWidth: true
Layout.preferredHeight: contentHeight
contentHeight: contentItem.childrenRect.height
contentWidth: root.biggestWidth
model: menuOpener.children
spacing: 0
delegate: Rectangle {
id: menuItem
property var child: QsMenuOpener {
menu: menuItem.modelData
}
property bool containsMouseAndEnabled: mouseArea.containsMouse && menuItem.modelData.enabled
property bool containsMouseAndNotEnabled: mouseArea.containsMouse && !menuItem.modelData.enabled
required property int index
required property QsMenuEntry modelData
anchors.left: parent.left
anchors.right: parent.right
color: menuItem.modelData.isSeparator ? "#20FFFFFF" : containsMouseAndEnabled ? root.highlightColor : containsMouseAndNotEnabled ? root.disabledHighlightColor : "transparent"
height: menuItem.modelData.isSeparator ? 1 : root.entryHeight
radius: 4
visible: true
width: widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20
Behavior on color {
CAnim {
duration: 150
}
}
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = widthMetrics.width + (menuItem.modelData.icon ?? "" ? 30 : 0) + (menuItem.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
TextMetrics {
id: widthMetrics
text: menuItem.modelData.text
}
MouseArea {
id: mouseArea
acceptedButtons: Qt.LeftButton
anchors.fill: parent
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
onClicked: {
if (!menuItem.modelData.hasChildren) {
if (menuItem.modelData.enabled) {
menuItem.modelData.triggered();
closeAnim.start();
}
} else {
root.menuStack.push(root.trayMenu);
menuChangeAnimation.start();
root.biggestWidth = 0;
root.trayMenu = menuItem.modelData;
listLayout.positionViewAtBeginning();
root.menuActionTriggered();
}
}
}
RowLayout {
anchors.fill: parent
Text {
id: menuText
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
Layout.leftMargin: 10
color: menuItem.modelData.enabled ? root.textColor : root.disabledTextColor
text: menuItem.modelData.text
}
Image {
id: iconImage
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.maximumHeight: 20
Layout.maximumWidth: 20
Layout.rightMargin: 10
fillMode: Image.PreserveAspectFit
layer.enabled: true
source: menuItem.modelData.icon
sourceSize.height: height
sourceSize.width: width
layer.effect: ColorOverlay {
color: menuItem.modelData.enabled ? "white" : "gray"
}
}
Text {
id: textArrow
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.bottomMargin: 5
Layout.maximumHeight: 20
Layout.maximumWidth: 20
Layout.rightMargin: 10
color: menuItem.modelData.enabled ? "white" : "gray"
text: ""
visible: menuItem.modelData.hasChildren ?? false
}
}
}
}
Rectangle {
id: backEntry
Layout.fillWidth: true
Layout.preferredHeight: root.entryHeight
color: mouseAreaBack.containsMouse ? "#15FFFFFF" : "transparent"
radius: 4
visible: false
MouseArea {
id: mouseAreaBack
anchors.fill: parent
hoverEnabled: true
onClicked: {
root.goBack();
}
}
Text {
anchors.fill: parent
anchors.leftMargin: 10
color: "white"
text: "Back "
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
+189 -190
View File
@@ -9,239 +9,238 @@ import QtQuick.Controls
import QtQuick.Effects
StackView {
id: root
id: root
required property Item popouts
required property QsMenuHandle trayItem
property int biggestWidth: 0
required property Item popouts
property int rootWidth: 0
required property QsMenuHandle trayItem
property int rootWidth: 0
property int biggestWidth: 0
implicitHeight: currentItem.implicitHeight
implicitWidth: currentItem.implicitWidth
implicitWidth: currentItem.implicitWidth
implicitHeight: currentItem.implicitHeight
initialItem: SubMenu {
handle: root.trayItem
}
popEnter: NoAnim {
}
popExit: NoAnim {
}
pushEnter: NoAnim {
}
pushExit: NoAnim {
}
initialItem: SubMenu {
handle: root.trayItem
}
Component {
id: subMenuComp
pushEnter: NoAnim {}
pushExit: NoAnim {}
popEnter: NoAnim {}
popExit: NoAnim {}
SubMenu {
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component NoAnim: Transition {
NumberAnimation {
duration: 0
}
}
component SubMenu: Column {
id: menu
component SubMenu: Column {
id: menu
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
required property QsMenuHandle handle
property bool isSubMenu
property bool shown
opacity: shown ? 1 : 0
padding: 0
scale: shown ? 1 : 0.8
spacing: 4
padding: 0
spacing: 4
Behavior on opacity {
Anim {
}
}
Behavior on scale {
Anim {
}
}
opacity: shown ? 1 : 0
scale: shown ? 1 : 0.8
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
Component.onCompleted: shown = true
StackView.onActivating: shown = true
StackView.onDeactivating: shown = false
StackView.onRemoved: destroy()
QsMenuOpener {
id: menuOpener
Behavior on opacity {
Anim {}
}
menu: menu.handle
}
Behavior on scale {
Anim {}
}
Repeater {
model: menuOpener.children
QsMenuOpener {
id: menuOpener
CustomRect {
id: item
menu: menu.handle
}
required property QsMenuEntry modelData
Repeater {
model: menuOpener.children
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
implicitWidth: root.biggestWidth
radius: 4
CustomRect {
id: item
Loader {
id: children
required property QsMenuEntry modelData
active: !item.modelData.isSeparator
anchors.left: parent.left
anchors.right: parent.right
asynchronous: true
implicitWidth: root.biggestWidth
implicitHeight: modelData.isSeparator ? 1 : children.implicitHeight
sourceComponent: Item {
implicitHeight: 30
radius: 4
color: modelData.isSeparator ? DynamicColors.palette.m3outlineVariant : "transparent"
StateLayer {
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
} else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
Loader {
id: children
disabled: !item.modelData.enabled
radius: item.radius
}
anchors.left: parent.left
anchors.right: parent.right
Loader {
id: icon
active: !item.modelData.isSeparator
asynchronous: true
active: item.modelData.icon !== ""
anchors.right: parent.right
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
sourceComponent: Item {
implicitHeight: 30
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
StateLayer {
radius: item.radius
disabled: !item.modelData.enabled
IconImage {
id: iconImage
function onClicked(): void {
const entry = item.modelData;
if (entry.hasChildren) {
root.rootWidth = root.biggestWidth;
root.biggestWidth = 0;
root.push(subMenuComp.createObject(null, {
handle: entry,
isSubMenu: true
}));
} else {
item.modelData.triggered();
root.popouts.hasCurrent = false;
}
}
}
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
Loader {
id: icon
MultiEffect {
anchors.fill: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
source: iconImage
}
}
}
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 10
CustomText {
id: label
active: item.modelData.icon !== ""
asynchronous: true
anchors.left: parent.left
anchors.leftMargin: 10
anchors.verticalCenter: parent.verticalCenter
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: labelMetrics.elidedText
}
sourceComponent: Item {
implicitHeight: label.implicitHeight
implicitWidth: label.implicitHeight
IconImage {
id: iconImage
implicitSize: parent.implicitHeight
source: item.modelData.icon
visible: false
}
TextMetrics {
id: labelMetrics
MultiEffect {
anchors.fill: iconImage
source: iconImage
colorization: 1.0
colorizationColor: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
}
}
}
font.family: label.font.family
font.pointSize: label.font.pointSize
text: item.modelData.text
CustomText {
id: label
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if (currentWidth > biggestWidth) {
root.biggestWidth = currentWidth;
}
}
}
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: 10
Loader {
id: expand
text: labelMetrics.elidedText
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
}
active: item.modelData.hasChildren
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
asynchronous: true
TextMetrics {
id: labelMetrics
sourceComponent: MaterialIcon {
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
text: "chevron_right"
}
}
}
}
}
}
text: item.modelData.text
font.pointSize: label.font.pointSize
font.family: label.font.family
Loader {
active: menu.isSubMenu
asynchronous: true
Component.onCompleted: {
var biggestWidth = root.biggestWidth;
var currentWidth = labelMetrics.width + (item.modelData.icon ?? "" ? 30 : 0) + (item.modelData.hasChildren ? 30 : 0) + 20;
if ( currentWidth > biggestWidth ) {
root.biggestWidth = currentWidth;
}
}
}
sourceComponent: Item {
implicitHeight: back.implicitHeight + 2 / 2
implicitWidth: back.implicitWidth
Loader {
id: expand
Item {
anchors.bottom: parent.bottom
implicitHeight: back.implicitHeight
implicitWidth: back.implicitWidth + 10
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
CustomRect {
anchors.fill: parent
color: DynamicColors.palette.m3secondaryContainer
radius: 4
active: item.modelData.hasChildren
asynchronous: true
StateLayer {
function onClicked(): void {
root.pop();
root.biggestWidth = root.rootWidth;
}
sourceComponent: MaterialIcon {
text: "chevron_right"
color: item.modelData.enabled ? DynamicColors.palette.m3onSurface : DynamicColors.palette.m3outline
}
}
}
}
}
}
color: DynamicColors.palette.m3onSecondaryContainer
radius: parent.radius
}
}
Loader {
active: menu.isSubMenu
asynchronous: true
Row {
id: back
sourceComponent: Item {
implicitWidth: back.implicitWidth
implicitHeight: back.implicitHeight + 2 / 2
anchors.verticalCenter: parent.verticalCenter
Item {
anchors.bottom: parent.bottom
implicitWidth: back.implicitWidth + 10
implicitHeight: back.implicitHeight
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: "chevron_left"
}
CustomRect {
anchors.fill: parent
radius: 4
color: DynamicColors.palette.m3secondaryContainer
StateLayer {
radius: parent.radius
color: DynamicColors.palette.m3onSecondaryContainer
function onClicked(): void {
root.pop();
root.biggestWidth = root.rootWidth;
}
}
}
Row {
id: back
anchors.verticalCenter: parent.verticalCenter
MaterialIcon {
anchors.verticalCenter: parent.verticalCenter
text: "chevron_left"
color: DynamicColors.palette.m3onSecondaryContainer
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Back")
color: DynamicColors.palette.m3onSecondaryContainer
}
}
}
}
}
}
Component {
id: subMenuComp
SubMenu {}
}
CustomText {
anchors.verticalCenter: parent.verticalCenter
color: DynamicColors.palette.m3onSecondaryContainer
text: qsTr("Back")
}
}
}
}
}
}
}
+24 -21
View File
@@ -6,32 +6,35 @@ import Quickshell
import Quickshell.Services.SystemTray
Row {
id: root
id: root
anchors.top: parent.top
anchors.bottom: parent.bottom
required property PanelWindow bar
required property Wrapper popouts
required property PanelWindow bar
readonly property alias items: repeater
required property RowLayout loader
readonly property alias items: repeater
required property Wrapper popouts
spacing: 0
anchors.bottom: parent.bottom
anchors.top: parent.top
spacing: 0
Repeater {
id: repeater
model: SystemTray.items
TrayItem {
id: trayItem
Repeater {
id: repeater
model: SystemTray.items
TrayItem {
id: trayItem
required property SystemTrayItem modelData
required property int index
required property SystemTrayItem modelData
bar: root.bar
implicitHeight: 34
implicitWidth: 34
ind: index
popouts: root.popouts
item: modelData
loader: root.loader
implicitHeight: 34
implicitWidth: 34
item: modelData
bar: root.bar
}
}
popouts: root.popouts
}
}
}
+110 -114
View File
@@ -11,173 +11,169 @@ Item {
required property var wrapper
implicitWidth: profiles.implicitWidth
implicitHeight: profiles.implicitHeight
implicitWidth: profiles.implicitWidth
CustomRect {
id: profiles
CustomRect {
id: profiles
property string current: {
const p = PowerProfiles.profile;
if (p === PowerProfile.PowerSaver)
return saver.icon;
if (p === PowerProfile.Performance)
return perf.icon;
return balance.icon;
}
property string current: {
const p = PowerProfiles.profile;
if (p === PowerProfile.PowerSaver)
return saver.icon;
if (p === PowerProfile.Performance)
return perf.icon;
return balance.icon;
}
anchors.horizontalCenter: parent.horizontalCenter
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
color: DynamicColors.tPalette.m3surfaceContainer
anchors.horizontalCenter: parent.horizontalCenter
color: DynamicColors.tPalette.m3surfaceContainer
implicitHeight: Math.max(saver.implicitHeight, balance.implicitHeight, perf.implicitHeight) + 5 * 2 + saverLabel.contentHeight
implicitWidth: saver.implicitHeight + balance.implicitHeight + perf.implicitHeight + 8 * 2 + saverLabel.contentWidth
// color: "transparent"
radius: 6
radius: 6
CustomRect {
id: indicator
CustomRect {
id: indicator
color: DynamicColors.palette.m3primary
radius: 1000
state: profiles.current
color: DynamicColors.palette.m3primary
radius: 1000
state: profiles.current
states: [
State {
name: saver.icon
states: [
State {
name: saver.icon
Fill {
item: saver
}
},
State {
name: balance.icon
Fill {
item: saver
}
},
State {
name: balance.icon
Fill {
item: balance
}
},
State {
name: perf.icon
Fill {
item: balance
}
},
State {
name: perf.icon
Fill {
item: perf
}
}
]
transitions: Transition {
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
Fill {
item: perf
}
}
]
transitions: Transition {
AnchorAnimation {
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
easing.type: Easing.BezierSpline
}
}
}
}
}
}
Profile {
id: saver
Profile {
id: saver
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
anchors.left: parent.left
anchors.leftMargin: 25
icon: "nest_eco_leaf"
profile: PowerProfile.PowerSaver
text: "Power Saver"
profile: PowerProfile.PowerSaver
icon: "nest_eco_leaf"
}
}
CustomText {
id: saverLabel
anchors.top: saver.bottom
anchors.horizontalCenter: saver.horizontalCenter
anchors.top: saver.bottom
font.bold: true
text: saver.text
}
Profile {
id: balance
Profile {
id: balance
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 8
anchors.horizontalCenter: parent.horizontalCenter
icon: "power_settings_new"
profile: PowerProfile.Balanced
text: "Balanced"
profile: PowerProfile.Balanced
icon: "power_settings_new"
}
}
CustomText {
id: balanceLabel
anchors.top: balance.bottom
anchors.horizontalCenter: balance.horizontalCenter
anchors.top: balance.bottom
font.bold: true
text: balance.text
}
Profile {
id: perf
Profile {
id: perf
anchors.top: parent.top
anchors.right: parent.right
anchors.rightMargin: 25
anchors.top: parent.top
anchors.topMargin: 8
anchors.right: parent.right
anchors.rightMargin: 25
icon: "bolt"
profile: PowerProfile.Performance
text: "Performance"
profile: PowerProfile.Performance
icon: "bolt"
}
}
CustomText {
id: perfLabel
anchors.top: perf.bottom
anchors.horizontalCenter: perf.horizontalCenter
anchors.top: perf.bottom
font.bold: true
text: perf.text
}
}
}
component Fill: AnchorChanges {
required property Item item
target: indicator
anchors.left: item.left
anchors.right: item.right
anchors.top: item.top
anchors.bottom: item.bottom
}
component Fill: AnchorChanges {
required property Item item
anchors.bottom: item.bottom
anchors.left: item.left
anchors.right: item.right
anchors.top: item.top
target: indicator
}
component Profile: Item {
required property string icon
required property int profile
required property string icon
required property int profile
required property string text
implicitWidth: icon.implicitHeight + 5 * 2
implicitHeight: icon.implicitHeight + 5 * 2
implicitHeight: icon.implicitHeight + 5 * 2
implicitWidth: icon.implicitHeight + 5 * 2
StateLayer {
radius: 1000
color: profiles.current === parent.icon ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
StateLayer {
function onClicked(): void {
PowerProfiles.profile = parent.profile;
}
function onClicked(): void {
PowerProfiles.profile = parent.profile;
}
}
color: profiles.current === parent.icon ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
radius: 1000
}
MaterialIcon {
id: icon
MaterialIcon {
id: icon
anchors.centerIn: parent
anchors.centerIn: parent
color: profiles.current === text ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0
font.pointSize: 36
text: parent.icon
text: parent.icon
font.pointSize: 36
color: profiles.current === text ? DynamicColors.palette.m3onPrimary : DynamicColors.palette.m3onSurface
fill: profiles.current === text ? 1 : 0
Behavior on fill {
Anim {}
}
}
}
Behavior on fill {
Anim {
}
}
}
}
}
+7 -6
View File
@@ -8,25 +8,28 @@ import qs.Helpers as Helpers
Item {
id: root
implicitWidth: layout.childrenRect.width + 10 * 2
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.top: parent.top
implicitWidth: layout.childrenRect.width + 10 * 2
CustomRect {
anchors.bottomMargin: 4
anchors.fill: parent
anchors.topMargin: 4
anchors.bottomMargin: 4
color: DynamicColors.tPalette.m3surfaceContainer
radius: 1000
}
RowLayout {
id: layout
anchors.centerIn: parent
MaterialIcon {
animate: true
Layout.alignment: Qt.AlignVCenter
animate: true
color: !Helpers.UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3error
fill: 1
text: {
if (!Helpers.UPower.displayDevice.isLaptopBattery) {
if (PowerProfiles.profile === PowerProfile.PowerSaver)
@@ -45,8 +48,6 @@ Item {
level--;
return charging ? `battery_charging_${(level + 3) * 10}` : `battery_${level}_bar`;
}
color: !Helpers.UPower.onBattery || UPower.displayDevice.percentage > 0.2 ? DynamicColors.palette.m3secondary : DynamicColors.palette.m3error
fill: 1
}
CustomText {
+24 -22
View File
@@ -7,29 +7,31 @@ import Quickshell.Io
import qs.Modules
Singleton {
property int availableUpdates: 0
property int availableUpdates: 0
Timer {
interval: 1
running: true
repeat: true
onTriggered: {
updatesProc.running = true
interval = 5000
}
}
Timer {
interval: 1
repeat: true
running: true
Process {
id: updatesProc
running: false
onTriggered: {
updatesProc.running = true;
interval = 5000;
}
}
command: ["checkupdates"]
stdout: StdioCollector {
onStreamFinished: {
const output = this.text
const lines = output.trim().split("\n").filter(line => line.length > 0)
availableUpdates = lines.length
}
}
}
Process {
id: updatesProc
command: ["checkupdates"]
running: false
stdout: StdioCollector {
onStreamFinished: {
const output = this.text;
const lines = output.trim().split("\n").filter(line => line.length > 0);
availableUpdates = lines.length;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More