diff --git a/bar/modules/Battery.qml b/bar/modules/Battery.qml index d9e3c6c..123ab1f 100644 --- a/bar/modules/Battery.qml +++ b/bar/modules/Battery.qml @@ -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 } } } - } } } diff --git a/bar/modules/Clock.qml b/bar/modules/Clock.qml index 9825fc7..7570904 100644 --- a/bar/modules/Clock.qml +++ b/bar/modules/Clock.qml @@ -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 } } } - } } } diff --git a/bar/modules/Crypto.qml b/bar/modules/Crypto.qml index c1f13f4..e459a4f 100644 --- a/bar/modules/Crypto.qml +++ b/bar/modules/Crypto.qml @@ -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 { } } } - } } } diff --git a/bar/modules/Logo.qml b/bar/modules/Logo.qml index 966778a..e7a3149 100644 --- a/bar/modules/Logo.qml +++ b/bar/modules/Logo.qml @@ -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 diff --git a/bar/modules/Network.qml b/bar/modules/Network.qml index 1d1d131..0b5922b 100644 --- a/bar/modules/Network.qml +++ b/bar/modules/Network.qml @@ -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 } } } - } } } diff --git a/bar/modules/Notifications.qml b/bar/modules/Notifications.qml index 61b2a36..541f762 100644 --- a/bar/modules/Notifications.qml +++ b/bar/modules/Notifications.qml @@ -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 } } } diff --git a/bar/modules/Stats.qml b/bar/modules/Stats.qml index 46e3543..c7d8e8a 100644 --- a/bar/modules/Stats.qml +++ b/bar/modules/Stats.qml @@ -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 diff --git a/bar/modules/Tray.qml b/bar/modules/Tray.qml index eeea697..5db96b0 100644 --- a/bar/modules/Tray.qml +++ b/bar/modules/Tray.qml @@ -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 } } } diff --git a/bar/modules/Volume.qml b/bar/modules/Volume.qml index d6da043..f92ce20 100644 --- a/bar/modules/Volume.qml +++ b/bar/modules/Volume.qml @@ -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 { diff --git a/bar/modules/Workspace.qml b/bar/modules/Workspace.qml index 63be5a1..fe0d84d 100644 --- a/bar/modules/Workspace.qml +++ b/bar/modules/Workspace.qml @@ -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 diff --git a/components/ModuleChip.qml b/components/ModuleChip.qml new file mode 100644 index 0000000..d5b02bf --- /dev/null +++ b/components/ModuleChip.qml @@ -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 +} diff --git a/components/PopupPanel.qml b/components/PopupPanel.qml new file mode 100644 index 0000000..350033d --- /dev/null +++ b/components/PopupPanel.qml @@ -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 + } +} diff --git a/config/config.qml b/config/config.qml index 9dbd85b..7f0d6f5 100644 --- a/config/config.qml +++ b/config/config.qml @@ -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) diff --git a/launcher/Launcher.qml b/launcher/Launcher.qml index 6c68b2d..75c5f0d 100644 --- a/launcher/Launcher.qml +++ b/launcher/Launcher.qml @@ -430,3 +430,4 @@ PanelWindow { } } } + diff --git a/polkit/Polkit.qml b/polkit/Polkit.qml index c442a6e..69de663 100644 --- a/polkit/Polkit.qml +++ b/polkit/Polkit.qml @@ -1,5 +1,4 @@ import QtQuick -import QtQuick.Controls import Quickshell import Quickshell.Wayland import Quickshell.Services.Polkit