better structure

This commit is contained in:
2025-10-19 00:14:19 +02:00
parent 057afc086e
commit 8733656ed9
630 changed files with 81 additions and 137 deletions

View File

@@ -0,0 +1,46 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Noctalia
import qs.Services
import qs.Utils
NBox {
id: lyricsBox
Component.onCompleted: {
LyricsService.startSyncing();
}
Component.onDestruction: {
LyricsService.stopSyncing();
}
ColumnLayout {
id: lyricsColumn
anchors.fill: parent
anchors.margins: Style.marginS
Repeater {
model: LyricsService.lyrics
NText {
Layout.fillWidth: true
text: modelData
font.pointSize: index === LyricsService.currentIndex ? Style.fontSizeM : Style.fontSizeS
font.weight: index === LyricsService.currentIndex ? Style.fontWeightBold : Style.fontWeightRegular
font.family: Fonts.sans
color: index === LyricsService.currentIndex ? Color.mOnSurface : Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
wrapMode: Text.WrapAnywhere
maximumLineCount: 1
}
}
}
}

View File

@@ -0,0 +1,96 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Modules.Bar.Misc
import qs.Noctalia
import qs.Services
GridLayout {
id: buttonsGrid
columns: 2
columnSpacing: 10
rowSpacing: 10
Layout.margins: 10
NIconButton {
id: slowerButton
baseSize: 32
colorBg: Color.transparent
colorBgHover: Colors.blue
colorFg: Colors.blue
icon: "arrow-bar-up"
onClicked: {
LyricsService.increaseOffset();
}
}
NIconButton {
id: playPauseButton
baseSize: 32
colorBg: Color.transparent
colorBgHover: Colors.yellow
colorFg: Colors.yellow
icon: "arrow-bar-down"
onClicked: {
LyricsService.decreaseOffset();
}
}
NIconButton {
id: nextButton
baseSize: 32
colorBg: Color.transparent
colorBgHover: Colors.green
colorFg: Colors.green
icon: "rotate-clockwise"
onClicked: {
LyricsService.resetOffset();
}
}
NIconButton {
id: fasterButton
baseSize: 32
colorBg: Color.transparent
colorBgHover: Colors.red
colorFg: Colors.red
icon: "trash"
onClicked: {
LyricsService.clearCache();
}
}
NIconButton {
id: barLyricsButton
baseSize: 32
colorBg: SettingsService.showLyricsBar ? Colors.peach : Color.transparent
colorBgHover: Colors.peach
colorFg: SettingsService.showLyricsBar ? Colors.base : Colors.peach
icon: "app-window"
onClicked: {
SettingsService.showLyricsBar = !SettingsService.showLyricsBar;
}
}
NIconButton {
id: textButton
baseSize: 32
colorBg: Color.transparent
colorBgHover: Colors.subtext1
colorFg: Colors.subtext1
icon: "align-box-left-bottom"
onClicked: {
LyricsService.showLyricsText();
controlCenterPanel.close();
}
}
}

View File

