mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-09 06:55:37 -05:00
Modularlize the shell
This commit is contained in:
@@ -1,16 +1,25 @@
|
||||
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
|
||||
|
||||
property var theme
|
||||
property var root
|
||||
// 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
|
||||
@@ -18,100 +27,779 @@ PanelWindow {
|
||||
right: true
|
||||
}
|
||||
|
||||
implicitHeight: theme.barHeight
|
||||
implicitHeight: Theme.barHeight
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(theme.surfaceContainer.r, theme.surfaceContainer.g, theme.surfaceContainer.b, 0.95)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.08)
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.12
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 0.06
|
||||
duration: Theme.extraLongDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(theme.surfaceTint.r, theme.surfaceTint.g, theme.surfaceTint.b, 0.08)
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
anchors.rightMargin: Theme.spacingL
|
||||
|
||||
SequentialAnimation on opacity {
|
||||
running: true
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
to: 0.12
|
||||
duration: theme.extraLongDuration
|
||||
easing.type: theme.standardEasing
|
||||
Row {
|
||||
id: leftSection
|
||||
height: parent.height
|
||||
spacing: Theme.spacingL
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Rectangle {
|
||||
id: archLauncher
|
||||
width: Math.max(120, launcherRow.implicitWidth + Theme.spacingM * 2)
|
||||
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
|
||||
|
||||
Row {
|
||||
id: launcherRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.osLogo || "apps" // Use OS logo if detected, fallback to apps icon
|
||||
font.family: root.osLogo ? "NerdFont" : Theme.iconFont
|
||||
font.pixelSize: root.osLogo ? Theme.iconSize - 2 : Theme.iconSize - 2
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.isSmallScreen ? "Apps" : "Applications"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
visible: !root.isSmallScreen || width > 60
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launcherArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
appLauncher.toggle()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
NumberAnimation {
|
||||
to: 0.06
|
||||
duration: theme.extraLongDuration
|
||||
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
|
||||
|
||||
property int currentWorkspace: 1
|
||||
property var workspaceList: []
|
||||
|
||||
Process {
|
||||
id: workspaceQuery
|
||||
command: ["niri", "msg", "workspaces"]
|
||||
running: true
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
workspaceSwitcher.parseWorkspaceOutput(text.trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseWorkspaceOutput(data) {
|
||||
const lines = data.split('\n')
|
||||
let currentOutputName = ""
|
||||
let focusedOutput = ""
|
||||
let focusedWorkspace = 1
|
||||
let outputWorkspaces = {}
|
||||
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('Output "')) {
|
||||
const outputMatch = line.match(/Output "(.+)"/)
|
||||
if (outputMatch) {
|
||||
currentOutputName = outputMatch[1]
|
||||
outputWorkspaces[currentOutputName] = []
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (line.trim() && line.match(/^\s*\*?\s*(\d+)$/)) {
|
||||
const wsMatch = line.match(/^\s*(\*?)\s*(\d+)$/)
|
||||
if (wsMatch) {
|
||||
const isActive = wsMatch[1] === '*'
|
||||
const wsNum = parseInt(wsMatch[2])
|
||||
|
||||
if (currentOutputName && outputWorkspaces[currentOutputName]) {
|
||||
outputWorkspaces[currentOutputName].push(wsNum)
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
focusedOutput = currentOutputName
|
||||
focusedWorkspace = wsNum
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show workspaces for THIS screen only
|
||||
if (topBar.screenName && outputWorkspaces[topBar.screenName]) {
|
||||
workspaceList = outputWorkspaces[topBar.screenName]
|
||||
|
||||
// Always track the active workspace for this display
|
||||
// Parse all lines to find which workspace is active on this display
|
||||
let thisDisplayActiveWorkspace = 1
|
||||
let inThisOutput = false
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('Output "')) {
|
||||
const outputMatch = line.match(/Output "(.+)"/)
|
||||
inThisOutput = outputMatch && outputMatch[1] === topBar.screenName
|
||||
continue
|
||||
}
|
||||
|
||||
if (inThisOutput && line.trim() && line.match(/^\s*\*\s*(\d+)$/)) {
|
||||
const wsMatch = line.match(/^\s*\*\s*(\d+)$/)
|
||||
if (wsMatch) {
|
||||
thisDisplayActiveWorkspace = parseInt(wsMatch[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentWorkspace = thisDisplayActiveWorkspace
|
||||
// console.log("Monitor", topBar.screenName, "active workspace:", thisDisplayActiveWorkspace)
|
||||
} else {
|
||||
// Fallback if screen name not found
|
||||
workspaceList = [1, 2]
|
||||
currentWorkspace = 1
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 500
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
workspaceQuery.running = true
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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: {
|
||||
// Set target workspace and focus monitor first
|
||||
console.log("Clicking workspace", modelData, "on monitor", topBar.screenName)
|
||||
workspaceSwitcher.targetWorkspace = modelData
|
||||
focusMonitorProcess.command = ["niri", "msg", "action", "focus-monitor", topBar.screenName]
|
||||
focusMonitorProcess.running = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: switchProcess
|
||||
running: false
|
||||
|
||||
onExited: {
|
||||
// Update current workspace and refresh query
|
||||
workspaceSwitcher.currentWorkspace = workspaceSwitcher.targetWorkspace
|
||||
Qt.callLater(() => {
|
||||
workspaceQuery.running = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: focusMonitorProcess
|
||||
running: false
|
||||
|
||||
onExited: {
|
||||
// After focusing the monitor, switch to the workspace
|
||||
Qt.callLater(() => {
|
||||
switchProcess.command = ["niri", "msg", "action", "focus-workspace", workspaceSwitcher.targetWorkspace.toString()]
|
||||
switchProcess.running = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
property int targetWorkspace: 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// Music icon when media is playing
|
||||
Text {
|
||||
text: "music_note"
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 2
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.hasActiveMedia
|
||||
|
||||
SequentialAnimation on scale {
|
||||
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation { to: 1.1; duration: 500 }
|
||||
NumberAnimation { to: 1.0; duration: 500 }
|
||||
}
|
||||
}
|
||||
|
||||
// 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: root.weatherIcons[root.weather.wCode] || "clear_day"
|
||||
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 - 180 - 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Color Picker Button
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: colorPickerArea.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: "colorize" // Material icon for color picker
|
||||
font.family: Theme.iconFont
|
||||
font.pixelSize: Theme.iconSize - 6
|
||||
font.weight: Theme.iconFontWeight
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: colorPickerArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
ColorPickerService.pickColor()
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: theme.spacingL
|
||||
anchors.rightMargin: theme.spacingL
|
||||
|
||||
// Left section - Apps and Workspace Switcher
|
||||
Row {
|
||||
id: leftSection
|
||||
height: parent.height
|
||||
spacing: theme.spacingL
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
AppLauncherButton {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
|
||||
WorkspaceSwitcher {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
}
|
||||
|
||||
// Center section - Clock/Media Player
|
||||
ClockWidget {
|
||||
id: clockWidget
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
// Right section - System controls
|
||||
Row {
|
||||
id: rightSection
|
||||
height: parent.height
|
||||
spacing: theme.spacingXS
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
SystemTrayWidget {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
|
||||
ClipboardButton {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
|
||||
ColorPickerButton {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
|
||||
NotificationButton {
|
||||
theme: topBar.theme
|
||||
root: topBar.root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user