Compare commits

..

No commits in common. "d72adec2efdb44cb0916b32f9489a669fe557159" and "b97ac0276c3ebc35a1a952ba9bb2aeaba6c2c49c" have entirely different histories.

22 changed files with 75 additions and 882 deletions

View File

@ -1,29 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: customColors
// Core Backgrounds
readonly property color background: "#24283B"
readonly property color foreground: "#C0CAF5"
readonly property color cursor: "#C0CAF5"
// The 16 Colors of the Apocalypse
readonly property color color0: "#414868"
readonly property color color1: "#F7768E"
readonly property color color2: "#9ECE6A"
readonly property color color3: "#E0AF68"
readonly property color color4: "#7AA2F7"
readonly property color color5: "#BB9AF7"
readonly property color color6: "#7DCFFF"
readonly property color color7: "#C0CAF5"
readonly property color color8: "#414868"
readonly property color color9: "#F7768E"
readonly property color color10: "#9ECE6A"
readonly property color color11: "#E0AF68"
readonly property color color12: "#7AA2F7"
readonly property color color13: "#BB9AF7"
readonly property color color14: "#7DCFFF"
readonly property color color15: "#C0CAF5"
}

View File

@ -2,6 +2,6 @@ pragma Singleton
import QtQuick import QtQuick
QtObject { QtObject {
readonly property string font: "Iosevka Nerd Font Propo" readonly property string font: "JetBrainsMono Nerd Font"
readonly property real fontSize: 14 readonly property real fontSize: 14
} }

View File

