1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

add shell

This commit is contained in:
bbedward
2025-07-10 11:28:27 -04:00
parent 2ef160d210
commit 53687266a1
21 changed files with 10854 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
import QtQuick
import QtQuick.Controls
Rectangle {
id: archLauncher
property var theme
property var root
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"
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: "Applications"
font.pixelSize: theme.fontSizeMedium
font.weight: Font.Medium
color: theme.surfaceText
}
}
MouseArea {
id: launcherArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.appLauncher.toggle()
}
}
Behavior on color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
}
}
}

View File

@@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls
Rectangle {
property var theme
property var root
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: {
root.clipboardHistoryPopup.toggle()
}
}
Behavior on color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
}
}
}

291
Widgets/ClockWidget.qml Normal file
View File

@@ -0,0 +1,291 @@
import QtQuick
import QtQuick.Controls
import Quickshell.Services.Mpris
Rectangle {
id: clockContainer
property var theme
property var root
width: Math.min(root.hasActiveMedia ? 500 : (root.weather.available ? 280 : 200), parent.width - theme.spacingL * 2)
height: root.hasActiveMedia ? 80 : 32
radius: theme.cornerRadius
color: clockMouseArea.containsMouse && root.hasActiveMedia ?
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
}
}
property date currentDate: new Date()
// Media player content (when active)
Column {
visible: root.hasActiveMedia
anchors.centerIn: parent
width: parent.width - theme.spacingM * 2
spacing: theme.spacingXS
Row {
width: parent.width
spacing: theme.spacingS
Rectangle {
width: 48
height: 48
radius: theme.cornerRadiusSmall
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
Item {
anchors.fill: parent
clip: true
Image {
anchors.fill: parent
source: root.activePlayer?.trackArtUrl || ""
fillMode: Image.PreserveAspectCrop
smooth: true
}
Rectangle {
anchors.fill: parent
visible: parent.children[0].status !== Image.Ready
color: "transparent"
Text {
anchors.centerIn: parent
text: "music_note"
font.family: theme.iconFont
font.pixelSize: theme.iconSize
color: theme.surfaceVariantText
}
}
}
}
Column {
width: parent.width - 48 - theme.spacingS - 120
spacing: 2
anchors.verticalCenter: parent.verticalCenter
Text {
text: root.activePlayer?.trackTitle || "Unknown Track"
font.pixelSize: theme.fontSizeMedium
color: theme.surfaceText
font.weight: Font.Medium
width: parent.width
elide: Text.ElideRight
}
Text {
text: root.activePlayer?.trackArtist || "Unknown Artist"
font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
width: parent.width
elide: Text.ElideRight
}
}
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: theme.spacingS
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: root.activePlayer?.previous()
}
}
Rectangle {
width: 28
height: 28
radius: 14
color: theme.primary
Text {
anchors.centerIn: parent
text: root.activePlayer?.playbackState === MprisPlaybackState.Playing ? "pause" : "play_arrow"
font.family: theme.iconFont
font.pixelSize: 16
color: theme.background
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.activePlayer?.togglePlaying()
}
}
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: root.activePlayer?.next()
}
}
}
}
Rectangle {
width: parent.width
height: 4
radius: 2
color: Qt.rgba(theme.surfaceVariant.r, theme.surfaceVariant.g, theme.surfaceVariant.b, 0.3)
Rectangle {
id: progressFill
width: parent.width * (root.activePlayer?.position / Math.max(root.activePlayer?.length || 1, 1))
height: parent.height
radius: parent.radius
color: theme.primary
Behavior on width {
NumberAnimation {
duration: 200
easing.type: Easing.OutQuad
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: (mouse) => {
if (root.activePlayer && root.activePlayer.length > 0) {
const newPosition = (mouse.x / width) * root.activePlayer.length
root.activePlayer.setPosition(newPosition)
}
}
}
}
}
// Normal clock/weather content (when no media)
Row {
anchors.centerIn: parent
spacing: theme.spacingM
visible: !root.hasActiveMedia
// Weather info (when available)
Row {
spacing: theme.spacingXS
visible: root.weather.available
anchors.verticalCenter: parent.verticalCenter
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
}
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
}
}
// Separator when weather is available
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.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: !root.hasActiveMedia ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: !root.hasActiveMedia
onClicked: {
root.calendarVisible = !root.calendarVisible
}
}
}

View File

@@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls
Rectangle {
property var theme
property var root
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"
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: {
root.colorPickerProcess.running = true
}
}
Behavior on color {
ColorAnimation {
duration: theme.shortDuration
easing.type: theme.standardEasing
}
}
}

View File

@@ -0,0 +1,57 @@
import QtQuick
import QtQuick.Controls
Rectangle {
property var theme
property var root
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: root.notificationHistory.count > 0
Text {
anchors.centerIn: parent
text: "notifications"
font.family: theme.iconFont
font.pixelSize: theme.iconSize - 6
font.weight: theme.iconFontWeight
color: notificationArea.containsMouse || root.notificationHistoryVisible ?
theme.primary : theme.surfaceText
}
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
}
}
}

View File

@@ -0,0 +1,111 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Services.SystemTray
Rectangle {
property var theme
property var root
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")
}
}
}
}
QtObject {
id: customTrayMenu
property bool menuVisible: false
function showMenu(x, y) {
root.currentTrayMenu = customTrayMenu
root.currentTrayItem = trayItem
root.trayMenuX = parent.parent.parent.parent.x + parent.parent.parent.parent.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
}
}
}
}
}
}

117
Widgets/TopBar.qml Normal file
View File

@@ -0,0 +1,117 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Widgets
import Quickshell.Wayland
import Quickshell.Services.SystemTray
import "../Services"
PanelWindow {
id: topBar
property var theme
property var root
anchors {
top: true
left: true
right: true
}
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
}
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
}
}
}
}
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
}
}
}
}

111
Widgets/TopBarSimple.qml Normal file
View File

@@ -0,0 +1,111 @@
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
}
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
}
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
}
}
}
}
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
ClipboardButton {
theme: topBar.theme
root: topBar.root
}
ColorPickerButton {
theme: topBar.theme
root: topBar.root
}
NotificationButton {
theme: topBar.theme
root: topBar.root
}
}
}
}

View File

@@ -0,0 +1,145 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Io
Rectangle {
id: workspaceSwitcher
property var theme
property var root
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: SplitParser {
splitMarker: "\n"
onRead: (data) => {
if (data.trim()) {
workspaceSwitcher.parseWorkspaceOutput(data.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
}
}
}
}
currentWorkspace = focusedWorkspace
if (focusedOutput && outputWorkspaces[focusedOutput]) {
workspaceList = outputWorkspaces[focusedOutput]
} else {
workspaceList = [1, 2]
}
}
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: {
switchProcess.command = ["niri", "msg", "action", "focus-workspace", modelData.toString()]
switchProcess.running = true
workspaceSwitcher.currentWorkspace = modelData
Qt.callLater(() => {
workspaceQuery.running = true
})
}
}
}
}
}
Process {
id: switchProcess
running: false
}
}

9
Widgets/qmldir Normal file
View File

@@ -0,0 +1,9 @@
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
SystemTrayWidget 1.0 SystemTrayWidget.qml
ClipboardButton 1.0 ClipboardButton.qml
ColorPickerButton 1.0 ColorPickerButton.qml
NotificationButton 1.0 NotificationButton.qml