diff --git a/bar/modules/Clock.qml b/bar/modules/Clock.qml index 79fb708..ab6bbe4 100644 --- a/bar/modules/Clock.qml +++ b/bar/modules/Clock.qml @@ -169,6 +169,55 @@ Rectangle { MouseArea { anchors.fill: parent; propagateComposedEvents: false } + component MonthNavButton: Rectangle { + id: navBtn + required property int direction + + width: 26; height: 26; radius: 6 + + color: btnMouse.containsMouse + ? (btnMouse.pressed ? root.t.calArrowBgPress : root.t.calArrowBgHover) + : root.t.calArrowBg + scale: btnMouse.pressed ? 0.88 : 1.0 + + Behavior on color { ColorAnimation { duration: 120 } } + Behavior on scale { NumberAnimation { duration: 100; easing.type: Easing.OutCubic } } + + MouseArea { + id: btnMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + + function navigate() { + if (slideAnim.running) return + cal.slideDir = navBtn.direction + let m = cal.displayMonth + navBtn.direction + let y = cal.displayYear + if (m > 11) { m = 0; y += 1 } + if (m < 0) { m = 11; y -= 1 } + cal.pendingMonth = m + cal.pendingYear = y + slideAnim.restart() + } + + onPressed: { navigate(); holdDelay.restart() } + onReleased: { holdDelay.stop(); holdRepeat.stop() } + onCanceled: { holdDelay.stop(); holdRepeat.stop() } + + Timer { id: holdDelay; interval: 400; repeat: false; onTriggered: holdRepeat.restart() } + Timer { id: holdRepeat; interval: 200; repeat: true; onTriggered: btnMouse.navigate() } + } + + Text { + anchors.centerIn: parent + text: navBtn.direction < 0 ? "‹" : "›" + color: btnMouse.containsMouse ? root.t.calArrowIconHover : root.t.calArrowIcon + font.pixelSize: 18; font.bold: true + Behavior on color { ColorAnimation { duration: 120 } } + } + } + ColumnLayout { id: cardCol anchors { left: parent.left; right: parent.right; top: parent.top } @@ -188,6 +237,7 @@ Rectangle { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter } + Text { text: timeManager.now.toLocaleDateString(Qt.locale(), "d MMMM yyyy") color: root.t.clockPopupHeader @@ -203,58 +253,7 @@ Rectangle { RowLayout { Layout.fillWidth: true - Rectangle { - id: prevMonthBtn - width: 26; height: 26; radius: 6 - color: prevBtnMouse.containsMouse - ? (prevBtnMouse.pressed ? root.t.calArrowBgPress : root.t.calArrowBgHover) - : root.t.calArrowBg - scale: prevBtnMouse.pressed ? 0.88 : 1.0 - - Behavior on color { ColorAnimation { duration: 120 } } - Behavior on scale { NumberAnimation { duration: 100; easing.type: Easing.OutCubic } } - - MouseArea { - id: prevBtnMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - function goToPrev() { - if (slideAnim.running) return - cal.slideDir = -1 - let m = cal.displayMonth - 1 - let y = cal.displayYear - if (m < 0) { m = 11; y -= 1 } - cal.pendingMonth = m - cal.pendingYear = y - slideAnim.restart() - } - - onPressed: { goToPrev(); prevHoldDelay.restart() } - onReleased: { prevHoldDelay.stop(); prevHoldRepeat.stop() } - onCanceled: { prevHoldDelay.stop(); prevHoldRepeat.stop() } - - Timer { - id: prevHoldDelay - interval: 400; repeat: false - onTriggered: prevHoldRepeat.restart() - } - Timer { - id: prevHoldRepeat - interval: 200; repeat: true - onTriggered: prevBtnMouse.goToPrev() - } - } - - Text { - anchors.centerIn: parent - text: "‹" - color: prevBtnMouse.containsMouse ? root.t.calArrowIconHover : root.t.calArrowIcon - font.pixelSize: 18; font.bold: true - Behavior on color { ColorAnimation { duration: 120 } } - } - } + MonthNavButton { direction: -1 } Text { text: new Date(cal.displayYear, cal.displayMonth, 1).toLocaleDateString(Qt.locale(), "MMMM yyyy") @@ -263,58 +262,7 @@ Rectangle { Layout.fillWidth: true; horizontalAlignment: Text.AlignHCenter } - Rectangle { - id: nextMonthBtn - width: 26; height: 26; radius: 6 - color: nextBtnMouse.containsMouse - ? (nextBtnMouse.pressed ? root.t.calArrowBgPress : root.t.calArrowBgHover) - : root.t.calArrowBg - scale: nextBtnMouse.pressed ? 0.88 : 1.0 - - Behavior on color { ColorAnimation { duration: 120 } } - Behavior on scale { NumberAnimation { duration: 100; easing.type: Easing.OutCubic } } - - MouseArea { - id: nextBtnMouse - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - - function goToNext() { - if (slideAnim.running) return - cal.slideDir = 1 - let m = cal.displayMonth + 1 - let y = cal.displayYear - if (m > 11) { m = 0; y += 1 } - cal.pendingMonth = m - cal.pendingYear = y - slideAnim.restart() - } - - onPressed: { goToNext(); nextHoldDelay.restart() } - onReleased: { nextHoldDelay.stop(); nextHoldRepeat.stop() } - onCanceled: { nextHoldDelay.stop(); nextHoldRepeat.stop() } - - Timer { - id: nextHoldDelay - interval: 400; repeat: false - onTriggered: nextHoldRepeat.restart() - } - Timer { - id: nextHoldRepeat - interval: 200; repeat: true - onTriggered: nextBtnMouse.goToNext() - } - } - - Text { - anchors.centerIn: parent - text: "›" - color: nextBtnMouse.containsMouse ? root.t.calArrowIconHover : root.t.calArrowIcon - font.pixelSize: 18; font.bold: true - Behavior on color { ColorAnimation { duration: 120 } } - } - } + MonthNavButton { direction: 1 } } Item { @@ -363,57 +311,57 @@ Rectangle { anchors.fill: parent clip: true - ColumnLayout { - id: gridCol - width: parent.width - spacing: 2 + ColumnLayout { + id: gridCol + width: parent.width + spacing: 2 - Row { - spacing: 0 - Repeater { - model: ["Mo","Tu","We","Th","Fr","Sa","Su"] - Text { - width: (card.width - 32) / 7 - text: modelData; color: root.t.clockPopupDim - font.pixelSize: 11; horizontalAlignment: Text.AlignHCenter + Row { + spacing: 0 + Repeater { + model: ["Mo","Tu","We","Th","Fr","Sa","Su"] + Text { + width: (card.width - 32) / 7 + text: modelData; color: root.t.clockPopupDim + font.pixelSize: 11; horizontalAlignment: Text.AlignHCenter + } } } - } - Repeater { - model: 6 - Row { - id: weekRow - property int weekIndex: index - Repeater { - model: 7 - delegate: Item { - width: (card.width - 32) / 7; height: 26 - readonly property int dayNum: (weekRow.weekIndex * 7 + index) - ((new Date(cal.displayYear, cal.displayMonth, 1).getDay() + 6) % 7) + 1 - readonly property bool inMonth: dayNum >= 1 && dayNum <= new Date(cal.displayYear, cal.displayMonth + 1, 0).getDate() - readonly property bool isToday: inMonth && dayNum === timeManager.now.getDate() && cal.displayYear === timeManager.now.getFullYear() && cal.displayMonth === timeManager.now.getMonth() + Repeater { + model: 6 + Row { + id: weekRow + property int weekIndex: index + Repeater { + model: 7 + delegate: Item { + width: (card.width - 32) / 7; height: 26 + readonly property int dayNum: (weekRow.weekIndex * 7 + index) - ((new Date(cal.displayYear, cal.displayMonth, 1).getDay() + 6) % 7) + 1 + readonly property bool inMonth: dayNum >= 1 && dayNum <= new Date(cal.displayYear, cal.displayMonth + 1, 0).getDate() + readonly property bool isToday: inMonth && dayNum === timeManager.now.getDate() && cal.displayYear === timeManager.now.getFullYear() && cal.displayMonth === timeManager.now.getMonth() - Rectangle { - anchors.centerIn: parent; width: 22; height: 22; radius: 11 - color: isToday ? root.t.accent : "transparent" - visible: inMonth - } + Rectangle { + anchors.centerIn: parent; width: 22; height: 22; radius: 11 + color: isToday ? root.t.accent : "transparent" + visible: inMonth + } - Text { - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: inMonth ? dayNum : "" - color: isToday ? root.t.todayText : (index >= 5 ? root.t.clockPopupDim : root.t.clockPopupText) - font.pixelSize: 12; font.bold: isToday + Text { + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: inMonth ? dayNum : "" + color: isToday ? root.t.todayText : (index >= 5 ? root.t.clockPopupDim : root.t.clockPopupText) + font.pixelSize: 12; font.bold: isToday + } } } } } } - } - } - } + } + } Rectangle { Layout.fillWidth: true; height: 1; color: root.t.calSeparator } diff --git a/bar/modules/Notifications.qml b/bar/modules/Notifications.qml index 0494ec5..b65a56c 100644 --- a/bar/modules/Notifications.qml +++ b/bar/modules/Notifications.qml @@ -5,8 +5,7 @@ import Quickshell import Quickshell.Wayland import Quickshell.Services.Notifications -import "../../config" as Cfg -import "../../notifications" as Notif +import "../../config" as Cfg Rectangle { id: root @@ -37,6 +36,287 @@ Rectangle { Behavior on border.color { ColorAnimation { duration: 150 } } Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } + component NotificationCard: Item { + id: toastRoot + + property string toastId: "" + property string appName: "" + property string summary: "" + property string body: "" + property int urgency: NotificationUrgency.Normal + property string timeStr: "" + property int timeout: 5000 + + property var t + property var urgencyColor: function(u) { return "#ffffff" } + property var urgencyIcon: function(u) { return "" } + + signal dismissed(string toastId) + + width: parent ? parent.width : 350 + height: toastCard.height + + property real progressVal: 1.0 + property bool slideFromRight: false + + property real slideX: slideFromRight ? (width + 20) : -(width + 20) + + transform: Translate { id: slideTranslate; x: toastRoot.slideX } + + Component.onCompleted: entranceSlide.start() + + NumberAnimation { + id: entranceSlide + target: slideTranslate; property: "x" + from: toastRoot.slideX; to: 0 + duration: 300; easing.type: Easing.OutCubic + } + + function triggerDismiss() { + if (exitSlide.running) return + autoTimer.stop() + exitSlide.start() + } + + NumberAnimation { + id: exitSlide + target: slideTranslate; property: "x" + to: toastRoot.slideX + duration: 220; easing.type: Easing.InCubic + onStopped: toastRoot.dismissed(toastRoot.toastId) + } + + Timer { + id: autoTimer + interval: toastRoot.timeout + running: toastRoot.timeout > 0 + repeat: false + onTriggered: toastRoot.triggerDismiss() + } + + NumberAnimation on progressVal { + from: 1.0; to: 0.0 + duration: toastRoot.timeout + running: toastRoot.urgency === NotificationUrgency.Low && toastRoot.timeout > 0 + } + + Rectangle { + id: toastCard + width: parent.width + height: toastContent.implicitHeight + (toastRoot.urgency === NotificationUrgency.Low ? 30 : 24) + radius: 16 + + color: cardHover.hovered ? toastRoot.t.notifToastBgHover : toastRoot.t.notifToastBg + border.color: toastRoot.urgency === NotificationUrgency.Critical + ? 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 } } + + HoverHandler { id: cardHover } + TapHandler { onTapped: toastRoot.triggerDismiss() } + + Rectangle { + width: 4 + height: Math.max(16, toastCard.height - 24) + radius: 2 + anchors { + left: parent.left + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: toastRoot.urgencyColor(toastRoot.urgency) + } + + ColumnLayout { + id: toastContent + anchors { + left: parent.left + right: parent.right + top: parent.top + leftMargin: 24 + rightMargin: 12 + topMargin: 12 + } + spacing: 4 + + RowLayout { + Layout.fillWidth: true + spacing: 6 + + Text { + text: toastRoot.urgencyIcon(toastRoot.urgency) + color: toastRoot.urgencyColor(toastRoot.urgency) + font.pixelSize: 12 + Layout.alignment: Qt.AlignVCenter + } + Text { + text: toastRoot.appName + color: toastRoot.t.notifToastAppName + font.pixelSize: 11 + font.bold: true + font.capitalization: Font.AllUppercase + Layout.fillWidth: true + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + Text { + text: toastRoot.timeStr + color: toastRoot.t.notifToastDim + font.pixelSize: 10 + verticalAlignment: Text.AlignVCenter + } + + Rectangle { + Layout.alignment: Qt.AlignVCenter + width: 20; height: 20; radius: 10 + color: closeHover.hovered ? toastRoot.t.notifHistoryHover : "transparent" + Behavior on color { ColorAnimation { duration: 100 } } + + Text { + anchors.centerIn: parent + text: "✕" + color: closeHover.hovered ? toastRoot.t.notifToastText : toastRoot.t.notifToastDim + font.pixelSize: 10 + } + HoverHandler { id: closeHover; cursorShape: Qt.PointingHandCursor } + TapHandler { onTapped: toastRoot.triggerDismiss() } + } + } + + Text { + text: toastRoot.summary + color: toastRoot.t.notifToastText + font.pixelSize: 14 + font.bold: true + Layout.fillWidth: true + elide: Text.ElideRight + textFormat: Text.PlainText + visible: toastRoot.summary !== "" + } + + Text { + text: toastRoot.body + color: toastRoot.t.notifToastDim + font.pixelSize: 12 + Layout.fillWidth: true + wrapMode: Text.WordWrap + maximumLineCount: 3 + elide: Text.ElideRight + textFormat: Text.PlainText + visible: toastRoot.body !== "" + Layout.bottomMargin: toastRoot.urgency === NotificationUrgency.Low ? 6 : 0 + lineHeight: 1.15 + } + } + + Item { + visible: toastRoot.urgency === NotificationUrgency.Low && toastRoot.timeout > 0 + anchors { + left: parent.left; right: parent.right; bottom: parent.bottom + margins: 10; bottomMargin: 10 + } + height: 4 + + Rectangle { + anchors.fill: parent + radius: 2 + color: toastRoot.t.notifProgressBg + } + Rectangle { + width: parent.width * toastRoot.progressVal + height: parent.height + radius: 2 + color: toastRoot.t.notifProgressFill + } + } + } + } + + component HistoryCard: Rectangle { + id: histRoot + + property int itemIndex: 0 + property string appName: "" + property string summary: "" + property string body: "" + property int urgency: 0 + property string timeStr: "" + + property var t + property var urgencyColor: function(u) { return "#ffffff" } + property var urgencyIcon: function(u) { return "" } + + signal dismissed(int itemIndex) + + height: histItemContent.implicitHeight + 24 + radius: 12 + clip: true + color: histItemHover.hovered ? histRoot.t.notifHistoryHover : "transparent" + Behavior on color { ColorAnimation { duration: 150; easing.type: Easing.OutCubic } } + + HoverHandler { id: histItemHover } + + SequentialAnimation { + id: dismissAnim + ParallelAnimation { + NumberAnimation { target: histRoot; property: "opacity"; to: 0; duration: 150; easing.type: Easing.OutCubic } + NumberAnimation { target: histRoot; property: "scale"; to: 0.95; duration: 150; easing.type: Easing.OutCubic } + } + NumberAnimation { target: histRoot; property: "height"; to: 0; duration: 150; easing.type: Easing.InOutQuad } + ScriptAction { script: histRoot.dismissed(histRoot.itemIndex) } + } + + Rectangle { + width: 3 + height: Math.max(12, histRoot.height - 24) + radius: 1.5 + anchors { left: parent.left; leftMargin: 10; verticalCenter: parent.verticalCenter } + color: histRoot.urgencyColor(histRoot.urgency) + } + + Rectangle { + anchors { right: parent.right; top: parent.top; margins: 8 } + width: 22; height: 22; radius: 11 + color: histCloseHover.hovered ? histRoot.t.notifHistoryHover : "transparent" + opacity: histItemHover.hovered ? 1 : 0 + Behavior on opacity { NumberAnimation { duration: 150 } } + Behavior on color { ColorAnimation { duration: 150 } } + + Text { anchors.centerIn: parent; text: "✕"; color: histRoot.t.notifToastText; font.pixelSize: 10 } + HoverHandler { id: histCloseHover; cursorShape: Qt.PointingHandCursor } + TapHandler { onTapped: dismissAnim.start() } + } + + ColumnLayout { + id: histItemContent + anchors { left: parent.left; right: parent.right; top: parent.top; leftMargin: 24; rightMargin: 36; topMargin: 12 } + spacing: 4 + + 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.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 { text: histRoot.timeStr; color: histRoot.t.notifToastDim; font.pixelSize: 11; verticalAlignment: Text.AlignVCenter } + } + + 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 !== "" + } + 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 + } + } + } + HoverHandler { id: hoverHandler; cursorShape: Qt.PointingHandCursor; onHoveredChanged: if (hovered) bellRingAnim.start() } TapHandler { onTapped: { @@ -135,39 +415,31 @@ Rectangle { onNotification: function(notif) { notif.tracked = true bellRingAnim.start() - var timeStr = new Date().toLocaleTimeString(Qt.locale(), "HH:mm") - var toastId = notif.id + "_" + Date.now() + + const timeStr = new Date().toLocaleTimeString(Qt.locale(), "HH:mm") + const toastId = notif.id + "_" + Date.now() + const appName = notif.appName || "Unknown" + const summary = notif.summary || "" + const body = notif.body || "" + const urgency = notif.urgency notifHistory.insert(0, { - nid: notif.id, - appName: notif.appName || "Unknown", - summary: notif.summary || "", - body: notif.body || "", - urgency: notif.urgency, - timeStr: timeStr + nid: notif.id, + appName, summary, body, urgency, timeStr }) while (notifHistory.count > Cfg.Config.notificationHistorySize) notifHistory.remove(notifHistory.count - 1) - var cfgTimeout = notif.urgency === NotificationUrgency.Critical + var cfgTimeout = urgency === NotificationUrgency.Critical ? Cfg.Config.notifTimeoutCritical - : (notif.urgency === NotificationUrgency.Low + : (urgency === NotificationUrgency.Low ? Cfg.Config.notifTimeoutLow : Cfg.Config.notifTimeoutNormal) var finalTimeout = (notif.expireTimeout > 0) ? notif.expireTimeout : cfgTimeout - var toastEntry = { - toastId: toastId, - nid: notif.id, - appName: notif.appName || "Unknown", - summary: notif.summary || "", - body: notif.body || "", - urgency: notif.urgency, - timeStr: timeStr, - timeout: finalTimeout - } if (!root.doNotDisturb) { + var toastEntry = { toastId, nid: notif.id, appName, summary, body, urgency, timeStr, timeout: finalTimeout } if (toastLayer.atBottom) activeToasts.append(toastEntry) else activeToasts.insert(0, toastEntry) if (!root.historyOpen) root.unreadCount++ @@ -197,7 +469,6 @@ Rectangle { readonly property bool shareEdge: (root.isTop && !atBottom) || (!root.isTop && atBottom) readonly property int edgeGap: shareEdge ? root.barMargin + root.barHeight + 10 : root.barMargin + 10 - // Extra clearance so cards don't overlap the frame border stroke readonly property int toastPad: root.barMargin + Cfg.Config.frameBorderWidth + Cfg.Config.notifToastEdgePad Column { @@ -209,7 +480,7 @@ Rectangle { Repeater { model: activeToasts - Notif.NotificationCard { + NotificationCard { width: toastCol.width toastId: model.toastId appName: model.appName @@ -286,11 +557,11 @@ Rectangle { transform: Translate { y: historyPopup.popupOpen ? 0 : (root.isTop ? -historyCard.height : historyCard.height) - - Behavior on y { - NumberAnimation { - duration: Cfg.Config.popupAnimDuration; - easing.type: Easing.OutExpo + + Behavior on y { + NumberAnimation { + duration: Cfg.Config.popupAnimDuration; + easing.type: Easing.OutExpo } enabled: historyPopup.visible } @@ -319,7 +590,6 @@ Rectangle { Layout.fillWidth: true; Layout.alignment: Qt.AlignVCenter } - // ── Do Not Disturb toggle ───────────────────────────── Rectangle { Layout.alignment: Qt.AlignVCenter width: dndRow.implicitWidth + 16 @@ -426,7 +696,7 @@ Rectangle { Repeater { model: notifHistory - Notif.HistoryCard { + HistoryCard { width: histCol.width - 10; x: 2; itemIndex: index appName: model.appName; summary: model.summary; body: model.body; urgency: model.urgency; timeStr: model.timeStr t: root.t; urgencyColor: root.urgencyColor; urgencyIcon: root.urgencyIcon @@ -439,4 +709,3 @@ Rectangle { } } } - diff --git a/bar/modules/Stats.qml b/bar/modules/Stats.qml index e97559d..b10a78f 100644 --- a/bar/modules/Stats.qml +++ b/bar/modules/Stats.qml @@ -108,7 +108,6 @@ Rectangle { onPaint: { var ctx = getContext("2d") ctx.reset() - ctx.clearRect(0, 0, width, height) var cx = width / 2 var cy = height / 2 @@ -134,19 +133,14 @@ Rectangle { } } - HoverHandler { id: hov } - + HoverHandler { + id: hov + onHoveredChanged: if (!hovered) ring.showAlt = false + } property bool showAlt: false onShowAltChanged: arc.requestPaint() - Connections { - target: hov - function onHoveredChanged() { - if (!hov.hovered) ring.showAlt = false - } - } - TapHandler { enabled: !Cfg.Config.statsHideTextUntilHover || hov.hovered onTapped: ring.showAlt = !ring.showAlt @@ -155,21 +149,12 @@ Rectangle { Text { anchors.centerIn: parent - text: { - if (Cfg.Config.statsHideTextUntilHover) { - if (!hov.hovered) return "" - var showPct = ring.showAlt - ? !Cfg.Config.statsShowPercentByDefault - : Cfg.Config.statsShowPercentByDefault - return showPct ? Math.round(ring.value) + "%" : ring.label - } else { - var baseShowPct = Cfg.Config.statsShowPercentByDefault - ? !hov.hovered - : hov.hovered - var finalShowPct = ring.showAlt ? !baseShowPct : baseShowPct - return finalShowPct ? Math.round(ring.value) + "%" : ring.label - } + const hide = Cfg.Config.statsHideTextUntilHover + const byDefault = Cfg.Config.statsShowPercentByDefault + if (hide && !hov.hovered) return "" + const showPct = ring.showAlt ? !byDefault : byDefault + return showPct ? Math.round(ring.value) + "%" : ring.label } color: ring.labelColor diff --git a/bar/modules/Volume.qml b/bar/modules/Volume.qml index 1b3ccd8..00839eb 100644 --- a/bar/modules/Volume.qml +++ b/bar/modules/Volume.qml @@ -356,7 +356,7 @@ Rectangle { horizontalAlignment: Text.AlignHCenter Layout.preferredWidth: 44 } - // ── Mute toggle button ──────────────────────────────────────── + Rectangle { width: 22; height: 22; radius: 6 color: muteHover.hovered ? (vs.muted ? "#33ffffff" : "#22ffffff") : (vs.muted ? root.t.volMuteBg : "transparent") @@ -387,7 +387,7 @@ Rectangle { color: vs.muted ? vs.dimColor : vs.accentColor } } - // ── Volume slider handle ────────────────────────────────────── + Rectangle { readonly property real ratio: vs.volume / Math.max(1, vs.maxVolume) x: Math.min(sliderItem.width - width, Math.max(0, sliderItem.width * ratio - width / 2)) @@ -427,7 +427,7 @@ Rectangle { elide: Text.ElideRight Layout.fillWidth: true } - // ── Device picker chevron ───────────────────────────────────── + Rectangle { width: 20; height: 20; radius: 5 color: chevHover.hovered ? "#22ffffff" : "transparent" @@ -472,7 +472,6 @@ Rectangle { readonly property bool isActive: vs.currentNode !== null && modelData.id === vs.currentNode.id Layout.fillWidth: true; height: 28; radius: 7 color: isActive ? Qt.alpha(vs.accentColor, 0.18) : (rowHover.hovered ? root.t.volPickerHover : "transparent") - // ── Device row border ───────────────────────────── border.color: isActive ? Qt.alpha(vs.accentColor, 0.45) : "transparent" border.width: Cfg.Config.popupBorderWidth RowLayout { @@ -523,7 +522,7 @@ Rectangle { font.pixelSize: 11 Layout.fillWidth: true } - // ── Decrement button ────────────────────────────────────────────── + Rectangle { width: 22; height: 22; radius: 5 color: decHover.hovered ? "#22ffffff" : "transparent" @@ -544,7 +543,7 @@ Rectangle { horizontalAlignment: Text.AlignHCenter Layout.preferredWidth: 44 } - // ── Increment button ────────────────────────────────────────────── + Rectangle { width: 22; height: 22; radius: 5 color: incHover.hovered ? "#22ffffff" : "transparent" diff --git a/bar/modules/Workspace.qml b/bar/modules/Workspace.qml index 6855199..63be5a1 100644 --- a/bar/modules/Workspace.qml +++ b/bar/modules/Workspace.qml @@ -34,7 +34,7 @@ Rectangle { color: modelData.active ? root.t.wsActiveBg - : (wsHover.containsMouse ? root.t.wsHoverBg : root.t.wsInactiveBg) + : (wsHover.hovered ? root.t.wsHoverBg : root.t.wsInactiveBg) Behavior on color { ColorAnimation { duration: 200 } } Behavior on Layout.preferredWidth { NumberAnimation { duration: 200 } } @@ -46,16 +46,11 @@ Rectangle { font.bold: true color: modelData.active ? root.t.wsActiveText - : (wsHover.containsMouse ? root.t.wsHoverText : root.t.wsInactiveText) + : (wsHover.hovered ? root.t.wsHoverText : root.t.wsInactiveText) } - MouseArea { - id: wsHover - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: modelData.activate() - } + HoverHandler { id: wsHover; cursorShape: Qt.PointingHandCursor } + TapHandler { onTapped: modelData.activate() } } } } diff --git a/config/config.qml b/config/config.qml index 6a9f4b5..140609a 100644 --- a/config/config.qml +++ b/config/config.qml @@ -18,7 +18,7 @@ QtObject { // ──────────────────────────────────────────────────────────────────────── // ── Position & Dimensions ───────────────────────────────────────────────── - property string barPosition: "top" + property string barPosition: "bottom" property int barHeight: 35 property int margin: 15 property int radius: 26 diff --git a/launcher/Launcher.qml b/launcher/Launcher.qml index 7d58782..7236e2e 100644 --- a/launcher/Launcher.qml +++ b/launcher/Launcher.qml @@ -129,7 +129,7 @@ PanelWindow { launchProc.running = true console.log("Launching: " + cleanExec) - if (typeof close === "function") close() + close() } onModeChanged: if (_isOpen) loadItems() @@ -274,9 +274,7 @@ PanelWindow { Keys.onReturnPressed: { if (root.filteredItems.length > 0) { - let idx = resultsList.currentIndex; - if (idx === -1) idx = 0; - root.launch(root.filteredItems[idx]); + root.launch(root.filteredItems[Math.max(0, resultsList.currentIndex)]) } } diff --git a/launcher/Launcher.sh b/launcher/Launcher.sh deleted file mode 100644 index fdbdc07..0000000 --- a/launcher/Launcher.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env bash - -CACHE_DIR="/tmp/qs_launcher" - -DRUN_CACHE="$CACHE_DIR/drun.txt" -RUN_CACHE="$CACHE_DIR/run.txt" - -mkdir -p "$CACHE_DIR" - - -get_desktop_files() { - IFS=':' read -ra DIRS <<< \ - "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" - - for dir in "${DIRS[@]}"; do - APP_DIR="$dir/applications" - - [ -d "$APP_DIR" ] || continue - - find "$APP_DIR" \ - -maxdepth 1 \ - -name "*.desktop" \ - 2>/dev/null - done | while read -r f; do - - name=$( - grep -m1 '^Name=' "$f" \ - | cut -d= -f2- - ) - - exec=$( - grep -m1 '^Exec=' "$f" \ - | cut -d= -f2- \ - | sed 's/%[fFuUik]//g' - ) - - type=$( - grep -m1 '^Type=' "$f" \ - | cut -d= -f2- - ) - - nodisplay=$( - grep -m1 '^NoDisplay=' "$f" \ - | cut -d= -f2- \ - | tr '[:upper:]' '[:lower:]' - ) - - icon=$( - grep -m1 '^Icon=' "$f" \ - | cut -d= -f2- - ) - - [[ "$type" == "Application" ]] || continue - [[ "$nodisplay" != "true" ]] || continue - [[ -n "$name" ]] || continue - - printf "%s\t%s\t%s\t%s\n" \ - "$name" \ - "$exec" \ - "$(basename "$f" .desktop)" \ - "$icon" - done -} - - -get_binaries() { - compgen -c \ - | sort -u \ - | head -600 \ - | awk '{ - print $1 "\t" $1 "\t" $1 - }' -} - - -build_drun() { - get_desktop_files \ - | sort -f -t$'\t' -k1,1 \ - | awk -F'\t' '!seen[$1]++' -} - -build_run() { - { - get_desktop_files - get_binaries - } \ - | sort -f -t$'\t' -k1,1 \ - | awk -F'\t' '!seen[$1]++' -} - - -update_cache() { - build_drun > "$DRUN_CACHE.tmp" - build_run > "$RUN_CACHE.tmp" - - mv "$DRUN_CACHE.tmp" "$DRUN_CACHE" - mv "$RUN_CACHE.tmp" "$RUN_CACHE" -} - - -if [ "$1" == "--update-only" ]; then - update_cache - exit 0 -fi diff --git a/launcher/launcher.sh b/launcher/launcher.sh index 084ae99..a997272 100755 --- a/launcher/launcher.sh +++ b/launcher/launcher.sh @@ -73,47 +73,27 @@ get_binaries() { } -build_drun() { - get_desktop_files \ - | sort -f -t$'\t' -k1,1 \ +sort_and_dedup() { + sort -f -t$'\t' -k1,1 \ | awk -F'\t' '!seen[$1]++' } + +build_drun() { + get_desktop_files | sort_and_dedup +} + build_run() { - { - get_desktop_files - get_binaries - } \ - | sort -f -t$'\t' -k1,1 \ - | awk -F'\t' '!seen[$1]++' + { get_desktop_files; get_binaries; } | sort_and_dedup } -extract_proxy() { - local conf="${PROXYCHAINS_CONF_FILE:-/etc/proxychains4.conf}" - [ -f "$conf" ] || conf="/etc/proxychains.conf" - [ -f "$conf" ] || return - - local line - line=$(awk '/^\[ProxyList\]/{found=1; next} found && NF && !/^#/{print; exit}' "$conf") - [ -n "$line" ] || return - - local ptype host port - ptype=$(echo "$line" | awk '{print $1}') - host=$(echo "$line" | awk '{print $2}') - port=$(echo "$line" | awk '{print $3}') - - echo "${ptype}://${host}:${port}" -} - update_cache() { build_drun > "$DRUN_CACHE.tmp" build_run > "$RUN_CACHE.tmp" mv "$DRUN_CACHE.tmp" "$DRUN_CACHE" mv "$RUN_CACHE.tmp" "$RUN_CACHE" - - extract_proxy > "$CACHE_DIR/proxy.conf" } diff --git a/notifications/HistoryCard.qml b/notifications/HistoryCard.qml deleted file mode 100644 index 5fa807c..0000000 --- a/notifications/HistoryCard.qml +++ /dev/null @@ -1,83 +0,0 @@ -import QtQuick -import QtQuick.Layouts - -Rectangle { - id: root - - property int itemIndex: 0 - property string appName: "" - property string summary: "" - property string body: "" - property int urgency: 0 - property string timeStr: "" - - property var t - property var urgencyColor: function(u) { return "#ffffff" } - property var urgencyIcon: function(u) { return "" } - - signal dismissed(int itemIndex) - - height: histItemContent.implicitHeight + 24 - radius: 12 - clip: true - color: histItemHover.hovered ? root.t.notifHistoryHover : "transparent" - Behavior on color { ColorAnimation { duration: 150; easing.type: Easing.OutCubic } } - - HoverHandler { id: histItemHover } - - SequentialAnimation { - id: dismissAnim - ParallelAnimation { - NumberAnimation { target: root; property: "opacity"; to: 0; duration: 150; easing.type: Easing.OutCubic } - NumberAnimation { target: root; property: "scale"; to: 0.95; duration: 150; easing.type: Easing.OutCubic } - } - NumberAnimation { target: root; property: "height"; to: 0; duration: 150; easing.type: Easing.InOutQuad } - ScriptAction { script: root.dismissed(root.itemIndex) } - } - - Rectangle { - width: 3 - height: Math.max(12, root.height - 24) - radius: 1.5 - anchors { left: parent.left; leftMargin: 10; verticalCenter: parent.verticalCenter } - color: root.urgencyColor(root.urgency) - } - - Rectangle { - anchors { right: parent.right; top: parent.top; margins: 8 } - width: 22; height: 22; radius: 11 - color: histCloseHover.hovered ? root.t.notifHistoryHover : "transparent" - opacity: histItemHover.hovered ? 1 : 0 - Behavior on opacity { NumberAnimation { duration: 150 } } - Behavior on color { ColorAnimation { duration: 150 } } - - Text { anchors.centerIn: parent; text: "✕"; color: root.t.notifToastText; font.pixelSize: 10 } - HoverHandler { id: histCloseHover; cursorShape: Qt.PointingHandCursor } - TapHandler { onTapped: dismissAnim.start() } - } - - ColumnLayout { - id: histItemContent - anchors { left: parent.left; right: parent.right; top: parent.top; leftMargin: 24; rightMargin: 36; topMargin: 12 } - spacing: 4 - - RowLayout { - Layout.fillWidth: true; spacing: 5 - Text { text: root.urgencyIcon(root.urgency); color: root.urgencyColor(root.urgency); font.pixelSize: 11; Layout.alignment: Qt.AlignVCenter } - Text { - text: root.appName; color: root.t.notifToastAppName; font.pixelSize: 11; font.bold: true; - font.capitalization: Font.AllUppercase; Layout.fillWidth: true; elide: Text.ElideRight; verticalAlignment: Text.AlignVCenter - } - Text { text: root.timeStr; color: root.t.notifToastDim; font.pixelSize: 11; verticalAlignment: Text.AlignVCenter } - } - - Text { - text: root.summary; color: root.t.notifToastText; font.pixelSize: 13; font.bold: true; - Layout.fillWidth: true; elide: Text.ElideRight; textFormat: Text.PlainText; visible: root.summary !== "" - } - Text { - text: root.body; color: root.t.notifToastDim; font.pixelSize: 12; Layout.fillWidth: true; - wrapMode: Text.WordWrap; maximumLineCount: 2; elide: Text.ElideRight; textFormat: Text.PlainText; visible: root.body !== ""; lineHeight: 1.15 - } - } -} diff --git a/notifications/NotificationCard.qml b/notifications/NotificationCard.qml deleted file mode 100644 index ad430f7..0000000 --- a/notifications/NotificationCard.qml +++ /dev/null @@ -1,206 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import Quickshell.Services.Notifications - -import "../../config" as Cfg - -Item { - id: root - - property string toastId: "" - property string appName: "" - property string summary: "" - property string body: "" - property int urgency: NotificationUrgency.Normal - property string timeStr: "" - property int timeout: 5000 - - property var t - property var urgencyColor: function(u) { return "#ffffff" } - property var urgencyIcon: function(u) { return "" } - - signal dismissed(string toastId) - - width: parent ? parent.width : 350 - height: toastCard.height - - property real progressVal: 1.0 - property bool slideFromRight: false - - property real slideX: slideFromRight ? (width + 20) : -(width + 20) - - transform: Translate { id: slideTranslate; x: root.slideX } - - Component.onCompleted: entranceSlide.start() - - NumberAnimation { - id: entranceSlide - target: slideTranslate; property: "x" - from: root.slideX; to: 0 - duration: 300; easing.type: Easing.OutCubic - } - - function triggerDismiss() { - if (exitSlide.running) return - autoTimer.stop() - exitSlide.start() - } - - NumberAnimation { - id: exitSlide - target: slideTranslate; property: "x" - to: root.slideX - duration: 220; easing.type: Easing.InCubic - onStopped: root.dismissed(root.toastId) - } - - Timer { - id: autoTimer - interval: root.timeout - running: root.timeout > 0 - repeat: false - onTriggered: root.triggerDismiss() - } - - NumberAnimation on progressVal { - from: 1.0; to: 0.0 - duration: root.timeout - running: root.urgency === NotificationUrgency.Low && root.timeout > 0 - } - - Rectangle { - id: toastCard - width: parent.width - height: toastContent.implicitHeight + (root.urgency === NotificationUrgency.Low ? 30 : 24) - radius: 16 - - color: cardHover.hovered ? root.t.notifToastBgHover : root.t.notifToastBg - border.color: root.urgency === NotificationUrgency.Critical - ? root.t.notifUrgencyCritical - : (cardHover.hovered ? root.t.notifBorderActive : root.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 } } - - HoverHandler { id: cardHover } - TapHandler { onTapped: root.triggerDismiss() } - - Rectangle { - width: 4 - height: Math.max(16, toastCard.height - 24) - radius: 2 - anchors { - left: parent.left - leftMargin: 10 - verticalCenter: parent.verticalCenter - } - color: root.urgencyColor(root.urgency) - } - - ColumnLayout { - id: toastContent - anchors { - left: parent.left - right: parent.right - top: parent.top - leftMargin: 24 - rightMargin: 12 - topMargin: 12 - } - spacing: 4 - - RowLayout { - Layout.fillWidth: true - spacing: 6 - - Text { - text: root.urgencyIcon(root.urgency) - color: root.urgencyColor(root.urgency) - font.pixelSize: 12 - Layout.alignment: Qt.AlignVCenter - } - Text { - text: root.appName - color: root.t.notifToastAppName - font.pixelSize: 11 - font.bold: true - font.capitalization: Font.AllUppercase - Layout.fillWidth: true - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - Text { - text: root.timeStr - color: root.t.notifToastDim - font.pixelSize: 10 - verticalAlignment: Text.AlignVCenter - } - - Rectangle { - Layout.alignment: Qt.AlignVCenter - width: 20; height: 20; radius: 10 - color: closeHover.hovered ? root.t.notifHistoryHover : "transparent" - Behavior on color { ColorAnimation { duration: 100 } } - - Text { - anchors.centerIn: parent - text: "✕" - color: closeHover.hovered ? root.t.notifToastText : root.t.notifToastDim - font.pixelSize: 10 - } - HoverHandler { id: closeHover; cursorShape: Qt.PointingHandCursor } - TapHandler { onTapped: root.triggerDismiss() } - } - } - - Text { - text: root.summary - color: root.t.notifToastText - font.pixelSize: 14 - font.bold: true - Layout.fillWidth: true - elide: Text.ElideRight - textFormat: Text.PlainText - visible: root.summary !== "" - } - - Text { - text: root.body - color: root.t.notifToastDim - font.pixelSize: 12 - Layout.fillWidth: true - wrapMode: Text.WordWrap - maximumLineCount: 3 - elide: Text.ElideRight - textFormat: Text.PlainText - visible: root.body !== "" - Layout.bottomMargin: root.urgency === NotificationUrgency.Low ? 6 : 0 - lineHeight: 1.15 - } - } - - Item { - visible: root.urgency === NotificationUrgency.Low && root.timeout > 0 - anchors { - left: parent.left; right: parent.right; bottom: parent.bottom - margins: 10; bottomMargin: 10 - } - height: 4 - - Rectangle { - anchors.fill: parent - radius: 2 - color: root.t.notifProgressBg - } - Rectangle { - width: parent.width * root.progressVal - height: parent.height - radius: 2 - color: root.t.notifProgressFill - } - } - } -} - diff --git a/shell.qml b/shell.qml index 84722a7..ffd1c27 100644 --- a/shell.qml +++ b/shell.qml @@ -245,3 +245,4 @@ ShellRoot { } } } + diff --git a/widgets/bgDate/BgDate.qml b/widgets/bgDate/BgDate.qml index 3bfe228..4f8c290 100644 --- a/widgets/bgDate/BgDate.qml +++ b/widgets/bgDate/BgDate.qml @@ -19,25 +19,21 @@ PanelWindow { property string secondsString: "" function startTyping() { - typedChars = 0; - secondsText.opacity = 0; - typingTimer.start(); + typedChars = 0 + secondsText.opacity = 0 + typingTimer.start() } - function updateDate() { - let d = new Date(); - fullDateString = d.toLocaleDateString(Qt.locale(), "dddd, d MMMM yyyy"); - startTyping(); - } - - function updateSeconds() { - let d = new Date(); - secondsString = Qt.formatTime(d, "hh:mm:ss"); + function updateDate(d) { + let now = d || new Date() + fullDateString = now.toLocaleDateString(Qt.locale(), "dddd, d MMMM yyyy") + startTyping() } Component.onCompleted: { - updateSeconds(); - updateDate(); + let d = new Date() + secondsString = Qt.formatTime(d, "hh:mm:ss") + updateDate(d) } Column { @@ -53,7 +49,7 @@ PanelWindow { font.weight: Font.ExtraLight font.letterSpacing: 4 opacity: 0.55 - style: Text.DropShadow + style: Text.Raised text: fullDateString.substring(0, typedChars) } @@ -66,7 +62,7 @@ PanelWindow { font.weight: Font.ExtraLight font.letterSpacing: 6 opacity: 0 - style: Text.DropShadow + style: Text.Raised text: secondsString Behavior on opacity { @@ -84,10 +80,10 @@ PanelWindow { repeat: true onTriggered: { if (typedChars < fullDateString.length) { - typedChars++; + typedChars++ } else { - stop(); - secondsText.opacity = 0.55; + stop() + secondsText.opacity = 0.55 } } } @@ -97,10 +93,10 @@ PanelWindow { running: true repeat: true onTriggered: { - updateSeconds(); - let d = new Date(); + let d = new Date() + secondsString = Qt.formatTime(d, "hh:mm:ss") if (d.getSeconds() === 0 && d.getMinutes() === 0 && d.getHours() === 0) - updateDate(); + updateDate(d) } } }