1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

iBluetooth repair, input device support, some media player improvements

This commit is contained in:
bbedward
2025-07-11 13:50:10 -04:00
parent b4f73ceb7b
commit d169f5d4a3
14 changed files with 1533 additions and 829 deletions

View File

@@ -13,6 +13,18 @@ PanelWindow {
visible: root.calendarVisible
// Timer to update MPRIS position like the example
Timer {
running: root.activePlayer?.playbackState === MprisPlaybackState.Playing
interval: 1000
repeat: true
onTriggered: {
if (root.activePlayer) {
root.activePlayer.positionChanged()
}
}
}
implicitWidth: 320
implicitHeight: 400
@@ -162,13 +174,6 @@ PanelWindow {
height: parent.height
radius: parent.radius
color: Theme.primary
Behavior on width {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
MouseArea {
@@ -180,11 +185,7 @@ PanelWindow {
const ratio = mouse.x / width
const newPosition = ratio * root.activePlayer.length
console.log("Seeking to position:", newPosition, "ratio:", ratio, "canSeek:", root.activePlayer.canSeek)
if (root.activePlayer.canSeek) {
root.activePlayer.position = newPosition
} else {
console.log("Player does not support seeking")
}
root.activePlayer.setPosition(newPosition)
}
}
}
@@ -214,7 +215,16 @@ PanelWindow {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activePlayer?.previous()
onClicked: {
if (!root.activePlayer) return
// >8 s → jump to start, otherwise previous track
if (root.activePlayer.position > 8000000) {
root.activePlayer.setPosition(0)
} else {
root.activePlayer.previous()
}
}
}
}

View File

@@ -0,0 +1,174 @@
import QtQuick
import QtQuick.Controls
import "../../Common"
Column {
id: calendarWidget
property var theme: Theme
property date displayDate: new Date()
property date selectedDate: new Date()
spacing: theme.spacingM
// Month navigation header
Row {
width: parent.width
height: 40
Rectangle {
width: 40
height: 40
radius: theme.cornerRadius
color: prevMonthArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "chevron_left"
font.family: theme.iconFont
font.pixelSize: theme.iconSize
color: theme.primary
font.weight: theme.iconFontWeight
}
MouseArea {
id: prevMonthArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
let newDate = new Date(displayDate)
newDate.setMonth(newDate.getMonth() - 1)
displayDate = newDate
}
}
}
Text {
width: parent.width - 80
height: 40
text: Qt.formatDate(displayDate, "MMMM yyyy")
font.pixelSize: theme.fontSizeLarge
color: theme.surfaceText
font.weight: Font.Medium
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
Rectangle {
width: 40
height: 40
radius: theme.cornerRadius
color: nextMonthArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "chevron_right"
font.family: theme.iconFont
font.pixelSize: theme.iconSize
color: theme.primary
font.weight: theme.iconFontWeight
}
MouseArea {
id: nextMonthArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
let newDate = new Date(displayDate)
newDate.setMonth(newDate.getMonth() + 1)
displayDate = newDate
}
}
}
}
// Days of week header
Row {
width: parent.width
height: 32
Repeater {
model: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
Rectangle {
width: parent.width / 7
height: 32
color: "transparent"
Text {
anchors.centerIn: parent
text: modelData
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
font.weight: Font.Medium
}
}
}
}
// Calendar grid
Grid {
width: parent.width
height: 200 // Fixed height for calendar
columns: 7
rows: 6
property date firstDay: {
let date = new Date(displayDate.getFullYear(), displayDate.getMonth(), 1)
let dayOfWeek = date.getDay()
date.setDate(date.getDate() - dayOfWeek)
return date
}
Repeater {
model: 42
Rectangle {
width: parent.width / 7
height: parent.height / 6
property date dayDate: {
let date = new Date(parent.firstDay)
date.setDate(date.getDate() + index)
return date
}
property bool isCurrentMonth: dayDate.getMonth() === displayDate.getMonth()
property bool isToday: dayDate.toDateString() === new Date().toDateString()
property bool isSelected: dayDate.toDateString() === selectedDate.toDateString()
color: isSelected ? theme.primary :
isToday ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.12) :
dayArea.containsMouse ? Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.08) : "transparent"
radius: theme.cornerRadiusSmall
Text {
anchors.centerIn: parent
text: dayDate.getDate()
font.pixelSize: theme.fontSizeMedium
color: isSelected ? theme.surface :
isToday ? theme.primary :
isCurrentMonth ? theme.surfaceText :
Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.4)
font.weight: isToday || isSelected ? Font.Medium : Font.Normal
}
MouseArea {
id: dayArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
selectedDate = dayDate
}
}
}
}
}
}

