Compare commits
No commits in common. "main" and "master" have entirely different histories.
18
#shell.qml#
Normal file
18
#shell.qml#
Normal file
@ -0,0 +1,18 @@
|
||||
//@ pragma UseQApplication
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.modules.Bar
|
||||
import qs.modules.ipc
|
||||
import qs.modules.wallpaper
|
||||
import qs.modules.widgets.wallpicker
|
||||
import qs.modules.notifications
|
||||
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
Bar {}
|
||||
Ipc {}
|
||||
Wallpaper {}
|
||||
WallPicker {}
|
||||
Notification {}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
./Colors.qml
|
||||
45
Colors.qml
45
Colors.qml
@ -1,29 +1,26 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
Singleton {
|
||||
id: customColors
|
||||
// Core Backgrounds
|
||||
readonly property color background: "#24273A"
|
||||
readonly property color foreground: "#CAD3F5"
|
||||
readonly property color cursor: "#CAD3F5"
|
||||
QtObject {
|
||||
// --- The Backgrounds (Darkest to Lightest) ---
|
||||
readonly property string base00: "#1e1e2e" // Default Background
|
||||
readonly property string base01: "#181825" // Lighter Background (Status bars, panels)
|
||||
readonly property string base02: "#313244" // Selection Background
|
||||
readonly property string base03: "#45475a" // Comments, Invisibles, line highlighting
|
||||
|
||||
// The 16 Colors of the Apocalypse
|
||||
readonly property color color0: "#494D64"
|
||||
readonly property color color1: "#ED8796"
|
||||
readonly property color color2: "#A6DA95"
|
||||
readonly property color color3: "#EED49F"
|
||||
readonly property color color4: "#8AADF4"
|
||||
readonly property color color5: "#F5BDE6"
|
||||
readonly property color color6: "#8BD5CA"
|
||||
readonly property color color7: "#B8C0E0"
|
||||
readonly property color color8: "#5B6078"
|
||||
readonly property color color9: "#ED8796"
|
||||
readonly property color color10: "#A6DA95"
|
||||
readonly property color color11: "#EED49F"
|
||||
readonly property color color12: "#8AADF4"
|
||||
readonly property color color13: "#F5BDE6"
|
||||
readonly property color color14: "#8BD5CA"
|
||||
readonly property color color15: "#A5ADCB"
|
||||
// --- The Foregrounds (Darkest to Lightest) ---
|
||||
readonly property string base04: "#585b70" // Dark Foreground (Used for status bars)
|
||||
readonly property string base05: "#cdd6f4" // Default Foreground, Caret
|
||||
readonly property string base06: "#f5e0dc" // Light Foreground (Rarely used)
|
||||
readonly property string base07: "#b4befe" // Lightest Foreground
|
||||
|
||||
// --- The Accent Colors ---
|
||||
readonly property string base08: "#f38ba8" // Red (Variables, errors)
|
||||
readonly property string base09: "#fab387" // Orange (Integers, booleans, constants)
|
||||
readonly property string base0A: "#f9e2af" // Yellow (Classes, search text bg, warnings)
|
||||
readonly property string base0B: "#a6e3a1" // Green (Strings, success states)
|
||||
readonly property string base0C: "#94e2d5" // Cyan (Support, regex, escape chars)
|
||||
readonly property string base0D: "#89b4fa" // Blue (Functions, methods, headings)
|
||||
readonly property string base0E: "#cba6f7" // Purple/Mauve (Keywords, storage, selectors)
|
||||
readonly property string base0F: "#f2cdcd" // Brown/Flamingo (Deprecated, embedded tags)
|
||||
}
|
||||
|
||||
16
Icons.qml
16
Icons.qml
@ -1,16 +0,0 @@
|
||||
import QtQuick
|
||||
import "./modules/settings/"
|
||||
|
||||
Text {
|
||||
property real fill
|
||||
font.family: "Material Symbols Rounded"
|
||||
property int grade: 20
|
||||
color: Colors.foreground
|
||||
font.pixelSize: 14
|
||||
font.variableAxes: ({
|
||||
FILL: fill.toFixed(1),
|
||||
GRAD: grade,
|
||||
opsz: Settings.fontSize,
|
||||
wght: 500
|
||||
})
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"currentWall": "file:///home/lucy/.walls/faris.jpg",
|
||||
"font": "MonaSpiceXe Nerd Font Propo",
|
||||
"fontSize": 13,
|
||||
"wallDir": "/home/lucy/.walls/"
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
67
modules/Bar/Bar.qml
Normal file
67
modules/Bar/Bar.qml
Normal file
@ -0,0 +1,67 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Layouts
|
||||
import qs.settings
|
||||
import qs
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
delegate: PanelWindow {
|
||||
id: root
|
||||
required property ShellScreen modelData
|
||||
aboveWindows: true
|
||||
screen: modelData
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
margins {
|
||||
top: Settings.config.floating ? Settings.config.margins : 0
|
||||
left: Settings.config.floating ? Settings.config.margins : 0
|
||||
right: Settings.config.floating ? Settings.config.margins : 0
|
||||
}
|
||||
implicitHeight: Settings.config.barHeight
|
||||
color: "transparent"
|
||||
Rectangle {
|
||||
id: bar
|
||||
anchors.fill: parent
|
||||
radius: Settings.config.floating ? Settings.config.rounding * 2 : 0
|
||||
color: Colors.base00
|
||||
RowLayout {
|
||||
id: left
|
||||
spacing: Settings.config.barSpacing
|
||||
anchors {
|
||||
left: parent.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
Ws {
|
||||
barScreen: root.modelData
|
||||
}
|
||||
MPris {}
|
||||
Title {}
|
||||
}
|
||||
RowLayout {
|
||||
id: center
|
||||
spacing: Settings.config.barSpacing
|
||||
anchors {
|
||||
centerIn: parent
|
||||
}
|
||||
Clock {}
|
||||
}
|
||||
RowLayout {
|
||||
id: right
|
||||
spacing: Settings.config.barSpacing
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
rightMargin: Settings.config.floating ? Settings.config.barmargins : 10
|
||||
}
|
||||
StatusIcons {}
|
||||
Tray {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
modules/Bar/Clock.qml
Normal file
39
modules/Bar/Clock.qml
Normal file
@ -0,0 +1,39 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs.settings
|
||||
import qs
|
||||
import qs.widgets
|
||||
|
||||
WrapperRectangle {
|
||||
id: root
|
||||
margin: Settings.config.barmargins
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 4
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
color: Colors.base02
|
||||
radius: Settings.config.rounding
|
||||
implicitWidth: clockText.implicitWidth + 20
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
child: Item {
|
||||
id: textWrap
|
||||
|
||||
CText {
|
||||
id: clockText
|
||||
text: Qt.formatDateTime(clock.date, "hh:mm")
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
}
|
||||
55
modules/Bar/MPris.qml
Normal file
55
modules/Bar/MPris.qml
Normal file
@ -0,0 +1,55 @@
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
WrapperRectangle {
|
||||
id: root
|
||||
margin: Settings.config.barmargins
|
||||
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 4
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
color: Colors.base02
|
||||
implicitWidth: songLayout.implicitWidth + 20
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
radius: Settings.config.rounding
|
||||
property var spotify: root.getSpotify()
|
||||
visible: getSpotify() == null ? false : true
|
||||
|
||||
function getSpotify() {
|
||||
for (var i = 0; i < Mpris.players.values.length; i++) {
|
||||
if (Mpris.players.values[i].identity == "Spotify" || Mpris.players.values[i] == "spotify") {
|
||||
return Mpris.players.values[i];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
child: Item {
|
||||
RowLayout {
|
||||
id: songLayout
|
||||
anchors.centerIn: parent
|
||||
CText {
|
||||
id: playingSong
|
||||
Layout.maximumWidth: 400
|
||||
text: root.spotify == null ? "" : root.spotify.trackTitle + " - " + root.spotify.trackArtist
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
modules/Bar/NiriWs.qml
Normal file
80
modules/Bar/NiriWs.qml
Normal file
@ -0,0 +1,80 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Niri
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
Rectangle {
|
||||
id: wsWrap
|
||||
Niri {
|
||||
id: niri
|
||||
Component.onCompleted: connect()
|
||||
|
||||
onConnected: console.log("Connected to niri")
|
||||
onErrorOccurred: function (error) {
|
||||
console.error("Error:", error);
|
||||
}
|
||||
}
|
||||
required property ShellScreen barScreen
|
||||
color: "transparent"
|
||||
radius: Settings.config.rounding
|
||||
implicitWidth: wsLayout.implicitWidth + 6
|
||||
implicitHeight: wsLayout.implicitHeight + 6
|
||||
RowLayout {
|
||||
id: wsLayout
|
||||
spacing: 6
|
||||
anchors.centerIn: parent
|
||||
Repeater {
|
||||
id: wsRep
|
||||
model: niri.workspaces
|
||||
delegate: Rectangle {
|
||||
id: wsRect
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: Colors.base01
|
||||
radius: 8
|
||||
verticalOffset: 1
|
||||
horizontalOffset: 1
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
implicitWidth: modelData.isFocused ? Settings.config.barHeight * 1.5 : Settings.config.barHeight / 2 + 10
|
||||
implicitHeight: Settings.config.barHeight / 1.5
|
||||
visible: modelData.id < 0 ? false : modelData.output == wsWrap.barScreen.name
|
||||
required property var modelData
|
||||
color: modelData.isFocused ? Colors.base0D : Colors.base02
|
||||
radius: Settings.config.rounding
|
||||
CText {
|
||||
id: wsText
|
||||
anchors.centerIn: parent
|
||||
text: wsRect.modelData.index
|
||||
color: parent.modelData.isFocused ? Colors.base01 : Colors.base07
|
||||
opacity: parent.modelData.isFocused ? 1 : 0.5
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
easing {
|
||||
type: Easing.OutBack
|
||||
overshoot: 2
|
||||
}
|
||||
duration: 400
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: mouseHandler
|
||||
acceptedButtons: Qt.LeftButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
niri.focusWorkspace(wsRect.modelData.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
modules/Bar/StatusIcons.qml
Normal file
103
modules/Bar/StatusIcons.qml
Normal file
@ -0,0 +1,103 @@
|
||||
import Quickshell.Services.UPower
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
WrapperRectangle {
|
||||
id: root
|
||||
margin: Settings.config.barmargins
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 4
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
color: Colors.base02
|
||||
implicitWidth: iconLayout.implicitWidth + 14
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
radius: Settings.config.rounding
|
||||
property var battery: UPower.displayDevice.isLaptopBattery ? UPower.displayDevice : null
|
||||
property var percentage: UPower.displayDevice.isLaptopBattery ? UPower.displayDevice.percentage : null
|
||||
property bool charging: UPower.displayDevice.isLaptopBattery ? UPower.displayDevice.state == UPowerDeviceState.Charging : null
|
||||
property bool hasBattery: UPower.displayDevice.isLaptopBattery
|
||||
property var audio: Pipewire.ready ? Pipewire.defaultAudioSink : ""
|
||||
property var audioPercentage: Pipewire.ready ? Pipewire.defaultAudioSink.audio.volume : 0
|
||||
property bool audioMute: Pipewire.ready ? Pipewire.defaultAudioSink.audio.muted : false
|
||||
|
||||
function getBatteryIcon() {
|
||||
if (charging) {
|
||||
return "\uf250";
|
||||
}
|
||||
if (percentage <= 0.12) {
|
||||
return "\uf251";
|
||||
}
|
||||
if (percentage <= 0.24) {
|
||||
return "\uf257";
|
||||
}
|
||||
if (percentage <= 0.36) {
|
||||
return "\uf256";
|
||||
}
|
||||
if (percentage <= 0.48) {
|
||||
return "\uf255";
|
||||
}
|
||||
if (percentage <= 0.60) {
|
||||
return "\uf254";
|
||||
}
|
||||
if (percentage <= 0.72) {
|
||||
return "\uf253";
|
||||
}
|
||||
if (percentage <= 0.84) {
|
||||
return "\uf252";
|
||||
}
|
||||
if (percentage >= 0.84) {
|
||||
return "\uf24f";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
function getVolumeIcon() {
|
||||
if (audioMute) {
|
||||
return "\ue04f";
|
||||
}
|
||||
if (audioPercentage <= 0.33) {
|
||||
return "\ue04e";
|
||||
}
|
||||
if (audioPercentage <= 0.66) {
|
||||
return "\ue04d";
|
||||
}
|
||||
if (audioPercentage >= 0.66) {
|
||||
return "\ue050";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
child: Item {
|
||||
RowLayout {
|
||||
id: iconLayout
|
||||
anchors.centerIn: parent
|
||||
CIcon {
|
||||
id: batteryIcon
|
||||
Layout.leftMargin: 2
|
||||
visible: root.hasBattery
|
||||
text: root.getBatteryIcon()
|
||||
}
|
||||
CIcon {
|
||||
id: volIcon
|
||||
text: root.getVolumeIcon()
|
||||
PwObjectTracker {
|
||||
id: audioTracker
|
||||
objects: Pipewire.ready ? Pipewire.defaultAudioSink : []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
43
modules/Bar/Title.qml
Normal file
43
modules/Bar/Title.qml
Normal file
@ -0,0 +1,43 @@
|
||||
import Quickshell
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.widgets
|
||||
import qs.settings
|
||||
|
||||
WrapperRectangle {
|
||||
id: root
|
||||
margin: Settings.config.barmargins
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 4
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
property var activeWindow: ToplevelManager.activeToplevel
|
||||
property bool active: activeWindow ? activeWindow.activated ? true : false : false
|
||||
radius: Settings.config.rounding
|
||||
color: active ? Colors.base02 : "transparent"
|
||||
implicitWidth: titleText.width + 40
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
child: Item {
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
CText {
|
||||
id: titleText
|
||||
Layout.maximumWidth: 250
|
||||
text: root.activeWindow ? root.activeWindow.activated ? root.activeWindow.title : "" : ""
|
||||
elide: Text.ElideRight // Allows wrapping
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
39
modules/Bar/Tray.qml
Normal file
39
modules/Bar/Tray.qml
Normal file
@ -0,0 +1,39 @@
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import qs
|
||||
import qs.settings
|
||||
import QtQuick.Layouts
|
||||
|
||||
WrapperRectangle {
|
||||
id: root
|
||||
margin: Settings.config.barmargins
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 4
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 18
|
||||
}
|
||||
}
|
||||
implicitWidth: trayRow.implicitWidth + 14
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
visible: trayRep.count > 0
|
||||
color: Colors.base02
|
||||
radius: Settings.config.rounding
|
||||
child: Item {
|
||||
RowLayout {
|
||||
id: trayRow
|
||||
anchors.centerIn: parent
|
||||
Repeater {
|
||||
id: trayRep
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
|
||||
model: SystemTray.items
|
||||
delegate: TrayItem {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,16 +2,24 @@ import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import qs
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
property var bar: root.QsWindow.window
|
||||
required property SystemTrayItem modelData
|
||||
|
||||
implicitWidth: trayIcon.implicitWidth
|
||||
implicitHeight: trayIcon.implicitHeight
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
implicitWidth: 16
|
||||
implicitHeight: 16
|
||||
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
implicitSize: 16
|
||||
source: parent.modelData.icon
|
||||
}
|
||||
QsMenuAnchor {
|
||||
id: menu
|
||||
menu: root.modelData.hasMenu ? root.modelData.menu : null
|
||||
anchor.item: root
|
||||
}
|
||||
onClicked: event => {
|
||||
if (event.button === Qt.LeftButton) {
|
||||
modelData.activate();
|
||||
@ -19,18 +27,4 @@ MouseArea {
|
||||
menu.open();
|
||||
}
|
||||
}
|
||||
|
||||
QsMenuAnchor {
|
||||
id: menu
|
||||
menu: root.modelData.menu
|
||||
anchor.item: root
|
||||
}
|
||||
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
width: parent.implicitWidth
|
||||
height: parent.implicitHeight
|
||||
source: root.modelData.icon
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
}
|
||||
76
modules/Bar/Ws.qml
Normal file
76
modules/Bar/Ws.qml
Normal file
@ -0,0 +1,76 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
WrapperRectangle {
|
||||
id: wsWrap
|
||||
margin: Settings.config.barmargins
|
||||
leftMargin: margin * 2
|
||||
required property ShellScreen barScreen
|
||||
color: "transparent"
|
||||
radius: Settings.config.rounding
|
||||
implicitWidth: wsLayout.implicitWidth + 6
|
||||
implicitHeight: Settings.config.barHeight - margin * 2
|
||||
child: Item {
|
||||
RowLayout {
|
||||
id: wsLayout
|
||||
spacing: 6
|
||||
anchors.centerIn: parent
|
||||
Repeater {
|
||||
id: wsRep
|
||||
model: Hyprland.workspaces
|
||||
delegate: Rectangle {
|
||||
id: wsRect
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
radius: 0
|
||||
verticalOffset: 2
|
||||
horizontalOffset: 2
|
||||
samples: 16
|
||||
}
|
||||
}
|
||||
implicitWidth: modelData.focused ? Settings.config.barHeight * 1.5 : Settings.config.barHeight / 2 + 10
|
||||
implicitHeight: Settings.config.barHeight - wsWrap.margin * 2
|
||||
visible: modelData.id < 0 ? false : modelData.monitor?.name == wsWrap.barScreen.name
|
||||
required property var modelData
|
||||
color: modelData.focused ? Colors.base0D : Colors.base02
|
||||
radius: Settings.config.rounding
|
||||
CText {
|
||||
id: wsText
|
||||
anchors.centerIn: parent
|
||||
text: wsRect.modelData.id
|
||||
opacity: 1
|
||||
color: parent.modelData.focused ? Colors.base00 : Colors.base05
|
||||
}
|
||||
Behavior on implicitWidth {
|
||||
NumberAnimation {
|
||||
easing {
|
||||
type: Easing.OutBack
|
||||
overshoot: 2
|
||||
}
|
||||
duration: 400
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: mouseHandler
|
||||
acceptedButtons: Qt.LeftButton
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
wsRect.modelData.activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../../"
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
required property var modelData
|
||||
implicitHeight: 36
|
||||
color: Colors.background
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: leftLayout
|
||||
spacing: 40
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Clock {
|
||||
Layout.leftMargin: 30
|
||||
}
|
||||
Mpris {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: centerLayout
|
||||
anchors.centerIn: parent
|
||||
Workspaces {}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rightLayout
|
||||
spacing: 40
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
Battery {}
|
||||
Volume {}
|
||||
PowerProfiles {}
|
||||
SystemTray {
|
||||
Layout.rightMargin: 30
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,50 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick
|
||||
import "../../"
|
||||
import "../settings/"
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
visible: UPower.displayDevice.isLaptopBattery
|
||||
implicitWidth: masterLayout.implicitWidth
|
||||
height: 34
|
||||
property bool isCharging: UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
ColumnLayout {
|
||||
id: masterLayout
|
||||
anchors.centerIn: parent
|
||||
implicitWidth: botText.width
|
||||
spacing: 0
|
||||
Row {
|
||||
spacing: 5
|
||||
Text {
|
||||
id: topText
|
||||
font.weight: 900
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
text: Math.round(UPower.displayDevice.percentage * 100) + "%"
|
||||
color: Colors.foreground
|
||||
}
|
||||
IconImage {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitSize: 12
|
||||
source: Quickshell.iconPath(UPower.displayDevice.iconName)
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: botText
|
||||
property var timeToEmpty: UPower.displayDevice.timeToEmpty / 60 / 60
|
||||
property var timeToFull: UPower.displayDevice.timeToFull / 60 / 60
|
||||
property bool isCharging: UPower.displayDevice.state === UPowerDeviceState.Charging
|
||||
font.weight: 600
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
opacity: 0.7
|
||||
color: Colors.foreground
|
||||
text: isCharging ? timeToFull.toFixed(1) + "h to full" : timeToEmpty.toFixed(1) + "h left"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import QtQuick.Layouts
|
||||
import "../settings/"
|
||||
import "../../"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: clockLayout.implicitWidth
|
||||
implicitHeight: 35
|
||||
|
||||
ColumnLayout {
|
||||
id: clockLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
id: clockHoursText
|
||||
font.weight: 900
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
color: Colors.foreground
|
||||
|
||||
text: Qt.formatDateTime(clockHours.date, "hh:mm")
|
||||
|
||||
SystemClock {
|
||||
id: clockHours
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: clockDateText
|
||||
font.weight: 900
|
||||
opacity: 0.7
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
color: Colors.foreground
|
||||
|
||||
text: Qt.formatDateTime(clockDate.date, "dd.MM.yy")
|
||||
|
||||
SystemClock {
|
||||
id: clockDate
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
// ⚠️ Ensure Colors is imported
|
||||
// import "../../"
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
import "../settings/"
|
||||
import "../../"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: mprisRepeater.implicitWidth + 10
|
||||
implicitHeight: 34
|
||||
|
||||
// 1. Let Repeater loop through the ObjectModel for us
|
||||
Repeater {
|
||||
id: mprisRepeater
|
||||
model: Mpris.players
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
|
||||
required property var modelData
|
||||
implicitHeight: 34
|
||||
implicitWidth: delegateLayout.implicitWidth
|
||||
MouseArea {
|
||||
id: playbackControl
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: mouse => {
|
||||
if (mouse.button == Qt.LeftButton) {
|
||||
console.log("Left button press");
|
||||
}
|
||||
if (mouse.button == Qt.RightButton) {
|
||||
parent.modelData.togglePlaying();
|
||||
}
|
||||
}
|
||||
onDoubleClicked: mouse => {
|
||||
if (mouse.button == Qt.LeftButton) {
|
||||
parent.modelData.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: delegateLayout
|
||||
anchors.centerIn: parent
|
||||
// 2. 🕵️♀️ FILTER LOGIC
|
||||
// Check if this specific player is Spotify.
|
||||
// We verify 'modelData' exists and check the name.
|
||||
property bool isSpotify: delegateItem.modelData && delegateItem.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
|
||||
|
||||
property string title: delegateItem.modelData.trackTitle
|
||||
property string artist: delegateItem.modelData.trackArtist
|
||||
property string artUrl: delegateItem.modelData.trackArtUrl
|
||||
property bool isPlaying: delegateItem.modelData.isPlaying
|
||||
|
||||
spacing: 10
|
||||
|
||||
// 🖼️ ALBUM ART
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
radius: 20
|
||||
|
||||
IconImage {
|
||||
source: delegateLayout.artUrl // Access property from delegate
|
||||
asynchronous: true
|
||||
implicitSize: root.implicitHeight * 0.6
|
||||
}
|
||||
}
|
||||
|
||||
// 📝 TEXT INFO
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
visible: parent.visible
|
||||
|
||||
Text {
|
||||
text: delegateLayout.title
|
||||
color: Colors.foreground
|
||||
font.bold: true
|
||||
font.pixelSize: Settings.fontSize
|
||||
font.family: Settings.font
|
||||
elide: Text.ElideRight
|
||||
Layout.preferredWidth: implicitWidth
|
||||
}
|
||||
|
||||
Text {
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
font.family: Settings.font
|
||||
text: delegateLayout.artist
|
||||
color: Colors.foreground
|
||||
opacity: 0.7
|
||||
Layout.preferredWidth: implicitWidth
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.UPower
|
||||
import QtQuick.Layouts
|
||||
import "../settings/"
|
||||
import "../../"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: powerLayout.implicitWidth
|
||||
implicitHeight: 34
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
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];
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
id: powerLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
Text {
|
||||
id: powerProfile
|
||||
text: PowerProfile.toString(PowerProfiles.profile)
|
||||
font.weight: 900
|
||||
color: Colors.foreground
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
}
|
||||
Text {
|
||||
text: "Profile"
|
||||
font.weight: 900
|
||||
color: Colors.foreground
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
|
||||
Item {
|
||||
id: root
|
||||
clip: true
|
||||
|
||||
implicitWidth: layout.implicitWidth < 0 ? 0 : layout.implicitWidth
|
||||
implicitHeight: 34
|
||||
|
||||
visible: layout.children.length > 0
|
||||
|
||||
Row {
|
||||
id: layout
|
||||
anchors.centerIn: parent
|
||||
spacing: 10 // Spacing between icons
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
delegate: TrayItem {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Widgets
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "../../"
|
||||
import "../settings/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: styleLayout.implicitWidth
|
||||
height: 34
|
||||
property var sink: Pipewire.defaultAudioSink
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
pavu.startDetached();
|
||||
}
|
||||
}
|
||||
}
|
||||
Process {
|
||||
id: pavu
|
||||
command: ["pavucontrol"] // The command and args list
|
||||
|
||||
}
|
||||
|
||||
// Logic to pick the correct icon name
|
||||
function getVolumeIcon() {
|
||||
// Safety check: if Pipewire is dead or sink is missing
|
||||
if (!sink)
|
||||
return "audio-volume-muted-symbolic";
|
||||
|
||||
// If muted, show the hush icon
|
||||
if (sink.audio.muted)
|
||||
return "audio-volume-muted-symbolic";
|
||||
|
||||
// Volume is usually 0.0 to 1.0 (0% to 100%)
|
||||
const vol = sink.audio.volume;
|
||||
|
||||
if (vol <= 0.25)
|
||||
return "audio-volume-low-symbolic";
|
||||
if (vol < 0.75)
|
||||
return "audio-volume-medium-symbolic";
|
||||
if (vol <= 1.00)
|
||||
return "audio-volume-high-symbolic";
|
||||
|
||||
// If it's loud, prepare the ears!
|
||||
return "audio-volume-high-danger-symbolic";
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: styleLayout
|
||||
anchors.centerIn: parent
|
||||
spacing: 0
|
||||
implicitWidth: topText.width
|
||||
Row {
|
||||
|
||||
spacing: 5
|
||||
Text {
|
||||
id: topText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
PwObjectTracker {
|
||||
|
||||
objects: Pipewire.ready ? root.sink : []
|
||||
}
|
||||
font.weight: 900
|
||||
color: Colors.foreground
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
text: Pipewire.ready ? root.sink.audio.volume.toFixed(2) + "%" : "0%"
|
||||
onTextChanged: console.log(Quickshell.iconPath)
|
||||
}
|
||||
IconImage {
|
||||
id: icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
implicitSize: 12
|
||||
source: Quickshell.iconPath(root.getVolumeIcon())
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: botText
|
||||
font.weight: 900
|
||||
color: Colors.foreground
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
opacity: 0.7
|
||||
text: Pipewire.ready ? Pipewire.defaultAudioSink.nickname : "failure"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
import "../../"
|
||||
import "../settings/"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: workspaceRow.implicitWidth
|
||||
height: 30
|
||||
Row {
|
||||
id: workspaceRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 10 // Slightly increase spacing between workspace buttons
|
||||
|
||||
Repeater {
|
||||
id: wsRepeater
|
||||
model: Hyprland.workspaces
|
||||
anchors.centerIn: parent
|
||||
Rectangle {
|
||||
id: workspaceNumber
|
||||
required property var modelData
|
||||
width: 16
|
||||
height: 16
|
||||
radius: 20
|
||||
color: modelData.active ? Colors.foreground : "transparent"
|
||||
|
||||
Text {
|
||||
font.weight: 900
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
anchors.centerIn: workspaceNumber
|
||||
text: parent.modelData.id
|
||||
color: parent.modelData.active ? Colors.background : Colors.foreground // Set contrasting color for workspace number
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
parent.modelData.activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
modules/ipc/Ipc.qml
Normal file
14
modules/ipc/Ipc.qml
Normal file
@ -0,0 +1,14 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.settings
|
||||
|
||||
Item {
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
function toggleWall() {
|
||||
Settings.config.wallswitchershown = !Settings.config.wallswitchershown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,172 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Hyprland
|
||||
import "."
|
||||
import "../../"
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Widgets
|
||||
import "../settings/"
|
||||
|
||||
WlrLayershell {
|
||||
id: root
|
||||
required property var modelData
|
||||
screen: {
|
||||
// Iterate through all connected Quickshell screens
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
let screenCandidate = Quickshell.screens[i];
|
||||
|
||||
// Ask: "Is this screen the one Hyprland is currently focusing?"
|
||||
if (Hyprland.monitorFor(screenCandidate) === Hyprland.focusedMonitor) {
|
||||
return screenCandidate;
|
||||
}
|
||||
}
|
||||
return null; // Fallback (should rarely happen)
|
||||
}
|
||||
|
||||
// 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: 36
|
||||
right: 00
|
||||
}
|
||||
|
||||
implicitWidth: 300
|
||||
implicitHeight: notifList.contentHeight + 20
|
||||
Behavior on implicitHeight {
|
||||
NumberAnimation {
|
||||
duration: 300
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
|
||||
// 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.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 0
|
||||
// Use 'spacing' to put gaps between notifications
|
||||
spacing: 00
|
||||
height: contentHeight
|
||||
|
||||
model: NotifServer.trackedNotifications
|
||||
delegate: Item {
|
||||
id: notifyItem
|
||||
required property var index
|
||||
readonly property bool isLast: index === (ListView.view.count - 1)
|
||||
implicitWidth: ListView.view.width
|
||||
implicitHeight: 85 // Fixed height is usually better for icon layouts
|
||||
height: implicitHeight
|
||||
|
||||
required property var modelData
|
||||
Timer {
|
||||
id: timout
|
||||
interval: 3000
|
||||
running: true
|
||||
onTriggered: notifyItem.modelData.dismiss()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Colors.background
|
||||
bottomLeftRadius: notifyItem.isLast ? 20 : 0
|
||||
border.color: Colors.color5
|
||||
border.width: 0
|
||||
|
||||
// 2. Use RowLayout to put Image | Text side-by-side
|
||||
RowLayout {
|
||||
id: fullLayout
|
||||
anchors.margins: 10
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
// 🖼️ THE IMAGE ON THE LEFT
|
||||
ClippingWrapperRectangle {
|
||||
radius: 10
|
||||
implicitWidth: 64
|
||||
implicitHeight: 64
|
||||
visible: notifyItem.modelData.image !== ""
|
||||
IconImage {
|
||||
|
||||
// Use the image if available, otherwise hide this space?
|
||||
// Or you could use an icon fallback.
|
||||
source: notifyItem.modelData.image
|
||||
|
||||
// Hide if no image exists so text takes full width
|
||||
visible: notifyItem.modelData.image !== ""
|
||||
|
||||
// Fixed size for consistency
|
||||
implicitSize: 30
|
||||
|
||||
// Crop it nicely so it doesn't stretch
|
||||
|
||||
// Optional: Cache it for performance
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
|
||||
// 📝 THE TEXT ON THE RIGHT
|
||||
ColumnLayout {
|
||||
id: textLayout
|
||||
// Take up all remaining width
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter // Center vertically
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
text: notifyItem.modelData.summary
|
||||
color: Colors.foreground
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Text {
|
||||
text: notifyItem.modelData.body
|
||||
color: Colors.foreground
|
||||
|
||||
// Limit to 2 lines
|
||||
font.family: Settings.font
|
||||
font.pixelSize: Settings.fontSize - 2
|
||||
maximumLineCount: 3
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
modules/notifications/Notification.qml
Normal file
73
modules/notifications/Notification.qml
Normal file
@ -0,0 +1,73 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.settings
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
delegate: WlrLayershell {
|
||||
id: root
|
||||
required property var modelData
|
||||
screen: modelData
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
right: true
|
||||
bottom: true
|
||||
}
|
||||
margins {
|
||||
top: Settings.config.floating ? Settings.config.barHeight + Settings.config.margins + 10 : Settings.config.barHeight + 10
|
||||
right: 10
|
||||
left: 10
|
||||
}
|
||||
|
||||
mask: Region {
|
||||
item: notifList
|
||||
}
|
||||
implicitHeight: notifList.contentHeight + 20
|
||||
implicitWidth: modelData.width / 6
|
||||
|
||||
layer: WlrLayer.Overlay
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
|
||||
color: "transparent"
|
||||
|
||||
ListView {
|
||||
id: notifList
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: 10
|
||||
height: contentHeight
|
||||
|
||||
model: NotiServer.trackedNotifications
|
||||
delegate: NotificationCard {}
|
||||
add: Transition {
|
||||
NumberAnimation {
|
||||
property: "x"
|
||||
from: notifList.width
|
||||
to: 0
|
||||
duration: 400
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
remove: Transition {
|
||||
NumberAnimation {
|
||||
property: "x"
|
||||
from: 0
|
||||
to: notifList.width
|
||||
duration: 400
|
||||
easing.type: Easing.OutExpo
|
||||
}
|
||||
}
|
||||
|
||||
move: Transition {
|
||||
NumberAnimation {
|
||||
properties: "y"
|
||||
duration: 300
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
99
modules/notifications/NotificationCard.qml
Normal file
99
modules/notifications/NotificationCard.qml
Normal file
@ -0,0 +1,99 @@
|
||||
import QtQuick
|
||||
import qs.settings
|
||||
import QtQuick.Layouts
|
||||
import qs
|
||||
import Quickshell
|
||||
import qs.widgets
|
||||
import Quickshell.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: notifyItem
|
||||
required property var modelData
|
||||
implicitWidth: ListView.view ? ListView.view.width : 500
|
||||
implicitHeight: fullLayout.implicitHeight + 40
|
||||
color: dismissArea.containsMouse ? Colors.base02 : Colors.base00
|
||||
radius: Settings.config.rounding
|
||||
border.width: 2
|
||||
border.color: Colors.base0D
|
||||
Timer {
|
||||
id: dismissTimer
|
||||
interval: 5000
|
||||
running: true
|
||||
onTriggered: notifyItem.modelData.expire()
|
||||
}
|
||||
RowLayout {
|
||||
id: fullLayout
|
||||
anchors.margins: 20
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
ColumnLayout {
|
||||
id: textLayout
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 0
|
||||
|
||||
// New RowLayout to hold the Icon and App Name together
|
||||
RowLayout {
|
||||
id: iconTextLayout
|
||||
spacing: 8
|
||||
|
||||
ClippingWrapperRectangle {
|
||||
id: notiIconWrapper
|
||||
radius: notifyItem.radius - notifyItem.radius / 3
|
||||
implicitWidth: notiIcon.implicitSize
|
||||
implicitHeight: notiIcon.implicitSize
|
||||
color: "transparent"
|
||||
|
||||
child: IconImage {
|
||||
id: notiIcon
|
||||
// Keep your existing source logic
|
||||
source: notifyItem.modelData.image !== "" ? notifyItem.modelData.image : Quickshell.iconPath("/usr/share/icons/Papirus/24x24/panel/notifications.svg")
|
||||
implicitSize: 22 // Slightly smaller to match text height
|
||||
asynchronous: true
|
||||
}
|
||||
}
|
||||
|
||||
CText {
|
||||
id: appName
|
||||
text: notifyItem.modelData.appName
|
||||
opacity: 0.5
|
||||
font.pixelSize: 10
|
||||
}
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.leftMargin: notiIcon.implicitWidth + iconTextLayout.spacing
|
||||
CText {
|
||||
id: summary
|
||||
text: notifyItem.modelData.summary
|
||||
font.bold: true
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 5
|
||||
}
|
||||
|
||||
CText {
|
||||
text: notifyItem.modelData.body
|
||||
font.pixelSize: Settings.config.fontSize - 2
|
||||
maximumLineCount: 1
|
||||
opacity: 0.3
|
||||
wrapMode: Text.WordWrap
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dismissArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onClicked: notifyItem.modelData.dismiss()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
singleton NotifServer 1.0 NotifServer.qml
|
||||
NotiPopup 1.0 NotiPopup.qml
|
||||
@ -1,36 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
property alias currentWall: jsonAdapter.currentWall
|
||||
property alias font: jsonAdapter.font
|
||||
property alias fontSize: jsonAdapter.fontSize
|
||||
property alias wallDir: jsonAdapter.wallDir
|
||||
onCurrentWallChanged: settingsView.writeAdapter()
|
||||
onWallDirChanged: settingsView.writeAdapter()
|
||||
onFontChanged: {
|
||||
Quickshell.reload();
|
||||
settingsView.writeAdapter();
|
||||
}
|
||||
onFontSizeChanged: settingsView.writeAdapter()
|
||||
|
||||
FileView {
|
||||
id: settingsView
|
||||
path: "/home/lucy/.config/quickshell/modules/settings/config.json"
|
||||
|
||||
watchChanges: true
|
||||
onAdapterChanged: reload()
|
||||
onAdapterUpdated: reload()
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: jsonAdapter
|
||||
property string currentWall: ""
|
||||
property string wallDir: "/home/lucy/.walls/"
|
||||
property string font: "Google Sans Code"
|
||||
property real fontSize: 14
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
{
|
||||
"currentWall": "file:///home/lucy/.walls/lain_room.png",
|
||||
"font": "Google Sans Code",
|
||||
"fontSize": 14,
|
||||
"wallDir": "/home/lucy/.walls/"
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
singleton Settings 1.0 Settings.qml
|
||||
51
modules/wallpaper/DesktopClock.qml
Normal file
51
modules/wallpaper/DesktopClock.qml
Normal file
@ -0,0 +1,51 @@
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property int gaps: 10
|
||||
implicitWidth: wrapper.width + gaps
|
||||
implicitHeight: wrapper.height + gaps
|
||||
ClippingWrapperRectangle {
|
||||
id: wrapper
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
horizontalOffset: 7
|
||||
verticalOffset: 8
|
||||
radius: 12
|
||||
samples: 14
|
||||
}
|
||||
}
|
||||
SystemClock {
|
||||
id: clock
|
||||
precision: SystemClock.Minutes
|
||||
}
|
||||
color: Colors.base01
|
||||
radius: Settings.config.rounding
|
||||
anchors.centerIn: parent
|
||||
margin: 10
|
||||
rightMargin: 15
|
||||
child: Column {
|
||||
id: dataLayout
|
||||
spacing: 0
|
||||
anchors.margins: 0
|
||||
CText {
|
||||
text: Qt.formatDateTime(clock.date, "hh:mm")
|
||||
font.pixelSize: 48
|
||||
}
|
||||
CText {
|
||||
text: Qt.formatDateTime(clock.date, "dd.MM.yy")
|
||||
opacity: 0.6
|
||||
font.pixelSize: 24
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import "../../"
|
||||
import "."
|
||||
|
||||
WlrLayershell {
|
||||
id: overlayRoot
|
||||
required property var modelData
|
||||
property var padding: 5
|
||||
property var rounding: 25
|
||||
property var hyprgaps: 5
|
||||
onPaddingChanged: {
|
||||
hyprGaps.exec(hyprGaps.command);
|
||||
console.log(hyprGaps.command);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: hyprGaps
|
||||
running: true
|
||||
property bool isZero: overlayRoot.padding === 0
|
||||
property var top: overlayRoot.hyprgaps
|
||||
property var sides: isZero ? overlayRoot.hyprgaps : overlayRoot.padding + overlayRoot.hyprgaps
|
||||
property var gaps: top + "," + sides + "," + sides + "," + sides
|
||||
command: ["hyprctl", "keyword", "general:gaps_out", gaps]
|
||||
onStarted: console.log("set gaps to ", gaps)
|
||||
}
|
||||
Process {
|
||||
id: hyprRounding
|
||||
property var rounding: overlayRoot.rounding - 4
|
||||
running: true
|
||||
command: ["hyprctl", "keyword", "decoration:rounding", rounding]
|
||||
onStarted: console.log("set rounding to ", overlayRoot.rounding)
|
||||
}
|
||||
// 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!
|
||||
ScreenPadding {
|
||||
paddingWidth: overlayRoot.padding
|
||||
paddingColor: Colors.background
|
||||
}
|
||||
ScreenCorners {
|
||||
// Adjust these to match your screen's aesthetic
|
||||
cornerRadius: overlayRoot.rounding
|
||||
margin: overlayRoot.padding
|
||||
cornerColor: Colors.background
|
||||
}
|
||||
}
|
||||
218
modules/wallpaper/PlayerWidget.qml
Normal file
218
modules/wallpaper/PlayerWidget.qml
Normal file
@ -0,0 +1,218 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls
|
||||
import qs
|
||||
import qs.settings
|
||||
import qs.widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: Colors.base00
|
||||
radius: Settings.config.rounding
|
||||
implicitWidth: 600
|
||||
implicitHeight: 200
|
||||
visible: getSpotify() != null
|
||||
layer {
|
||||
enabled: true
|
||||
effect: DropShadow {
|
||||
color: "#111111"
|
||||
horizontalOffset: 7
|
||||
verticalOffset: 8
|
||||
radius: 12
|
||||
samples: 14
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
id: hoverDetect
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onExited: title.x = 0
|
||||
}
|
||||
function getSpotify() {
|
||||
for (var i = 0; i < Mpris.players.values.length; i++) {
|
||||
if (Mpris.players.values[i].identity == "Spotify" || Mpris.players.values[i] == "spotify") {
|
||||
return Mpris.players.values[i];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
property var spotify: getSpotify() != null ? getSpotify() : null
|
||||
property var title: getSpotify() != null ? getSpotify().trackTitle : ""
|
||||
property var album: getSpotify() != null ? getSpotify().trackAlbum : ""
|
||||
property var art: getSpotify() != null ? getSpotify().trackArtUrl : ""
|
||||
property var artist: getSpotify() != null ? getSpotify().trackArtist : ""
|
||||
ClippingWrapperRectangle {
|
||||
id: songWrapper
|
||||
radius: Settings.config.rounding / 1.5
|
||||
anchors.margins: 8
|
||||
margin: 0
|
||||
anchors.fill: parent
|
||||
color: Colors.base00
|
||||
|
||||
RowLayout {
|
||||
id: songLayout
|
||||
|
||||
spacing: 10
|
||||
ClippingWrapperRectangle {
|
||||
id: coverRounder
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.maximumWidth: songCover.sourceSize.width
|
||||
radius: Settings.config.rounding / 1.5
|
||||
Image {
|
||||
id: songCover
|
||||
source: root.art
|
||||
sourceSize {
|
||||
width: 180
|
||||
height: 180
|
||||
}
|
||||
}
|
||||
}
|
||||
WrapperRectangle {
|
||||
color: Colors.base01
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignTop
|
||||
radius: Settings.config.rounding / 1.5
|
||||
margin: 20
|
||||
child: ColumnLayout {
|
||||
id: songInfo
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: 20
|
||||
Layout.topMargin: 2
|
||||
Item {
|
||||
id: titleContainer
|
||||
Layout.fillWidth: true
|
||||
x: 0
|
||||
implicitHeight: title.implicitHeight
|
||||
clip: true // Keeps the text inside this "window"
|
||||
|
||||
CText {
|
||||
id: title
|
||||
Layout.maximumWidth: 300
|
||||
text: root.title
|
||||
font.pixelSize: 30
|
||||
SequentialAnimation on x {
|
||||
id: scrollAnimation
|
||||
running: hoverDetect.containsMouse && title.width > titleContainer.width
|
||||
loops: Animation.Infinite
|
||||
|
||||
// Scroll to the end
|
||||
NumberAnimation {
|
||||
to: titleContainer.width - title.width
|
||||
duration: Math.max(2000, (title.width - titleContainer.width) * 30)
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
// Scroll back to the start
|
||||
NumberAnimation {
|
||||
to: 0
|
||||
duration: Math.max(2000, (title.width - titleContainer.width) * 30)
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
CText {
|
||||
id: album
|
||||
text: root.album + " - " + root.artist
|
||||
opacity: 0.6
|
||||
Layout.maximumWidth: 250
|
||||
Layout.alignment: Qt.AlignTop
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ProgressBar {
|
||||
id: songProgress
|
||||
FrameAnimation {
|
||||
// only emit the signal when the position is actually changing.
|
||||
running: root.spotify ? root.spotify.playbackState == MprisPlaybackState.Playing || root.visible : false
|
||||
// emit the positionChanged signal every frame.
|
||||
onTriggered: root.spotify.positionChanged()
|
||||
}
|
||||
implicitWidth: 200
|
||||
implicitHeight: 10
|
||||
from: 0
|
||||
to: root.spotify != null ? root.spotify.length : 0
|
||||
value: root.spotify != null ? root.spotify.position : 0
|
||||
background: Rectangle {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 6
|
||||
color: Colors.base02
|
||||
radius: Settings.config.rounding
|
||||
}
|
||||
contentItem: Item {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 4
|
||||
|
||||
// Progress indicator for determinate state.
|
||||
Rectangle {
|
||||
width: songProgress.visualPosition * parent.width
|
||||
height: parent.height
|
||||
radius: Settings.config.rounding
|
||||
color: Colors.base07
|
||||
visible: !songProgress.indeterminate
|
||||
}
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
id: playerControls
|
||||
Layout.maximumWidth: 200
|
||||
CIcon {
|
||||
id: previous
|
||||
text: "\ue045"
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
MouseArea {
|
||||
id: prevHandler
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.spotify.previous();
|
||||
title.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
CIcon {
|
||||
id: pause
|
||||
text: root.spotify ? root.spotify.isPlaying ? "\ue034" : "\ue037" : ""
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
MouseArea {
|
||||
id: pauseHandler
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.spotify.togglePlaying();
|
||||
title.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
CIcon {
|
||||
id: next
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: "\ue044"
|
||||
MouseArea {
|
||||
id: nextHandler
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.spotify.next();
|
||||
title.x = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
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
|
||||
property real margin
|
||||
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// 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: root.margin
|
||||
readonly property real leftMargin: root.margin
|
||||
readonly property real rightMargin: root.margin
|
||||
|
||||
readonly property real screenWidth: cornersShape.width
|
||||
readonly property real screenHeight: cornersShape.height
|
||||
|
||||
strokeWidth: -1 // No outline
|
||||
fillColor: root.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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import Quickshell
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// Make sure this fills the screen!
|
||||
anchors.fill: parent
|
||||
|
||||
property real paddingWidth // Example default
|
||||
property color paddingColor
|
||||
|
||||
Shape {
|
||||
anchors.fill: parent
|
||||
|
||||
// 1. LEFT PADDING (Your existing one, cleaned up)
|
||||
ShapePath {
|
||||
strokeWidth: root.paddingWidth * 2
|
||||
strokeColor: root.paddingColor
|
||||
fillColor: "transparent" // We only want the stroke
|
||||
|
||||
// Start at Top-Left (x=0, y=0)
|
||||
// We use '0' to align center with edge, so half is in, half is out
|
||||
startX: 0
|
||||
startY: 0
|
||||
|
||||
PathLine {
|
||||
x: root.paddingWidth - root.paddingWidth
|
||||
y: root.height + root.paddingWidth// Go to Bottom-Left
|
||||
}
|
||||
}
|
||||
|
||||
// 2. RIGHT PADDING
|
||||
ShapePath {
|
||||
strokeWidth: root.paddingWidth * 2
|
||||
strokeColor: root.paddingColor
|
||||
fillColor: "transparent"
|
||||
|
||||
// Start at Top-Right
|
||||
startX: root.width
|
||||
startY: 0
|
||||
|
||||
PathLine {
|
||||
x: root.width
|
||||
y: root.height // Go to Bottom-Right
|
||||
}
|
||||
}
|
||||
|
||||
// 3. BOTTOM PADDING (The one you wanted!)
|
||||
ShapePath {
|
||||
strokeWidth: root.paddingWidth * 2
|
||||
strokeColor: root.paddingColor
|
||||
fillColor: "transparent"
|
||||
|
||||
// Start at Bottom-Left
|
||||
startX: 0
|
||||
startY: root.height
|
||||
|
||||
PathLine {
|
||||
// Draw to Bottom-Right
|
||||
x: root.width
|
||||
y: root.height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel 2.15 // <--- The magic file scanner!
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import "../../"
|
||||
import "../settings/"
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Make it float above everything else
|
||||
Text {
|
||||
id: titleText
|
||||
text: "Wallpapers in " + Settings.wallDir.replace("file://", "")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
font.pixelSize: 20
|
||||
topPadding: 20
|
||||
bottomPadding: 10
|
||||
font.family: Settings.font
|
||||
color: Colors.foreground
|
||||
}
|
||||
|
||||
color: Colors.background // Dark background
|
||||
|
||||
// 1. The File Scanner
|
||||
FolderListModel {
|
||||
id: folderModel
|
||||
folder: "file://" + Settings.wallDir // <--- 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 {
|
||||
property string cleanPath: modelData.fileUrl.toString().replace("file://", "")
|
||||
required property var modelData
|
||||
width: 200
|
||||
height: 100
|
||||
|
||||
Image {
|
||||
id: wallImage
|
||||
width: 180
|
||||
height: 90
|
||||
anchors.centerIn: parent
|
||||
// "fileUrl" is provided by FolderListModel
|
||||
source: parent.modelData.fileUrl
|
||||
|
||||
// IMPORTANT: Downscale the image for the thumbnail!
|
||||
// If you don't do this, loading 50 4K images will eat your RAM
|
||||
sourceSize.width: 140
|
||||
sourceSize.height: 90
|
||||
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
let cleanPath = parent.modelData.fileUrl.toString().replace("file://", "");
|
||||
Settings.currentWall = parent.modelData.fileUrl.toString();
|
||||
console.log(Settings.currentWall);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,75 +1,43 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Controls // <--- Needed for StackView
|
||||
import qs
|
||||
import qs.settings
|
||||
import Quickshell.Wayland
|
||||
import "../settings/"
|
||||
|
||||
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
|
||||
required 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: wallStack.width
|
||||
height: wallStack.height
|
||||
asynchronous: true // ⚡ VERY IMPORTANT: Prevents lag while loading!
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
delegate: WlrLayershell {
|
||||
id: wallpaperShell
|
||||
exclusionMode: ExclusionMode.Ignore
|
||||
aboveWindows: false
|
||||
required property ShellScreen modelData
|
||||
layer: WlrLayer.Background
|
||||
screen: modelData
|
||||
anchors {
|
||||
top: true
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
Image {
|
||||
id: wallpaper
|
||||
source: Settings.config.currentWall ? Settings.config.currentWall : ""
|
||||
anchors.fill: parent
|
||||
}
|
||||
DesktopClock {
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
right: parent.right
|
||||
margins: 25
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
PlayerWidget {
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
margins: 35
|
||||
topMargin: Settings.config.barHeight + 35 + (Settings.config.floating ? Settings.config.margins : 0)
|
||||
}
|
||||
}
|
||||
|
||||
// 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: Settings
|
||||
|
||||
function onCurrentWallChanged() {
|
||||
wallStack.replace(wallComponent, {
|
||||
"source": Settings.currentWall
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
Wallpaper 1.0 Wallpaper.qml
|
||||
WallSwitcher 1.0 WallSwitcher.qml
|
||||
Overlay 1.0 Overlay.qml
|
||||
ScreenCorners 1.0 ScreenCorners.qml
|
||||
ScreenPadding 1.0 ScreenPadding.qml
|
||||
73
modules/widgets/wallpicker/WallPicker.qml
Normal file
73
modules/widgets/wallpicker/WallPicker.qml
Normal file
@ -0,0 +1,73 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import Qt.labs.folderlistmodel 2.10
|
||||
import qs
|
||||
import qs.settings
|
||||
|
||||
Loader {
|
||||
active: Settings.config.wallswitchershown
|
||||
sourceComponent: root
|
||||
Component {
|
||||
id: root
|
||||
FloatingWindow {
|
||||
implicitWidth: 700
|
||||
title: "qs-wallpicker"
|
||||
implicitHeight: 600
|
||||
color: Colors.base00
|
||||
visible: Settings.config.wallswitchershown
|
||||
onClosed: Settings.config.wallswitchershown = false
|
||||
|
||||
Rectangle {
|
||||
id: container
|
||||
radius: Settings.config.rounding
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: 8
|
||||
}
|
||||
color: Colors.base02
|
||||
FolderListModel {
|
||||
id: wpModel
|
||||
folder: "file:///home/lucy/.walls/"
|
||||
nameFilters: ["*.png"]
|
||||
}
|
||||
Component {
|
||||
id: wallDelegate
|
||||
Rectangle {
|
||||
id: wpPreview
|
||||
required property var filePath
|
||||
implicitWidth: 80
|
||||
implicitHeight: 60
|
||||
color: "transparent"
|
||||
Image {
|
||||
asynchronous: true
|
||||
anchors.fill: parent
|
||||
source: wpPreview.filePath ? wpPreview.filePath : null
|
||||
}
|
||||
MouseArea {
|
||||
id: updater
|
||||
acceptedButtons: Qt.LeftButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
Settings.config.currentWall = wpPreview.filePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
GridView {
|
||||
id: wallLayout
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: 20
|
||||
anchors.leftMargin: 40
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
model: wpModel
|
||||
delegate: wallDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
settings/Settings.qml
Normal file
31
settings/Settings.qml
Normal file
@ -0,0 +1,31 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
pragma Singleton
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
property alias config: settingsAdapter
|
||||
FileView {
|
||||
id: settingsView
|
||||
path: "file:///home/lucy/.config/quickshell/settings/settings.json"
|
||||
onFileChanged: reload()
|
||||
onAdapterUpdated: writeAdapter()
|
||||
|
||||
watchChanges: true
|
||||
|
||||
adapter: JsonAdapter {
|
||||
id: settingsAdapter
|
||||
property int barHeight
|
||||
property int rounding
|
||||
property bool floating
|
||||
property string font
|
||||
property int fontSize
|
||||
property int margins
|
||||
property var currentWall
|
||||
property bool wallswitchershown
|
||||
property int barmargins
|
||||
property int barSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
12
settings/settings.json
Normal file
12
settings/settings.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"barHeight": 32,
|
||||
"barSpacing": 10,
|
||||
"barmargins": 6,
|
||||
"currentWall": "/home/lucy/.walls/frierensuff.png",
|
||||
"floating": true,
|
||||
"font": "Google Sans",
|
||||
"fontSize": 13,
|
||||
"margins": 10,
|
||||
"rounding": 26,
|
||||
"wallswitchershown": false
|
||||
}
|
||||
49
shell.qml
49
shell.qml
@ -1,42 +1,17 @@
|
||||
//@ pragma UseQApplication
|
||||
pragma ComponentBehavior: Bound
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import "./modules/bar/"
|
||||
import "./modules/wallpaper/"
|
||||
import "./modules/notifications/"
|
||||
import QtQuick
|
||||
import qs.modules.Bar
|
||||
import qs.modules.ipc
|
||||
import qs.modules.wallpaper
|
||||
import qs.modules.widgets.wallpicker
|
||||
import qs.modules.notifications
|
||||
|
||||
ShellRoot {
|
||||
id: shellRoot
|
||||
|
||||
Variants {
|
||||
id: barVariants
|
||||
model: Quickshell.screens
|
||||
delegate: Bar {
|
||||
screen: modelData
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
id: overlayVariants
|
||||
model: Quickshell.screens
|
||||
delegate: Overlay {
|
||||
screen: modelData
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
id: wallVariants
|
||||
model: Quickshell.screens
|
||||
delegate: Wallpaper {
|
||||
screen: modelData
|
||||
}
|
||||
}
|
||||
Variants {
|
||||
id: notiVariants
|
||||
model: Quickshell.screens
|
||||
delegate: NotiPopup {
|
||||
screen: modelData
|
||||
}
|
||||
}
|
||||
|
||||
WallSwitcher {}
|
||||
id: root
|
||||
Ipc {}
|
||||
Bar {}
|
||||
Wallpaper {}
|
||||
WallPicker {}
|
||||
Notification {}
|
||||
}
|
||||
|
||||
21
widgets/CIcon.qml
Normal file
21
widgets/CIcon.qml
Normal file
@ -0,0 +1,21 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.settings
|
||||
|
||||
Text {
|
||||
id: root
|
||||
color: Colors.base05
|
||||
property real iconSize: 18
|
||||
property var fill: true
|
||||
renderType: Text.NativeRendering
|
||||
font {
|
||||
hintingPreference: Font.PreferNoHinting
|
||||
family: "Material Symbols Rounded"
|
||||
pixelSize: iconSize
|
||||
weight: Font.Normal + (Font.DemiBold - Font.Normal) * fill
|
||||
variableAxes: {
|
||||
"FILL": fill,
|
||||
"opsz": iconSize
|
||||
}
|
||||
}
|
||||
}
|
||||
10
widgets/CText.qml
Normal file
10
widgets/CText.qml
Normal file
@ -0,0 +1,10 @@
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.settings
|
||||
|
||||
Text {
|
||||
font.family: Settings.config.font
|
||||
font.pixelSize: Settings.config.fontSize
|
||||
color: Colors.base05
|
||||
font.weight: 500
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user