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
+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()
}
}