View File

@@ -0,0 +1,117 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Services.Mpris
import "../../Common"
import "../../Services"
PanelWindow {
id: centerCommandCenter
property var theme: Theme
property bool hasActiveMedia: root.hasActiveMedia
property var weather: root.weather
property bool useFahrenheit: false
// Prevent media player from disappearing during track changes
property bool showMediaPlayer: hasActiveMedia || hideMediaTimer.running
Timer {
id: hideMediaTimer
interval: 3000 // 3 second grace period
running: false
repeat: false
}
onHasActiveMediaChanged: {
if (hasActiveMedia) {
hideMediaTimer.stop()
} else {
hideMediaTimer.start()
}
}
visible: root.calendarVisible
implicitWidth: 320
implicitHeight: 400
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
color: "transparent"
anchors {
top: true
left: true
right: true
bottom: true
}
Rectangle {
width: 400
height: showMediaPlayer ? 540 : (weather?.available ? 480 : 400)
x: (parent.width - width) / 2
y: theme.barHeight + theme.spacingS
color: theme.surfaceContainer
radius: theme.cornerRadiusLarge
border.color: Qt.rgba(theme.outline.r, theme.outline.g, theme.outline.b, 0.12)
border.width: 1
opacity: root.calendarVisible ? 1.0 : 0.0
scale: root.calendarVisible ? 1.0 : 0.85
Behavior on opacity {
NumberAnimation {
duration: theme.mediumDuration
easing.type: theme.emphasizedEasing
}
}
Behavior on scale {
NumberAnimation {
duration: theme.mediumDuration
easing.type: theme.emphasizedEasing
}
}
Column {
anchors.fill: parent
anchors.margins: theme.spacingL
spacing: theme.spacingM
// Media Player (when active)
MediaPlayerWidget {
visible: showMediaPlayer
theme: centerCommandCenter.theme
}
// Weather header (when available and no media)
WeatherWidget {
visible: weather?.available && !showMediaPlayer
theme: centerCommandCenter.theme
weather: centerCommandCenter.weather
useFahrenheit: centerCommandCenter.useFahrenheit
}
// Calendar
CalendarWidget {
width: parent.width
height: showMediaPlayer ? parent.height - 200 : (weather?.available ? parent.height - 120 : parent.height - 40)
theme: centerCommandCenter.theme
}
}
}
MouseArea {
anchors.fill: parent
z: -1
onClicked: {
root.calendarVisible = false
}
}
}

View File