@ -1,13 +1,11 @@
import Quickshell import Quickshell
import QtQuick import QtQuick
import qs.modules.bar
import QtQuick.Layouts import QtQuick.Layouts
import qs
PanelWindow { PanelWindow {
id: root id: root
required property var modelData
implicitHeight: 30 implicitHeight: 30
//color: Colors.background
color: Colors.background color: Colors.background
anchors { anchors {
top: true top: true
@ -17,31 +15,25 @@ PanelWindow {
RowLayout { RowLayout {
id: leftLayout id: leftLayout
spacing: 30
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Clock { Workspaces {}
Layout.leftMargin: 30
}
Mpris {}
} }
RowLayout { RowLayout {
id: centerLayout id: centerLayout
anchors.centerIn: parent anchors.centerIn: parent
Workspaces {}
} }
RowLayout { RowLayout {
id: rightLayout id: rightLayout
spacing: 20 spacing: 0
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Volume {} Volume {}
Battery {} Battery {}
PowerProfiles {}
SystemTray { SystemTray {
Layout.rightMargin: 30 Layout.rightMargin: 10
} }
} }
} }

View File

@ -1,8 +1,6 @@
import Quickshell.Services.UPower import Quickshell.Services.UPower
import QtQuick import QtQuick
import Quickshell.Widgets import Quickshell.Widgets
import "."
import qs
Item { Item {
id: root id: root
@ -11,17 +9,14 @@ Item {
Row { Row {
id: batRow id: batRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: 5 spacing: 0
IconImage { IconImage {
anchors.verticalCenter: parent.verticalCenter
source: "root:/icons/" + UPower.displayDevice.iconName + ".svg" source: "root:/icons/" + UPower.displayDevice.iconName + ".svg"
width: 12 implicitWidth: 16
height: 12 implicitHeight: 16
} }
Text { Text {
id: batteryText id: batteryText
anchors.verticalCenter: parent.verticalCenter
font.weight: 900
font.family: Appearance.font font.family: Appearance.font
font.pixelSize: Appearance.fontSize font.pixelSize: Appearance.fontSize
color: Colors.foreground color: Colors.foreground

View File

@ -1,26 +1,12 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import qs
Item { Item {
id: root id: root
// FIX: Real pixels please! implicitWidth: clockText.text.length + 10
implicitWidth: clockText.implicitWidth
implicitHeight: 30
Text { Text {
id: clockText id: clockText
anchors.centerIn: parent anchors.centerIn: parent
font.weight: 900 text: "sigma balls"
font.family: Appearance.font
font.pixelSize: Appearance.fontSize
color: Colors.foreground
text: Qt.formatDateTime(clock.date, "hh:mm")
SystemClock {
id: clock
precision: SystemClock.Minutes
}
} }
} }

29
modules/bar/Colors.qml Normal file
View File

@ -0,0 +1,29 @@
pragma Singleton
import QtQuick
import Quickshell
Singleton {
id: customColors
// Core Backgrounds
readonly property color background: "#1F1F28"
readonly property color foreground: "#DCD7BA"
readonly property color cursor: "#DCD7BA"
// The 16 Colors of the Apocalypse
readonly property color color0: "#090618"
readonly property color color1: "#C34043"
readonly property color color2: "#76946A"
readonly property color color3: "#C0A36E"
readonly property color color4: "#7E9CD8"
readonly property color color5: "#957FB8"
readonly property color color6: "#6A9589"
readonly property color color7: "#C8C093"
readonly property color color8: "#727169"
readonly property color color9: "#E82424"
readonly property color color10: "#98BB6C"
readonly property color color11: "#E6C384"
readonly property color color12: "#7FB4CA"
readonly property color color13: "#938AA9"
readonly property color color14: "#7AA89F"
readonly property color color15: "#DCD7BA"
}

View File

@ -1,134 +0,0 @@
// Ensure Colors is imported
// import "../../"
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import Quickshell
import Quickshell.Services.Mpris
import qs
RowLayout {
id: root
// 1. Let Repeater loop through the ObjectModel for us
Repeater {
id: mprisRepeater
model: Mpris.players
delegate: RowLayout {
required property var modelData
// 2. 🕵 FILTER LOGIC
// Check if this specific player is Spotify.
// We verify 'modelData' exists and check the name.
property bool isSpotify: modelData && modelData.identity.toLowerCase().includes("spotify")
// 3. 👻 HIDE NON-SPOTIFY PLAYERS
visible: isSpotify
// If hidden, take up ZERO space
Layout.preferredWidth: isSpotify ? Math.min(implicitWidth, 400) : 0
Layout.fillHeight: true
// 4. 🎵 USE 'modelData' DIRECTLY
// property string title: modelData.metadata["xesam:title"] || "No Title"
// property string artist: modelData.metadata["xesam:artist"] || "Unknown"
// property string artUrl: modelData.metadata["mpris:artUrl"] || ""
// property bool isPlaying: modelData.playbackStatus === MprisPlaybackStatus.Playing
property string title: modelData.trackTitle
property string artist: modelData.trackArtist
property string artUrl: modelData.trackArtUrl
property bool isPlaying: modelData.isPlaying
spacing: 8
// 🖼 ALBUM ART
Rectangle {
Layout.preferredHeight: parent.height * 0.8
Layout.preferredWidth: Layout.preferredHeight
Layout.alignment: Qt.AlignVCenter
radius: 4
color: Colors.background
clip: true
visible: parent.visible // Optimization
Image {
anchors.fill: parent
source: parent.parent.artUrl // Access property from delegate
fillMode: Image.PreserveAspectCrop
asynchronous: true
sourceSize.width: 128
sourceSize.height: 128
}
}
// 📝 TEXT INFO
ColumnLayout {
Layout.alignment: Qt.AlignVCenter
spacing: 0
visible: parent.visible
Text {
text: parent.parent.title
color: Colors.foreground
font.bold: true
font.pixelSize: 12
elide: Text.ElideRight
Layout.preferredWidth: implicitWidth
}
Text {
text: parent.parent.artist
color: Colors.foreground
opacity: 0.7
font.pixelSize: 10
Layout.preferredWidth: implicitWidth
}
}
// CONTROLS
RowLayout {
Layout.alignment: Qt.AlignVCenter
spacing: 8
visible: parent.visible
// PREV
Text {
text: "󰒮"
color: Colors.foreground
font.pixelSize: 24
MouseArea {
anchors.fill: parent
// Use modelData to control THIS player
onClicked: modelData.previous()
cursorShape: Qt.PointingHandCursor
}
}
// PLAY / PAUSE
Text {
text: parent.parent.isPlaying ? "󰏤" : "󰐊"
color: Colors.foreground
font.pixelSize: 24
MouseArea {
anchors.fill: parent
onClicked: modelData.playPause()
cursorShape: Qt.PointingHandCursor
}
}
// NEXT
Text {
text: "󰒭"
color: Colors.foreground
font.pixelSize: 24
MouseArea {
anchors.fill: parent
onClicked: modelData.next()
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
}

View File

@ -1,33 +0,0 @@
import QtQuick
import Quickshell.Services.UPower
import qs
Item {
id: root
implicitWidth: 80
Text {
id: powerProfile
text: PowerProfile.toString(PowerProfiles.profile)
font.weight: 900
color: Colors.foreground
font.family: Appearance.font
font.pixelSize: Appearance.fontSize
anchors.centerIn: parent
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: Qt.OpenHandCursor
anchors.fill: parent
onClicked: mouse => {
const modes = [PowerProfile.PowerSaver, PowerProfile.Balanced, PowerProfile.Performance];
let current = PowerProfiles.profile;
let currentIndex = modes.indexOf(current);
let nextIndex = (currentIndex + 1) % modes.length;
let prevIndex = (currentIndex - 1) % modes.length;
if (mouse.button == Qt.LeftButton)
PowerProfiles.profile = modes[nextIndex];
if (mouse.button == Qt.RightButton)
PowerProfiles.profile = modes[prevIndex];
}
}
}
}

View File

@ -6,7 +6,7 @@ Item {
clip: true clip: true
// This was already correct in your last file, but keep it this way! // This was already correct in your last file, but keep it this way!
implicitWidth: layout.implicitWidth implicitWidth: layout.implicitWidth + 10
implicitHeight: 30 implicitHeight: 30
// Hide if empty so we don't have a 50px gap for nothing // Hide if empty so we don't have a 50px gap for nothing

View File

@ -1,3 +1,5 @@
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray

View File

@ -1,8 +1,6 @@
import QtQuick import QtQuick
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import Quickshell.Widgets import Quickshell.Widgets
import Quickshell.Io
import qs
Item { Item {
id: root id: root
@ -10,23 +8,7 @@ Item {
implicitHeight: volRow.implicitHeight implicitHeight: volRow.implicitHeight
// grab the default speaker (Sink) // grab the default speaker (Sink)
property var sink: Pipewire.defaultAudioSink property var sink: Pipewire.defaultAudioSink
Process {
id: pavu
command: ["pavucontrol"] // The command and args list
}
MouseArea {
cursorShape: Qt.OpenHandCursor
onClicked: mouse => {
if (mouse.button === Qt.LeftButton) {
// Left Click: Summon the Mixer!
console.log("Summoning Pavucontrol... Nya!");
pavu.startDetached();
}
}
anchors.fill: parent
// Scroll to change volume (The fancy stuff!)
}
// Logic to pick the correct icon name // Logic to pick the correct icon name
function getVolumeIcon() { function getVolumeIcon() {
// Safety check: if Pipewire is dead or sink is missing // Safety check: if Pipewire is dead or sink is missing
@ -57,9 +39,8 @@ Item {
spacing: 5 spacing: 5
IconImage { IconImage {
anchors.verticalCenter: parent.verticalCenter width: 16
width: 12 height: 16
height: 12
// The magic: 'image://theme/' pulls from your system icon theme (Papirus, Adwaita, etc.) // The magic: 'image://theme/' pulls from your system icon theme (Papirus, Adwaita, etc.)
source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg" source: "root:/icons/" + root.getVolumeIcon() + "-symbolic.svg"
@ -70,16 +51,30 @@ Item {
Text { Text {
PwObjectTracker { PwObjectTracker {
objects: Pipewire.defaultAudioSink objects: Pipewire.defaultAudioSink
} }
anchors.verticalCenter: parent.verticalCenter
width: 20
font.weight: 900
color: Colors.foreground color: Colors.foreground
font.family: Appearance.font font.family: Appearance.font
font.pixelSize: Appearance.fontSize font.pixelSize: Appearance.fontSize
text: Math.round(Pipewire.defaultAudioSink.audio.volume * 100) + "%" text: Math.round(Pipewire.defaultAudioSink.audio.volume * 100) + "%"
MouseArea {
anchors.fill: parent
onClicked: {
if (root.sink) {
root.sink.audio.muted = !root.sink.audio.muted;
}
}
// Scroll to change volume (The fancy stuff!)
onWheel: wheel => {
if (root.sink) {
if (wheel.angleDelta.y > 0) {
root.sink.audio.volume += 0.05; // Up 5%
} else {
root.sink.audio.volume -= 0.05; // Down 5%
}
}
}
}
} }
// Click to toggle mute! (Bonus feature) // Click to toggle mute! (Bonus feature)

View File

@ -1,37 +1,38 @@
import Quickshell.Hyprland import Quickshell.Hyprland
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import qs
Item { Item {
id: root id: root
property var modelData implicitWidth: workspaceRepeater.count * 30
implicitWidth: workspaceRow.implicitWidth
height: 30 height: 30
Row { Row {
id: workspaceRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: 10 // Slightly increase spacing between workspace buttons spacing: 10 // Slightly increase spacing between workspace buttons
Repeater { Repeater {
anchors.centerIn: parent id: workspaceRepeater
model: Hyprland.workspaces
Rectangle { Rectangle {
id: workspaceNumber width: 20
width: 16 height: 20
height: 16
radius: 20 radius: 20
//color: modelData.active ? myPallete.accent : myPallete.window
color: modelData.active ? Colors.foreground : "transparent" color: modelData.active ? Colors.foreground : "transparent"
Text { Text {
id: workspaceNumber
font.weight: 900 font.weight: 900
font.family: Appearance.font font.family: Appearance.font
font.pixelSize: Appearance.fontSize font.pixelSize: Appearance.fontSize
anchors.centerIn: workspaceNumber anchors.centerIn: parent
text: modelData.id text: modelData.id
color: modelData.active ? Colors.background : Colors.foreground // Set contrasting color for workspace number color: modelData.active ? Colors.background : Colors.foreground // Set contrasting color for workspace number
} }
} }
model: Hyprland.workspaces
} }
} }
} }

View File

@ -1,142 +0,0 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs
import "."
import QtQuick.Layouts
WlrLayershell {
id: root
// 1. Position: Top Right Corner, covering the full height
// We make it a fixed width (e.g., 400px) so it doesn't block the whole screen
anchors {
top: true
right: true
}
margins {
top: 30
}
implicitWidth: 400
implicitHeight: notifList.contentHeight + 10
// 2. Layer: Put it ABOVE normal windows
layer: WlrLayer.Overlay
exclusionMode: ExclusionMode.Ignore
// 3. CRITICAL: Make the window itself invisible!
// We only want to see the notifications, not the container.
color: "transparent"
// 4. Input: Let clicks pass through empty areas
// (This is default behavior if the background is transparent in some compositors,
// but usually you need to be careful with handling mouse events here)
// THE SPAWNER
ListView {
id: notifList
anchors.fill: parent
anchors.margins: 10
// Use 'spacing' to put gaps between notifications
spacing: 10
// Align to the bottom (like Windows) or Top (like GNOME)?
// verticalLayoutDirection: ListView.BottomToTop
// 🔗 CONNECT TO THE SERVER
// Assuming your NotificationServer is a singleton or globally accessible
// ... other imports
// Inside your ListView...
model: NotifServer.trackedNotifications
delegate: Item {
id: notifyItem
implicitWidth: ListView.view.width
implicitHeight: 60 // Fixed height is usually better for icon layouts
required property var modelData
Timer {
id: timout
interval: 30000
running: true
onRunningChanged: notifyItem.modelData.dismiss()
}
Rectangle {
anchors.fill: parent
color: Colors.background
radius: 10
border.color: Colors.color5
// 2. Use RowLayout to put Image | Text side-by-side
Row {
anchors.margins: 10
anchors.fill: parent
anchors.centerIn: parent
spacing: 15
// 🖼 THE IMAGE ON THE LEFT
Image {
// Use the image if available, otherwise hide this space?
// Or you could use an icon fallback.
source: notifyItem.modelData.image
anchors.verticalCenter: parent.verticalCenter
// Hide if no image exists so text takes full width
visible: notifyItem.modelData.image !== ""
// Fixed size for consistency
width: 48
height: 48
// Crop it nicely so it doesn't stretch
fillMode: Image.PreserveAspectCrop
// Optional: Cache it for performance
asynchronous: true
}
// 📝 THE TEXT ON THE RIGHT
ColumnLayout {
// Take up all remaining width
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignTop // Center vertically
spacing: 2
Text {
text: notifyItem.modelData.summary
color: Colors.foreground
font.bold: true
elide: Text.ElideRight
Layout.fillWidth: true
}
Text {
text: notifyItem.modelData.body
color: Colors.foreground
// Limit to 2 lines
maximumLineCount: 2
wrapMode: Text.WordWrap
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
// (Your MouseArea for closing can still go here covering the whole thing)
MouseArea {
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
acceptedButtons: Qt.LeftButton
onClicked: notifyItem.modelData.dismiss()
}
}
}
}
}

View File

@ -1,15 +0,0 @@
pragma ComponentBehavior: Bound
pragma Singleton
import Quickshell.Services.Notifications
import QtQuick
import Quickshell
NotificationServer {
bodyMarkupSupported: true
persistenceSupported: true
imageSupported: true
onNotification: notification => {
notification.tracked = true;
console.log("got notification!!! arf woof");
}
}

View File

@ -1,2 +0,0 @@
singleton NotifServer 1.0 NotifServer.qml
NotiPopup 1.0 NotiPopup.qml

View File

@ -1,39 +0,0 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import "../../"
import "."
WlrLayershell {
id: overlayRoot
// 1. Fill the entire screen
anchors {
top: true
bottom: true
left: true
right: true
}
// 2. Sit on top of EVERYTHING (even fullscreen apps if compositor allows)
layer: WlrLayer.Top
// 3. Invisible background
color: "transparent"
// 4. 👻 GHOST MODE ENABLED 👻
// An empty Region means "I accept mouse events nowhere".
// This guarantees you can click through the black corners.
mask: Region {}
// 5. Load the corners!
ScreenCorners {
// Adjust these to match your screen's aesthetic
cornerRadius: 25
cornerColor: Colors.background
shouldShow: true
// Ensure it stays on top of any other items in this window
z: 999
}
}

View File

@ -1,175 +0,0 @@
import QtQuick
import QtQuick.Shapes
Item {
id: root
anchors.fill: parent
// ---------------------------------------------------------
// 🛠 CONFIGURATION (Tweaked to match your setup)
// ---------------------------------------------------------
// How round do you want the screen?
property real cornerRadius: 20
// What color should the corners be? (Usually black to match the bezel)
// You can change this to "transparent" or a theme color if you want.
property color cornerColor
// Enable/Disable toggle
property bool shouldShow: true
// ---------------------------------------------------------
// Wrapper with layer caching to reduce GPU usage
Item {
anchors.fill: parent
layer.enabled: true
Shape {
id: cornersShape
anchors.fill: parent
preferredRendererType: Shape.CurveRenderer
enabled: false // Click-through
ShapePath {
id: cornersPath
// Map our local properties to the variables the code expects
readonly property real cornerRadius: root.cornerRadius
readonly property real cornerSize: root.cornerRadius // Usually same as radius
// Margins (Leave 0 unless your bar overlaps)
readonly property real topMargin: 0
readonly property real bottomMargin: 0
readonly property real leftMargin: 0
readonly property real rightMargin: 0
readonly property real screenWidth: cornersShape.width
readonly property real screenHeight: cornersShape.height
strokeWidth: -1 // No outline
fillColor: cornerColor
// Smooth fade if you toggle it
// ==========================================
// 📐 GEOMETRY LOGIC (Untouched)
// ==========================================
// Top-Left
startX: leftMargin
startY: topMargin
PathLine {
relativeX: cornersPath.cornerSize
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: cornersPath.cornerSize - cornersPath.cornerRadius
}
PathArc {
relativeX: -cornersPath.cornerRadius
relativeY: cornersPath.cornerRadius
radiusX: cornersPath.cornerRadius
radiusY: cornersPath.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius)
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: -cornersPath.cornerSize
}
// Top-Right
PathMove {
x: cornersPath.screenWidth - cornersPath.rightMargin - cornersPath.cornerSize
y: cornersPath.topMargin
}
PathLine {
relativeX: cornersPath.cornerSize
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: cornersPath.cornerSize
}
PathLine {
relativeX: -(cornersPath.cornerSize - cornersPath.cornerRadius)
relativeY: 0
}
PathArc {
relativeX: -cornersPath.cornerRadius
relativeY: -cornersPath.cornerRadius
radiusX: cornersPath.cornerRadius
radiusY: cornersPath.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius)
}
// Bottom-Left
PathMove {
x: cornersPath.leftMargin
y: cornersPath.screenHeight - cornersPath.bottomMargin - cornersPath.cornerSize
}
PathLine {
relativeX: cornersPath.cornerSize - cornersPath.cornerRadius
relativeY: 0
}
PathArc {
relativeX: cornersPath.cornerRadius
relativeY: cornersPath.cornerRadius
radiusX: cornersPath.cornerRadius
radiusY: cornersPath.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: 0
relativeY: cornersPath.cornerSize - cornersPath.cornerRadius
}
PathLine {
relativeX: -cornersPath.cornerSize
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: -cornersPath.cornerSize
}
// Bottom-Right
PathMove {
x: cornersPath.screenWidth - cornersPath.rightMargin
y: cornersPath.screenHeight - cornersPath.bottomMargin
}
PathLine {
relativeX: -cornersPath.cornerSize
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: -(cornersPath.cornerSize - cornersPath.cornerRadius)
}
PathArc {
relativeX: cornersPath.cornerRadius
relativeY: -cornersPath.cornerRadius
radiusX: cornersPath.cornerRadius
radiusY: cornersPath.cornerRadius
direction: PathArc.Counterclockwise
}
PathLine {
relativeX: cornersPath.cornerSize - cornersPath.cornerRadius
relativeY: 0
}
PathLine {
relativeX: 0
relativeY: cornersPath.cornerSize
}
}
}
}
}

View File

@ -1,102 +0,0 @@
import QtQuick
import Qt.labs.folderlistmodel 2.15 // <--- The magic file scanner!
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import "."
import qs.modules.bar
import qs
FloatingWindow {
id: root
title: "quickshell-WallSwitcher"
visible: false
implicitWidth: 840
implicitHeight: 640
GlobalShortcut {
// This is the "Secret Password" Hyprland will use
name: "toggle-walls"
onPressed: {
// Toggle visibility!
root.visible = !root.visible;
console.log("Shortcut pressed! Switcher is now: " + (root.visible ? "Visible" : "Hidden"));
}
}
// Make it float above everything else
Text {
id: titleText
text: "Wallpapers in " + WallpaperStore.wallDir
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
font.pixelSize: 20
topPadding: 20
bottomPadding: 10
font.family: Appearance.font
color: Colors.foreground
}
color: Colors.background // Dark background
// 1. The File Scanner
FolderListModel {
id: folderModel
folder: "file:///home/lucy/.walls/" // <--- Your stash!
nameFilters: ["*.png", "*.jpg", "*.jpeg"]
showDirs: false
}
// 2. The Grid Display
GridView {
anchors.top: titleText.bottom // Sit below the title!
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.margins: 20
cellWidth: 200
cellHeight: 100
clip: true
model: folderModel
delegate: Item {
width: 200
height: 100
Image {
width: 180
height: 90
anchors.centerIn: parent
// "fileUrl" is provided by FolderListModel
source: fileUrl
// IMPORTANT: Downscale the image for the thumbnail!
// If you don't do this, loading 50 4K images will eat your RAM
// faster than Chrome eats memory! 🙀
sourceSize.width: 140
sourceSize.height: 90
fillMode: Image.PreserveAspectCrop
}
MouseArea {
Process {
id: generateScheme
property string cleanPath: fileUrl.toString().replace("file://", "")
command: ["wallust", "run", cleanPath]
}
anchors.fill: parent
onClicked: {
let cleanPath = fileUrl.toString().replace("file://", "");
// Update the Singleton!
WallpaperStore.currentWall = fileUrl.toString();
//generateScheme.startDetached();
console.log(generateScheme.stdout);
}
}
}
}
}

View File

@ -1,81 +0,0 @@
import QtQuick
import QtQuick.Controls // <--- Needed for StackView
import Quickshell
import Quickshell.Wayland
WlrLayershell {
id: root
layer: WlrLayer.Background
keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
bottom: true
left: true
right: true
}
// We need to accept the screen from Variants
property var screen
property var modelData
// 1. The StackView manages the images
StackView {
id: wallStack
width: parent.width
height: parent.height
// 2. Define what a "Wallpaper" looks like
Component {
id: wallComponent
Image {
fillMode: Image.PreserveAspectCrop
width: StackView.view.width
height: StackView.view.height
asynchronous: true // VERY IMPORTANT: Prevents lag while loading!
}
}
// 3. Load the initial wallpaper immediately (No animation on boot)
initialItem: wallComponent.createObject(wallStack, {
"source": WallpaperStore.currentWall
})
// 4. THE ANIMATIONS 🎬
// When a new wall replaces the old one:
// New One: Fades In (0 -> 1)
replaceEnter: Transition {
NumberAnimation {
property: "x"
from: wallStack.width
to: 0
duration: 800 // Slower = Smoother
easing.type: Easing.OutQuad
}
}
// Old One: Fades Out (1 -> 0)
replaceExit: Transition {
NumberAnimation {
property: "x"
from: 0
to: -wallStack.width
duration: 800
easing.type: Easing.OutQuad
}
}
}
// 5. The Trigger 🔫
// We listen for the singleton to change, then tell the Stack to update
Connections {
target: WallpaperStore
function onCurrentWallChanged() {
// "Replace the current item with a new wallComponent using the new source"
wallStack.replace(wallComponent, {
"source": WallpaperStore.currentWall
});
}
}
}

View File

@ -1,31 +0,0 @@
pragma Singleton
import QtQuick
import Quickshell.Io // <--- Import for FileView and JsonAdapter
QtObject {
id: store
// 1. The File Manager
property string wallDir: "~/.walls/"
property var settings: FileView {
path: "/home/lucy/.cache/quickshell_settings.json"
// Auto-save when properties change
onAdapterUpdated: writeAdapter()
// Auto-load when the file changes on disk
watchChanges: true
onFileChanged: reload()
// 2. The Magic Adapter
JsonAdapter {
id: adapter
// This property corresponds to a key in your JSON file!
property string lastWallpaper: "/home/lucy/.walls/frieren_river.jpg"
}
}
// 3. Create a helper property for the rest of your app to use
// This keeps the "WallpaperStore.currentWall" name working!
property alias currentWall: adapter.lastWallpaper
}

View File

@ -1,5 +0,0 @@
singleton WallpaperStore 1.0 WallpaperStore.qml
Wallpaper 1.0 Wallpaper.qml
WallSwitcher 1.0 WallSwitcher.qml
Overlay 1.0 Overlay.qml
ScreenCorners 1.0 ScreenCorners.qml

View File

@ -1,23 +1,4 @@
//@ pragma UseQApplication //@ pragma UseQApplication
//pragma ComponentBehavior: Bound import qs.modules.bar
pragma ComponentBehavior: Bound
import Quickshell
import "./modules/bar/"
import "./modules/wallpaper/"
import "./modules/notifications/"
Scope { Bar {}
WallSwitcher {}
Variants {
id: wallVariants
model: Quickshell.screens
delegate: Wallpaper {}
}
Variants {
id: barVariants
model: Quickshell.screens
delegate: Bar {}
}
NotiPopup {}
Overlay {}
}