fixed notifications font rendering

This commit is contained in:
SomeElse
2026-05-16 21:17:45 +00:00
parent 526607ee8d
commit 0c7b5188a8
15 changed files with 292 additions and 590 deletions

View File

@@ -5,24 +5,15 @@ import Quickshell.Wayland
import Quickshell.Services.UPower
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
readonly property var bat: UPower.displayDevice
readonly property bool batReady: bat && bat.ready && bat.isLaptopBattery
readonly property bool moduleEnabled: batReady
readonly property bool moduleEnabled: false
readonly property real pct: batReady ? bat.percentage * 100 : 72
readonly property int state: batReady ? bat.state : UPowerDeviceState.Unknown
@@ -163,53 +154,11 @@ Rectangle {
}
}
PanelWindow {
PopupPanel {
id: batteryPopup
screen: root.targetScreen
visible: false
color: "transparent"
property bool popupOpen: false
function open() { hideTimer.stop(); visible = true; popupOpen = true }
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: batteryPopup.visible = false
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-battery"
WlrLayershell.exclusiveZone: -1
mask: popupOpen ? null : _noInput
Region { id: _noInput }
TapHandler { onTapped: batteryPopup.close() }
Item {
id: popupClipContainer
readonly property int cardW: 260
readonly property int screenPad: root.barMargin + 10
x: {
let centerX = root.mapToItem(null, 0, 0).x + root.width / 2
let desiredX = centerX - cardW / 2
return Math.max(screenPad,
Math.min(desiredX, parent.width - cardW - screenPad))
}
width: cardW
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
height: parent.height - root.barHeight - 10
clip: true
ns: "battery"
chipRoot: root
cardWidth: 260
Rectangle {
id: cardShadow
@@ -584,6 +533,5 @@ Rectangle {
Item { height: 2 }
}
}
}
}
}

View File

@@ -6,19 +6,9 @@ import Quickshell.Wayland
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
TimezoneProvider { id: timezone }
width: clockLayout.implicitWidth + 24
@@ -92,52 +82,11 @@ Rectangle {
}
}
PanelWindow {
PopupPanel {
id: calendarPopup
screen: root.targetScreen
visible: false
color: "transparent"
property bool popupOpen: false
function open() { hideTimer.stop(); visible = true; popupOpen = true }
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: calendarPopup.visible = false
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-calendar"
WlrLayershell.exclusiveZone: -1
mask: popupOpen ? null : _noInput
Region { id: _noInput }
TapHandler { onTapped: calendarPopup.close() }
Item {
id: calClipContainer
readonly property int cardW: 284
readonly property int screenPad: root.barMargin + 10
x: {
let centerX = root.mapToItem(null, 0, 0).x + root.width / 2
let desiredX = centerX - cardW / 2
return Math.max(screenPad, Math.min(desiredX, parent.width - cardW - screenPad))
}
width: cardW
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
height: parent.height - root.barHeight - 10
clip: true
ns: "calendar"
chipRoot: root
cardWidth: 284
Rectangle {
id: card
@@ -407,6 +356,5 @@ Rectangle {
Item { height: 4 }
}
}
}
}
}

View File