@@ -0,0 +1,269 @@
import QtQuick
import QtQuick.Controls
import Qt5Compat.GraphicalEffects
import Quickshell
import Quickshell.Services.Mpris
import "../../Common"
import "../../Services"
Rectangle {
id: mediaPlayerWidget
property MprisPlayer activePlayer: MprisController.activePlayer
property var theme: Theme
width: parent.width
height: 160 // Reduced height to prevent overflow
radius: theme.cornerRadius
color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.08)
border.color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.2)
border.width: 1
// Timer to update MPRIS position
property bool justSeeked: false
property real seekTargetPosition: 0
Timer {
id: positionTimer
running: activePlayer?.playbackState === MprisPlaybackState.Playing && !justSeeked
interval: 1000
repeat: true
onTriggered: {
if (activePlayer) {
activePlayer.positionChanged()
}
}
}
// Timer to resume position updates after seeking
Timer {
id: seekCooldownTimer
interval: 1000 // Reduced from 2000
repeat: false
onTriggered: {
justSeeked = false
// Force position update after seek
if (activePlayer) {
activePlayer.positionChanged()
}
}
}
Column {
anchors.fill: parent
anchors.margins: theme.spacingM
spacing: theme.spacingM
// Album art and track info
Row {
width: parent.width
height: 70 // Reduced height
spacing: theme.spacingM
// Album Art
Rectangle {
width: 70
height: 70
radius: theme.cornerRadius
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
Item {
anchors.fill: parent
clip: true
Image {
id: albumArt
anchors.fill: parent
source: activePlayer?.trackArtUrl || ""
fillMode: Image.PreserveAspectCrop
smooth: true
}
Rectangle {
anchors.fill: parent
visible: albumArt.status !== Image.Ready
color: "transparent"
Text {
anchors.centerIn: parent
text: "album"
font.family: theme.iconFont
font.pixelSize: 28
color: theme.surfaceVariantText
}
}
}
}
// Track Info
Column {
width: parent.width - 70 - theme.spacingM
spacing: theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
Text {
text: activePlayer?.trackTitle || "Unknown Track"
font.pixelSize: theme.fontSizeMedium
font.weight: Font.Bold
color: theme.surfaceText
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer?.trackArtist || "Unknown Artist"
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
}
Text {
text: activePlayer?.trackAlbum || ""
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
width: parent.width
elide: Text.ElideRight
visible: text.length > 0
}
}
}
// Simple progress bar - click to seek only
Rectangle {
width: parent.width
height: 6
radius: 3
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
Rectangle {
width: {
if (!activePlayer || !activePlayer.length || activePlayer.length === 0) return 0
// Use seek target position if we just seeked
const currentPos = justSeeked ? seekTargetPosition : activePlayer.position
return Math.max(0, Math.min(parent.width, parent.width * (currentPos / activePlayer.length)))
}
height: parent.height
radius: parent.radius
color: theme.primary
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (activePlayer && activePlayer.length > 0 && activePlayer.canSeek) {
const ratio = mouse.x / width
const targetPosition = Math.floor(ratio * activePlayer.length)
const currentPosition = activePlayer.position || 0
const seekOffset = targetPosition - currentPosition
console.log("Simple seek - offset:", seekOffset, "target:", targetPosition, "current:", currentPosition)
// Store target position for visual feedback
seekTargetPosition = targetPosition
justSeeked = true
seekCooldownTimer.restart()
activePlayer.seek(seekOffset)
}
}
}
}
// Control buttons - compact to fit
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: theme.spacingL
// Previous button
Rectangle {
width: 28
height: 28
radius: 14
color: prevBtnArea.containsMouse ? Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_previous"
font.family: theme.iconFont
font.pixelSize: 16
color: theme.surfaceText
}
MouseArea {
id: prevBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (!activePlayer) return
// >8 s → jump to start, otherwise previous track
if (activePlayer.position > 8000000) {
console.log("Jumping to start - current position:", activePlayer.position)
// Store target position for visual feedback
seekTargetPosition = 0
justSeeked = true
seekCooldownTimer.restart()
// Seek to the beginning
activePlayer.seek(-activePlayer.position)
} else {
activePlayer.previous()
}
}
}
}
// Play/Pause button
Rectangle {
width: 36
height: 36
radius: 18
color: theme.primary
Text {
anchors.centerIn: parent
text: activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
font.family: theme.iconFont
font.pixelSize: 20
color: theme.background
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer?.togglePlaying()
}
}
// Next button
Rectangle {
width: 28
height: 28
radius: 14
color: nextBtnArea.containsMouse ? Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.12) : "transparent"
Text {
anchors.centerIn: parent
text: "skip_next"
font.family: theme.iconFont
font.pixelSize: 16
color: theme.surfaceText
}
MouseArea {
id: nextBtnArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: activePlayer?.next()
}
}
}
}
}

View File