@@ -0,0 +1,458 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Noctalia
import qs.Services
import qs.Utils
NBox {
id: root
// Background artwork that covers everything
Item {
anchors.fill: parent
clip: true
NImageRounded {
id: bgArtImage
anchors.fill: parent
imagePath: MusicManager.trackArtUrl
imageRadius: Style.radiusM
visible: MusicManager.trackArtUrl !== ""
}
// Dark overlay for readability
Rectangle {
anchors.fill: parent
color: Color.mSurfaceVariant
opacity: 0.85
radius: Style.radiusM
}
// Border
Rectangle {
anchors.fill: parent
color: Color.transparent
radius: Style.radiusM
}
}
// Background visualizer on top of the artwork
Item {
id: visualizerContainer
anchors.fill: parent
layer.enabled: true
Item {
anchors.fill: parent
Cava {
id: cava
count: 32
}
Repeater {
model: cava.values
Rectangle {
anchors.bottom: parent.bottom
width: (parent.width - (cava.count - 1) * Style.marginXS) / cava.count
height: modelData * parent.height
x: index * (width + Style.marginXS)
color: Color.mPrimary
radius: width / 2
opacity: 0.25
}
}
}
layer.effect: MultiEffect {
maskEnabled: true
maskThresholdMin: 0.5
maskSpreadAtMin: 0
maskSource: ShaderEffectSource {
sourceItem: Rectangle {
width: root.width
height: root.height
radius: Style.radiusM
color: "white"
}
}
}
}
// Player selector - positioned at the very top
Rectangle {
id: playerSelectorButton
property var currentPlayer: MusicManager.getAvailablePlayers()[MusicManager.selectedPlayerIndex]
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: Style.marginXS
anchors.leftMargin: Style.marginM
anchors.rightMargin: Style.marginM
height: Style.barHeight
visible: MusicManager.getAvailablePlayers().length > 1
radius: Style.radiusM
color: Color.transparent
Component.onCompleted: {
MusicManager.selectedPlayerIndex = -1;
}
Component.onDestruction: {
MusicManager.selectedPlayerIndex = -1;
}
RowLayout {
anchors.fill: parent
spacing: Style.marginS
NIcon {
icon: "caret-down"
pointSize: Style.fontSizeXXL
color: Color.mOnSurfaceVariant
}
NText {
text: playerSelectorButton.currentPlayer ? playerSelectorButton.currentPlayer.identity : ""
pointSize: Style.fontSizeXS
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
}
}
MouseArea {
id: playerSelectorMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
var menuItems = [];
var players = MusicManager.getAvailablePlayers();
for (var i = 0; i < players.length; i++) {
menuItems.push({
"label": players[i].identity,
"action": i.toString(),
"icon": "disc",
"enabled": true,
"visible": true
});
}
playerContextMenu.model = menuItems;
playerContextMenu.openAtItem(playerSelectorButton, playerSelectorButton.width - playerContextMenu.width, playerSelectorButton.height);
}
}
NContextMenu {
id: playerContextMenu
parent: root
width: 200
onTriggered: function(action) {
var index = parseInt(action);
if (!isNaN(index)) {
MusicManager.selectedPlayerIndex = index;
MusicManager.updateCurrentPlayer();
}
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginM
// No media player detected
ColumnLayout {
id: fallback
visible: !main.visible
spacing: Style.marginS
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
ColumnLayout {
anchors.centerIn: parent
spacing: Style.marginL
Item {
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: Style.fontSizeXXXL * 4
Layout.preferredHeight: Style.fontSizeXXXL * 4
// Pulsating audio circles (background)
Repeater {
model: 3
Rectangle {
anchors.centerIn: parent
width: parent.width * (1 + index * 0.2)
height: width
radius: width / 2
color: "transparent"
border.color: Color.mOnSurfaceVariant
border.width: 2
opacity: 0
SequentialAnimation on opacity {
running: true
loops: Animation.Infinite
PauseAnimation {
duration: index * 600
}
NumberAnimation {
from: 1
to: 0
duration: 2000
easing.type: Easing.OutQuad
}
}
SequentialAnimation on scale {
running: true
loops: Animation.Infinite
PauseAnimation {
duration: index * 600
}
NumberAnimation {
from: 0.5
to: 1.2
duration: 2000
easing.type: Easing.OutQuad
}
}
}
}
// Spinning disc
NIcon {
anchors.centerIn: parent
icon: "disc"
pointSize: Style.fontSizeXXXL * 3
color: Color.mOnSurfaceVariant
RotationAnimator on rotation {
from: 0
to: 360
duration: 8000
loops: Animation.Infinite
running: true
}
}
}
// Descriptive text
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginXS
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
}
}
// MediaPlayer Main Content
ColumnLayout {
id: main
visible: MusicManager.currentPlayer && MusicManager.canPlay
spacing: Style.marginS
// Spacer to push content down
Item {
Layout.preferredHeight: Style.marginM
}
// Metadata at the bottom left
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft
spacing: Style.marginXS
NText {
visible: MusicManager.trackTitle !== ""
text: MusicManager.trackTitle
pointSize: Style.fontSizeM
font.weight: Style.fontWeightBold
elide: Text.ElideRight
wrapMode: Text.Wrap
Layout.fillWidth: true
maximumLineCount: 1
}
NText {
visible: MusicManager.trackArtist !== ""
text: MusicManager.trackArtist
color: Color.mPrimary
pointSize: Style.fontSizeS
elide: Text.ElideRight
Layout.fillWidth: true
maximumLineCount: 1
}
NText {
visible: MusicManager.trackAlbum !== ""
text: MusicManager.trackAlbum
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeM
elide: Text.ElideRight
Layout.fillWidth: true
maximumLineCount: 1
}
}
// Progress slider
Item {
id: progressWrapper
property real localSeekRatio: -1
property real lastSentSeekRatio: -1
property real seekEpsilon: 0.01
property real progressRatio: {
if (!MusicManager.currentPlayer || MusicManager.trackLength <= 0)
return 0;
const r = MusicManager.currentPosition / MusicManager.trackLength;
if (isNaN(r) || !isFinite(r))
return 0;
return Math.max(0, Math.min(1, r));
}
property real effectiveRatio: (MusicManager.isSeeking && localSeekRatio >= 0) ? Math.max(0, Math.min(1, localSeekRatio)) : progressRatio
visible: (MusicManager.currentPlayer && MusicManager.trackLength > 0)
Layout.fillWidth: true
height: Style.baseWidgetSize * 0.5
Timer {
id: seekDebounce
interval: 75
repeat: false
onTriggered: {
if (MusicManager.isSeeking && progressWrapper.localSeekRatio >= 0) {
const next = Math.max(0, Math.min(1, progressWrapper.localSeekRatio));
if (progressWrapper.lastSentSeekRatio < 0 || Math.abs(next - progressWrapper.lastSentSeekRatio) >= progressWrapper.seekEpsilon) {
MusicManager.seekByRatio(next);
progressWrapper.lastSentSeekRatio = next;
}
}
}
}
NSlider {
id: progressSlider
anchors.fill: parent
from: 0
to: 1
stepSize: 0
snapAlways: false
enabled: MusicManager.trackLength > 0 && MusicManager.canSeek
heightRatio: 0.65
onMoved: {
progressWrapper.localSeekRatio = value;
seekDebounce.restart();
}
onPressedChanged: {
if (pressed) {
MusicManager.isSeeking = true;
progressWrapper.localSeekRatio = value;
MusicManager.seekByRatio(value);
progressWrapper.lastSentSeekRatio = value;
} else {
seekDebounce.stop();
MusicManager.seekByRatio(value);
MusicManager.isSeeking = false;
progressWrapper.localSeekRatio = -1;
progressWrapper.lastSentSeekRatio = -1;
}
}
}
Binding {
target: progressSlider
property: "value"
value: progressWrapper.progressRatio
when: !MusicManager.isSeeking
}
}
// Media controls
RowLayout {
spacing: Style.marginS
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
NIconButton {
icon: "media-prev"
visible: MusicManager.canGoPrevious
onClicked: MusicManager.canGoPrevious ? MusicManager.previous() : {
}
}
NIconButton {
icon: MusicManager.isPlaying ? "media-pause" : "media-play"
visible: (MusicManager.canPlay || MusicManager.canPause)
onClicked: (MusicManager.canPlay || MusicManager.canPause) ? MusicManager.playPause() : {
}
}
NIconButton {
icon: "media-next"
visible: MusicManager.canGoNext
onClicked: MusicManager.canGoNext ? MusicManager.next() : {
}
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Constants
import qs.Modules.Panel.Misc
import qs.Noctalia
import qs.Services
import qs.Utils
// Unified system card: monitors CPU, temp, memory, disk
NBox {
id: root
compact: true
ColumnLayout {
id: content
anchors.fill: parent
anchors.margins: Style.marginXS
spacing: Style.marginS
MonitorSlider {
icon: "cpu-usage"
value: SystemStatService.cpuUsage
from: 0
to: 100
colorFill: Colors.teal
Layout.fillWidth: true
}
MonitorSlider {
icon: "memory"
value: SystemStatService.memPercent
from: 0
to: 100
colorFill: Colors.green
Layout.fillWidth: true
}
MonitorSlider {
icon: "cpu-temperature"
value: SystemStatService.cpuTemp
from: 0
to: 100
colorFill: Colors.yellow
Layout.fillWidth: true
}
MonitorSlider {
icon: "storage"
value: SystemStatService.diskPercent
from: 0
to: 100
colorFill: Colors.peach
Layout.fillWidth: true
}
}
}

View File

@@ -0,0 +1,175 @@
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Services.UPower
import Quickshell.Widgets
import qs.Constants
import qs.Noctalia
import qs.Services
import qs.Utils
ColumnLayout {
id: root
readonly property bool hasPP: PowerProfileService.available
spacing: Style.marginM
NBox {
id: whoamiBox
property string uptimeText: "--"
property string hostname: "--"
function updateSystemInfo() {
uptimeProcess.running = true;
hostnameProcess.running = true;
}
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
id: content
spacing: root.spacing
anchors.fill: parent
anchors.margins: root.spacing
NImageCircled {
width: Style.baseWidgetSize * 1.5
height: Style.baseWidgetSize * 1.5
imagePath: Quickshell.shellDir + "/Assets/Images/Avatar.jpg"
fallbackIcon: "person"
borderColor: Color.mPrimary
borderWidth: Math.max(1, Style.borderM)
Layout.alignment: Qt.AlignVCenter
Layout.topMargin: Style.marginXS
}
ColumnLayout {
Layout.fillWidth: true
spacing: Style.marginXXS
NText {
text: `${Quickshell.env("USER") || "user"} @ ${whoamiBox.hostname}`
font.weight: Style.fontWeightBold
font.pointSize: Style.fontSizeL
font.capitalization: Font.Capitalize
}
NText {
text: "Uptime: " + whoamiBox.uptimeText
font.pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
}
}
Item {
Layout.fillWidth: true
}
}
// ----------------------------------
// Uptime
Timer {
interval: 60000
repeat: true
running: true
onTriggered: uptimeProcess.running = true
}
Process {
id: uptimeProcess
command: ["cat", "/proc/uptime"]
running: true
stdout: StdioCollector {
onStreamFinished: {
var uptimeSeconds = parseFloat(this.text.trim().split(' ')[0]);
whoamiBox.uptimeText = Time.formatVagueHumanReadableDuration(uptimeSeconds);
uptimeProcess.running = false;
}
}
}
Process {
id: hostnameProcess
command: ["cat", "/etc/hostname"]
running: true
stdout: StdioCollector {
onStreamFinished: {
whoamiBox.hostname = this.text.trim();
hostnameProcess.running = false;
}
}
}
}
RowLayout {
id: utilitiesRow
Layout.fillWidth: true
// Performance
NIconButton {
implicitHeight: 32
implicitWidth: 32
icon: PowerProfileService.getIcon(PowerProfile.Performance)
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBgHover: Colors.red
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Colors.red : Color.transparent
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Performance) ? Color.mOnPrimary : Colors.red
onClicked: PowerProfileService.setProfile(PowerProfile.Performance)
}
// Balanced
NIconButton {
implicitHeight: 32
implicitWidth: 32
icon: PowerProfileService.getIcon(PowerProfile.Balanced)
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBgHover: Colors.blue
colorBg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Colors.blue : Color.transparent
colorFg: (enabled && PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnPrimary : Colors.blue
onClicked: PowerProfileService.setProfile(PowerProfile.Balanced)
}
// Eco
NIconButton {
implicitHeight: 32
implicitWidth: 32
icon: PowerProfileService.getIcon(PowerProfile.PowerSaver)
enabled: hasPP
opacity: enabled ? Style.opacityFull : Style.opacityMedium
colorBgHover: Colors.green
colorBg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Colors.green : Color.transparent
colorFg: (enabled && PowerProfileService.profile === PowerProfile.PowerSaver) ? Color.mOnPrimary : Colors.green
onClicked: PowerProfileService.setProfile(PowerProfile.PowerSaver)
}
Item {
Layout.fillWidth: true
}
// Lyrics Offset
NText {
text: `Lyrics Offset: ${LyricsService.offset >= 0 ? '+' : ''}${LyricsService.offset} ms`
Layout.alignment: Qt.AlignVCenter
}
}
}