Compare commits
No commits in common. "master" and "main" have entirely different histories.
18
#shell.qml#
18
#shell.qml#
@ -1,18 +0,0 @@
|
||||
//@ 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
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
./Colors.qml
|
||||
45
Colors.qml
45
Colors.qml
@ -1,26 +1,29 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
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
|
||||
Singleton {
|
||||
id: customColors
|
||||
// Core Backgrounds
|
||||
readonly property color background: "#24273A"
|
||||
readonly property color foreground: "#CAD3F5"
|
||||
readonly property color cursor: "#CAD3F5"
|
||||
|
||||
// --- 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)
|
||||
// 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"
|
||||
}
|
||||
|
||||
16
Icons.qml
Normal file
16
Icons.qml
Normal file
@ -0,0 +1,16 @@
|
||||
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
|
||||
})
|
||||
}
|
||||
6
config.json
Normal file
6
config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"currentWall": "file:///home/lucy/.walls/faris.jpg",
|
||||
"font": "MonaSpiceXe Nerd Font Propo",
|
||||
"fontSize": 13,
|
||||
"wallDir": "/home/lucy/.walls/"
|
||||
}
|
||||
BIN
fonts/SFMono-Nerd-Font/SFMono Bold Italic Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Bold Italic Nerd Font Complete.otf
Normal file
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Bold Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Bold Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Heavy Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Heavy Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Light Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Light Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Medium Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Medium Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Regular Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Regular Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
fonts/SFMono-Nerd-Font/SFMono Semibold Nerd Font Complete.otf
Normal file
BIN
fonts/SFMono-Nerd-Font/SFMono Semibold Nerd Font Complete.otf
Normal file
Binary file not shown.
Binary file not shown.
@ -1,67 +0,0 @@
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
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 : []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
46
modules/bar/Bar.qml
Normal file
46
modules/bar/Bar.qml
Normal file
@ -0,0 +1,46 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
50
modules/bar/Battery.qml
Normal file
50
modules/bar/Battery.qml
Normal file
@ -0,0 +1,50 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
47
modules/bar/Clock.qml
Normal file
47
modules/bar/Clock.qml
Normal file
@ -0,0 +1,47 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
modules/bar/Mpris.qml
Normal file
110
modules/bar/Mpris.qml
Normal file
@ -0,0 +1,110 @@
|
||||
// ⚠️ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
modules/bar/PowerProfiles.qml
Normal file
48
modules/bar/PowerProfiles.qml
Normal file
@ -0,0 +1,48 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
23
modules/bar/SystemTray.qml
Normal file
23
modules/bar/SystemTray.qml
Normal file
@ -0,0 +1,23 @@
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,24 +2,16 @@ 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
|
||||
IconImage {
|
||||
id: trayIcon
|
||||
implicitSize: 16
|
||||
source: parent.modelData.icon
|
||||
}
|
||||
QsMenuAnchor {
|
||||
id: menu
|
||||
menu: root.modelData.hasMenu ? root.modelData.menu : null
|
||||
anchor.item: root
|
||||
}
|
||||
implicitWidth: 16
|
||||
implicitHeight: 16
|
||||
|
||||
onClicked: event => {
|
||||
if (event.button === Qt.LeftButton) {
|
||||
modelData.activate();
|
||||
@ -27,4 +19,18 @@ 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
|
||||
}
|
||||
}
|
||||
93
modules/bar/Volume.qml
Normal file
93
modules/bar/Volume.qml
Normal file
@ -0,0 +1,93 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
47
modules/bar/Workspaces.qml
Normal file
47
modules/bar/Workspaces.qml
Normal file
@ -0,0 +1,47 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import Quickshell.Io
|
||||
import qs
|
||||
import qs.settings
|
||||
|
||||
Item {
|
||||
IpcHandler {
|
||||
target: "settings"
|
||||
function toggleWall() {
|
||||
Settings.config.wallswitchershown = !Settings.config.wallswitchershown;
|
||||
}
|
||||
}
|
||||
}
|
||||
172
modules/notifications/NotiPopup.qml
Normal file
172
modules/notifications/NotiPopup.qml
Normal file
@ -0,0 +1,172 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,73 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
2
modules/notifications/qmldir
Normal file
2
modules/notifications/qmldir
Normal file
@ -0,0 +1,2 @@
|
||||
singleton NotifServer 1.0 NotifServer.qml
|
||||
NotiPopup 1.0 NotiPopup.qml
|
||||
36
modules/settings/Settings.qml
Normal file
36
modules/settings/Settings.qml
Normal file
@ -0,0 +1,36 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
6
modules/settings/config.json
Normal file
6
modules/settings/config.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"currentWall": "file:///home/lucy/.walls/lain_room.png",
|
||||
"font": "Google Sans Code",
|
||||
"fontSize": 14,
|
||||
"wallDir": "/home/lucy/.walls/"
|
||||
}
|
||||
1
modules/settings/qmldir
Normal file
1
modules/settings/qmldir
Normal file
@ -0,0 +1 @@
|
||||
singleton Settings 1.0 Settings.qml
|
||||
@ -1,51 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
modules/wallpaper/Overlay.qml
Normal file
66
modules/wallpaper/Overlay.qml
Normal file
@ -0,0 +1,66 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,218 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
177
modules/wallpaper/ScreenCorners.qml
Normal file
177
modules/wallpaper/ScreenCorners.qml
Normal file
@ -0,0 +1,177 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
modules/wallpaper/ScreenPadding.qml
Normal file
67
modules/wallpaper/ScreenPadding.qml
Normal file
@ -0,0 +1,67 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
94
modules/wallpaper/WallSwitcher.qml
Normal file
94
modules/wallpaper/WallSwitcher.qml
Normal file
@ -0,0 +1,94 @@
|
||||
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,43 +1,75 @@
|
||||
import Quickshell
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import qs
|
||||
import qs.settings
|
||||
import QtQuick.Controls // <--- Needed for StackView
|
||||
import Quickshell.Wayland
|
||||
import "../settings/"
|
||||
|
||||
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
|
||||
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!
|
||||
}
|
||||
}
|
||||
PlayerWidget {
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
margins: 35
|
||||
topMargin: Settings.config.barHeight + 35 + (Settings.config.floating ? Settings.config.margins : 0)
|
||||
|
||||
// 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: Settings
|
||||
|
||||
function onCurrentWallChanged() {
|
||||
wallStack.replace(wallComponent, {
|
||||
"source": Settings.currentWall
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
5
modules/wallpaper/qmldir
Normal file
5
modules/wallpaper/qmldir
Normal file
@ -0,0 +1,5 @@
|
||||
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
|
||||
@ -1,73 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
{
|
||||
"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,17 +1,42 @@
|
||||
//@ pragma UseQApplication
|
||||
pragma ComponentBehavior: Bound
|
||||
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
|
||||
import Quickshell.Io
|
||||
import "./modules/bar/"
|
||||
import "./modules/wallpaper/"
|
||||
import "./modules/notifications/"
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
Ipc {}
|
||||
Bar {}
|
||||
Wallpaper {}
|
||||
WallPicker {}
|
||||
Notification {}
|
||||
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 {}
|
||||
}
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
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