@@ -0,0 +1,135 @@
import QtQuick
import QtQuick.Controls
import "../../Common"
import "../../Services"
Rectangle {
id: weatherWidget
property var theme: Theme
property var weather
property bool useFahrenheit: false
width: parent.width
height: 80
radius: theme.cornerRadius
color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.08)
border.color: Qt.rgba(theme.primary.r, theme.primary.g, theme.primary.b, 0.2)
border.width: 1
Row {
anchors.centerIn: parent
spacing: theme.spacingL
// Weather icon and temp
Column {
spacing: 2
anchors.verticalCenter: parent.verticalCenter
Text {
text: WeatherService.getWeatherIcon(weather.wCode)
font.family: theme.iconFont
font.pixelSize: theme.iconSize + 4
color: theme.primary
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
text: (useFahrenheit ? weather.tempF : weather.temp) + "°" + (useFahrenheit ? "F" : "C")
font.pixelSize: theme.fontSizeLarge
color: theme.surfaceText
font.weight: Font.Bold
anchors.horizontalCenter: parent.horizontalCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: useFahrenheit = !useFahrenheit
}
}
Text {
text: weather.city
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Weather details grid
Grid {
columns: 2
spacing: theme.spacingS
anchors.verticalCenter: parent.verticalCenter
Row {
spacing: theme.spacingXS
Text {
text: "humidity_low"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather.humidity + "%"
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
Text {
text: "air"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather.wind
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
Text {
text: "wb_twilight"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather.sunrise
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: theme.spacingXS
Text {
text: "bedtime"
font.family: theme.iconFont
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: weather.sunset
font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}

View File

@@ -0,0 +1,4 @@
CenterCommandCenter 1.0 CenterCommandCenter.qml
MediaPlayerWidget 1.0 MediaPlayerWidget.qml
WeatherWidget 1.0 WeatherWidget.qml
CalendarWidget 1.0 CalendarWidget.qml

View File

@@ -828,232 +828,532 @@ PanelWindow {
}
// Audio Tab
ScrollView {
Item {
id: audioTabContainer
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === 1
clip: true
property int audioSubTab: 0 // 0: Output, 1: Input
Column {
width: parent.width
spacing: Theme.spacingL
anchors.fill: parent
spacing: Theme.spacingM
// Volume Control
Column {
// Audio Sub-tabs
Row {
width: parent.width
spacing: Theme.spacingM
height: 40
spacing: 2
Text {
text: "Volume"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: Theme.spacingM
Rectangle {
width: parent.width / 2 - 1
height: parent.height
radius: Theme.cornerRadius
color: audioTabContainer.audioSubTab === 0 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text {
text: "volume_down"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
anchors.centerIn: parent
text: "Output"
font.pixelSize: Theme.fontSizeMedium
color: audioTabContainer.audioSubTab === 0 ? Theme.primaryText : Theme.surfaceText
font.weight: Font.Medium
}
Rectangle {
id: volumeSliderTrack
width: parent.width - 80
height: 8
radius: 4
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: volumeSliderFill
width: parent.width * (root.volumeLevel / 100)
height: parent.height
radius: parent.radius
color: Theme.primary
Behavior on width {
NumberAnimation { duration: 100 }
}
}
// Draggable handle
Rectangle {
id: volumeHandle
width: 18
height: 18
radius: 9
color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 2
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width/2))
anchors.verticalCenter: parent.verticalCenter
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
MouseArea {
id: volumeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newVolume = Math.round(ratio * 100)
AudioService.setVolume(newVolume)
}
onPositionChanged: (mouse) => {
if (pressed) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newVolume = Math.round(ratio * 100)
AudioService.setVolume(newVolume)
}
}
}
}
Text {
text: "volume_up"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: audioTabContainer.audioSubTab = 0
}
}
Text {
text: root.volumeLevel + "%"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
Rectangle {
width: parent.width / 2 - 1
height: parent.height
radius: Theme.cornerRadius
color: audioTabContainer.audioSubTab === 1 ? Theme.primary : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Text {
anchors.centerIn: parent
text: "Input"
font.pixelSize: Theme.fontSizeMedium
color: audioTabContainer.audioSubTab === 1 ? Theme.primaryText : Theme.surfaceText
font.weight: Font.Medium
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: audioTabContainer.audioSubTab = 1
}
}
}
// Output Devices
Column {
// Output Tab Content
ScrollView {
width: parent.width
spacing: Theme.spacingM
height: parent.height - 48
visible: audioTabContainer.audioSubTab === 0
clip: true
Text {
text: "Output Device"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
// Current device indicator
Rectangle {
Column {
width: parent.width
height: 35
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 1
visible: root.currentAudioSink !== ""
spacing: Theme.spacingL
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
// Volume Control
Column {
width: parent.width
spacing: Theme.spacingM
Text {
text: "check_circle"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.primary
}
Text {
text: "Current: " + (function() {
for (let sink of root.audioSinks) {
if (sink.name === root.currentAudioSink) {
return sink.displayName
}
}
return root.currentAudioSink
})()
font.pixelSize: Theme.fontSizeMedium
color: Theme.primary
text: "Volume"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
}
}
// Real audio devices
Repeater {
model: root.audioSinks
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
(modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
border.color: modelData.active ? Theme.primary : "transparent"
border.width: 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
width: parent.width
spacing: Theme.spacingM
Text {
text: {
if (modelData.name.includes("bluez")) return "headset"
else if (modelData.name.includes("hdmi")) return "tv"
else if (modelData.name.includes("usb")) return "headset"
else return "speaker"
}
text: "volume_down"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: modelData.active ? Theme.primary : Theme.surfaceText
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 2
Rectangle {
id: volumeSliderTrack
width: parent.width - 80
height: 8
radius: 4
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: volumeSliderFill
width: parent.width * (root.volumeLevel / 100)
height: parent.height
radius: parent.radius
color: Theme.primary
Behavior on width {
NumberAnimation { duration: 100 }
}
}
// Draggable handle
Rectangle {
id: volumeHandle
width: 18
height: 18
radius: 9
color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 2
x: Math.max(0, Math.min(parent.width - width, volumeSliderFill.width - width/2))
anchors.verticalCenter: parent.verticalCenter
scale: volumeMouseArea.containsMouse || volumeMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
MouseArea {
id: volumeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newVolume = Math.round(ratio * 100)
AudioService.setVolume(newVolume)
}
onPositionChanged: (mouse) => {
if (pressed) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newVolume = Math.round(ratio * 100)
AudioService.setVolume(newVolume)
}
}
}
}
Text {
text: "volume_up"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
text: root.volumeLevel + "%"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Output Devices
Column {
width: parent.width
spacing: Theme.spacingM
Text {
text: "Output Device"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
// Current device indicator
Rectangle {
width: parent.width
height: 35
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 1
visible: root.currentAudioSink !== ""
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Text {
text: modelData.displayName
font.pixelSize: Theme.fontSizeMedium
color: modelData.active ? Theme.primary : Theme.surfaceText
font.weight: modelData.active ? Font.Medium : Font.Normal
text: "check_circle"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.primary
}
Text {
text: modelData.active ? "Selected" : ""
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
visible: modelData.active
text: "Current: " + (function() {
for (let sink of root.audioSinks) {
if (sink.name === root.currentAudioSink) {
return sink.displayName
}
}
return root.currentAudioSink
})()
font.pixelSize: Theme.fontSizeMedium
color: Theme.primary
font.weight: Font.Medium
}
}
}
MouseArea {
id: deviceArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
// Real audio devices
Repeater {
model: root.audioSinks
onClicked: {
console.log("Clicked audio device:", JSON.stringify(modelData))
console.log("Device name to set:", modelData.name)
AudioService.setAudioSink(modelData.name)
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: deviceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
(modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
border.color: modelData.active ? Theme.primary : "transparent"
border.width: 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Text {
text: {
if (modelData.name.includes("bluez")) return "headset"
else if (modelData.name.includes("hdmi")) return "tv"
else if (modelData.name.includes("usb")) return "headset"
else return "speaker"
}
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: modelData.active ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 2
anchors.verticalCenter: parent.verticalCenter
Text {
text: modelData.displayName
font.pixelSize: Theme.fontSizeMedium
color: modelData.active ? Theme.primary : Theme.surfaceText
font.weight: modelData.active ? Font.Medium : Font.Normal
}
Text {
text: modelData.active ? "Selected" : ""
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
visible: modelData.active
}
}
}
MouseArea {
id: deviceArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
console.log("Clicked audio device:", JSON.stringify(modelData))
console.log("Device name to set:", modelData.name)
AudioService.setAudioSink(modelData.name)
}
}
}
}
}
}
}
// Input Tab Content
ScrollView {
width: parent.width
height: parent.height - 48
visible: audioTabContainer.audioSubTab === 1
clip: true
Column {
width: parent.width
spacing: Theme.spacingL
// Microphone Level Control
Column {
width: parent.width
spacing: Theme.spacingM
Text {
text: "Microphone Level"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
Row {
width: parent.width
spacing: Theme.spacingM
Text {
text: "mic"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: micSliderTrack
width: parent.width - 80
height: 8
radius: 4
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
id: micSliderFill
width: parent.width * (root.micLevel / 100)
height: parent.height
radius: parent.radius
color: Theme.primary
Behavior on width {
NumberAnimation { duration: 100 }
}
}
// Draggable handle
Rectangle {
id: micHandle
width: 18
height: 18
radius: 9
color: Theme.primary
border.color: Qt.lighter(Theme.primary, 1.3)
border.width: 2
x: Math.max(0, Math.min(parent.width - width, micSliderFill.width - width/2))
anchors.verticalCenter: parent.verticalCenter
scale: micMouseArea.containsMouse || micMouseArea.pressed ? 1.2 : 1.0
Behavior on scale {
NumberAnimation { duration: 150 }
}
}
MouseArea {
id: micMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newMicLevel = Math.round(ratio * 100)
AudioService.setMicLevel(newMicLevel)
}
onPositionChanged: (mouse) => {
if (pressed) {
let ratio = Math.max(0, Math.min(1, mouse.x / width))
let newMicLevel = Math.round(ratio * 100)
AudioService.setMicLevel(newMicLevel)
}
}
}
}
Text {
text: "mic"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Text {
text: root.micLevel + "%"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
}
// Input Devices
Column {
width: parent.width
spacing: Theme.spacingM
Text {
text: "Input Device"
font.pixelSize: Theme.fontSizeLarge
color: Theme.surfaceText
font.weight: Font.Medium
}
// Current device indicator
Rectangle {
width: parent.width
height: 35
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
border.width: 1
visible: root.currentAudioSource !== ""
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
Text {
text: "check_circle"
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4
color: Theme.primary
}
Text {
text: "Current: " + (function() {
for (let source of root.audioSources) {
if (source.name === root.currentAudioSource) {
return source.displayName
}
}
return root.currentAudioSource
})()
font.pixelSize: Theme.fontSizeMedium
color: Theme.primary
font.weight: Font.Medium
}
}
}
// Real audio input devices
Repeater {
model: root.audioSources
Rectangle {
width: parent.width
height: 50
radius: Theme.cornerRadius
color: sourceArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) :
(modelData.active ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08))
border.color: modelData.active ? Theme.primary : "transparent"
border.width: 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Text {
text: {
if (modelData.name.includes("bluez")) return "headset_mic"
else if (modelData.name.includes("usb")) return "headset_mic"
else return "mic"
}
font.family: Theme.iconFont
font.pixelSize: Theme.iconSize
color: modelData.active ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Column {
spacing: 2
anchors.verticalCenter: parent.verticalCenter
Text {
text: modelData.displayName
font.pixelSize: Theme.fontSizeMedium
color: modelData.active ? Theme.primary : Theme.surfaceText
font.weight: modelData.active ? Font.Medium : Font.Normal
}
Text {
text: modelData.active ? "Selected" : ""
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.8)
visible: modelData.active
}
}
}
MouseArea {
id: sourceArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
console.log("Clicked audio source:", JSON.stringify(modelData))
console.log("Source name to set:", modelData.name)
AudioService.setAudioSource(modelData.name)
}
}
}
}
}

View File

@@ -1,139 +0,0 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Services.SystemTray
PanelWindow {
id: topBar
property var theme
property var root
anchors {
top: true
left: true
right: true
}
WlrLayershell.topMargin: 8
WlrLayershell.bottomMargin: 8
WlrLayershell.leftMargin: 16
WlrLayershell.rightMargin: 16
implicitHeight: theme.barHeight - 4
color: "transparent"
Rectangle {
anchors.fill: parent
anchors.margins: 2
anchors.topMargin: 6
anchors.bottomMargin: 2
anchors.leftMargin: 8
anchors.rightMargin: 8
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.spacingL
anchors.rightMargin: theme.spacingL
anchors.topMargin: theme.spacingXS
anchors.bottomMargin: theme.spacingXS
// 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
ClipboardButton {
theme: topBar.theme
root: topBar.root
}
ColorPickerButton {
theme: topBar.theme
root: topBar.root
}
NotificationButton {
theme: topBar.theme
root: topBar.root
}
}
}
}

View File

@@ -1,5 +1,4 @@
TopBar 1.0 TopBar.qml
TopBarSimple 1.0 TopBarSimple.qml
AppLauncherButton 1.0 AppLauncherButton.qml
WorkspaceSwitcher 1.0 WorkspaceSwitcher.qml
ClockWidget 1.0 ClockWidget.qml