diff --git a/IpcToggle.qml b/IpcToggle.qml new file mode 100644 index 0000000..a989058 --- /dev/null +++ b/IpcToggle.qml @@ -0,0 +1,15 @@ +import Quickshell.Io + +IpcHandler { + target: target + required property var item + function close() { + item.close(); + } + function open() { + item.open(); + } + function toggle() { + item.visible ? close() : open() + } +} diff --git a/Launcher.qml b/Launcher.qml index cd68251..1a29564 100644 --- a/Launcher.qml +++ b/Launcher.qml @@ -12,12 +12,12 @@ import Quickshell.Widgets PanelWindow { id: launcher - function hide() { + function close() { visible = false list.visible = false searchInput.text = "" } - function show() { + function open() { visible = true list.visible = true list.modelChanged() @@ -31,10 +31,9 @@ PanelWindow { top: true bottom: true } + visible: false + readonly property int radius: 20 - readonly property var font: { - family: "comfortaa" - } readonly property int entry_height: 100 property var currentApps: { let apps = Array.from(DesktopEntries.applications.values); @@ -98,7 +97,7 @@ PanelWindow { function execute() { modelData.execute() launcher.lastLaunched = modelData.id - launcher.hide() + launcher.close() } Behavior on selectedOffset { NumberAnimation {duration: 500; easing.type: Easing.OutQuint} @@ -168,7 +167,7 @@ PanelWindow { delegate: Shape { id: tri property real x_size: Math.random() * button.width/12 + button.width/10 - property real y_size: x_size * Math.SQRT1_2 + property real y_size: x_size * 0.8 property real y_anim: 0 property real y_off: Math.random() * entry_height @@ -205,12 +204,12 @@ PanelWindow { visible: false layer.enabled: true radius: launcher.radius - color: "red" } ShaderEffect { anchors.fill: triangles property var src: triangles property var mask: triMask + property bool fadeDirection: false vertexShader: "default.vert.qsb" fragmentShader: "trifade.frag.qsb" @@ -242,7 +241,7 @@ PanelWindow { spacing: 4 Text { text: modelData.name - font.family: launcher.font + font.family: root.font font.pixelSize: 16 font.bold: true color: "white" @@ -251,7 +250,7 @@ PanelWindow { visible: modelData.comment.length > 0 width: parent.parent.width - icon.anchors.rightMargin-icon.width - 20 text: modelData.comment - font.family: launcher.font + font.family: root.font color: "white" elide: Text.ElideRight } @@ -282,7 +281,7 @@ PanelWindow { TextInput { z: 5 id: searchInput - font.family: launcher.font + font.family: root.font font.pixelSize: 16 font.bold: true focus: true diff --git a/Lock.qml b/Lock.qml new file mode 100644 index 0000000..92cbcb7 --- /dev/null +++ b/Lock.qml @@ -0,0 +1,214 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Effects +import QtQuick.Shapes +import Quickshell +import Quickshell.Wayland + +WlSessionLock { + id: lock + readonly property string bgPath: "assets/lockbg.jpg" + readonly property string logoPath: "assets/logo.svg" + function open() { + locked = true + } + function close() { + locked = false + } + locked: false + surface: WlSessionLockSurface { + id: surface + color: "transparent" + Item { + id: content + visible: true + layer.enabled: true + anchors.fill: parent + Image { + visible: false + source: lock.bgPath + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: surface.width + height: surface.height + fillMode: Image.PreserveAspectCrop + } + Item { + id: logo + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + } + width: Math.min(surface.width, surface.height) * 0.6 + borderWidth*2 + height: width + + readonly property real borderWidth: 24 + + // Background + Shape { + id: circleBg + layer.enabled: true + anchors.fill: parent + containsMode: Shape.FillContains + preferredRendererType: Shape.CurveRenderer + ShapePath { + fillGradient: LinearGradient { + orientation: Gradient.Vertical + GradientStop { position: 0; color: "#FB64A8" } + GradientStop { position: 1; color: "#CA5289" } + } + strokeWidth: 0 + PathAngleArc { + moveToStart: true + centerX: logo.width/2 + centerY: logo.height/2 + radiusX: surface.height * 0.3 + logo.borderWidth/2 + radiusY: radiusX + startAngle: 0 + sweepAngle: 360 + } + } + } + Item { + id: triangles + visible: false + anchors.fill: parent + clip: true + layer.enabled: true + Instantiator { + model: 24 + delegate: Shape { + id: tri + property real x_size: Math.random() * logo.width + logo.width/8 + property real y_size: x_size * 0.8 + + property real y_anim: 0 + property real y_off: Math.random() * logo.height + x: Math.random() * (logo.width + x_size*2) - x_size + y: (y_anim + y_off) % (logo.height + y_size) - y_size + ShapePath { + strokeWidth: 2 + strokeColor: "#9A4272" + fillColor: "transparent" + + startX: tri.x_size/2 + startY: 0 + PathLine {x: 0; y: tri.y_size} + PathLine {x: tri.x_size; y: tri.y_size} + PathLine {x: tri.x_size/2; y: 0} + } + PropertyAnimation on y_anim { + from: logo.height + tri.y_size + running: lock.secure + to: 0 + duration: Math.random() * 15000 + 12000 + loops: Animation.Infinite + } + } + onObjectAdded: (index, obj) => obj.parent = triangles + } + } + ShaderEffect { + anchors.fill: parent + property var src: triangles + property var mask: circleBg + property bool fadeDirection: true + vertexShader: "default.vert.qsb" + fragmentShader: "trifade.frag.qsb" + } + + // Group border and logo to shadow together + Item { + anchors.fill: parent + layer.enabled: true + // Circle border + Shape { + anchors.fill: parent + preferredRendererType: Shape.CurveRenderer + ShapePath { + fillColor: "transparent" + strokeColor: "white" + strokeWidth: logo.borderWidth + PathAngleArc { + moveToStart: true + centerX: logo.width/2 + centerY: logo.height/2 + radiusX: surface.height * 0.3 + radiusY: radiusX + startAngle: 0 + sweepAngle: 360 + } + } + } + // Logo + Image { + anchors { + verticalCenter: parent.verticalCenter + horizontalCenter: parent.horizontalCenter + verticalCenterOffset: logo.height * 0.05 * -1 + } + width: logo.width * 0.65 + height: width + source: lock.logoPath + sourceSize { + width: width + height: height + } + } + layer.effect: MultiEffect { + anchors.fill: parent + blurMax: 48 + shadowEnabled: true + shadowOpacity: 0.4 + shadowVerticalOffset: logo.height * 0.015 + } + } + } + + Rectangle { + id: flash + color: "white" + anchors.fill: parent + PropertyAnimation on opacity { + running: lock.secure + from: 1 + + to: 0 + duration: 800 + } + } + Button { + text: "unlock" + onClicked: lock.locked = false + } + layer.effect: MultiEffect { + anchors.fill: parent + maskEnabled: true + maskSource: contentMask + } + } + + Item { + id: contentMask + layer.enabled: true + anchors.fill: parent + visible: false + Rectangle { + x: 0 + y: surface.height/2 * (1 - progress) + width: surface.width + height: surface.height * progress + property real progress: 0 + PropertyAnimation on progress { + running: lock.secure + from: 0 + to: 1 + duration: 800 + easing.type: Easing.OutQuint + } + } + } + } +} diff --git a/assets/lockbg.jpg b/assets/lockbg.jpg new file mode 100644 index 0000000..371c7a2 Binary files /dev/null and b/assets/lockbg.jpg differ diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..dbcdd13 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shell.qml b/shell.qml index 31af72d..0551021 100644 --- a/shell.qml +++ b/shell.qml @@ -3,18 +3,16 @@ import Quickshell import Quickshell.Io ShellRoot { - IpcHandler { - target: "launcher" - function hide() { - launcher.hide() - } - function show() { - launcher.show() - } - function toggle() { launcher.visible ? hide() : show() } + id: root + readonly property var font: { + family: "comfortaa" } - - Osu.Launcher { - id: launcher + Osu.IpcToggle { + target: "launcher" + item: Osu.Launcher {} + } + Osu.IpcToggle { + target: "lock" + item: Osu.Lock { } } } diff --git a/trifade.frag b/trifade.frag index 5a83065..ed146d8 100644 --- a/trifade.frag +++ b/trifade.frag @@ -4,11 +4,12 @@ layout(location = 0) out vec4 fragColor; layout(std140, binding = 0) uniform buf { mat4 qt_Matrix; float qt_Opacity; + bool fadeDirection; // true: fade top, false: fade bottom }; layout(binding = 1) uniform sampler2D src; layout(binding = 2) uniform sampler2D mask; void main() { vec4 tex = texture(src, coord); - float a = texture(mask, coord).a * tex.a * (1-coord.y); + float a = texture(mask, coord).a * tex.a * (fadeDirection ? coord.y : 1-coord.y); fragColor = vec4(tex.r * a, tex.g * a, tex.b * a, a); }