@@ -5,18 +5,11 @@ import Quickshell.Wayland
import Quickshell.Io
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
readonly property var cfg: Cfg.Config
readonly property string configPath:
@@ -43,36 +36,24 @@ Rectangle {
Behavior on color { ColorAnimation { duration: 150 } }
Behavior on border.color { ColorAnimation { duration: 150 } }
property var localPairs: [{ coin: cfg.cryptoCoinId ?? "bitcoin", currency: cfg.cryptoVsCurrency ?? "usd" }]
property var localPairs: [{ coin: "bitcoin", currency: "usd" }]
property int activeCoinIndex: 0
readonly property string localCoinId: localPairs.length > 0
? localPairs[activeCoinIndex % localPairs.length].coin
: (cfg.cryptoCoinId ?? "bitcoin")
: "bitcoin"
readonly property string localActiveCurrency: localPairs.length > 0
? localPairs[activeCoinIndex % localPairs.length].currency
: (cfg.cryptoVsCurrency ?? "usd")
: "usd"
property string localVsCur: cfg.cryptoVsCurrency ?? "usd"
property string localVsCur: "usd"
property int localDecimals: cfg.cryptoDecimals ?? 1
property int localRefreshSec: cfg.cryptoRefreshSec ?? 60
property bool localShowIcon: cfg.cryptoShowIcon ?? true
function resolveWithPersistence() {
const cfgCoin = cfg.cryptoCoinId ?? "bitcoin"
const cfgCur = cfg.cryptoVsCurrency ?? "usd"
let cfgPairs
if (typeof cfg.cryptoPairs !== "undefined" && Array.isArray(cfg.cryptoPairs) && cfg.cryptoPairs.length > 0) {
cfgPairs = cfg.cryptoPairs
} else if (typeof cfg.cryptoCoinIds !== "undefined" && Array.isArray(cfg.cryptoCoinIds) && cfg.cryptoCoinIds.length > 0) {
cfgPairs = cfg.cryptoCoinIds.map(c => ({ coin: c, currency: cfgCur }))
} else {
cfgPairs = [{ coin: cfgCoin, currency: cfgCur }]
}
root.localPairs = cfgPairs
root.localVsCur = cfgPairs.length > 0 ? cfgPairs[0].currency : cfgCur
root.displayCurrency = cfgPairs.length > 0 ? cfgPairs[0].currency : cfgCur
root.localPairs = Array.isArray(cfg.cryptoPairs) && cfg.cryptoPairs.length > 0 ? cfg.cryptoPairs : [{ coin: "bitcoin", currency: "usd" }]
root.localVsCur = root.localPairs[0].currency
root.displayCurrency = root.localPairs[0].currency
root.activeCoinIndex = 0
root.localDecimals = cfg.cryptoDecimals ?? 1
root.localRefreshSec = cfg.cryptoRefreshSec ?? 60
@@ -123,7 +104,7 @@ Rectangle {
property string coinSymbol: localCoinId.toUpperCase().slice(0, 4)
property string coinName: ""
property string lastUpdated: ""
property string displayCurrency: cfg.cryptoVsCurrency ?? "usd"
property string displayCurrency: "usd"
property bool loading: false
property bool loadingVisible: false
property bool hasError: false
@@ -289,54 +270,18 @@ Rectangle {
}
PanelWindow {
PopupPanel {
id: cryptoPopup
screen: root.targetScreen
visible: false
color: "transparent"
ns: "crypto"
chipRoot: root
cardWidth: 300
hasKeyboardFocus: true
property bool popupOpen: false
property real popupCenterX: 0
function open() { popupCenterX = root.mapToItem(null, 0, 0).x + root.width / 2; hideTimer.stop(); visible = true; popupOpen = true }
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: cryptoPopup.visible = false
function open() {
popupCenterX = root.mapToItem(null, 0, 0).x + root.width / 2
_doOpen()
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-crypto"
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
mask: popupOpen ? null : _noInput
Region { id: _noInput }
TapHandler { onTapped: cryptoPopup.close() }
Item {
id: popClip
readonly property int cardW: 300
readonly property int screenPad: root.barMargin + 10
x: {
let cx = cryptoPopup.popupCenterX
return Math.max(screenPad,
Math.min(cx - cardW / 2, parent.width - cardW - screenPad))
}
width: cardW
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
height: parent.height - root.barHeight - 10
clip: true
Rectangle {
id: card
width: parent.width
@@ -1166,19 +1111,9 @@ Rectangle {
MouseArea {
anchors.fill: parent; cursorShape: Qt.PointingHandCursor
onClicked: {
const cfgCoin = cfg.cryptoCoinId ?? "bitcoin"
const cfgCur = cfg.cryptoVsCurrency ?? "usd"
let cfgPairs
if (typeof cfg.cryptoPairs !== "undefined" && Array.isArray(cfg.cryptoPairs) && cfg.cryptoPairs.length > 0) {
cfgPairs = cfg.cryptoPairs
} else if (typeof cfg.cryptoCoinIds !== "undefined" && Array.isArray(cfg.cryptoCoinIds) && cfg.cryptoCoinIds.length > 0) {
cfgPairs = cfg.cryptoCoinIds.map(c => ({ coin: c, currency: cfgCur }))
} else {
cfgPairs = [{ coin: cfgCoin, currency: cfgCur }]
}
root.localPairs = cfgPairs
root.localVsCur = cfgCur
root.displayCurrency = cfgPairs.length > 0 ? cfgPairs[0].currency : cfgCur
root.localPairs = Array.isArray(cfg.cryptoPairs) && cfg.cryptoPairs.length > 0 ? cfg.cryptoPairs : [{ coin: "bitcoin", currency: "usd" }]
root.localVsCur = "usd"
root.displayCurrency = root.localPairs.length > 0 ? root.localPairs[0].currency : "usd"
root.activeCoinIndex = 0
root.localDecimals = cfg.cryptoDecimals ?? 1
root.localRefreshSec = cfg.cryptoRefreshSec ?? 60
@@ -1195,6 +1130,5 @@ Rectangle {
}
}
}
}
}
}

View File

@@ -1,20 +1,11 @@
import QtQuick
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
property var launcher: null
readonly property bool launcherActive: !!launcher && launcher.isOpen

View File

@@ -3,22 +3,13 @@ import QtQuick.Layouts
import Quickshell
import Quickshell.Wayland
import Quickshell.Networking
import Quickshell.Services.Polkit
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
readonly property var wifiDevice: {
for (const dev of (Networking.devices?.values ?? []))
if (dev.type === DeviceType.Wifi) return dev
@@ -159,58 +150,18 @@ Rectangle {
}
}
PanelWindow {
PopupPanel {
id: netPopup
screen: root.targetScreen
visible: false
color: "transparent"
property bool popupOpen: false
ns: "network"
chipRoot: root
cardWidth: 310
function open() {
root.cancelConnect()
hideTimer.stop()
visible = true
popupOpen = true
_doOpen()
if (root.hasWifi && root.wifiDevice)
root.wifiDevice.scannerEnabled = true
}
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: netPopup.visible = false
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-network"
WlrLayershell.exclusiveZone: -1
mask: netPopup.popupOpen ? null : _noInput
Region { id: _noInput }
TapHandler { onTapped: netPopup.close() }
Item {
id: popupClip
readonly property int cardW: 310
readonly property int screenPad: root.barMargin + 10
x: {
var cx = root.mapToItem(null, 0, 0).x + root.width / 2
var dx = cx - cardW / 2
return Math.max(screenPad, Math.min(dx, parent.width - cardW - screenPad))
}
width: cardW
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
height: parent.height - root.barHeight - 10
clip: true
Rectangle {
id: card
@@ -723,6 +674,5 @@ Rectangle {
Item { height: 4 }
}
}
}
}
}

View File

@@ -6,18 +6,11 @@ import Quickshell.Wayland
import Quickshell.Services.Notifications
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
property int unreadCount: 0
property bool doNotDisturb: false
property bool historyOpen: historyPopup.popupOpen
@@ -111,7 +104,6 @@ Rectangle {
? toastRoot.t.notifUrgencyCritical
: (cardHover.hovered ? toastRoot.t.notifBorderActive : toastRoot.t.notifToastBorder)
border.width: Cfg.Config.popupBorderWidth
layer.enabled: true
Behavior on color { ColorAnimation { duration: 150; easing.type: Easing.OutCubic } }
Behavior on border.color { ColorAnimation { duration: 150 } }
@@ -152,22 +144,26 @@ Rectangle {
color: toastRoot.urgencyColor(toastRoot.urgency)
font.pixelSize: 12
Layout.alignment: Qt.AlignVCenter
renderType: Text.NativeRendering
}
Text {
text: toastRoot.appName
color: toastRoot.t.notifToastAppName
font.pixelSize: 11
font.pixelSize: 12
font.bold: true
font.capitalization: Font.AllUppercase
font.letterSpacing: 0.4
Layout.fillWidth: true
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
Text {
text: toastRoot.timeStr
color: toastRoot.t.notifToastDim
font.pixelSize: 10
font.pixelSize: 11
verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
Rectangle {
@@ -181,6 +177,7 @@ Rectangle {
text: "✕"
color: closeHover.hovered ? toastRoot.t.notifToastText : toastRoot.t.notifToastDim
font.pixelSize: 10
renderType: Text.NativeRendering
}
HoverHandler { id: closeHover; cursorShape: Qt.PointingHandCursor }
TapHandler { onTapped: toastRoot.triggerDismiss() }
@@ -196,6 +193,7 @@ Rectangle {
elide: Text.ElideRight
textFormat: Text.PlainText
visible: toastRoot.summary !== ""
renderType: Text.NativeRendering
}
Text {
@@ -209,7 +207,8 @@ Rectangle {
textFormat: Text.PlainText
visible: toastRoot.body !== ""
Layout.bottomMargin: toastRoot.urgency === NotificationUrgency.Low ? 6 : 0
lineHeight: 1.15
lineHeight: 1.2
renderType: Text.NativeRendering
}
}
@@ -286,7 +285,7 @@ Rectangle {
Behavior on opacity { NumberAnimation { duration: 150 } }
Behavior on color { ColorAnimation { duration: 150 } }
Text { anchors.centerIn: parent; text: "✕"; color: histRoot.t.notifToastText; font.pixelSize: 10 }
Text { anchors.centerIn: parent; text: "✕"; color: histRoot.t.notifToastText; font.pixelSize: 10; renderType: Text.NativeRendering }
HoverHandler { id: histCloseHover; cursorShape: Qt.PointingHandCursor }
TapHandler { onTapped: dismissAnim.start() }
}
@@ -298,21 +297,24 @@ Rectangle {
RowLayout {
Layout.fillWidth: true; spacing: 5
Text { text: histRoot.urgencyIcon(histRoot.urgency); color: histRoot.urgencyColor(histRoot.urgency); font.pixelSize: 11; Layout.alignment: Qt.AlignVCenter }
Text { text: histRoot.urgencyIcon(histRoot.urgency); color: histRoot.urgencyColor(histRoot.urgency); font.pixelSize: 11; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering }
Text {
text: histRoot.appName; color: histRoot.t.notifToastAppName; font.pixelSize: 11; font.bold: true
font.capitalization: Font.AllUppercase; Layout.fillWidth: true; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
text: histRoot.appName; color: histRoot.t.notifToastAppName; font.pixelSize: 12; font.bold: true
font.capitalization: Font.AllUppercase; font.letterSpacing: 0.4; Layout.fillWidth: true; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
}
Text { text: histRoot.timeStr; color: histRoot.t.notifToastDim; font.pixelSize: 11; verticalAlignment: Text.AlignVCenter }
Text { text: histRoot.timeStr; color: histRoot.t.notifToastDim; font.pixelSize: 11; verticalAlignment: Text.AlignVCenter; renderType: Text.NativeRendering }
}
Text {
text: histRoot.summary; color: histRoot.t.notifToastText; font.pixelSize: 13; font.bold: true
Layout.fillWidth: true; elide: Text.ElideRight; textFormat: Text.PlainText; visible: histRoot.summary !== ""
renderType: Text.NativeRendering
}
Text {
text: histRoot.body; color: histRoot.t.notifToastDim; font.pixelSize: 12; Layout.fillWidth: true
wrapMode: Text.WordWrap; maximumLineCount: 2; elide: Text.ElideRight; textFormat: Text.PlainText; visible: histRoot.body !== ""; lineHeight: 1.15
wrapMode: Text.WordWrap; maximumLineCount: 2; elide: Text.ElideRight; textFormat: Text.PlainText; visible: histRoot.body !== ""; lineHeight: 1.2
renderType: Text.NativeRendering
}
}
}
@@ -375,6 +377,7 @@ Rectangle {
color: t.notifBadgeText
font.pixelSize: 8
font.bold: true
renderType: Text.NativeRendering
}
}
}
@@ -574,7 +577,6 @@ Rectangle {
radius: Cfg.Config.popupRadius
border.color: t.notifHistoryBorder
border.width: Cfg.Config.popupBorderWidth
layer.enabled: true
MouseArea { anchors.fill: parent; propagateComposedEvents: false }
@@ -584,10 +586,10 @@ Rectangle {
anchors.topMargin: 20; anchors.leftMargin: 20; anchors.rightMargin: 20
spacing: 10
Text { text: "󰂚"; color: t.notifToastAppName; font.pixelSize: 18; Layout.alignment: Qt.AlignVCenter }
Text { text: "󰂚"; color: t.notifToastAppName; font.pixelSize: 18; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering }
Text {
text: "Notifications"; color: t.notifToastAppName; font.pixelSize: 15; font.bold: true
Layout.fillWidth: true; Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true; Layout.alignment: Qt.AlignVCenter; renderType: Text.NativeRendering
}
Rectangle {
@@ -613,6 +615,7 @@ Rectangle {
font.pixelSize: 12
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 150 } }
renderType: Text.NativeRendering
}
Text {
text: root.doNotDisturb ? "On" : "Off"
@@ -621,6 +624,7 @@ Rectangle {
font.bold: root.doNotDisturb
anchors.verticalCenter: parent.verticalCenter
Behavior on color { ColorAnimation { duration: 150 } }
renderType: Text.NativeRendering
}
}
@@ -637,7 +641,7 @@ Rectangle {
color: clearHover.hovered ? t.notifHistoryHover : "transparent"
Behavior on color { ColorAnimation { duration: 150 } }
Text { id: clearText; anchors.centerIn: parent; text: "Clear all"; color: t.notifToastDim; font.pixelSize: 11 }
Text { id: clearText; anchors.centerIn: parent; text: "Clear all"; color: t.notifToastDim; font.pixelSize: 11; renderType: Text.NativeRendering }
HoverHandler { id: clearHover; cursorShape: Qt.PointingHandCursor }
TapHandler { onTapped: clearAllAnim.start() }
}
@@ -689,8 +693,8 @@ Rectangle {
width: histCol.width - 16; height: 90; visible: notifHistory.count === 0
ColumnLayout {
anchors.centerIn: parent; spacing: 8
Text { text: "󰂚"; color: t.notifHistoryEmpty; font.pixelSize: 30; Layout.alignment: Qt.AlignHCenter }
Text { text: "No notifications"; color: t.notifHistoryEmpty; font.pixelSize: 12; Layout.alignment: Qt.AlignHCenter }
Text { text: "󰂚"; color: t.notifHistoryEmpty; font.pixelSize: 30; Layout.alignment: Qt.AlignHCenter; renderType: Text.NativeRendering }
Text { text: "No notifications"; color: t.notifHistoryEmpty; font.pixelSize: 12; Layout.alignment: Qt.AlignHCenter; renderType: Text.NativeRendering }
}
}

View File

@@ -3,16 +3,11 @@ import QtQuick.Layouts
import Quickshell.Io
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
width: statsLayout.implicitWidth + 20
height: Cfg.Config.moduleHeight
color: t.statsBg

View File

@@ -239,6 +239,130 @@ Item {
QsMenuOpener { id: rootMenuOpener }
Component {
id: trayMenuItemComponent
Item {
id: menuItem
required property var modelData
readonly property bool isSep: modelData.isSeparator
width: parent.width
height: isSep ? 5 : 32
Rectangle {
visible: isSep
anchors {
left: parent.left; right: parent.right
verticalCenter: parent.verticalCenter
}
anchors.leftMargin: 8; anchors.rightMargin: 8
height: 1
color: root.tmSeparator
}
Rectangle {
visible: !isSep
anchors.fill: parent
anchors.margins: 2
radius: 10
readonly property bool active: menuMouse.containsMouse
|| (submenuCard.visible
&& trayMenuPopup._submenuParentRef === menuItem)
color: active ? root.tmHover : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8; anchors.rightMargin: 8
spacing: 8
Item {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: !isSep
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.CheckBox
&& modelData.checkState === Qt.Checked
text: "✓"; color: root.tmCheck
font.pixelSize: 14
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState === Qt.Checked
text: "●"; color: root.tmCheck
font.pixelSize: 9
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState !== Qt.Checked
text: "○"; color: root.tmDim
font.pixelSize: 11
}
}
Image {
visible: !isSep && modelData.icon !== ""
source: modelData.icon
width: 16; height: 16
sourceSize: Qt.size(16, 16)
fillMode: Image.PreserveAspectFit
}
Text {
text: modelData.text
color: modelData.enabled ? root.tmText : root.tmDim
font.pixelSize: 13
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
visible: !isSep && modelData.hasChildren
text: "▶"
color: root.tmDim
font.pixelSize: 9
}
}
MouseArea {
id: menuMouse
anchors.fill: parent
hoverEnabled: true
enabled: !isSep && modelData.enabled
onClicked: {
if (modelData.hasChildren) {
closeSubmenuTimer.stop()
submenuDelayTimer.itemRef = menuItem
submenuDelayTimer.restart()
} else {
modelData.triggered()
trayMenuPopup.close()
}
}
onEntered: {
if (modelData.hasChildren) {
closeSubmenuTimer.stop()
submenuDelayTimer.itemRef = menuItem
submenuDelayTimer.restart()
} else {
closeSubmenuTimer.stop()
submenuCard.visible = false
}
}
onExited: {
if (modelData.hasChildren)
closeSubmenuTimer.restart()
}
}
}
}
}
Item {
id: menuCardWrapper
@@ -301,133 +425,7 @@ Item {
Repeater {
model: rootMenuOpener.children
delegate: Item {
id: menuDelegate
required property var modelData
property bool isSep: modelData.isSeparator
width: menuCol.width
height: isSep ? 5 : 32
Rectangle {
visible: isSep
anchors {
left: parent.left; right: parent.right
verticalCenter: parent.verticalCenter
}
anchors.leftMargin: 8
anchors.rightMargin: 8
height: 1
color: root.tmSeparator
}
Rectangle {
id: itemBg
visible: !isSep
anchors.fill: parent
anchors.margins: 2
radius: 10
property bool active: itemMouse.containsMouse
|| (submenuCard.visible
&& trayMenuPopup._submenuParentRef === menuDelegate)
color: active ? root.tmHover : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8
anchors.rightMargin: 8
spacing: 8
Item {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: !isSep
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.CheckBox
&& modelData.checkState === Qt.Checked
text: "✓"
color: root.tmCheck
font.pixelSize: 14
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState === Qt.Checked
text: "●"
color: root.tmCheck
font.pixelSize: 9
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState !== Qt.Checked
text: "○"
color: root.tmDim
font.pixelSize: 11
}
}
Image {
visible: !isSep && modelData.icon !== ""
source: modelData.icon
width: 16; height: 16
sourceSize: Qt.size(16, 16)
fillMode: Image.PreserveAspectFit
}
Text {
text: modelData.text
color: modelData.enabled ? root.tmText : root.tmDim
font.pixelSize: 13
Layout.fillWidth: true
elide: Text.ElideRight
}
Text {
visible: !isSep && modelData.hasChildren
text: "▶"
color: root.tmDim
font.pixelSize: 9
}
}
MouseArea {
id: itemMouse
anchors.fill: parent
hoverEnabled: true
enabled: !isSep && modelData.enabled
onClicked: {
if (modelData.hasChildren) {
closeSubmenuTimer.stop()
submenuDelayTimer.itemRef = menuDelegate
submenuDelayTimer.restart()
} else {
modelData.triggered()
trayMenuPopup.close()
}
}
onEntered: {
if (modelData.hasChildren) {
closeSubmenuTimer.stop()
submenuDelayTimer.itemRef = menuDelegate
submenuDelayTimer.restart()
} else {
closeSubmenuTimer.stop()
submenuCard.visible = false
}
}
onExited: {
if (modelData.hasChildren)
closeSubmenuTimer.restart()
}
}
}
}
delegate: trayMenuItemComponent
}
}
}
@@ -515,96 +513,7 @@ Item {
Repeater {
model: subMenuOpener.children
delegate: Item {
required property var modelData
property bool isSep: modelData.isSeparator
width: submenuCol.width
height: isSep ? 5 : 32
Rectangle {
visible: isSep
anchors {
left: parent.left; right: parent.right
verticalCenter: parent.verticalCenter
}
anchors.leftMargin: 8; anchors.rightMargin: 8
height: 1
color: root.tmSeparator
}
Rectangle {
id: subBg
visible: !isSep
anchors.fill: parent
anchors.margins: 2
radius: 10
color: subMouse.containsMouse ? root.tmHover : "transparent"
Behavior on color { ColorAnimation { duration: 120 } }
RowLayout {
anchors.fill: parent
anchors.leftMargin: 8; anchors.rightMargin: 8
spacing: 8
Item {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
visible: !isSep
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.CheckBox
&& modelData.checkState === Qt.Checked
text: "✓"; color: root.tmCheck
font.pixelSize: 14
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState === Qt.Checked
text: "●"; color: root.tmCheck
font.pixelSize: 9
}
Text {
anchors.centerIn: parent
visible: modelData.buttonType === QsMenuButtonType.RadioButton
&& modelData.checkState !== Qt.Checked
text: "○"; color: root.tmDim
font.pixelSize: 11
}
}
Image {
visible: !isSep && modelData.icon !== ""
source: modelData.icon
width: 16; height: 16
sourceSize: Qt.size(16, 16)
fillMode: Image.PreserveAspectFit
}
Text {
text: modelData.text
color: modelData.enabled ? root.tmText : root.tmDim
font.pixelSize: 13
Layout.fillWidth: true
elide: Text.ElideRight
}
}
MouseArea {
id: subMouse
anchors.fill: parent
hoverEnabled: true
enabled: !isSep && modelData.enabled
onClicked: {
modelData.triggered()
trayMenuPopup.close()
}
}
}
}
delegate: trayMenuItemComponent
}
}
}

View File

@@ -6,19 +6,11 @@ import Quickshell.Wayland
import Quickshell.Services.Pipewire
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
property int outputMaxVolume: Cfg.Config.volumeOutputMax
property int inputMaxVolume: Cfg.Config.volumeInputMax
@@ -165,54 +157,11 @@ Rectangle {
}
}
PanelWindow {
PopupPanel {
id: volumePopup
screen: root.targetScreen
visible: false
color: "transparent"
property bool popupOpen: false
function open() { hideTimer.stop(); visible = true; popupOpen = true }
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: volumePopup.visible = false
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-volume"
WlrLayershell.exclusiveZone: -1
mask: popupOpen ? null : _noInput
Region { id: _noInput }
MouseArea {
anchors.fill: parent
onClicked: volumePopup.close()
}
Item {
id: volClipContainer
readonly property int cardW: Cfg.Config.volumePopupWidth
readonly property int screenPad: root.barMargin + 10
x: {
let cx = root.mapToItem(null, 0, 0).x + root.width / 2
return Math.max(screenPad, Math.min(cx - cardW / 2, parent.width - cardW - screenPad))
}
width: cardW
anchors.top: root.isTop ? parent.top : undefined
anchors.bottom: root.isTop ? undefined : parent.bottom
anchors.topMargin: root.isTop ? (root.barHeight + 10) : 0
anchors.bottomMargin: root.isTop ? 0 : (root.barHeight + 10)
height: parent.height - root.barHeight - 10
clip: true
ns: "volume"
chipRoot: root
cardWidth: Cfg.Config.volumePopupWidth
Rectangle {
id: volCard
@@ -340,7 +289,6 @@ Rectangle {
Item { height: 2 }
}
}
}
}
component VolumeSection: ColumnLayout {

View File

@@ -3,15 +3,11 @@ import QtQuick.Layouts
import Quickshell.Hyprland
import "../../config" as Cfg
import "../../components"
Rectangle {
ModuleChip {
id: root
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
readonly property var t: Cfg.Config.theme
width: workspaceLayout.implicitWidth + 24
height: Cfg.Config.moduleHeight
color: t.wsPanelBg

16
components/ModuleChip.qml Normal file
View File

@@ -0,0 +1,16 @@
import QtQuick
import "../config" as Cfg
Rectangle {
id: chip
property real panelRadius: Cfg.Config.radius / 2
property int borderWidth: Cfg.Config.panelBorderWidth
property bool isTop: true
property var targetScreen: null
property int barHeight: Cfg.Config.barHeight
property int barMargin: Cfg.Config.margin
readonly property var t: Cfg.Config.theme
property bool moduleEnabled: true
}

68
components/PopupPanel.qml Normal file
View File

@@ -0,0 +1,68 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import "../config" as Cfg
PanelWindow {
id: popup
property string ns: ""
property var chipRoot: null
property int cardWidth: 260
property bool hasKeyboardFocus: false
property alias clipItem: popupClip
default property alias content: popupClip.data
screen: chipRoot ? chipRoot.targetScreen : null
visible: false
color: "transparent"
property bool popupOpen: false
property real popupCenterX: -1
function _doOpen() { hideTimer.stop(); visible = true; popupOpen = true }
function open() {
if (popupCenterX < 0 && chipRoot)
popupCenterX = chipRoot.mapToItem(null, 0, 0).x + chipRoot.width / 2
_doOpen()
}
function close() { popupOpen = false; hideTimer.restart() }
function toggle() { popupOpen ? close() : open() }
Timer {
id: hideTimer
interval: Cfg.Config.popupAnimDuration + 70
onTriggered: popup.visible = false
}
anchors { top: true; bottom: true; left: true; right: true }
WlrLayershell.layer: WlrLayer.Top
WlrLayershell.namespace: "main-shell-" + ns
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: hasKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
mask: popupOpen ? null : _noInput
Region { id: _noInput }
TapHandler { onTapped: popup.close() }
Item {
id: popupClip
property int screenPad: chipRoot ? chipRoot.barMargin + 10 : 30
x: {
if (!chipRoot && popupCenterX < 0) return 0
let cx = popupCenterX >= 0 ? popupCenterX : chipRoot.mapToItem(null, 0, 0).x + chipRoot.width / 2
return Math.max(screenPad, Math.min(cx - cardWidth / 2, parent.width - cardWidth - screenPad))
}
width: cardWidth
anchors.top: chipRoot && chipRoot.isTop ? parent.top : undefined
anchors.bottom: chipRoot && chipRoot.isTop ? undefined : parent.bottom
anchors.topMargin: chipRoot && chipRoot.isTop ? (chipRoot.barHeight + 10) : 0
anchors.bottomMargin: chipRoot && chipRoot.isTop ? 0 : (chipRoot.barHeight + 10)
height: chipRoot ? parent.height - chipRoot.barHeight - 10 : parent.height
clip: true
}
}

View File

@@ -129,11 +129,6 @@ QtObject {
// Settings popup and those changes are persisted across restarts.
property var cryptoPairs: [{ coin: "bitcoin", currency: "usd" }, { coin: "ethereum", currency: "eur" }, { coin: "monero", currency: "usd" }, { coin: "solana", currency: "brl" }]
// Legacy fallbacks used by Crypto.qml when cryptoPairs is absent.
// You can leave these as-is; they are derived from the first pair above.
property string cryptoCoinId: cryptoPairs[0].coin ?? "bitcoin"
property string cryptoVsCurrency: cryptoPairs[0].currency ?? "usd"
// ── Compact label decimals ────────────────────────────────────────────────
// 0 → "80k"
// 1 → "80.0k" (default)

View File

@@ -430,3 +430,4 @@ PanelWindow {
}
}
}

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Polkit