mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
Modularlize the TopBar
This commit is contained in:
@@ -1,869 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.Mpris
|
||||
import "../Common"
|
||||
import "../Services"
|
||||
|
||||
PanelWindow {
|
||||
id: topBar
|
||||
|
||||
// modelData contains the screen from Quickshell.screens
|
||||
property var modelData
|
||||
screen: modelData
|
||||
|
||||
// Get the screen name (e.g., "DP-1", "DP-2")
|
||||
property string screenName: modelData.name
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
implicitHeight: Theme.barHeight - 4
|
||||
color: "transparent"
|
||||
|
||||
// Audio visualization data
|
||||
property list<real> audioLevels: [0, 0, 0, 0]
|
||||
|
||||
// Real-time audio visualization using cava (with fallback)
|
||||
property bool cavaAvailable: false
|
||||
|
||||
Process {
|
||||
id: cavaCheck
|
||||
command: ["which", "cava"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
topBar.cavaAvailable = exitCode === 0
|
||||
if (topBar.cavaAvailable) {
|
||||
console.log("cava found - creating config and enabling real audio visualization")
|
||||
configWriter.running = true
|
||||
} else {
|
||||
console.log("cava not found - using fallback animation")
|
||||
fallbackTimer.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create temporary config file for cava
|
||||
Process {
|
||||
id: configWriter
|
||||
running: topBar.cavaAvailable
|
||||
command: [
|
||||
"sh", "-c",
|
||||
`cat > /tmp/quickshell_cava_config << 'EOF'
|
||||
[general]
|
||||
mode = normal
|
||||
framerate = 30
|
||||
autosens = 0
|
||||
sensitivity = 50
|
||||
bars = 4
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
channels = mono
|
||||
mono_option = average
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
EOF`
|
||||
]
|
||||
|
||||
onExited: {
|
||||
// Start cava after config is written
|
||||
if (topBar.cavaAvailable) {
|
||||
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProcess
|
||||
running: false
|
||||
command: ["cava", "-p", "/tmp/quickshell_cava_config"]
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: data => {
|
||||
if (data.trim()) {
|
||||
// Parse semicolon-separated values from cava
|
||||
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p))
|
||||
if (points.length >= 4) {
|
||||
topBar.audioLevels = [points[0], points[1], points[2], points[3]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
topBar.audioLevels = [0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback animation when cava is not available
|
||||
Timer {
|
||||
id: fallbackTimer
|
||||
running: false
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
// Generate smooth random values for fallback (0-100 range)
|
||||
topBar.audioLevels = [
|
||||
Math.random() * 40 + 10, // 10-50
|
||||
Math.random() * 60 + 20, // 20-80
|
||||
Math.random() * 50 + 15, // 15-65
|
||||
Math.random() * 35 + 20 // 20-55
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Floating panel container with margins
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 0
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.75)
|
||||
|
||||
// Material 3 elevation shadow
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
radius: 16
|
||||
samples: 33
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
transparentBorder: true
|
||||
}
|
||||
|
||||
// Subtle border for definition
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
radius: parent.radius
|
||||
}
|
||||
|
||||
// Subtle surface tint overlay with animation
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
id: leftSection
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: archLauncher
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.osLogo || "apps" // Use OS logo if detected, fallback to apps icon
|
||||
font.family: root.osLogo ? "NerdFont" : Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
LauncherService.toggleAppLauncher()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: workspaceSwitcher
|
||||
width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.8)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NiriWorkspaceService.niriAvailable
|
||||
|
||||
|
||||
// Use reactive properties from NiriWorkspaceService
|
||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||
property var workspaceList: getDisplayWorkspaces()
|
||||
|
||||
|
||||
|
||||
// Get workspaces for this display
|
||||
function getDisplayWorkspaces() {
|
||||
// Always return something for now, even if service isn't ready
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return [1, 2] // Fallback
|
||||
}
|
||||
|
||||
if (!topBar.screenName) {
|
||||
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers()
|
||||
}
|
||||
|
||||
// Filter workspaces for this specific display
|
||||
var displayWorkspaces = []
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === topBar.screenName) {
|
||||
displayWorkspaces.push(ws.idx + 1) // Convert to 1-based
|
||||
}
|
||||
}
|
||||
|
||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
||||
}
|
||||
|
||||
// Get active workspace for this display
|
||||
function getDisplayActiveWorkspace() {
|
||||
// Always return something for now, even if service isn't ready
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return 1 // Fallback
|
||||
}
|
||||
|
||||
if (!topBar.screenName) {
|
||||
return NiriWorkspaceService.getCurrentWorkspaceNumber()
|
||||
}
|
||||
|
||||
// Find active workspace for this display (is_active, not is_focused)
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === topBar.screenName && ws.is_active) {
|
||||
return ws.idx + 1 // Convert to 1-based
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
// React to workspace changes
|
||||
Connections {
|
||||
target: NiriWorkspaceService
|
||||
function onAllWorkspacesChanged() {
|
||||
workspaceSwitcher.workspaceList = workspaceSwitcher.getDisplayWorkspaces()
|
||||
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||
}
|
||||
function onFocusedWorkspaceIndexChanged() {
|
||||
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||
}
|
||||
function onNiriAvailableChanged() {
|
||||
if (NiriWorkspaceService.niriAvailable) {
|
||||
workspaceSwitcher.workspaceList = workspaceSwitcher.getDisplayWorkspaces()
|
||||
workspaceSwitcher.currentWorkspace = workspaceSwitcher.getDisplayActiveWorkspace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: workspaceRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: workspaceSwitcher.workspaceList
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: modelData === workspaceSwitcher.currentWorkspace
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
property int sequentialNumber: index + 1 // 1-based sequential number per monitor
|
||||
|
||||
|
||||
width: isActive ? Theme.spacingXL + Theme.spacingS : Theme.spacingL
|
||||
height: Theme.spacingS
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary :
|
||||
isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
// Use sequential workspace number directly - niri focus-workspace uses 1,2,3,etc per monitor
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", sequentialNumber.toString()])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clockContainer
|
||||
width: {
|
||||
let baseWidth = 200
|
||||
if (root.hasActiveMedia) {
|
||||
// Calculate width needed for media info + time/date + spacing + padding
|
||||
let mediaWidth = 24 + Theme.spacingXS + mediaTitleText.implicitWidth + Theme.spacingM + 180
|
||||
return Math.min(Math.max(mediaWidth, 300), parent.width - Theme.spacingL * 2)
|
||||
} else if (root.weather.available) {
|
||||
return Math.min(280, parent.width - Theme.spacingL * 2)
|
||||
} else {
|
||||
return Math.min(baseWidth, parent.width - Theme.spacingL * 2)
|
||||
}
|
||||
}
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: clockMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
anchors.centerIn: parent
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
property date currentDate: new Date()
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Media info or Weather info
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.hasActiveMedia || root.weather.available
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Animated equalizer when media is playing
|
||||
Item {
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: {
|
||||
if (root.activePlayer?.playbackState === MprisPlaybackState.Playing && topBar.audioLevels.length > index) {
|
||||
// Scale and compress audio data for better visual range
|
||||
const rawLevel = topBar.audioLevels[index] || 0
|
||||
// Use square root to compress high values and expand low values
|
||||
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100
|
||||
const maxHeight = Theme.iconSize - 2
|
||||
const minHeight = 3
|
||||
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight)
|
||||
}
|
||||
return 3 // Minimum height when not playing
|
||||
}
|
||||
radius: 1.5
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 80 // Slightly slower for smoother movement
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Song title when media is playing
|
||||
Text {
|
||||
id: mediaTitleText
|
||||
text: root.activePlayer?.trackTitle || "Unknown Track"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia
|
||||
width: Math.min(implicitWidth, clockContainer.width - 100)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Weather icon when no media but weather available
|
||||
Text {
|
||||
text: WeatherService.getWeatherIcon(root.weather.wCode)
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.hasActiveMedia && root.weather.available
|
||||
}
|
||||
|
||||
// Weather temp when no media but weather available
|
||||
Text {
|
||||
text: (root.useFahrenheit ? root.weather.tempF : root.weather.temp) + "°" + (root.useFahrenheit ? "F" : "C")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.hasActiveMedia && root.weather.available
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia || root.weather.available
|
||||
}
|
||||
|
||||
// Time and date
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: Qt.formatTime(clockContainer.currentDate, "h:mm AP")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Qt.formatDate(clockContainer.currentDate, "ddd d")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
clockContainer.currentDate = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.calendarVisible = !root.calendarVisible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightSection
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
width: Math.max(40, systemTrayRow.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: systemTrayRow.children.length > 0
|
||||
|
||||
Row {
|
||||
id: systemTrayRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
delegate: Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
property var trayItem: modelData
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
source: {
|
||||
let icon = trayItem?.icon || "";
|
||||
if (!icon) return "";
|
||||
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem) return;
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate()
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (trayItem.hasMenu) {
|
||||
console.log("Right-click detected, showing menu for:", trayItem.title || "Unknown")
|
||||
customTrayMenu.showMenu(mouse.x, mouse.y)
|
||||
} else {
|
||||
console.log("No menu available for:", trayItem.title || "Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Custom Material 3 styled menu
|
||||
QtObject {
|
||||
id: customTrayMenu
|
||||
|
||||
property bool menuVisible: false
|
||||
|
||||
function showMenu(x, y) {
|
||||
root.currentTrayMenu = customTrayMenu
|
||||
root.currentTrayItem = trayItem
|
||||
|
||||
// Simple positioning: right side of screen, below the panel
|
||||
root.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL
|
||||
root.trayMenuY = Theme.barHeight + Theme.spacingS
|
||||
|
||||
console.log("Showing menu at:", root.trayMenuX, root.trayMenuY)
|
||||
menuVisible = true
|
||||
root.showTrayMenu = true
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
menuVisible = false
|
||||
root.showTrayMenu = false
|
||||
root.currentTrayMenu = null
|
||||
root.currentTrayItem = null
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clipboard History Button
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: clipboardArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "content_paste" // Material icon for clipboard
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clipboardArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
clipboardHistoryPopup.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System Monitor Widgets
|
||||
CpuMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
RamMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
|
||||
// Notification Center Button
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: notificationArea.containsMouse || root.notificationHistoryVisible ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
property bool hasUnread: notificationHistory.count > 0
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "notifications" // Material icon for notifications
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: notificationArea.containsMouse || root.notificationHistoryVisible ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Notification dot indicator
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 6
|
||||
anchors.topMargin: 6
|
||||
visible: parent.hasUnread
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: notificationArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.notificationHistoryVisible = !root.notificationHistoryVisible
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Widget
|
||||
BatteryWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Control Center Indicators
|
||||
Rectangle {
|
||||
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: controlCenterArea.containsMouse || root.controlCenterVisible ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Network Status Icon
|
||||
Text {
|
||||
text: {
|
||||
if (root.networkStatus === "ethernet") return "lan"
|
||||
else if (root.networkStatus === "wifi") {
|
||||
switch (root.wifiSignalStrength) {
|
||||
case "excellent": return "wifi"
|
||||
case "good": return "wifi_2_bar"
|
||||
case "fair": return "wifi_1_bar"
|
||||
case "poor": return "wifi_calling_3"
|
||||
default: return "wifi"
|
||||
}
|
||||
}
|
||||
else return "wifi_off"
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: root.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: true
|
||||
}
|
||||
|
||||
// Audio Icon
|
||||
Text {
|
||||
text: root.volumeLevel === 0 ? "volume_off" :
|
||||
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: controlCenterArea.containsMouse || root.controlCenterVisible ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Microphone Icon (when active)
|
||||
Text {
|
||||
text: "mic"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
}
|
||||
|
||||
// Bluetooth Icon (when available and enabled)
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: root.bluetoothEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.bluetoothAvailable && root.bluetoothEnabled
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.controlCenterVisible = !root.controlCenterVisible
|
||||
if (root.controlCenterVisible) {
|
||||
// Refresh data when opening control center
|
||||
WifiService.scanWifi()
|
||||
BluetoothService.scanDevices()
|
||||
// Audio sink info is automatically refreshed by AudioService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power Button
|
||||
PowerButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Widgets/TopBar/AudioVisualization.qml
Normal file
137
Widgets/TopBar/AudioVisualization.qml
Normal file
@@ -0,0 +1,137 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../Common"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property list<real> audioLevels: [0, 0, 0, 0]
|
||||
property bool hasActiveMedia: false
|
||||
property var activePlayer: null
|
||||
property bool cavaAvailable: false
|
||||
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
|
||||
Process {
|
||||
id: cavaCheck
|
||||
command: ["which", "cava"]
|
||||
running: true
|
||||
onExited: (exitCode) => {
|
||||
root.cavaAvailable = exitCode === 0
|
||||
if (root.cavaAvailable) {
|
||||
console.log("cava found - creating config and enabling real audio visualization")
|
||||
configWriter.running = true
|
||||
} else {
|
||||
console.log("cava not found - using fallback animation")
|
||||
fallbackTimer.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: configWriter
|
||||
running: root.cavaAvailable
|
||||
command: [
|
||||
"sh", "-c",
|
||||
`cat > /tmp/quickshell_cava_config << 'EOF'
|
||||
[general]
|
||||
mode = normal
|
||||
framerate = 30
|
||||
autosens = 0
|
||||
sensitivity = 50
|
||||
bars = 4
|
||||
|
||||
[output]
|
||||
method = raw
|
||||
raw_target = /dev/stdout
|
||||
data_format = ascii
|
||||
channels = mono
|
||||
mono_option = average
|
||||
|
||||
[smoothing]
|
||||
noise_reduction = 20
|
||||
EOF`
|
||||
]
|
||||
|
||||
onExited: {
|
||||
if (root.cavaAvailable) {
|
||||
cavaProcess.running = Qt.binding(() => root.hasActiveMedia && root.activePlayer?.playbackState === MprisPlaybackState.Playing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: cavaProcess
|
||||
running: false
|
||||
command: ["cava", "-p", "/tmp/quickshell_cava_config"]
|
||||
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: data => {
|
||||
if (data.trim()) {
|
||||
let points = data.split(";").map(p => parseFloat(p.trim())).filter(p => !isNaN(p))
|
||||
if (points.length >= 4) {
|
||||
root.audioLevels = [points[0], points[1], points[2], points[3]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onRunningChanged: {
|
||||
if (!running) {
|
||||
root.audioLevels = [0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: fallbackTimer
|
||||
running: false
|
||||
interval: 100
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.audioLevels = [
|
||||
Math.random() * 40 + 10,
|
||||
Math.random() * 60 + 20,
|
||||
Math.random() * 50 + 15,
|
||||
Math.random() * 35 + 20
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: 4
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: {
|
||||
if (root.activePlayer?.playbackState === MprisPlaybackState.Playing && root.audioLevels.length > index) {
|
||||
const rawLevel = root.audioLevels[index] || 0
|
||||
const scaledLevel = Math.sqrt(Math.min(Math.max(rawLevel, 0), 100) / 100) * 100
|
||||
const maxHeight = Theme.iconSize - 2
|
||||
const minHeight = 3
|
||||
return minHeight + (scaledLevel / 100) * (maxHeight - minHeight)
|
||||
}
|
||||
return 3
|
||||
}
|
||||
radius: 1.5
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 80
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Widgets/TopBar/ClockWidget.qml
Normal file
155
Widgets/TopBar/ClockWidget.qml
Normal file
@@ -0,0 +1,155 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool hasActiveMedia: false
|
||||
property var activePlayer: null
|
||||
property bool weatherAvailable: false
|
||||
property string weatherCode: ""
|
||||
property int weatherTemp: 0
|
||||
property int weatherTempF: 0
|
||||
property bool useFahrenheit: false
|
||||
property date currentDate: new Date()
|
||||
|
||||
signal clockClicked()
|
||||
|
||||
width: {
|
||||
let baseWidth = 200
|
||||
if (root.hasActiveMedia) {
|
||||
let mediaWidth = 24 + Theme.spacingXS + mediaTitleText.implicitWidth + Theme.spacingM + 180
|
||||
return Math.min(Math.max(mediaWidth, 300), parent.width - Theme.spacingL * 2)
|
||||
} else if (root.weatherAvailable) {
|
||||
return Math.min(280, parent.width - Theme.spacingL * 2)
|
||||
} else {
|
||||
return Math.min(baseWidth, parent.width - Theme.spacingL * 2)
|
||||
}
|
||||
}
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: clockMouseArea.containsMouse ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Media info or Weather info
|
||||
Row {
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.hasActiveMedia || root.weatherAvailable
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// Audio visualization placeholder - will be replaced by parent
|
||||
Item {
|
||||
id: audioVisualizationPlaceholder
|
||||
width: 20
|
||||
height: Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia
|
||||
}
|
||||
|
||||
// Song title when media is playing
|
||||
Text {
|
||||
id: mediaTitleText
|
||||
text: root.activePlayer?.trackTitle || "Unknown Track"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia
|
||||
width: Math.min(implicitWidth, root.width - 100)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
// Weather icon when no media but weather available
|
||||
Text {
|
||||
text: WeatherService.getWeatherIcon(root.weatherCode)
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.hasActiveMedia && root.weatherAvailable
|
||||
}
|
||||
|
||||
// Weather temp when no media but weather available
|
||||
Text {
|
||||
text: (root.useFahrenheit ? root.weatherTempF : root.weatherTemp) + "°" + (root.useFahrenheit ? "F" : "C")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.hasActiveMedia && root.weatherAvailable
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia || root.weatherAvailable
|
||||
}
|
||||
|
||||
// Time and date
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
text: Qt.formatTime(root.currentDate, "h:mm AP")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "•"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Qt.formatDate(root.currentDate, "ddd d")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
root.currentDate = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clockClicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Widgets/TopBar/ControlCenterButton.qml
Normal file
104
Widgets/TopBar/ControlCenterButton.qml
Normal file
@@ -0,0 +1,104 @@
|
||||
import QtQuick
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
property bool isActive: false
|
||||
|
||||
signal clicked()
|
||||
|
||||
width: Math.max(80, controlIndicators.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: controlCenterArea.containsMouse || root.isActive ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
Row {
|
||||
id: controlIndicators
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
// Network Status Icon
|
||||
Text {
|
||||
text: {
|
||||
if (root.networkStatus === "ethernet") return "lan"
|
||||
else if (root.networkStatus === "wifi") {
|
||||
switch (root.wifiSignalStrength) {
|
||||
case "excellent": return "wifi"
|
||||
case "good": return "wifi_2_bar"
|
||||
case "fair": return "wifi_1_bar"
|
||||
case "poor": return "wifi_calling_3"
|
||||
default: return "wifi"
|
||||
}
|
||||
}
|
||||
else return "wifi_off"
|
||||
}
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: root.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: true
|
||||
}
|
||||
|
||||
// Audio Icon
|
||||
Text {
|
||||
text: root.volumeLevel === 0 ? "volume_off" :
|
||||
root.volumeLevel < 33 ? "volume_down" : "volume_up"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: controlCenterArea.containsMouse || root.isActive ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
// Microphone Icon (when active)
|
||||
Text {
|
||||
text: "mic"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false // TODO: Add mic detection
|
||||
}
|
||||
|
||||
// Bluetooth Icon (when available and enabled)
|
||||
Text {
|
||||
text: "bluetooth"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 8
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: root.bluetoothEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.bluetoothAvailable && root.bluetoothEnabled
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlCenterArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Widgets/TopBar/LauncherButton.qml
Normal file
41
Widgets/TopBar/LauncherButton.qml
Normal file
@@ -0,0 +1,41 @@
|
||||
import QtQuick
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
|
||||
|
||||
property string osLogo: ""
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root.osLogo || "apps"
|
||||
font.family: root.osLogo ? "NerdFont" : Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
LauncherService.toggleAppLauncher()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
58
Widgets/TopBar/NotificationCenterButton.qml
Normal file
58
Widgets/TopBar/NotificationCenterButton.qml
Normal file
@@ -0,0 +1,58 @@
|
||||
import QtQuick
|
||||
import "../../Common"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool hasUnread: false
|
||||
property bool isActive: false
|
||||
signal clicked()
|
||||
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: notificationArea.containsMouse || root.isActive ?
|
||||
Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) :
|
||||
Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "notifications"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: notificationArea.containsMouse || root.isActive ?
|
||||
Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
// Notification dot indicator
|
||||
Rectangle {
|
||||
width: 8
|
||||
height: 8
|
||||
radius: 4
|
||||
color: Theme.error
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: 6
|
||||
anchors.topMargin: 6
|
||||
visible: root.hasUnread
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: notificationArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
root.clicked()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Widgets/TopBar/SystemTrayWidget.qml
Normal file
100
Widgets/TopBar/SystemTrayWidget.qml
Normal file
@@ -0,0 +1,100 @@
|
||||
import QtQuick
|
||||
import Quickshell.Services.SystemTray
|
||||
import "../../Common"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
signal menuRequested(var menu, var item, real x, real y)
|
||||
|
||||
width: Math.max(40, systemTrayRow.implicitWidth + Theme.spacingS * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
visible: systemTrayRow.children.length > 0
|
||||
|
||||
Row {
|
||||
id: systemTrayRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
delegate: Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: trayItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
property var trayItem: modelData
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 18
|
||||
height: 18
|
||||
source: {
|
||||
let icon = trayItem?.icon || "";
|
||||
if (!icon) return "";
|
||||
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=");
|
||||
const fileName = name.substring(name.lastIndexOf("/") + 1);
|
||||
return `file://${path}/${fileName}`;
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
asynchronous: true
|
||||
smooth: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: trayItemArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (!trayItem) return;
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
if (!trayItem.onlyMenu) {
|
||||
trayItem.activate()
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (trayItem.hasMenu) {
|
||||
console.log("Right-click detected, showing menu for:", trayItem.title || "Unknown")
|
||||
customTrayMenu.showMenu(mouse.x, mouse.y)
|
||||
} else {
|
||||
console.log("No menu available for:", trayItem.title || "Unknown")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: customTrayMenu
|
||||
|
||||
property bool menuVisible: false
|
||||
|
||||
function showMenu(x, y) {
|
||||
root.menuRequested(customTrayMenu, trayItem, x, y)
|
||||
menuVisible = true
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
menuVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
292
Widgets/TopBar/TopBar.qml
Normal file
292
Widgets/TopBar/TopBar.qml
Normal file
@@ -0,0 +1,292 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Services.Notifications
|
||||
import Quickshell.Services.Mpris
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
import ".."
|
||||
|
||||
PanelWindow {
|
||||
id: topBar
|
||||
|
||||
property var modelData
|
||||
screen: modelData
|
||||
property string screenName: modelData.name
|
||||
|
||||
// Properties exposed to shell
|
||||
property bool hasActiveMedia: false
|
||||
property var activePlayer: null
|
||||
property bool weatherAvailable: false
|
||||
property string weatherCode: ""
|
||||
property int weatherTemp: 0
|
||||
property int weatherTempF: 0
|
||||
property bool useFahrenheit: false
|
||||
property string osLogo: ""
|
||||
property string networkStatus: "disconnected"
|
||||
property string wifiSignalStrength: "good"
|
||||
property int volumeLevel: 50
|
||||
property bool bluetoothAvailable: false
|
||||
property bool bluetoothEnabled: false
|
||||
|
||||
// Notification properties
|
||||
property bool notificationHistoryVisible: false
|
||||
property int notificationCount: 0
|
||||
|
||||
// Control center properties
|
||||
property bool controlCenterVisible: false
|
||||
|
||||
// Calendar properties
|
||||
property bool calendarVisible: false
|
||||
|
||||
// Clipboard properties
|
||||
signal clipboardRequested()
|
||||
|
||||
// Tray menu properties
|
||||
property bool showTrayMenu: false
|
||||
property var currentTrayMenu: null
|
||||
property var currentTrayItem: null
|
||||
property real trayMenuX: 0
|
||||
property real trayMenuY: 0
|
||||
|
||||
// Proxy objects for external connections
|
||||
|
||||
QtObject {
|
||||
id: notificationHistory
|
||||
property int count: 0
|
||||
}
|
||||
|
||||
// Battery widget and other widgets are imported from their original locations
|
||||
// These will be handled by the parent shell
|
||||
property alias batteryWidget: batteryWidgetProxy
|
||||
property alias cpuMonitorWidget: cpuMonitorWidgetProxy
|
||||
property alias ramMonitorWidget: ramMonitorWidgetProxy
|
||||
property alias powerButton: powerButtonProxy
|
||||
|
||||
QtObject { id: batteryWidgetProxy }
|
||||
QtObject { id: cpuMonitorWidgetProxy }
|
||||
QtObject { id: ramMonitorWidgetProxy }
|
||||
QtObject { id: powerButtonProxy }
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
implicitHeight: Theme.barHeight - 4
|
||||
color: "transparent"
|
||||
|
||||
// Floating panel container with margins
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 2
|
||||
anchors.topMargin: 6
|
||||
anchors.bottomMargin: 0
|
||||
anchors.leftMargin: 8
|
||||
anchors.rightMargin: 8
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.75)
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DropShadow {
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
radius: 16
|
||||
samples: 33
|
||||
color: Qt.rgba(0, 0, 0, 0.15)
|
||||
transparentBorder: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
radius: parent.radius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.08
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 0.02
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingXS
|
||||
anchors.bottomMargin: Theme.spacingXS
|
||||
|
||||
Row {
|
||||
id: leftSection
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
LauncherButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
osLogo: topBar.osLogo
|
||||
}
|
||||
|
||||
WorkspaceSwitcher {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
screenName: topBar.screenName
|
||||
}
|
||||
}
|
||||
|
||||
ClockWidget {
|
||||
id: clockWidget
|
||||
anchors.centerIn: parent
|
||||
hasActiveMedia: topBar.hasActiveMedia
|
||||
activePlayer: topBar.activePlayer
|
||||
weatherAvailable: topBar.weatherAvailable
|
||||
weatherCode: topBar.weatherCode
|
||||
weatherTemp: topBar.weatherTemp
|
||||
weatherTempF: topBar.weatherTempF
|
||||
useFahrenheit: topBar.useFahrenheit
|
||||
|
||||
onClockClicked: {
|
||||
topBar.calendarVisible = !topBar.calendarVisible
|
||||
}
|
||||
|
||||
// Insert audio visualization into the clock widget
|
||||
AudioVisualization {
|
||||
parent: clockWidget.children[0].children[0] // Row -> Row (media info)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
hasActiveMedia: topBar.hasActiveMedia
|
||||
activePlayer: topBar.activePlayer
|
||||
visible: topBar.hasActiveMedia
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightSection
|
||||
height: parent.height
|
||||
spacing: Theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SystemTrayWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onMenuRequested: (menu, item, x, y) => {
|
||||
topBar.currentTrayMenu = menu
|
||||
topBar.currentTrayItem = item
|
||||
topBar.trayMenuX = rightSection.x + rightSection.width - 400 - Theme.spacingL
|
||||
topBar.trayMenuY = Theme.barHeight + Theme.spacingS
|
||||
console.log("Showing menu at:", topBar.trayMenuX, topBar.trayMenuY)
|
||||
menu.menuVisible = true
|
||||
topBar.showTrayMenu = true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: clipboardArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "content_paste"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clipboardArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
topBar.clipboardRequested()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// System Monitor Widgets
|
||||
CpuMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
RamMonitorWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NotificationCenterButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
hasUnread: topBar.notificationCount > 0
|
||||
isActive: topBar.notificationHistoryVisible
|
||||
onClicked: {
|
||||
topBar.notificationHistoryVisible = !topBar.notificationHistoryVisible
|
||||
}
|
||||
}
|
||||
|
||||
// Battery Widget
|
||||
BatteryWidget {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ControlCenterButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
networkStatus: topBar.networkStatus
|
||||
wifiSignalStrength: topBar.wifiSignalStrength
|
||||
volumeLevel: topBar.volumeLevel
|
||||
bluetoothAvailable: topBar.bluetoothAvailable
|
||||
bluetoothEnabled: topBar.bluetoothEnabled
|
||||
isActive: topBar.controlCenterVisible
|
||||
|
||||
onClicked: {
|
||||
topBar.controlCenterVisible = !topBar.controlCenterVisible
|
||||
if (topBar.controlCenterVisible) {
|
||||
WifiService.scanWifi()
|
||||
BluetoothService.scanDevices()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Power Button
|
||||
PowerButton {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
Widgets/TopBar/WorkspaceSwitcher.qml
Normal file
123
Widgets/TopBar/WorkspaceSwitcher.qml
Normal file
@@ -0,0 +1,123 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import "../../Common"
|
||||
import "../../Services"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property string screenName: ""
|
||||
|
||||
width: Math.max(120, workspaceRow.implicitWidth + Theme.spacingL * 2)
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.8)
|
||||
visible: NiriWorkspaceService.niriAvailable
|
||||
|
||||
property int currentWorkspace: getDisplayActiveWorkspace()
|
||||
property var workspaceList: getDisplayWorkspaces()
|
||||
|
||||
function getDisplayWorkspaces() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return [1, 2]
|
||||
}
|
||||
|
||||
if (!root.screenName) {
|
||||
return NiriWorkspaceService.getCurrentOutputWorkspaceNumbers()
|
||||
}
|
||||
|
||||
var displayWorkspaces = []
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === root.screenName) {
|
||||
displayWorkspaces.push(ws.idx + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return displayWorkspaces.length > 0 ? displayWorkspaces : [1, 2]
|
||||
}
|
||||
|
||||
function getDisplayActiveWorkspace() {
|
||||
if (!NiriWorkspaceService.niriAvailable || NiriWorkspaceService.allWorkspaces.length === 0) {
|
||||
return 1
|
||||
}
|
||||
|
||||
if (!root.screenName) {
|
||||
return NiriWorkspaceService.getCurrentWorkspaceNumber()
|
||||
}
|
||||
|
||||
for (var i = 0; i < NiriWorkspaceService.allWorkspaces.length; i++) {
|
||||
var ws = NiriWorkspaceService.allWorkspaces[i]
|
||||
if (ws.output === root.screenName && ws.is_active) {
|
||||
return ws.idx + 1
|
||||
}
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NiriWorkspaceService
|
||||
function onAllWorkspacesChanged() {
|
||||
root.workspaceList = root.getDisplayWorkspaces()
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
}
|
||||
function onFocusedWorkspaceIndexChanged() {
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
}
|
||||
function onNiriAvailableChanged() {
|
||||
if (NiriWorkspaceService.niriAvailable) {
|
||||
root.workspaceList = root.getDisplayWorkspaces()
|
||||
root.currentWorkspace = root.getDisplayActiveWorkspace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: workspaceRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.workspaceList
|
||||
|
||||
Rectangle {
|
||||
property bool isActive: modelData === root.currentWorkspace
|
||||
property bool isHovered: mouseArea.containsMouse
|
||||
property int sequentialNumber: index + 1
|
||||
|
||||
width: isActive ? Theme.spacingXL + Theme.spacingS : Theme.spacingL
|
||||
height: Theme.spacingS
|
||||
radius: height / 2
|
||||
color: isActive ? Theme.primary :
|
||||
isHovered ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5) :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
|
||||
|
||||
Behavior on width {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", sequentialNumber.toString()])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Widgets/TopBar/qmldir
Normal file
8
Widgets/TopBar/qmldir
Normal file
@@ -0,0 +1,8 @@
|
||||
TopBar 1.0 TopBar.qml
|
||||
LauncherButton 1.0 LauncherButton.qml
|
||||
WorkspaceSwitcher 1.0 WorkspaceSwitcher.qml
|
||||
ClockWidget 1.0 ClockWidget.qml
|
||||
SystemTrayWidget 1.0 SystemTrayWidget.qml
|
||||
NotificationCenterButton 1.0 NotificationCenterButton.qml
|
||||
ControlCenterButton 1.0 ControlCenterButton.qml
|
||||
AudioVisualization 1.0 AudioVisualization.qml
|
||||
@@ -1,4 +1,4 @@
|
||||
TopBar 1.0 TopBar.qml
|
||||
TopBar 1.0 TopBar/TopBar.qml
|
||||
TrayMenuPopup 1.0 TrayMenuPopup.qml
|
||||
NotificationPopup 1.0 NotificationPopup.qml
|
||||
NotificationHistoryPopup 1.0 NotificationHistoryPopup.qml
|
||||
|
||||
41
shell.qml
41
shell.qml
@@ -283,6 +283,47 @@ ShellRoot {
|
||||
model: Quickshell.screens
|
||||
delegate: TopBar {
|
||||
modelData: item
|
||||
|
||||
// Connect shell properties
|
||||
hasActiveMedia: root.hasActiveMedia
|
||||
activePlayer: root.activePlayer
|
||||
weatherAvailable: root.weather.available
|
||||
weatherCode: root.weather.wCode
|
||||
weatherTemp: root.weather.temp
|
||||
weatherTempF: root.weather.tempF
|
||||
useFahrenheit: root.useFahrenheit
|
||||
osLogo: root.osLogo
|
||||
networkStatus: root.networkStatus
|
||||
wifiSignalStrength: root.wifiSignalStrength
|
||||
volumeLevel: root.volumeLevel
|
||||
bluetoothAvailable: root.bluetoothAvailable
|
||||
bluetoothEnabled: root.bluetoothEnabled
|
||||
notificationHistoryVisible: root.notificationHistoryVisible
|
||||
notificationCount: notificationHistory.count
|
||||
controlCenterVisible: root.controlCenterVisible
|
||||
calendarVisible: root.calendarVisible
|
||||
|
||||
// Connect tray menu properties
|
||||
showTrayMenu: root.showTrayMenu
|
||||
currentTrayMenu: root.currentTrayMenu
|
||||
currentTrayItem: root.currentTrayItem
|
||||
trayMenuX: root.trayMenuX
|
||||
trayMenuY: root.trayMenuY
|
||||
|
||||
// Connect clipboard
|
||||
onClipboardRequested: {
|
||||
clipboardHistoryPopup.toggle()
|
||||
}
|
||||
|
||||
// Property change handlers
|
||||
onCalendarVisibleChanged: root.calendarVisible = calendarVisible
|
||||
onControlCenterVisibleChanged: root.controlCenterVisible = controlCenterVisible
|
||||
onNotificationHistoryVisibleChanged: root.notificationHistoryVisible = notificationHistoryVisible
|
||||
onShowTrayMenuChanged: root.showTrayMenu = showTrayMenu
|
||||
onCurrentTrayMenuChanged: root.currentTrayMenu = currentTrayMenu
|
||||
onCurrentTrayItemChanged: root.currentTrayItem = currentTrayItem
|
||||
onTrayMenuXChanged: root.trayMenuX = trayMenuX
|
||||
onTrayMenuYChanged: root.trayMenuY = trayMenuY
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user