lockscreen?
This commit is contained in:
@@ -0,0 +1,108 @@
|
|||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
import qs.Modules
|
||||||
|
import ZShell.Internal
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Templates
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
enum AnimType {
|
||||||
|
Advance = 0,
|
||||||
|
Retreat
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AnimState {
|
||||||
|
Stopped,
|
||||||
|
Running,
|
||||||
|
Completing
|
||||||
|
}
|
||||||
|
|
||||||
|
property real implicitSize: Appearance.font.size.normal * 3
|
||||||
|
property real strokeWidth: Appearance.padding.small * 0.8
|
||||||
|
property color fgColour: DynamicColors.palette.m3primary
|
||||||
|
property color bgColour: DynamicColors.palette.m3secondaryContainer
|
||||||
|
|
||||||
|
property alias type: manager.indeterminateAnimationType
|
||||||
|
readonly property alias progress: manager.progress
|
||||||
|
|
||||||
|
property real internalStrokeWidth: strokeWidth
|
||||||
|
property int animState
|
||||||
|
|
||||||
|
padding: 0
|
||||||
|
implicitWidth: implicitSize
|
||||||
|
implicitHeight: implicitSize
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (running) {
|
||||||
|
running = false;
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRunningChanged: {
|
||||||
|
if (running) {
|
||||||
|
manager.completeEndProgress = 0;
|
||||||
|
animState = CircularIndicator.Running;
|
||||||
|
} else {
|
||||||
|
if (animState == CircularIndicator.Running)
|
||||||
|
animState = CircularIndicator.Completing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
states: State {
|
||||||
|
name: "stopped"
|
||||||
|
when: !root.running
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
root.opacity: 0
|
||||||
|
root.internalStrokeWidth: root.strokeWidth / 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
Anim {
|
||||||
|
properties: "opacity,internalStrokeWidth"
|
||||||
|
duration: manager.completeEndDuration * Appearance.anim.durations.scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: CircularProgress {
|
||||||
|
anchors.fill: parent
|
||||||
|
strokeWidth: root.internalStrokeWidth
|
||||||
|
fgColour: root.fgColour
|
||||||
|
bgColour: root.bgColour
|
||||||
|
padding: root.padding
|
||||||
|
rotation: manager.rotation
|
||||||
|
startAngle: manager.startFraction * 360
|
||||||
|
value: manager.endFraction - manager.startFraction
|
||||||
|
}
|
||||||
|
|
||||||
|
CircularIndicatorManager {
|
||||||
|
id: manager
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
running: root.animState !== CircularIndicator.Stopped
|
||||||
|
loops: Animation.Infinite
|
||||||
|
target: manager
|
||||||
|
property: "progress"
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: manager.duration * Appearance.anim.durations.scale
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
running: root.animState === CircularIndicator.Completing
|
||||||
|
target: manager
|
||||||
|
property: "completeEndProgress"
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: manager.completeEndDuration * Appearance.anim.durations.scale
|
||||||
|
onFinished: {
|
||||||
|
if (root.animState === CircularIndicator.Completing)
|
||||||
|
root.animState = CircularIndicator.Stopped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Shapes
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
import qs.Modules
|
||||||
|
|
||||||
|
Shape {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real value
|
||||||
|
property int startAngle: -90
|
||||||
|
property int strokeWidth: Appearance.padding.smaller
|
||||||
|
property int padding: 0
|
||||||
|
property int spacing: Appearance.spacing.small
|
||||||
|
property color fgColour: DynamicColors.palette.m3primary
|
||||||
|
property color bgColour: DynamicColors.palette.m3secondaryContainer
|
||||||
|
|
||||||
|
readonly property real size: Math.min(width, height)
|
||||||
|
readonly property real arcRadius: (size - padding - strokeWidth) / 2
|
||||||
|
readonly property real vValue: value || 1 / 360
|
||||||
|
readonly property real gapAngle: ((spacing + strokeWidth) / (arcRadius || 1)) * (180 / Math.PI)
|
||||||
|
|
||||||
|
preferredRendererType: Shape.CurveRenderer
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeColor: root.bgColour
|
||||||
|
strokeWidth: root.strokeWidth
|
||||||
|
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
||||||
|
|
||||||
|
PathAngleArc {
|
||||||
|
startAngle: root.startAngle + 360 * root.vValue + root.gapAngle
|
||||||
|
sweepAngle: Math.max(-root.gapAngle, 360 * (1 - root.vValue) - root.gapAngle * 2)
|
||||||
|
radiusX: root.arcRadius
|
||||||
|
radiusY: root.arcRadius
|
||||||
|
centerX: root.size / 2
|
||||||
|
centerY: root.size / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on strokeColor {
|
||||||
|
CAnim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapePath {
|
||||||
|
fillColor: "transparent"
|
||||||
|
strokeColor: root.fgColour
|
||||||
|
strokeWidth: root.strokeWidth
|
||||||
|
capStyle: Appearance.rounding.scale === 0 ? ShapePath.SquareCap : ShapePath.RoundCap
|
||||||
|
|
||||||
|
PathAngleArc {
|
||||||
|
startAngle: root.startAngle
|
||||||
|
sweepAngle: 360 * root.vValue
|
||||||
|
radiusX: root.arcRadius
|
||||||
|
radiusY: root.arcRadius
|
||||||
|
centerX: root.size / 2
|
||||||
|
centerY: root.size / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on strokeColor {
|
||||||
|
CAnim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
-2
@@ -1,6 +1,14 @@
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
|
|
||||||
JsonObject {
|
JsonObject {
|
||||||
property bool fixLockScreen: false
|
property bool recolorLogo: false
|
||||||
property bool useWallpaper: true
|
property bool enableFprint: true
|
||||||
|
property int maxFprintTries: 3
|
||||||
|
property Sizes sizes: Sizes {}
|
||||||
|
|
||||||
|
component Sizes: JsonObject {
|
||||||
|
property real heightMult: 0.7
|
||||||
|
property real ratio: 16 / 9
|
||||||
|
property int centerWidth: 600
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ import QtQuick
|
|||||||
|
|
||||||
JsonObject {
|
JsonObject {
|
||||||
property string weatherLocation: ""
|
property string weatherLocation: ""
|
||||||
property bool useFahrenheit: [Locale.ImperialUSSystem, Locale.ImperialSystem].includes(Qt.locale().measurementSystem)
|
property bool useFahrenheit: false
|
||||||
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
|
property bool useTwelveHourClock: Qt.locale().timeFormat(Locale.ShortFormat).toLowerCase().includes("a")
|
||||||
property string gpuType: ""
|
property string gpuType: ""
|
||||||
property real audioIncrement: 0.1
|
property real audioIncrement: 0.1
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ Item {
|
|||||||
|
|
||||||
implicitWidth: parent.width * ( Pipewire.defaultAudioSource?.audio.volume ?? 0 )
|
implicitWidth: parent.width * ( Pipewire.defaultAudioSource?.audio.volume ?? 0 )
|
||||||
radius: parent.radius
|
radius: parent.radius
|
||||||
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3onError : "#ff4444") : root.barColor
|
color: ( Pipewire.defaultAudioSource?.audio.muted ?? false ) ? (Config.useDynamicColors ? DynamicColors.palette.m3error : "#ff4444") : root.barColor
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
CAnim {}
|
CAnim {}
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ Item {
|
|||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
text: Time.time
|
text: Time.dateStr
|
||||||
color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white"
|
color: Config.useDynamicColors ? DynamicColors.palette.m3tertiary : "white"
|
||||||
|
|
||||||
Behavior on color {
|
Behavior on color {
|
||||||
|
|||||||
@@ -0,0 +1,415 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Paths
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.leftMargin: Appearance.spacing.small
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
active: Config.services.useTwelveHourClock
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: CustomText {
|
||||||
|
text: Time.amPmStr
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
font.pointSize: Math.floor(Appearance.font.size.extraLarge * 2 * 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var lock
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.large * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: weather.implicitHeight
|
||||||
|
|
||||||
|
topLeftRadius: Appearance.rounding.large
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
|
WeatherInfo {
|
||||||
|
id: weather
|
||||||
|
|
||||||
|
rootHeight: root.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: resources.implicitHeight
|
||||||
|
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
|
Resources {
|
||||||
|
id: resources
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomClippingRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
bottomLeftRadius: Appearance.rounding.large
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
|
Media {
|
||||||
|
id: media
|
||||||
|
|
||||||
|
lock: root.lock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Center {
|
||||||
|
lock: root.lock
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
CustomRect {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
topRightRadius: Appearance.rounding.large
|
||||||
|
bottomRightRadius: Appearance.rounding.large
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: DynamicColors.tPalette.m3surfaceContainer
|
||||||
|
|
||||||
|
NotifDock {
|
||||||
|
lock: root.lock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell.Services.UPower
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.large * 2
|
||||||
|
anchors.topMargin: Appearance.padding.large
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
implicitWidth: prompt.implicitWidth + Appearance.padding.normal * 2
|
||||||
|
implicitHeight: prompt.implicitHeight + Appearance.padding.normal * 2
|
||||||
|
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
|
||||||
|
MonoText {
|
||||||
|
id: prompt
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: ">"
|
||||||
|
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
|
||||||
|
color: DynamicColors.palette.m3onPrimary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MonoText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: "caelestiafetch.sh"
|
||||||
|
font.pointSize: root.width > 400 ? Appearance.font.size.larger : Appearance.font.size.normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
active: !iconLoader.active
|
||||||
|
|
||||||
|
sourceComponent: OsLogo {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: false
|
||||||
|
spacing: height * 0.15
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
id: iconLoader
|
||||||
|
|
||||||
|
Layout.fillHeight: true
|
||||||
|
active: root.width > 320
|
||||||
|
|
||||||
|
sourceComponent: OsLogo {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
active: !batLoader.active && root.height > 200
|
||||||
|
|
||||||
|
sourceComponent: FetchText {
|
||||||
|
text: `OS : ${SystemInfo.osPrettyName || SysInfo.osName}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
active: root.height > (batLoader.active ? 200 : 110)
|
||||||
|
|
||||||
|
sourceComponent: FetchText {
|
||||||
|
text: `WM : ${SystemInfo.wm}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
active: !batLoader.active || root.height > 110
|
||||||
|
|
||||||
|
sourceComponent: FetchText {
|
||||||
|
text: `USER: ${SystemInfo.user}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchText {
|
||||||
|
text: `UP : ${SystemInfo.uptime}`
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
id: batLoader
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
active: UPower.displayDevice.isLaptopBattery
|
||||||
|
|
||||||
|
sourceComponent: FetchText {
|
||||||
|
text: `BATT: ${[UPowerDeviceState.Charging, UPowerDeviceState.FullyCharged, UPowerDeviceState.PendingCharge].includes(UPower.displayDevice.state) ? "(+) " : ""}${Math.round(UPower.displayDevice.percentage * 100)}%`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WrappedLoader {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
active: root.height > 180
|
||||||
|
|
||||||
|
sourceComponent: RowLayout {
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Math.max(0, Math.min(8, root.width / (Appearance.font.size.larger * 2 + Appearance.spacing.large)))
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property Pam pam
|
||||||
|
readonly property alias placeholder: placeholder
|
||||||
|
property string buffer
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.pam
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.buffer = root.pam.buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: placeholder
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
opacity: root.buffer ? 0 : 1
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: charList
|
||||||
|
|
||||||
|
readonly property int fullWidth: count * (implicitHeight + spacing) - spacing
|
||||||
|
|
||||||
|
function bindImWidth(): void {
|
||||||
|
imWidthBehavior.enabled = false;
|
||||||
|
implicitWidth = Qt.binding(() => fullWidth);
|
||||||
|
imWidthBehavior.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
anchors.horizontalCenterOffset: implicitWidth > root.width ? -(implicitWidth - root.width) / 2 : 0
|
||||||
|
|
||||||
|
implicitWidth: fullWidth
|
||||||
|
implicitHeight: Appearance.font.size.normal
|
||||||
|
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
spacing: Appearance.spacing.small / 2
|
||||||
|
interactive: false
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.buffer.split("")
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: CustomRect {
|
||||||
|
id: ch
|
||||||
|
|
||||||
|
implicitWidth: implicitHeight
|
||||||
|
implicitHeight: charList.implicitHeight
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,18 +14,6 @@ Scope {
|
|||||||
property alias lock: lock
|
property alias lock: lock
|
||||||
property int seenOnce: 0
|
property int seenOnce: 0
|
||||||
|
|
||||||
Timer {
|
|
||||||
interval: 500
|
|
||||||
running: true
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if ( Config.lock.fixLockScreen ) {
|
|
||||||
Quickshell.execDetached(["hyprctl", "keyword", "misc:session_lock_xray", "true"]);
|
|
||||||
console.log("Fixed lock screen X-ray issue.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WlSessionLock {
|
WlSessionLock {
|
||||||
id: lock
|
id: lock
|
||||||
|
|
||||||
@@ -36,7 +24,6 @@ Scope {
|
|||||||
id: lockSurface
|
id: lockSurface
|
||||||
lock: lock
|
lock: lock
|
||||||
pam: pam
|
pam: pam
|
||||||
scope: root
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+164
-323
@@ -10,379 +10,220 @@ import qs.Config
|
|||||||
import qs.Helpers
|
import qs.Helpers
|
||||||
import qs.Effects
|
import qs.Effects
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Modules
|
import qs.Modules as Modules
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property WlSessionLock lock
|
required property WlSessionLock lock
|
||||||
required property Pam pam
|
required property Pam pam
|
||||||
required property Scope scope
|
|
||||||
|
|
||||||
property string buffer
|
readonly property alias unlocking: unlockAnim.running
|
||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.pam
|
target: root.lock
|
||||||
|
|
||||||
function onBufferChanged(): void {
|
function onUnlock(): void {
|
||||||
if (root.pam.buffer.length > root.buffer.length) {
|
unlockAnim.start();
|
||||||
charList.bindImWidth();
|
|
||||||
} else if (root.pam.buffer.length === 0) {
|
|
||||||
charList.implicitWidth = charList.implicitWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.buffer = root.pam.buffer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
SequentialAnimation {
|
||||||
interval: 5
|
id: unlockAnim
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockContent
|
||||||
|
properties: "implicitWidth,implicitHeight"
|
||||||
|
to: lockContent.size
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockBg
|
||||||
|
property: "radius"
|
||||||
|
to: lockContent.radius
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: content
|
||||||
|
property: "scale"
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: content
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockIcon
|
||||||
|
property: "opacity"
|
||||||
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: background
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
SequentialAnimation {
|
||||||
|
PauseAnimation {
|
||||||
|
duration: Appearance.anim.durations.small
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockContent
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: root.lock
|
||||||
|
property: "locked"
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
id: initAnim
|
||||||
|
|
||||||
running: true
|
running: true
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
Modules.Anim {
|
||||||
if ( Config.lock.fixLockScreen && root.scope.seenOnce === 0 ) {
|
target: background
|
||||||
Quickshell.execDetached(["hyprctl", "keyword", "misc:session_lock_xray", "false;"]);
|
property: "opacity"
|
||||||
root.scope.seenOnce += 1;
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
SequentialAnimation {
|
||||||
|
ParallelAnimation {
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockContent
|
||||||
|
property: "scale"
|
||||||
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveFastSpatial
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockContent
|
||||||
|
property: "rotation"
|
||||||
|
to: 360
|
||||||
|
duration: Appearance.anim.durations.expressiveFastSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardAccel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ParallelAnimation {
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockIcon
|
||||||
|
property: "rotation"
|
||||||
|
to: 360
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standardDecel
|
||||||
}
|
}
|
||||||
|
Modules.Anim {
|
||||||
TextInput {
|
target: lockIcon
|
||||||
id: hiddenInput
|
property: "opacity"
|
||||||
focus: true
|
to: 0
|
||||||
visible: false
|
}
|
||||||
|
Modules.Anim {
|
||||||
Keys.onPressed: function(event: KeyEvent): void {
|
target: content
|
||||||
root.pam.handleKey(event);
|
property: "opacity"
|
||||||
event.accepted = true;
|
to: 1
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: content
|
||||||
|
property: "scale"
|
||||||
|
to: 1
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
Modules.Anim {
|
||||||
|
target: lockBg
|
||||||
|
property: "radius"
|
||||||
|
to: Appearance.rounding.large * 1.5
|
||||||
|
}
|
||||||
|
Modules.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
|
||||||
|
}
|
||||||
|
Modules.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
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChanged: text = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreencopyView {
|
|
||||||
id: background
|
|
||||||
|
|
||||||
live: false
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
captureSource: root.screen
|
|
||||||
opacity: 1
|
|
||||||
visible: !Config.lock.useWallpaper
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: MultiEffect {
|
|
||||||
autoPaddingEnabled: false
|
|
||||||
blurEnabled: true
|
|
||||||
blur: 0.8
|
|
||||||
blurMax: 64
|
|
||||||
blurMultiplier: 1
|
|
||||||
brightness: 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CachingImage {
|
CachingImage {
|
||||||
id: backgroundImage
|
id: background
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
asynchronous: false
|
asynchronous: false
|
||||||
path: WallpaperPath.currentWallpaperPath
|
path: WallpaperPath.currentWallpaperPath
|
||||||
visible: Config.lock.useWallpaper
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
console.log(source);
|
console.log(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: overlay
|
id: lockContent
|
||||||
|
|
||||||
|
readonly property int size: lockIcon.implicitHeight + Appearance.padding.large * 4
|
||||||
|
readonly property int radius: size / 4 * Appearance.rounding.scale
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitWidth: size
|
||||||
|
implicitHeight: size
|
||||||
|
|
||||||
|
rotation: 180
|
||||||
|
scale: 0
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: lockBg
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "transparent"
|
color: DynamicColors.palette.m3surface
|
||||||
|
radius: parent.radius
|
||||||
visible: background.hasContent
|
opacity: DynamicColors.transparency.enabled ? DynamicColors.transparency.base : 1
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: contentBox
|
|
||||||
anchors.bottom: !Config.lock.useWallpaper ? "" : parent.bottom
|
|
||||||
anchors.centerIn: !Config.lock.useWallpaper ? parent : ""
|
|
||||||
anchors.horizontalCenter: !Config.lock.useWallpaper ? "" : parent.horizontalCenter
|
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainer
|
|
||||||
radius: 28
|
|
||||||
|
|
||||||
implicitWidth: Math.floor(childrenRect.width + 48)
|
|
||||||
implicitHeight: Math.floor(childrenRect.height + 64)
|
|
||||||
|
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
layer.effect: MultiEffect {
|
layer.effect: MultiEffect {
|
||||||
source: contentBox
|
|
||||||
blurEnabled: false
|
|
||||||
blurMax: 12
|
|
||||||
shadowBlur: 1
|
|
||||||
shadowColor: DynamicColors.palette.m3shadow
|
|
||||||
shadowOpacity: 1
|
|
||||||
shadowEnabled: true
|
shadowEnabled: true
|
||||||
autoPaddingEnabled: true
|
blurMax: 15
|
||||||
}
|
shadowColor: Qt.alpha(DynamicColors.palette.m3shadow, 0.7)
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
id: mainLayout
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: childrenRect.width
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: 16
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
UserImage {
|
|
||||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignLeft
|
|
||||||
Layout.bottomMargin: 16
|
|
||||||
Layout.preferredWidth: 128
|
|
||||||
Layout.preferredHeight: 128
|
|
||||||
}
|
|
||||||
|
|
||||||
LockTime {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: 8
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
MaterialIcon {
|
||||||
id: inputContainer
|
id: lockIcon
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
Layout.bottomMargin: 16
|
|
||||||
|
|
||||||
Layout.preferredWidth: 320
|
|
||||||
Layout.preferredHeight: 48
|
|
||||||
|
|
||||||
color: DynamicColors.tPalette.m3surfaceContainerHigh
|
|
||||||
radius: 1000
|
|
||||||
|
|
||||||
border.width: 1
|
|
||||||
border.color: {
|
|
||||||
if (root.pam.state === "error" || root.pam.state === "fail") {
|
|
||||||
return DynamicColors.palette.m3error;
|
|
||||||
}
|
|
||||||
return DynamicColors.palette.m3outline;
|
|
||||||
}
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
Behavior on border.color {
|
|
||||||
ColorAnimation { duration: 150 }
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Translate {
|
|
||||||
id: wobbleTransform
|
|
||||||
x: 0
|
|
||||||
}
|
|
||||||
|
|
||||||
SequentialAnimation {
|
|
||||||
id: wobbleAnimation
|
|
||||||
|
|
||||||
ColorAnimation {
|
|
||||||
target: inputContainer
|
|
||||||
property: "color"
|
|
||||||
to: DynamicColors.tPalette.m3onError
|
|
||||||
duration: MaterialEasing.expressiveEffectsTime
|
|
||||||
}
|
|
||||||
ParallelAnimation {
|
|
||||||
NumberAnimation {
|
|
||||||
target: wobbleTransform
|
|
||||||
property: "x"
|
|
||||||
to: -8
|
|
||||||
duration: MaterialEasing.expressiveFastSpatialTime / 4
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: wobbleTransform
|
|
||||||
property: "x"
|
|
||||||
to: 8
|
|
||||||
duration: MaterialEasing.expressiveFastSpatialTime / 4
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: wobbleTransform
|
|
||||||
property: "x"
|
|
||||||
to: -8
|
|
||||||
duration: MaterialEasing.expressiveFastSpatialTime / 4
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
NumberAnimation {
|
|
||||||
target: wobbleTransform
|
|
||||||
property: "x"
|
|
||||||
to: 0
|
|
||||||
duration: MaterialEasing.expressiveFastSpatialTime / 4
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
|
|
||||||
}
|
|
||||||
ColorAnimation {
|
|
||||||
target: inputContainer
|
|
||||||
property: "color"
|
|
||||||
to: DynamicColors.tPalette.m3surfaceContainerHigh
|
|
||||||
duration: MaterialEasing.expressiveEffectsTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: root.pam
|
|
||||||
|
|
||||||
function onStateChanged(): void {
|
|
||||||
if (root.pam.state === "error" || root.pam.state === "fail") {
|
|
||||||
wobbleAnimation.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomText {
|
|
||||||
id: messageDisplay
|
|
||||||
|
|
||||||
anchors.centerIn: inputContainer
|
|
||||||
|
|
||||||
text: {
|
|
||||||
if ( root.pam.buffer.length > 0 || root.pam.passwd.active ) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (root.pam.lockMessage) {
|
|
||||||
return root.pam.lockMessage;
|
|
||||||
}
|
|
||||||
if (root.pam.state === "error") {
|
|
||||||
return "Authentication error";
|
|
||||||
}
|
|
||||||
if (root.pam.state === "fail") {
|
|
||||||
return "Invalid password";
|
|
||||||
}
|
|
||||||
if (root.pam.state === "max") {
|
|
||||||
return "Maximum attempts reached";
|
|
||||||
}
|
|
||||||
return "Enter your password";
|
|
||||||
}
|
|
||||||
visible: true
|
|
||||||
font.pointSize: 14
|
|
||||||
font.weight: Font.Medium
|
|
||||||
|
|
||||||
animate: true
|
|
||||||
|
|
||||||
Behavior on text {
|
|
||||||
SequentialAnimation {
|
|
||||||
OAnim {
|
|
||||||
target: messageDisplay
|
|
||||||
property: "opacity"
|
|
||||||
to: 0
|
|
||||||
}
|
|
||||||
PropertyAction {}
|
|
||||||
OAnim {
|
|
||||||
target: messageDisplay
|
|
||||||
property: "opacity"
|
|
||||||
to: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component OAnim: NumberAnimation {
|
|
||||||
target: messageDisplay
|
|
||||||
property: "opacity"
|
|
||||||
duration: 100
|
|
||||||
}
|
|
||||||
|
|
||||||
color: root.pam.state === "max" ? DynamicColors.palette.m3error : DynamicColors.palette.m3onSurfaceVariant
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView {
|
|
||||||
id: charList
|
|
||||||
|
|
||||||
readonly property int fullWidth: count * (implicitHeight + spacing)
|
|
||||||
|
|
||||||
function bindImWidth(): void {
|
|
||||||
imWidthBehavior.enabled = false;
|
|
||||||
implicitWidth = Qt.binding(() => fullWidth);
|
|
||||||
imWidthBehavior.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
anchors.horizontalCenterOffset: implicitWidth > inputContainer.width - 20 ? -(implicitWidth - inputContainer.width + 20) / 2 : 0
|
text: "lock"
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge * 4
|
||||||
implicitWidth: fullWidth
|
font.bold: true
|
||||||
implicitHeight: 16
|
rotation: 180
|
||||||
|
|
||||||
orientation: Qt.Horizontal
|
|
||||||
spacing: 8
|
|
||||||
interactive: false
|
|
||||||
|
|
||||||
model: ScriptModel {
|
|
||||||
values: root.pam.buffer.split("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: CustomRect {
|
Content {
|
||||||
id: ch
|
id: content
|
||||||
|
|
||||||
implicitWidth: implicitHeight
|
anchors.centerIn: parent
|
||||||
implicitHeight: charList.implicitHeight
|
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
|
||||||
color: DynamicColors.palette.m3onSurface
|
|
||||||
radius: 1000
|
|
||||||
|
|
||||||
|
lock: root
|
||||||
opacity: 0
|
opacity: 0
|
||||||
scale: 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: MaterialEasing.expressiveFastSpatialTime
|
|
||||||
easing.bezierCurve: MaterialEasing.expressiveFastSpatial
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on implicitWidth {
|
|
||||||
id: imWidthBehavior
|
|
||||||
|
|
||||||
Anim {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Component.onCompleted: hiddenInput.forceActiveFocus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var lock
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: Players.active?.trackArtUrl ?? ""
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: height
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: mask
|
||||||
|
}
|
||||||
|
|
||||||
|
opacity: status === Image.Ready ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.extraLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: mask
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
gradient: Gradient {
|
||||||
|
orientation: Gradient.Horizontal
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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.topMargin: Appearance.spacing.large * 1.2
|
||||||
|
Layout.bottomMargin: Appearance.padding.large
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
PlayerControl {
|
||||||
|
icon: "skip_previous"
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
if (Players.active?.canGoPrevious)
|
||||||
|
Players.active.previous();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerControl {
|
||||||
|
animate: true
|
||||||
|
icon: active ? "pause" : "play_arrow"
|
||||||
|
colour: "Primary"
|
||||||
|
level: active ? 2 : 1
|
||||||
|
active: Players.active?.isPlaying ?? false
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
if (Players.active?.canTogglePlaying)
|
||||||
|
Players.active.togglePlaying();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerControl {
|
||||||
|
icon: "skip_next"
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
if (Players.active?.canGoNext)
|
||||||
|
Players.active.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PlayerControl: CustomRect {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
property alias animate: controlIcon.animate
|
||||||
|
property alias icon: controlIcon.text
|
||||||
|
property bool active
|
||||||
|
property string colour: "Secondary"
|
||||||
|
property int level: 1
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
color: active ? DynamicColors.palette[`m3${colour.toLowerCase()}`] : DynamicColors.palette[`m3${colour.toLowerCase()}Container`]
|
||||||
|
radius: active || controlState.pressed ? Appearance.rounding.normal : Math.min(implicitWidth, implicitHeight) / 2 * Math.min(1, Appearance.rounding.scale)
|
||||||
|
|
||||||
|
Elevation {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: parent.radius
|
||||||
|
z: -1
|
||||||
|
level: controlState.containsMouse && !controlState.pressed ? control.level + 1 : control.level
|
||||||
|
}
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
id: controlState
|
||||||
|
|
||||||
|
color: control.active ? DynamicColors.palette[`m3on${control.colour}`] : DynamicColors.palette[`m3on${control.colour}Container`]
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
control.onClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
import qs.Daemons
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var lock
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.smaller
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
active: opacity > 0
|
||||||
|
opacity: NotifServer.list.length > 0 ? 0 : 1
|
||||||
|
|
||||||
|
sourceComponent: ColumnLayout {
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
Image {
|
||||||
|
asynchronous: true
|
||||||
|
source: Qt.resolvedUrl(`${Quickshell.shellDir}/assets/dino.png`)
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
sourceSize.width: clipRect.width * 0.8
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: Coloriser {
|
||||||
|
colorizationColor: DynamicColors.palette.m3outlineVariant
|
||||||
|
brightness: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.extraLarge
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomListView {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,316 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
|
import Quickshell.Services.Notifications
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
import qs.Daemons
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property string modelData
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
property bool expanded
|
||||||
|
|
||||||
|
anchors.left: parent?.left
|
||||||
|
anchors.right: parent?.right
|
||||||
|
implicitHeight: content.implicitHeight + Appearance.padding.normal * 2
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
color: root.urgency === "critical" ? DynamicColors.palette.m3secondaryContainer : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: content
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Appearance.padding.normal
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.normal
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
implicitWidth: Config.notifs.sizes.image
|
||||||
|
implicitHeight: Config.notifs.sizes.image
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: imageComp
|
||||||
|
|
||||||
|
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: appIconComp
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
sourceComponent: root.image ? imageComp : root.appIcon ? appIconComp : materialIconComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
active: root.appIcon && root.image
|
||||||
|
|
||||||
|
sourceComponent: CustomRect {
|
||||||
|
implicitWidth: Config.notifs.sizes.badge
|
||||||
|
implicitHeight: Config.notifs.sizes.badge
|
||||||
|
|
||||||
|
color: root.urgency === "critical" ? DynamicColors.palette.m3error : root.urgency === "low" ? DynamicColors.palette.m3surfaceContainerHighest : DynamicColors.palette.m3secondaryContainer
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.bottomMargin: -parent.spacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.smaller
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: root.modelData
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
animate: true
|
||||||
|
text: root.notifs[0]?.timeStr ?? ""
|
||||||
|
color: DynamicColors.palette.m3outline
|
||||||
|
font.pointSize: Appearance.font.size.small
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomRect {
|
||||||
|
implicitWidth: expandBtn.implicitWidth + Appearance.padding.smaller * 2
|
||||||
|
implicitHeight: groupCount.implicitHeight + Appearance.padding.small
|
||||||
|
|
||||||
|
color: root.urgency === "critical" ? DynamicColors.palette.m3error : DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHighest, 2)
|
||||||
|
radius: Appearance.rounding.full
|
||||||
|
|
||||||
|
opacity: root.notifs.length > Config.notifs.groupPreviewNum ? 1 : 0
|
||||||
|
Layout.preferredWidth: root.notifs.length > Config.notifs.groupPreviewNum ? implicitWidth : 0
|
||||||
|
|
||||||
|
StateLayer {
|
||||||
|
color: root.urgency === "critical" ? DynamicColors.palette.m3onError : DynamicColors.palette.m3onSurface
|
||||||
|
|
||||||
|
function onClicked(): void {
|
||||||
|
root.expanded = !root.expanded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: expandBtn
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: Appearance.spacing.small / 2
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
id: groupCount
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on Layout.preferredWidth {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.notifs.slice(0, Config.notifs.groupPreviewNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotifLine {
|
||||||
|
id: notif
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
running: true
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
running: notif.modelData.closed
|
||||||
|
onFinished: notif.modelData.unlock(notif)
|
||||||
|
|
||||||
|
Anim {
|
||||||
|
target: notif
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: notif
|
||||||
|
property: "scale"
|
||||||
|
to: 0.7
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: notif.Layout
|
||||||
|
property: "preferredHeight"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
opacity: root.expanded ? 1 : 0
|
||||||
|
Layout.preferredHeight: root.expanded ? implicitHeight : 0
|
||||||
|
active: opacity > 0
|
||||||
|
|
||||||
|
sourceComponent: ColumnLayout {
|
||||||
|
Repeater {
|
||||||
|
model: ScriptModel {
|
||||||
|
values: root.notifs.slice(Config.notifs.groupPreviewNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
NotifLine {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on implicitHeight {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component NotifLine: CustomText {
|
||||||
|
id: notifLine
|
||||||
|
|
||||||
|
required property NotifServer.Notif modelData
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+99
-3
@@ -1,7 +1,9 @@
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Services.Pam
|
import Quickshell.Services.Pam
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
@@ -9,6 +11,7 @@ Scope {
|
|||||||
required property WlSessionLock lock
|
required property WlSessionLock lock
|
||||||
|
|
||||||
readonly property alias passwd: passwd
|
readonly property alias passwd: passwd
|
||||||
|
readonly property alias fprint: fprint
|
||||||
property string lockMessage
|
property string lockMessage
|
||||||
property string state
|
property string state
|
||||||
property string fprintState
|
property string fprintState
|
||||||
@@ -28,9 +31,8 @@ Scope {
|
|||||||
} else {
|
} else {
|
||||||
buffer = buffer.slice(0, -1);
|
buffer = buffer.slice(0, -1);
|
||||||
}
|
}
|
||||||
} else if ( event.key === Qt.Key_Escape ) {
|
|
||||||
buffer = "";
|
|
||||||
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
|
} else if (" abcdefghijklmnopqrstuvwxyz1234567890`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?".includes(event.text.toLowerCase())) {
|
||||||
|
// No illegal characters (you are insane if you use unicode in your password)
|
||||||
buffer += event.text;
|
buffer += event.text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +60,7 @@ Scope {
|
|||||||
|
|
||||||
onCompleted: res => {
|
onCompleted: res => {
|
||||||
if (res === PamResult.Success)
|
if (res === PamResult.Success)
|
||||||
return root.lock.locked = false;
|
return root.lock.unlock();
|
||||||
|
|
||||||
if (res === PamResult.Error)
|
if (res === PamResult.Error)
|
||||||
root.state = "error";
|
root.state = "error";
|
||||||
@@ -72,6 +74,77 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PamContext {
|
||||||
|
id: fprint
|
||||||
|
|
||||||
|
property bool available
|
||||||
|
property int tries
|
||||||
|
property int errorTries
|
||||||
|
|
||||||
|
function checkAvail(): void {
|
||||||
|
if (!available || !Config.lock.enableFprint || !root.lock.secure) {
|
||||||
|
abort();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tries = 0;
|
||||||
|
errorTries = 0;
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
|
||||||
|
config: "fprint"
|
||||||
|
configDirectory: Quickshell.shellDir + "/assets/pam.d"
|
||||||
|
|
||||||
|
onCompleted: res => {
|
||||||
|
if (!available)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (res === PamResult.Success)
|
||||||
|
return root.lock.unlock();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
root.flashMsg();
|
||||||
|
fprintStateReset.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: availProc
|
||||||
|
|
||||||
|
command: ["sh", "-c", "fprintd-list $USER"]
|
||||||
|
onExited: code => {
|
||||||
|
fprint.available = code === 0;
|
||||||
|
fprint.checkAvail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: errorRetry
|
||||||
|
|
||||||
|
interval: 800
|
||||||
|
onTriggered: fprint.start()
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: stateReset
|
id: stateReset
|
||||||
|
|
||||||
@@ -82,16 +155,39 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: fprintStateReset
|
||||||
|
|
||||||
|
interval: 4000
|
||||||
|
onTriggered: {
|
||||||
|
root.fprintState = "";
|
||||||
|
fprint.errorTries = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.lock
|
target: root.lock
|
||||||
|
|
||||||
function onSecureChanged(): void {
|
function onSecureChanged(): void {
|
||||||
if (root.lock.secure) {
|
if (root.lock.secure) {
|
||||||
|
availProc.running = true;
|
||||||
root.buffer = "";
|
root.buffer = "";
|
||||||
root.state = "";
|
root.state = "";
|
||||||
root.fprintState = "";
|
root.fprintState = "";
|
||||||
root.lockMessage = "";
|
root.lockMessage = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUnlock(): void {
|
||||||
|
fprint.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Config.lock
|
||||||
|
|
||||||
|
function onEnableFprintChanged(): void {
|
||||||
|
fprint.checkAvail();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Modules
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Appearance.padding.large
|
||||||
|
|
||||||
|
rowSpacing: Appearance.spacing.large
|
||||||
|
columnSpacing: Appearance.spacing.large
|
||||||
|
rows: 1
|
||||||
|
columns: 2
|
||||||
|
|
||||||
|
Ref {
|
||||||
|
service: SystemUsage
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
icon: "memory_alt"
|
||||||
|
value: SystemUsage.memPerc
|
||||||
|
colour: DynamicColors.palette.m3secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
component Resource: CustomRect {
|
||||||
|
id: res
|
||||||
|
|
||||||
|
required property string icon
|
||||||
|
required property real value
|
||||||
|
required property color colour
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: width
|
||||||
|
|
||||||
|
color: DynamicColors.layer(DynamicColors.palette.m3surfaceContainerHigh, 2)
|
||||||
|
radius: Appearance.rounding.large
|
||||||
|
|
||||||
|
CircularProgress {
|
||||||
|
id: circ
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import qs.Components
|
||||||
|
import qs.Helpers
|
||||||
|
import qs.Config
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property int rootHeight
|
||||||
|
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.margins: Appearance.padding.large * 2
|
||||||
|
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.topMargin: Appearance.padding.large * 2
|
||||||
|
Layout.bottomMargin: -Appearance.padding.large
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
active: root.rootHeight > 610
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: CustomText {
|
||||||
|
text: qsTr("Weather")
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge
|
||||||
|
font.weight: 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
MaterialIcon {
|
||||||
|
animate: true
|
||||||
|
text: Weather.icon
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge * 2.5
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: Weather.description
|
||||||
|
color: DynamicColors.palette.m3secondary
|
||||||
|
font.pointSize: Appearance.font.size.large
|
||||||
|
font.weight: 500
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: qsTr("Humidity: %1%").arg(Weather.humidity)
|
||||||
|
color: DynamicColors.palette.m3onSurfaceVariant
|
||||||
|
font.pointSize: Appearance.font.size.normal
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
Layout.rightMargin: Appearance.padding.smaller
|
||||||
|
active: root.width > 400
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: ColumnLayout {
|
||||||
|
spacing: Appearance.spacing.small
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
animate: true
|
||||||
|
text: Weather.temp
|
||||||
|
color: DynamicColors.palette.m3primary
|
||||||
|
horizontalAlignment: Text.AlignRight
|
||||||
|
font.pointSize: Appearance.font.size.extraLarge
|
||||||
|
font.weight: 500
|
||||||
|
elide: Text.ElideLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomText {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: forecastLoader
|
||||||
|
|
||||||
|
Layout.topMargin: Appearance.spacing.smaller
|
||||||
|
Layout.bottomMargin: Appearance.padding.large * 2
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
active: root.rootHeight > 820
|
||||||
|
visible: active
|
||||||
|
|
||||||
|
sourceComponent: RowLayout {
|
||||||
|
spacing: Appearance.spacing.large
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return forecast.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ ShapePath {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Wrapper wrapper
|
required property Wrapper wrapper
|
||||||
readonly property real rounding: 8
|
readonly property real rounding: 10
|
||||||
readonly property bool flatten: wrapper.width < rounding * 2
|
readonly property bool flatten: wrapper.width < rounding * 2
|
||||||
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
readonly property real roundingX: flatten ? wrapper.width / 2 : rounding
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -5,16 +5,21 @@ import QtQuick
|
|||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
readonly property string time: {
|
property alias enabled: clock.enabled
|
||||||
Qt.formatDateTime(clock.date, "ddd d MMM - hh:mm:ss")
|
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 shortTime: {
|
readonly property string timeStr: format("hh:mm:ss")
|
||||||
Qt.formatDateTime(clock.date, "hh:mm")
|
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] ?? ""
|
||||||
|
|
||||||
readonly property string longTime: {
|
function format(fmt: string): string {
|
||||||
Qt.formatDateTime(clock.date, "hh:mm:ss")
|
return Qt.formatDateTime(clock.date, fmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemClock {
|
SystemClock {
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ qml_module(ZShell-internal
|
|||||||
hyprextras.hpp hyprextras.cpp
|
hyprextras.hpp hyprextras.cpp
|
||||||
hyprdevices.hpp hyprdevices.cpp
|
hyprdevices.hpp hyprdevices.cpp
|
||||||
cachingimagemanager.hpp cachingimagemanager.cpp
|
cachingimagemanager.hpp cachingimagemanager.cpp
|
||||||
|
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
Qt::Quick
|
Qt::Quick
|
||||||
Qt::Concurrent
|
Qt::Concurrent
|
||||||
Qt::Core
|
Qt::Core
|
||||||
|
Qt::Network
|
||||||
|
Qt::DBus
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
#include "circularindicatormanager.hpp"
|
||||||
|
#include <qeasingcurve.h>
|
||||||
|
#include <qpoint.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
namespace advance {
|
||||||
|
|
||||||
|
constexpr qint32 TOTAL_CYCLES = 4;
|
||||||
|
constexpr qint32 TOTAL_DURATION_IN_MS = 5400;
|
||||||
|
constexpr qint32 DURATION_TO_EXPAND_IN_MS = 667;
|
||||||
|
constexpr qint32 DURATION_TO_COLLAPSE_IN_MS = 667;
|
||||||
|
constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 333;
|
||||||
|
constexpr qint32 TAIL_DEGREES_OFFSET = -20;
|
||||||
|
constexpr qint32 EXTRA_DEGREES_PER_CYCLE = 250;
|
||||||
|
constexpr qint32 CONSTANT_ROTATION_DEGREES = 1520;
|
||||||
|
|
||||||
|
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_EXPAND_IN_MS = { 0, 1350, 2700, 4050 };
|
||||||
|
constexpr std::array<qint32, TOTAL_CYCLES> DELAY_TO_COLLAPSE_IN_MS = { 667, 2017, 3367, 4717 };
|
||||||
|
|
||||||
|
} // namespace advance
|
||||||
|
|
||||||
|
namespace retreat {
|
||||||
|
|
||||||
|
constexpr qint32 TOTAL_DURATION_IN_MS = 6000;
|
||||||
|
constexpr qint32 DURATION_SPIN_IN_MS = 500;
|
||||||
|
constexpr qint32 DURATION_GROW_ACTIVE_IN_MS = 3000;
|
||||||
|
constexpr qint32 DURATION_SHRINK_ACTIVE_IN_MS = 3000;
|
||||||
|
constexpr std::array DELAY_SPINS_IN_MS = { 0, 1500, 3000, 4500 };
|
||||||
|
constexpr qint32 DELAY_GROW_ACTIVE_IN_MS = 0;
|
||||||
|
constexpr qint32 DELAY_SHRINK_ACTIVE_IN_MS = 3000;
|
||||||
|
constexpr qint32 DURATION_TO_COMPLETE_END_IN_MS = 500;
|
||||||
|
|
||||||
|
// Constants for animation values.
|
||||||
|
|
||||||
|
// The total degrees that a constant rotation goes by.
|
||||||
|
constexpr qint32 CONSTANT_ROTATION_DEGREES = 1080;
|
||||||
|
// Despite of the constant rotation, there are also 5 extra rotations the entire animation. The
|
||||||
|
// total degrees that each extra rotation goes by.
|
||||||
|
constexpr qint32 SPIN_ROTATION_DEGREES = 90;
|
||||||
|
constexpr std::array<qreal, 2> END_FRACTION_RANGE = { 0.10, 0.87 };
|
||||||
|
|
||||||
|
} // namespace retreat
|
||||||
|
|
||||||
|
inline qreal getFractionInRange(qreal playtime, qreal start, qreal duration) {
|
||||||
|
const auto fraction = (playtime - start) / duration;
|
||||||
|
return std::clamp(fraction, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace ZShell::internal {
|
||||||
|
|
||||||
|
CircularIndicatorManager::CircularIndicatorManager(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_type(IndeterminateAnimationType::Advance)
|
||||||
|
, m_curve(QEasingCurve(QEasingCurve::BezierSpline))
|
||||||
|
, m_progress(0)
|
||||||
|
, m_startFraction(0)
|
||||||
|
, m_endFraction(0)
|
||||||
|
, m_rotation(0)
|
||||||
|
, m_completeEndProgress(0) {
|
||||||
|
// Fast out slow in
|
||||||
|
m_curve.addCubicBezierSegment({ 0.4, 0.0 }, { 0.2, 1.0 }, { 1.0, 1.0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::startFraction() const {
|
||||||
|
return m_startFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::endFraction() const {
|
||||||
|
return m_endFraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::rotation() const {
|
||||||
|
return m_rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::progress() const {
|
||||||
|
return m_progress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::setProgress(qreal progress) {
|
||||||
|
update(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::duration() const {
|
||||||
|
if (m_type == IndeterminateAnimationType::Advance) {
|
||||||
|
return advance::TOTAL_DURATION_IN_MS;
|
||||||
|
} else {
|
||||||
|
return retreat::TOTAL_DURATION_IN_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::completeEndDuration() const {
|
||||||
|
if (m_type == IndeterminateAnimationType::Advance) {
|
||||||
|
return advance::DURATION_TO_COMPLETE_END_IN_MS;
|
||||||
|
} else {
|
||||||
|
return retreat::DURATION_TO_COMPLETE_END_IN_MS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircularIndicatorManager::IndeterminateAnimationType CircularIndicatorManager::indeterminateAnimationType() const {
|
||||||
|
return m_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::setIndeterminateAnimationType(IndeterminateAnimationType t) {
|
||||||
|
if (m_type != t) {
|
||||||
|
m_type = t;
|
||||||
|
emit indeterminateAnimationTypeChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qreal CircularIndicatorManager::completeEndProgress() const {
|
||||||
|
return m_completeEndProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::setCompleteEndProgress(qreal progress) {
|
||||||
|
if (qFuzzyCompare(m_completeEndProgress + 1.0, progress + 1.0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_completeEndProgress = progress;
|
||||||
|
emit completeEndProgressChanged();
|
||||||
|
|
||||||
|
update(m_progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::update(qreal progress) {
|
||||||
|
if (qFuzzyCompare(m_progress + 1.0, progress + 1.0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_type == IndeterminateAnimationType::Advance) {
|
||||||
|
updateAdvance(progress);
|
||||||
|
} else {
|
||||||
|
updateRetreat(progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_progress = progress;
|
||||||
|
emit progressChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::updateRetreat(qreal progress) {
|
||||||
|
using namespace retreat;
|
||||||
|
const auto playtime = progress * TOTAL_DURATION_IN_MS;
|
||||||
|
|
||||||
|
// Constant rotation.
|
||||||
|
const qreal constantRotation = CONSTANT_ROTATION_DEGREES * progress;
|
||||||
|
// Extra rotation for the faster spinning.
|
||||||
|
qreal spinRotation = 0;
|
||||||
|
for (const int spinDelay : DELAY_SPINS_IN_MS) {
|
||||||
|
spinRotation += m_curve.valueForProgress(getFractionInRange(playtime, spinDelay, DURATION_SPIN_IN_MS)) *
|
||||||
|
SPIN_ROTATION_DEGREES;
|
||||||
|
}
|
||||||
|
m_rotation = constantRotation + spinRotation;
|
||||||
|
emit rotationChanged();
|
||||||
|
|
||||||
|
// Grow active indicator.
|
||||||
|
qreal fraction =
|
||||||
|
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_GROW_ACTIVE_IN_MS, DURATION_GROW_ACTIVE_IN_MS));
|
||||||
|
fraction -=
|
||||||
|
m_curve.valueForProgress(getFractionInRange(playtime, DELAY_SHRINK_ACTIVE_IN_MS, DURATION_SHRINK_ACTIVE_IN_MS));
|
||||||
|
|
||||||
|
if (!qFuzzyIsNull(m_startFraction)) {
|
||||||
|
m_startFraction = 0.0;
|
||||||
|
emit startFractionChanged();
|
||||||
|
}
|
||||||
|
const auto oldEndFrac = m_endFraction;
|
||||||
|
m_endFraction = std::lerp(END_FRACTION_RANGE[0], END_FRACTION_RANGE[1], fraction);
|
||||||
|
|
||||||
|
// Completing animation.
|
||||||
|
if (m_completeEndProgress > 0) {
|
||||||
|
m_endFraction *= 1 - m_completeEndProgress;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!qFuzzyCompare(m_endFraction + 1.0, oldEndFrac + 1.0)) {
|
||||||
|
emit endFractionChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CircularIndicatorManager::updateAdvance(qreal progress) {
|
||||||
|
using namespace advance;
|
||||||
|
const auto playtime = progress * TOTAL_DURATION_IN_MS;
|
||||||
|
|
||||||
|
// Adds constant rotation to segment positions.
|
||||||
|
m_startFraction = CONSTANT_ROTATION_DEGREES * progress + TAIL_DEGREES_OFFSET;
|
||||||
|
m_endFraction = CONSTANT_ROTATION_DEGREES * progress;
|
||||||
|
|
||||||
|
// Adds cycle specific rotation to segment positions.
|
||||||
|
for (size_t cycleIndex = 0; cycleIndex < TOTAL_CYCLES; ++cycleIndex) {
|
||||||
|
// While expanding.
|
||||||
|
qreal fraction = getFractionInRange(playtime, DELAY_TO_EXPAND_IN_MS[cycleIndex], DURATION_TO_EXPAND_IN_MS);
|
||||||
|
m_endFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
||||||
|
|
||||||
|
// While collapsing.
|
||||||
|
fraction = getFractionInRange(playtime, DELAY_TO_COLLAPSE_IN_MS[cycleIndex], DURATION_TO_COLLAPSE_IN_MS);
|
||||||
|
m_startFraction += m_curve.valueForProgress(fraction) * EXTRA_DEGREES_PER_CYCLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the gap between head and tail for complete end.
|
||||||
|
m_startFraction += (m_endFraction - m_startFraction) * m_completeEndProgress;
|
||||||
|
|
||||||
|
m_startFraction /= 360.0;
|
||||||
|
m_endFraction /= 360.0;
|
||||||
|
|
||||||
|
emit startFractionChanged();
|
||||||
|
emit endFractionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::internal
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qeasingcurve.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
|
||||||
|
namespace ZShell::internal {
|
||||||
|
|
||||||
|
class CircularIndicatorManager : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
Q_PROPERTY(qreal startFraction READ startFraction NOTIFY startFractionChanged)
|
||||||
|
Q_PROPERTY(qreal endFraction READ endFraction NOTIFY endFractionChanged)
|
||||||
|
Q_PROPERTY(qreal rotation READ rotation NOTIFY rotationChanged)
|
||||||
|
Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
|
||||||
|
Q_PROPERTY(qreal completeEndProgress READ completeEndProgress WRITE setCompleteEndProgress NOTIFY
|
||||||
|
completeEndProgressChanged)
|
||||||
|
Q_PROPERTY(qreal duration READ duration NOTIFY indeterminateAnimationTypeChanged)
|
||||||
|
Q_PROPERTY(qreal completeEndDuration READ completeEndDuration NOTIFY indeterminateAnimationTypeChanged)
|
||||||
|
Q_PROPERTY(IndeterminateAnimationType indeterminateAnimationType READ indeterminateAnimationType WRITE
|
||||||
|
setIndeterminateAnimationType NOTIFY indeterminateAnimationTypeChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit CircularIndicatorManager(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
enum IndeterminateAnimationType {
|
||||||
|
Advance = 0,
|
||||||
|
Retreat
|
||||||
|
};
|
||||||
|
Q_ENUM(IndeterminateAnimationType)
|
||||||
|
|
||||||
|
[[nodiscard]] qreal startFraction() const;
|
||||||
|
[[nodiscard]] qreal endFraction() const;
|
||||||
|
[[nodiscard]] qreal rotation() const;
|
||||||
|
|
||||||
|
[[nodiscard]] qreal progress() const;
|
||||||
|
void setProgress(qreal progress);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal completeEndProgress() const;
|
||||||
|
void setCompleteEndProgress(qreal progress);
|
||||||
|
|
||||||
|
[[nodiscard]] qreal duration() const;
|
||||||
|
[[nodiscard]] qreal completeEndDuration() const;
|
||||||
|
|
||||||
|
[[nodiscard]] IndeterminateAnimationType indeterminateAnimationType() const;
|
||||||
|
void setIndeterminateAnimationType(IndeterminateAnimationType t);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void startFractionChanged();
|
||||||
|
void endFractionChanged();
|
||||||
|
void rotationChanged();
|
||||||
|
void progressChanged();
|
||||||
|
void completeEndProgressChanged();
|
||||||
|
void indeterminateAnimationTypeChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
IndeterminateAnimationType m_type;
|
||||||
|
QEasingCurve m_curve;
|
||||||
|
|
||||||
|
qreal m_progress;
|
||||||
|
qreal m_startFraction;
|
||||||
|
qreal m_endFraction;
|
||||||
|
qreal m_rotation;
|
||||||
|
qreal m_completeEndProgress;
|
||||||
|
|
||||||
|
void update(qreal progress);
|
||||||
|
void updateAdvance(qreal progress);
|
||||||
|
void updateRetreat(qreal progress);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::internal
|
||||||
Reference in New Issue
Block a user