refactor: unified BarModule base component, click-to-open panels, remove pinning
This commit is contained in:
parent
034f0b6d85
commit
26476dc930
33 changed files with 273 additions and 517 deletions
|
|
@ -1,19 +1,16 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
M.HoverPanel {
|
||||
id: menuWindow
|
||||
|
||||
popupMode: true
|
||||
contentWidth: 180
|
||||
Column {
|
||||
id: root
|
||||
|
||||
required property color accentColor
|
||||
signal runCommand(var cmd)
|
||||
signal dismiss
|
||||
|
||||
readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== ""
|
||||
|
||||
// Confirmation state: null = normal menu, object = pending confirm
|
||||
property var _confirmItem: null
|
||||
|
||||
function _run(cmd) {
|
||||
|
|
@ -35,8 +32,8 @@ M.HoverPanel {
|
|||
|
||||
// Normal menu entries
|
||||
Column {
|
||||
visible: !menuWindow._confirmItem
|
||||
width: menuWindow.contentWidth
|
||||
visible: !root._confirmItem
|
||||
width: root.width
|
||||
|
||||
Repeater {
|
||||
model: [
|
||||
|
|
@ -57,7 +54,7 @@ M.HoverPanel {
|
|||
{
|
||||
label: "Logout",
|
||||
icon: "\uF2F5",
|
||||
cmd: menuWindow._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""],
|
||||
cmd: root._isNiri ? ["niri", "msg", "action", "quit"] : ["loginctl", "terminate-user", ""],
|
||||
color: S.Theme.base0A,
|
||||
confirm: false
|
||||
},
|
||||
|
|
@ -83,7 +80,7 @@ M.HoverPanel {
|
|||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: menuWindow.contentWidth
|
||||
width: root.width
|
||||
height: 32
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -121,7 +118,7 @@ M.HoverPanel {
|
|||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: menuWindow._requestAction(entry.modelData)
|
||||
onTapped: root._requestAction(entry.modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -129,8 +126,8 @@ M.HoverPanel {
|
|||
|
||||
// Confirmation view
|
||||
Column {
|
||||
visible: !!menuWindow._confirmItem
|
||||
width: menuWindow.contentWidth
|
||||
visible: !!root._confirmItem
|
||||
width: root.width
|
||||
spacing: 4
|
||||
|
||||
Item {
|
||||
|
|
@ -139,8 +136,8 @@ M.HoverPanel {
|
|||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: menuWindow._confirmItem ? menuWindow._confirmItem.label + "?" : ""
|
||||
color: menuWindow._confirmItem ? menuWindow._confirmItem.color : S.Theme.base05
|
||||
text: root._confirmItem ? root._confirmItem.label + "?" : ""
|
||||
color: root._confirmItem ? root._confirmItem.color : S.Theme.base05
|
||||
font.pixelSize: S.Theme.fontSize
|
||||
font.family: S.Theme.fontFamily
|
||||
font.bold: true
|
||||
|
|
@ -151,7 +148,6 @@ M.HoverPanel {
|
|||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: 8
|
||||
|
||||
// Cancel
|
||||
Item {
|
||||
width: 72
|
||||
height: 28
|
||||
|
|
@ -178,11 +174,10 @@ M.HoverPanel {
|
|||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: menuWindow._cancelConfirm()
|
||||
onTapped: root._cancelConfirm()
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm
|
||||
Item {
|
||||
width: 72
|
||||
height: 28
|
||||
|
|
@ -190,20 +185,20 @@ M.HoverPanel {
|
|||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: {
|
||||
if (!menuWindow._confirmItem)
|
||||
if (!root._confirmItem)
|
||||
return S.Theme.base02;
|
||||
const c = menuWindow._confirmItem.color;
|
||||
const c = root._confirmItem.color;
|
||||
return confirmHover.hovered ? Qt.rgba(c.r, c.g, c.b, 0.3) : Qt.rgba(c.r, c.g, c.b, 0.15);
|
||||
}
|
||||
radius: S.Theme.radius
|
||||
border.width: 1
|
||||
border.color: menuWindow._confirmItem ? menuWindow._confirmItem.color : S.Theme.base03
|
||||
border.color: root._confirmItem ? root._confirmItem.color : S.Theme.base03
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Confirm"
|
||||
color: menuWindow._confirmItem ? menuWindow._confirmItem.color : S.Theme.base05
|
||||
color: root._confirmItem ? root._confirmItem.color : S.Theme.base05
|
||||
font.pixelSize: S.Theme.fontSize
|
||||
font.family: S.Theme.fontFamily
|
||||
font.bold: true
|
||||
|
|
@ -216,8 +211,8 @@ M.HoverPanel {
|
|||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
if (menuWindow._confirmItem)
|
||||
menuWindow._run(menuWindow._confirmItem.cmd);
|
||||
if (root._confirmItem)
|
||||
root._run(root._confirmItem.cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ MemoryApplet 1.0 MemoryApplet.qml
|
|||
MprisApplet 1.0 MprisApplet.qml
|
||||
NetworkApplet 1.0 NetworkApplet.qml
|
||||
NotifApplet 1.0 NotifApplet.qml
|
||||
PowerApplet 1.0 PowerApplet.qml
|
||||
Separator 1.0 Separator.qml
|
||||
SparklineCanvas 1.0 SparklineCanvas.qml
|
||||
TemperatureApplet 1.0 TemperatureApplet.qml
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.OsdSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
opacity: S.Modules.backlight.enable && S.BacklightService.available ? 1 : 0
|
||||
visible: opacity > 0
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Brightness: " + percent + "%"
|
||||
|
||||
property int percent: S.BacklightService.percent
|
||||
property bool _percentInit: false
|
||||
|
|
@ -27,24 +27,6 @@ M.OsdSection {
|
|||
onWheel: event => S.BacklightService.adjust(event.angleDelta.y)
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
id: hoverPanel
|
||||
showPanel: root._showPanel
|
||||
screen: QsWindow.window?.screen ?? null
|
||||
anchorItem: root
|
||||
accentColor: root.accentColor
|
||||
panelNamespace: "nova-backlight"
|
||||
panelTitle: "Brightness"
|
||||
contentWidth: 200
|
||||
|
||||
C.BacklightApplet {
|
||||
width: parent.width
|
||||
percent: root.percent
|
||||
accentColor: root.accentColor
|
||||
onSetPercent: pct => S.BacklightService.setPercent(pct)
|
||||
}
|
||||
}
|
||||
|
||||
M.BarIcon {
|
||||
icon: "\uF185"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
|
@ -54,4 +36,23 @@ M.OsdSection {
|
|||
minText: "100%"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
id: hoverPanel
|
||||
showPanel: root._showPanel
|
||||
screen: QsWindow.window?.screen ?? null
|
||||
anchorItem: root
|
||||
accentColor: root.accentColor
|
||||
panelNamespace: "nova-backlight"
|
||||
panelTitle: "Brightness"
|
||||
contentWidth: 200
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.BacklightApplet {
|
||||
width: parent.width
|
||||
percent: root.percent
|
||||
accentColor: root.accentColor
|
||||
onSetPercent: pct => S.BacklightService.setPercent(pct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,7 +221,6 @@ PanelWindow {
|
|||
rightEdge: true
|
||||
M.BatteryModule {}
|
||||
M.PowerModule {
|
||||
bar: bar
|
||||
visible: S.Modules.power.enable
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
// Icon element with crossfade animation on icon change.
|
||||
// Pure visual component - tooltip handling lives in the parent BarModule.
|
||||
Text {
|
||||
id: root
|
||||
property string icon: ""
|
||||
property string tooltip: ""
|
||||
property string minIcon: ""
|
||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||
property bool _hovered: false
|
||||
property string _displayIcon: icon
|
||||
property string _pendingIcon: ""
|
||||
|
||||
|
|
@ -54,23 +52,4 @@ Text {
|
|||
font.pixelSize: root.font.pixelSize
|
||||
font.family: root.font.family
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onHoveredChanged: {
|
||||
root._hovered = hovered;
|
||||
if (hovered && root.tooltip !== "") {
|
||||
M.TooltipState.text = root.tooltip;
|
||||
M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else if (!hovered && root.tooltip !== "") {
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTooltipChanged: if (_hovered && tooltip !== "")
|
||||
M.TooltipState.text = tooltip
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,13 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
// Label element with minimum-width support via minText.
|
||||
// Pure visual component - tooltip handling lives in the parent BarModule.
|
||||
Text {
|
||||
id: root
|
||||
property string label: ""
|
||||
property string tooltip: ""
|
||||
property string minText: ""
|
||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||
property bool _hovered: false
|
||||
|
||||
text: label
|
||||
width: minText ? Math.max(implicitWidth, _minMetrics.width) : implicitWidth
|
||||
|
|
@ -25,23 +23,4 @@ Text {
|
|||
font.pixelSize: root.font.pixelSize
|
||||
font.family: root.font.family
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onHoveredChanged: {
|
||||
root._hovered = hovered;
|
||||
if (hovered && root.tooltip !== "") {
|
||||
M.TooltipState.text = root.tooltip;
|
||||
M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else if (!hovered && root.tooltip !== "") {
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTooltipChanged: if (_hovered && tooltip !== "")
|
||||
M.TooltipState.text = tooltip
|
||||
}
|
||||
|
|
|
|||
84
shell/modules/BarModule.qml
Normal file
84
shell/modules/BarModule.qml
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
// Unified base component for all bar modules.
|
||||
// Provides: tooltip on hover, panel state management, OSD flash support.
|
||||
//
|
||||
// On tap: toggles _panelOpen and emits tapped(). Modules that want custom tap
|
||||
// behavior connect onTapped to their action (the toggle still happens).
|
||||
//
|
||||
// Modules with a HoverPanel bind:
|
||||
// M.HoverPanel { showPanel: root._showPanel; onDismissed: root.dismissPanel() }
|
||||
//
|
||||
// Modules without a panel need nothing - the toggle is a harmless no-op.
|
||||
Row {
|
||||
id: root
|
||||
property string tooltip: ""
|
||||
property bool _hovered: false
|
||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||
property int cursorShape: Qt.PointingHandCursor
|
||||
|
||||
// Panel state
|
||||
property bool _panelOpen: false
|
||||
property bool _osdActive: false
|
||||
readonly property bool _showPanel: _panelOpen || _osdActive
|
||||
|
||||
signal tapped
|
||||
|
||||
function flashPanel() {
|
||||
_osdActive = true;
|
||||
_osdTimer.restart();
|
||||
}
|
||||
|
||||
function dismissPanel() {
|
||||
_panelOpen = false;
|
||||
_osdActive = false;
|
||||
_osdTimer.stop();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _osdTimer
|
||||
interval: 1500
|
||||
onTriggered: if (!root._panelOpen)
|
||||
root._osdActive = false
|
||||
}
|
||||
|
||||
on_PanelOpenChanged: {
|
||||
if (_panelOpen)
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: root.cursorShape
|
||||
onHoveredChanged: {
|
||||
root._hovered = hovered;
|
||||
if (hovered && root.tooltip !== "" && !root._panelOpen) {
|
||||
M.TooltipState.text = root.tooltip;
|
||||
M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else if (!hovered && root.tooltip !== "") {
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
root._panelOpen = !root._panelOpen;
|
||||
root.tapped();
|
||||
}
|
||||
}
|
||||
|
||||
onTooltipChanged: if (_hovered && tooltip !== "" && !_panelOpen)
|
||||
M.TooltipState.text = tooltip
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import QtQuick
|
||||
import Quickshell
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
Row {
|
||||
id: root
|
||||
property string tooltip: ""
|
||||
property bool _hovered: false
|
||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onHoveredChanged: {
|
||||
root._hovered = hovered;
|
||||
if (hovered && root.tooltip !== "") {
|
||||
M.TooltipState.text = root.tooltip;
|
||||
M.TooltipState.itemX = root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else if (!hovered && root.tooltip !== "") {
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTooltipChanged: if (_hovered && tooltip !== "")
|
||||
M.TooltipState.text = tooltip
|
||||
}
|
||||
|
|
@ -4,12 +4,12 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
opacity: S.Modules.battery.enable && S.BatteryService.available ? 1 : 0
|
||||
visible: opacity > 0
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Battery: " + Math.round(S.BatteryService.percent) + "%" + (S.BatteryService.charging ? " (charging)" : "")
|
||||
|
||||
property real _blinkOpacity: 1
|
||||
|
||||
|
|
@ -18,7 +18,6 @@ M.PinnableSection {
|
|||
minOpacity: 0.45
|
||||
}
|
||||
|
||||
// Bar widgets
|
||||
M.BarIcon {
|
||||
icon: {
|
||||
if (S.BatteryService.charging)
|
||||
|
|
@ -30,9 +29,6 @@ M.PinnableSection {
|
|||
opacity: root._blinkOpacity
|
||||
font.pixelSize: S.Theme.fontSize + 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: Math.round(S.BatteryService.percent) + "%"
|
||||
|
|
@ -40,12 +36,8 @@ M.PinnableSection {
|
|||
color: S.BatteryService.stateColor
|
||||
opacity: root._blinkOpacity
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
// Hover panel
|
||||
M.HoverPanel {
|
||||
id: hoverPanel
|
||||
showPanel: root._showPanel
|
||||
|
|
@ -55,6 +47,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-battery"
|
||||
panelTitle: "Battery"
|
||||
contentWidth: 240
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.BatteryApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,28 +4,28 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0
|
||||
visible: opacity > 0
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: {
|
||||
if (S.BluetoothService.state === "connected")
|
||||
return "Bluetooth: " + S.BluetoothService.device;
|
||||
if (S.BluetoothService.state === "off")
|
||||
return "Bluetooth: off";
|
||||
return "Bluetooth: on";
|
||||
}
|
||||
|
||||
M.BarIcon {
|
||||
icon: "\uF294"
|
||||
color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
visible: S.BluetoothService.state === "connected"
|
||||
label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
@ -44,6 +44,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-bluetooth"
|
||||
panelTitle: "Bluetooth"
|
||||
contentWidth: 250
|
||||
onDismissed: root.dismissPanel()
|
||||
titleActionsComponent: Component {
|
||||
Item {
|
||||
width: 20
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: Qt.formatDateTime(clock.date, "dddd, dd. MMMM yyyy")
|
||||
|
||||
SystemClock {
|
||||
id: clock
|
||||
|
|
@ -19,9 +19,6 @@ M.PinnableSection {
|
|||
label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm")
|
||||
minText: "Wed, 00. Sep 00:00"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -33,6 +30,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-clock"
|
||||
panelTitle: Qt.formatTime(clock.date, "HH:mm:ss")
|
||||
contentWidth: 220
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.ClockApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "CPU: " + S.SystemStats.cpuUsage + "% @ " + S.SystemStats.cpuFreqGhz.toFixed(2) + " GHz"
|
||||
|
||||
readonly property var _cores: S.SystemStats.cpuCores
|
||||
readonly property var _coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
|
||||
|
|
@ -34,17 +34,11 @@ M.PinnableSection {
|
|||
M.BarIcon {
|
||||
icon: "\uF2DB"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: S.SystemStats.cpuUsage.toString().padStart(2) + "%@" + S.SystemStats.cpuFreqGhz.toFixed(2)
|
||||
minText: "99%@9.99"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -56,6 +50,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-cpu"
|
||||
panelTitle: "CPU"
|
||||
contentWidth: 260
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.CpuApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Disk: " + _rootPct + "% used"
|
||||
|
||||
property var _mounts: S.SystemStats.diskMounts
|
||||
property int _rootPct: S.SystemStats.diskRootPct
|
||||
|
|
@ -23,18 +23,12 @@ M.PinnableSection {
|
|||
icon: "\uF0C9"
|
||||
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: root._rootPct + "%"
|
||||
minText: "100%"
|
||||
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -46,6 +40,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-disk"
|
||||
panelTitle: "Disk"
|
||||
contentWidth: 260
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.DiskApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,26 +4,20 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||
visible: S.Modules.gpu.enable && S.SystemStats.gpuAvailable
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "GPU: " + S.SystemStats.gpuUsage + "%"
|
||||
|
||||
M.BarIcon {
|
||||
icon: "\uEB4C"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: S.SystemStats.gpuUsage + "%"
|
||||
minText: "100%"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -35,6 +29,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-gpu"
|
||||
panelTitle: "GPU"
|
||||
contentWidth: 240
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.GpuApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,33 +4,19 @@ import Quickshell.Wayland
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
// Unified bar panel — fullscreen transparent window so content can resize
|
||||
// freely without triggering Wayland surface resizes.
|
||||
// Bar panel - fullscreen transparent window so content can resize freely
|
||||
// without triggering Wayland surface resizes.
|
||||
//
|
||||
// Hover mode (popupMode: false, default):
|
||||
// Parent drives visibility via showPanel. Panel auto-closes when showPanel
|
||||
// drops, with a 50ms debounce. Reports panelHovered back to parent.
|
||||
// Pass anchorItem for lazy position computation on each show.
|
||||
//
|
||||
// Popup mode (popupMode: true):
|
||||
// Shows immediately on creation. Stays open until click-outside or
|
||||
// dismiss() call. Emits dismissed() when closed — caller's LazyLoader
|
||||
// sets active: false. Pass anchorX (screen-relative centre x).
|
||||
// Parent drives visibility via showPanel. Click-outside or Esc dismisses
|
||||
// and emits dismissed(). Pass anchorItem for lazy position computation.
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool popupMode: false
|
||||
|
||||
// Hover mode
|
||||
property bool showPanel: true
|
||||
property bool showPanel: false
|
||||
property Item anchorItem: null
|
||||
property bool panelHovered: false
|
||||
|
||||
// Popup mode
|
||||
property real anchorX: -1
|
||||
signal dismissed
|
||||
|
||||
// Shared
|
||||
required property color accentColor
|
||||
property string panelTitle: ""
|
||||
property Component titleActionsComponent: null
|
||||
|
|
@ -43,22 +29,6 @@ PanelWindow {
|
|||
color: "transparent"
|
||||
|
||||
property bool _winVisible: false
|
||||
property bool _pinned: false
|
||||
property real _dragStartX: 0
|
||||
property real _dragStartY: 0
|
||||
property bool _dragging: false
|
||||
|
||||
// When pinned: mask = full panel so content is interactive, desktop accessible outside.
|
||||
// When dragging: mask = null (full screen) so Niri keeps delivering events when cursor
|
||||
// leaves the panel bounds mid-drag.
|
||||
mask: (_pinned && !_dragging) ? _pinMask : null
|
||||
|
||||
property Region _pinMask: Region {
|
||||
x: panelContainer.x
|
||||
y: panelContainer.y
|
||||
width: panelContainer.width
|
||||
height: panelContainer.height
|
||||
}
|
||||
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
WlrLayershell.exclusiveZone: 0
|
||||
|
|
@ -80,38 +50,29 @@ PanelWindow {
|
|||
if (root.anchorItem) {
|
||||
const pt = root.anchorItem.mapToGlobal(root.anchorItem.width / 2, 0);
|
||||
cx = pt.x - (scr?.x ?? 0);
|
||||
} else {
|
||||
} else if (root.anchorX >= 0) {
|
||||
cx = root.anchorX;
|
||||
} else {
|
||||
cx = sw / 2;
|
||||
}
|
||||
panelContainer.x = Math.max(0, Math.min(Math.round(cx - root.contentWidth / 2), sw - root.contentWidth));
|
||||
}
|
||||
|
||||
// Grace period: after _show(), suppress auto-close briefly so Niri has time
|
||||
// to route wl_pointer.enter to the new overlay surface (cursor may be stationary).
|
||||
// Popup mode gets a longer window (1500ms) so the user can move the cursor to the
|
||||
// panel content after clicking the bar without accidentally dismissing it.
|
||||
// Grace period: after _show(), suppress click-outside dismiss briefly so
|
||||
// Niri has time to route wl_pointer.enter to the new overlay surface.
|
||||
property bool _grace: false
|
||||
Timer {
|
||||
id: _graceTimer
|
||||
interval: root.popupMode ? 1500 : 400
|
||||
onTriggered: {
|
||||
root._grace = false;
|
||||
if (!root.showPanel && !root._pinned)
|
||||
root.dismiss();
|
||||
}
|
||||
interval: 400
|
||||
onTriggered: root._grace = false
|
||||
}
|
||||
|
||||
// Content-change grace: call keepOpen(ms) when panel content is about to
|
||||
// resize/rebuild (session switch, device list change, etc.) to prevent the
|
||||
// hover-drop-on-resize from closing the panel.
|
||||
// resize/rebuild (session switch, device list change, etc.).
|
||||
property bool _contentBusy: false
|
||||
Timer {
|
||||
id: _contentBusyTimer
|
||||
onTriggered: {
|
||||
root._contentBusy = false;
|
||||
if (!root.showPanel && !root._grace && !root._pinned)
|
||||
_hideTimer.restart();
|
||||
}
|
||||
onTriggered: root._contentBusy = false
|
||||
}
|
||||
function keepOpen(ms) {
|
||||
_contentBusy = true;
|
||||
|
|
@ -121,13 +82,7 @@ PanelWindow {
|
|||
|
||||
function _show() {
|
||||
_updatePosition();
|
||||
// Only snap to closed position when genuinely opening from scratch.
|
||||
// If we are interrupting a hide animation, animate back from the current
|
||||
// y/opacity so there's no visible jump — the showAnim NumberAnimations
|
||||
// always run from the *current* value to their target.
|
||||
if (!hideAnim.running) {
|
||||
// Explicitly set y before animating — avoids the y:-height binding (live, depends on
|
||||
// _panelColumn.height) surviving a 0→0 no-op animation when layout isn't done yet.
|
||||
panelContainer.y = -(panelContainer.height > 0 ? panelContainer.height : 400);
|
||||
panelContainer.opacity = 0;
|
||||
}
|
||||
|
|
@ -144,11 +99,11 @@ PanelWindow {
|
|||
}
|
||||
|
||||
function dismiss() {
|
||||
_pinned = false;
|
||||
if (!_winVisible)
|
||||
return;
|
||||
showAnim.stop();
|
||||
if (S.Theme.reducedMotion) {
|
||||
_winVisible = false;
|
||||
if (popupMode)
|
||||
dismissed();
|
||||
} else {
|
||||
hideAnim.start();
|
||||
|
|
@ -157,30 +112,12 @@ PanelWindow {
|
|||
_graceTimer.stop();
|
||||
}
|
||||
|
||||
Component.onCompleted: if (popupMode)
|
||||
_show()
|
||||
|
||||
Timer {
|
||||
id: _hideTimer
|
||||
interval: 150
|
||||
onTriggered: if (!root.showPanel && !root._grace && !root._pinned && !root._contentBusy)
|
||||
root.dismiss()
|
||||
}
|
||||
|
||||
onShowPanelChanged: {
|
||||
if (root.popupMode)
|
||||
return;
|
||||
if (showPanel) {
|
||||
_hideTimer.stop();
|
||||
// Only replay the open animation if the panel is actually closed or
|
||||
// currently animating away. If it is already visible, stopping the
|
||||
// hide timer is sufficient — calling _show() would reset y/opacity to
|
||||
// 0 and cause a visible flash when the cursor crosses the gap between
|
||||
// the bar module and the panel.
|
||||
if (!_winVisible || hideAnim.running)
|
||||
_show();
|
||||
} else {
|
||||
_hideTimer.restart();
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -220,17 +157,15 @@ PanelWindow {
|
|||
}
|
||||
onFinished: {
|
||||
root._winVisible = false;
|
||||
if (root.popupMode)
|
||||
root.dismissed();
|
||||
}
|
||||
}
|
||||
|
||||
// Popup mode: click-outside dismiss.
|
||||
// Click-outside dismiss.
|
||||
// TapHandler fires for all taps; position check skips taps inside panelContainer.
|
||||
// Gated on !_grace so spurious events during the 400ms opening window don't dismiss.
|
||||
// Gated on !_grace so spurious events during the opening window don't dismiss.
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: root.popupMode
|
||||
|
||||
TapHandler {
|
||||
enabled: !root._grace
|
||||
|
|
@ -243,6 +178,13 @@ PanelWindow {
|
|||
}
|
||||
}
|
||||
|
||||
// Esc dismiss
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
enabled: root._winVisible
|
||||
onActivated: root.dismiss()
|
||||
}
|
||||
|
||||
M.PopupBackground {
|
||||
x: panelContainer.x
|
||||
y: panelContainer.y
|
||||
|
|
@ -260,52 +202,17 @@ PanelWindow {
|
|||
height: _panelColumn.height
|
||||
opacity: 0
|
||||
|
||||
HoverHandler {
|
||||
enabled: !root.popupMode && !root._pinned
|
||||
onHoveredChanged: if (!root.popupMode && !root._pinned)
|
||||
root.panelHovered = hovered
|
||||
}
|
||||
|
||||
Column {
|
||||
id: _panelColumn
|
||||
width: root.contentWidth
|
||||
|
||||
// Header row: title + action buttons + pin — shown in hover mode always,
|
||||
// and in popup mode when a title or actions are provided.
|
||||
// Header row: title + action buttons
|
||||
Item {
|
||||
id: _headerItem
|
||||
visible: !root.popupMode || root.panelTitle !== "" || root.titleActionsComponent !== null
|
||||
visible: root.panelTitle !== "" || root.titleActionsComponent !== null
|
||||
width: parent.width
|
||||
height: 24
|
||||
|
||||
// Drag header to freely reposition panel while pinned (hover mode only).
|
||||
// _dragging clears the input mask so Niri keeps delivering events when the
|
||||
// cursor leaves the panel bounds during a fast drag.
|
||||
DragHandler {
|
||||
enabled: root._pinned && !root.popupMode
|
||||
onActiveChanged: {
|
||||
root._dragging = active;
|
||||
if (active) {
|
||||
root._dragStartX = panelContainer.x;
|
||||
root._dragStartY = panelContainer.y;
|
||||
}
|
||||
}
|
||||
onActiveTranslationChanged: {
|
||||
if (active) {
|
||||
const sw = root.screen?.width ?? 1920;
|
||||
const sh = root.screen?.height ?? 1080;
|
||||
panelContainer.x = Math.max(0, Math.min(root._dragStartX + activeTranslation.x, sw - root.contentWidth));
|
||||
panelContainer.y = Math.max(0, Math.min(root._dragStartY + activeTranslation.y, sh - panelContainer.height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show move cursor on header when pinned
|
||||
HoverHandler {
|
||||
enabled: root._pinned && !root.popupMode
|
||||
cursorShape: Qt.SizeAllCursor
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: root.panelTitle !== ""
|
||||
anchors.left: parent.left
|
||||
|
|
@ -318,52 +225,14 @@ PanelWindow {
|
|||
font.family: S.Theme.fontFamily
|
||||
}
|
||||
|
||||
// Action buttons — anchored left of pin button slot
|
||||
Loader {
|
||||
id: _titleActionsLoader
|
||||
anchors.right: _pinBtn.left
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
sourceComponent: root.titleActionsComponent
|
||||
}
|
||||
|
||||
// Pin button — zero-width in popup mode so actions anchor flush to right
|
||||
Item {
|
||||
id: _pinBtn
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.popupMode ? 0 : 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: root.popupMode ? 0 : 20
|
||||
height: 20
|
||||
visible: !root.popupMode
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
root._pinned = !root._pinned;
|
||||
if (!root._pinned && !root.showPanel)
|
||||
root.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: root._pinned ? "\uDB81\uDC03" : "\uDB82\uDD31"
|
||||
color: root._pinned ? root.accentColor : S.Theme.base04
|
||||
font.pixelSize: S.Theme.fontSize - 1
|
||||
font.family: S.Theme.iconFontFamily
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider at bottom of header
|
||||
Rectangle {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
|
@ -391,7 +260,7 @@ PanelWindow {
|
|||
}
|
||||
}
|
||||
|
||||
// Border overlay — on top of content so full-bleed items don't cover it
|
||||
// Border overlay - on top of content so full-bleed items don't cover it
|
||||
Rectangle {
|
||||
x: panelContainer.x
|
||||
y: panelContainer.y
|
||||
|
|
|
|||
|
|
@ -3,17 +3,15 @@ import Quickshell
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
M.BarIcon {
|
||||
M.BarModule {
|
||||
id: root
|
||||
color: S.IdleInhibitService.active ? S.Theme.base09 : root.accentColor
|
||||
tooltip: {
|
||||
const parts = ["Idle inhibition: " + (S.IdleInhibitService.active ? "active" : "inactive")];
|
||||
if (S.IdleInhibitService.inhibitors)
|
||||
parts.push(S.IdleInhibitService.inhibitors);
|
||||
return parts.join("\n");
|
||||
}
|
||||
|
||||
icon: S.IdleInhibitService.active ? "\uF06E" : "\uF070"
|
||||
onTapped: S.IdleInhibitService.toggle()
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
|
|
@ -23,9 +21,9 @@ M.BarIcon {
|
|||
onTriggered: S.IdleInhibitService.refreshInhibitors()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: S.IdleInhibitService.toggle()
|
||||
M.BarIcon {
|
||||
color: S.IdleInhibitService.active ? S.Theme.base09 : root.accentColor
|
||||
icon: S.IdleInhibitService.active ? "\uF06E" : "\uF070"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Memory: " + usedGb.toFixed(1) + " / " + totalGb.toFixed(1) + " GB"
|
||||
|
||||
property int percent: S.SystemStats.memPercent
|
||||
property real usedGb: S.SystemStats.memUsedGb
|
||||
|
|
@ -25,17 +25,11 @@ M.PinnableSection {
|
|||
M.BarIcon {
|
||||
icon: "\uEFC5"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: root.percent + "%"
|
||||
minText: "100%"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -47,6 +41,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-memory"
|
||||
panelTitle: "Memory"
|
||||
contentWidth: 240
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.MemoryApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
opacity: S.Modules.mpris.enable && player !== null ? 1 : 0
|
||||
visible: opacity > 0
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: player ? (player.trackTitle || player.identity || "Media") + (playing ? " (playing)" : " (paused)") : "Media"
|
||||
|
||||
readonly property var _players: S.MprisService.players
|
||||
readonly property MprisPlayer player: S.MprisService.player
|
||||
|
|
@ -19,7 +19,6 @@ M.PinnableSection {
|
|||
property string _cachedArt: ""
|
||||
property string _artTrack: ""
|
||||
|
||||
// Cache art URL at root level so it's captured even when panel is hidden
|
||||
readonly property string _artUrl: player?.trackArtUrl ?? ""
|
||||
readonly property string _currentTrack: player?.trackTitle ?? ""
|
||||
on_ArtUrlChanged: if (_artUrl)
|
||||
|
|
@ -74,22 +73,12 @@ M.PinnableSection {
|
|||
M.BarIcon {
|
||||
icon: root.playing ? "\uF04B" : (root.player?.playbackState === MprisPlaybackState.Paused ? "\uDB80\uDFE4" : "\uDB81\uDCDB")
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: root.player?.trackTitle || root.player?.identity || ""
|
||||
elide: Text.ElideRight
|
||||
width: Math.min(implicitWidth, 200)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -101,6 +90,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-mpris"
|
||||
panelTitle: "Now Playing"
|
||||
contentWidth: 280
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.MprisApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,10 +4,18 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: {
|
||||
if (state === "wifi")
|
||||
return "Wi-Fi: " + S.NetworkService.essid;
|
||||
if (state === "eth")
|
||||
return "Ethernet: connected";
|
||||
if (state === "linked")
|
||||
return "Network: linked";
|
||||
return "Network: disconnected";
|
||||
}
|
||||
|
||||
readonly property string state: S.NetworkService.state
|
||||
|
||||
|
|
@ -23,18 +31,12 @@ M.PinnableSection {
|
|||
}
|
||||
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
visible: root.state === "wifi"
|
||||
label: S.NetworkService.essid
|
||||
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
|
@ -53,6 +55,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-network"
|
||||
panelTitle: "Wi-Fi"
|
||||
contentWidth: 250
|
||||
onDismissed: root.dismissPanel()
|
||||
titleActionsComponent: Component {
|
||||
Item {
|
||||
width: 20
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: S.NotifService.count > 0 ? "Notifications: " + S.NotifService.count + (S.NotifService.dnd ? " (DND)" : "") : (S.NotifService.dnd ? "Do not disturb" : "No notifications")
|
||||
|
||||
readonly property bool hasUrgent: S.NotifService.list.some(n => n.urgency === NotificationUrgency.Critical && n.state !== "dismissed")
|
||||
|
||||
|
|
@ -20,18 +20,12 @@ M.PinnableSection {
|
|||
}
|
||||
color: S.NotifService.dnd ? S.Theme.base04 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
id: countLabel
|
||||
label: S.NotifService.count > 0 ? String(S.NotifService.count) + (root.hasUrgent ? "!" : "") : ""
|
||||
color: root.hasUrgent ? S.Theme.base08 : root.accentColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
id: countScale
|
||||
|
|
@ -83,6 +77,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-notifications"
|
||||
panelTitle: "Notifications"
|
||||
contentWidth: 350
|
||||
onDismissed: root.dismissPanel()
|
||||
titleActionsComponent: Component {
|
||||
Row {
|
||||
spacing: 8
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
import QtQuick
|
||||
|
||||
// Base component for bar modules with OSD flash behavior (Volume, Backlight).
|
||||
// Panel shows on hover or when flashPanel() is called, auto-dismisses after 1.5s.
|
||||
// Modules bind _panelHovered to their HoverPanel's panelHovered property.
|
||||
BarSection {
|
||||
id: root
|
||||
tooltip: ""
|
||||
|
||||
property bool _panelHovered: false
|
||||
property bool _osdActive: false
|
||||
readonly property bool _anyHover: root._hovered || _panelHovered
|
||||
readonly property bool _showPanel: _anyHover || _osdActive
|
||||
|
||||
function flashPanel() {
|
||||
_osdActive = true;
|
||||
_osdTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _osdTimer
|
||||
interval: 1500
|
||||
onTriggered: root._osdActive = false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import QtQuick
|
||||
|
||||
// Base component for bar modules with a pinnable hover panel.
|
||||
// Provides the _pinned/_anyHover/_showPanel/_unpinTimer boilerplate.
|
||||
// Modules bind _panelHovered to their HoverPanel's panelHovered property.
|
||||
BarSection {
|
||||
id: root
|
||||
tooltip: ""
|
||||
|
||||
property bool _pinned: false
|
||||
property bool _panelHovered: false
|
||||
readonly property bool _anyHover: root._hovered || _panelHovered
|
||||
readonly property bool _showPanel: _anyHover || _pinned
|
||||
|
||||
on_AnyHoverChanged: {
|
||||
if (_anyHover)
|
||||
_unpinTimer.stop();
|
||||
else if (_pinned)
|
||||
_unpinTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _unpinTimer
|
||||
interval: 500
|
||||
onTriggered: root._pinned = false
|
||||
}
|
||||
}
|
||||
|
|
@ -3,39 +3,40 @@ import Quickshell
|
|||
import Quickshell.Io
|
||||
import "." as M
|
||||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.BarIcon {
|
||||
M.BarModule {
|
||||
id: root
|
||||
icon: "\uF011"
|
||||
tooltip: "Power menu"
|
||||
|
||||
required property var bar
|
||||
|
||||
Process {
|
||||
id: runner
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
menuLoader.active = !menuLoader.active;
|
||||
M.TooltipState.visible = false;
|
||||
}
|
||||
M.BarIcon {
|
||||
icon: "\uF011"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: menuLoader
|
||||
active: false
|
||||
M.PowerMenu {
|
||||
M.HoverPanel {
|
||||
id: hoverPanel
|
||||
showPanel: root._showPanel
|
||||
screen: QsWindow.window?.screen ?? null
|
||||
anchorItem: root
|
||||
accentColor: root.accentColor
|
||||
panelNamespace: "nova-power"
|
||||
panelTitle: "Power"
|
||||
contentWidth: 180
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.PowerApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
accentColor: root.accentColor
|
||||
screen: root.bar.screen
|
||||
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
|
||||
onDismissed: menuLoader.active = false
|
||||
onRunCommand: cmd => {
|
||||
runner.command = cmd;
|
||||
runner.running = true;
|
||||
}
|
||||
onDismiss: root.dismissPanel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@ import QtQuick
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
M.BarIcon {
|
||||
M.BarModule {
|
||||
id: root
|
||||
tooltip: "Power profile: " + (S.PowerProfileService.profile || "unknown")
|
||||
onTapped: {
|
||||
const cycle = ["performance", "balanced", "power-saver"];
|
||||
const idx = cycle.indexOf(S.PowerProfileService.profile);
|
||||
S.PowerProfileService.set(cycle[(idx + 1) % cycle.length]);
|
||||
}
|
||||
|
||||
M.BarIcon {
|
||||
color: S.PowerProfileService.profile === "performance" ? S.Theme.base09 : S.PowerProfileService.profile === "power-saver" ? S.Theme.base0B : root.accentColor
|
||||
|
||||
icon: {
|
||||
if (S.PowerProfileService.profile === "performance")
|
||||
return "\uF0E7";
|
||||
|
|
@ -17,14 +22,6 @@ M.BarIcon {
|
|||
return "\uF24E";
|
||||
return "\uF0E7";
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const cycle = ["performance", "balanced", "power-saver"];
|
||||
const idx = cycle.indexOf(S.PowerProfileService.profile);
|
||||
S.PowerProfileService.set(cycle[(idx + 1) % cycle.length]);
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import Quickshell.Services.Pipewire
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
Row {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
cursorShape: Qt.ArrowCursor
|
||||
|
||||
// Only detect active client streams, not hardware sources/devices
|
||||
readonly property bool _videoCapture: {
|
||||
if (!Pipewire.nodes)
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -4,16 +4,15 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Temperature: " + _temp + "\u00B0C"
|
||||
|
||||
readonly property int _warm: S.Modules.temperature.warm || 80
|
||||
readonly property int _hot: S.Modules.temperature.hot || 90
|
||||
readonly property string _deviceFilter: S.Modules.temperature.device || ""
|
||||
|
||||
// If a device filter is set, use that device's temp; otherwise fall back to system max
|
||||
readonly property int _temp: {
|
||||
if (_deviceFilter !== "") {
|
||||
const dev = S.SystemStats.tempDevices.find(d => d.name === _deviceFilter);
|
||||
|
|
@ -34,18 +33,12 @@ M.PinnableSection {
|
|||
icon: "\uF2C9"
|
||||
color: root._stateColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: root._temp + "\u00B0C"
|
||||
minText: "100\u00B0C"
|
||||
color: root._stateColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -57,6 +50,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-temperature"
|
||||
panelTitle: "Temperature"
|
||||
contentWidth: 220
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.TemperatureApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import "../services" as S
|
|||
M.HoverPanel {
|
||||
id: menuWindow
|
||||
|
||||
popupMode: true
|
||||
showPanel: true
|
||||
|
||||
required property var handle
|
||||
|
||||
|
|
|
|||
|
|
@ -7,10 +7,11 @@ import Quickshell.Services.SystemTray
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
RowLayout {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing + 2
|
||||
visible: S.Modules.tray.enable && _trayRepeater.count > 0
|
||||
cursorShape: Qt.ArrowCursor
|
||||
|
||||
required property var bar
|
||||
property var _activeMenu: null
|
||||
|
|
@ -69,7 +70,7 @@ RowLayout {
|
|||
M.ThemedIcon {
|
||||
anchors.fill: parent
|
||||
source: iconItem.modelData.icon
|
||||
tint: iconItem._needsAttention ? S.Theme.base08 : (root.parent?.accentColor ?? S.Theme.base05)
|
||||
tint: iconItem._needsAttention ? S.Theme.base08 : root.accentColor
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +82,7 @@ RowLayout {
|
|||
M.TooltipState.text = tip;
|
||||
M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else if (!hovered) {
|
||||
M.TooltipState.visible = false;
|
||||
|
|
@ -114,7 +115,7 @@ RowLayout {
|
|||
id: menuLoader
|
||||
active: false
|
||||
M.TrayMenu {
|
||||
accentColor: root.parent?.accentColor ?? S.Theme.base05
|
||||
accentColor: root.accentColor
|
||||
handle: iconItem.modelData.menu
|
||||
screen: root.bar.screen
|
||||
anchorX: iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.OsdSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: "Volume: " + Math.round(volume * 100) + "%" + (muted ? " (muted)" : "")
|
||||
|
||||
PwObjectTracker {
|
||||
objects: [Pipewire.defaultAudioSink, ...root._streamList]
|
||||
|
|
@ -63,24 +63,12 @@ M.OsdSection {
|
|||
minIcon: "\uF028"
|
||||
color: root._volumeColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (root.sink?.audio)
|
||||
root.sink.audio.muted = !root.sink.audio.muted
|
||||
}
|
||||
}
|
||||
M.BarLabel {
|
||||
label: Math.round(root.volume * 100) + "%"
|
||||
minText: "100%"
|
||||
color: root._volumeColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (root.sink?.audio)
|
||||
root.sink.audio.muted = !root.sink.audio.muted
|
||||
}
|
||||
}
|
||||
|
||||
WheelHandler {
|
||||
|
|
@ -100,6 +88,7 @@ M.OsdSection {
|
|||
panelNamespace: "nova-volume"
|
||||
panelTitle: "Sound"
|
||||
contentWidth: 220
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.VolumeApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -4,18 +4,15 @@ import "." as M
|
|||
import "../services" as S
|
||||
import "../applets" as C
|
||||
|
||||
M.PinnableSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
visible: S.Modules.weather.enable && S.WeatherService.available
|
||||
_panelHovered: hoverPanel.panelHovered
|
||||
tooltip: S.WeatherService.summary || "Weather"
|
||||
|
||||
M.BarIcon {
|
||||
icon: S.WeatherService.icon
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
TapHandler {
|
||||
onTapped: root._pinned = !root._pinned
|
||||
}
|
||||
}
|
||||
|
||||
M.HoverPanel {
|
||||
|
|
@ -27,6 +24,7 @@ M.PinnableSection {
|
|||
panelNamespace: "nova-weather"
|
||||
panelTitle: "Weather"
|
||||
contentWidth: 280
|
||||
onDismissed: root.dismissPanel()
|
||||
|
||||
C.WeatherApplet {
|
||||
width: hoverPanel.contentWidth
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import Quickshell.Widgets
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
M.BarSection {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: S.Theme.moduleSpacing
|
||||
tooltip: S.NiriIpc.focusedAppId ? S.NiriIpc.focusedAppId + "\n" + S.NiriIpc.focusedTitle : S.NiriIpc.focusedTitle
|
||||
cursorShape: Qt.ArrowCursor
|
||||
|
||||
readonly property string _iconSource: {
|
||||
if (!S.NiriIpc.focusedAppId)
|
||||
|
|
@ -19,7 +20,7 @@ M.BarSection {
|
|||
|
||||
readonly property real _iconOffset: _icon.visible ? _icon.width + root.spacing : 0
|
||||
|
||||
// Natural content width — Bar.qml uses this to cap the group width
|
||||
// Natural content width - Bar.qml uses this to cap the group width
|
||||
readonly property real naturalWidth: _iconOffset + _label.implicitWidth
|
||||
|
||||
IconImage {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import Quickshell.Io
|
|||
import "." as M
|
||||
import "../services" as S
|
||||
|
||||
Row {
|
||||
M.BarModule {
|
||||
id: root
|
||||
spacing: 4
|
||||
cursorShape: Qt.ArrowCursor
|
||||
|
||||
required property var bar
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ Row {
|
|||
M.TooltipState.text = name;
|
||||
M.TooltipState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
||||
M.TooltipState.accentColor = root.parent?.accentColor ?? S.Theme.base05;
|
||||
M.TooltipState.accentColor = root.accentColor;
|
||||
M.TooltipState.visible = true;
|
||||
} else {
|
||||
M.TooltipState.visible = false;
|
||||
|
|
@ -82,7 +83,7 @@ Row {
|
|||
width: S.Theme.fontSize + 4
|
||||
height: S.Theme.fontSize + 4
|
||||
radius: width / 2
|
||||
color: pill.active ? (root.parent?.accentColor ?? S.Theme.base0D) : (pill._hovered ? S.Theme.base03 : S.Theme.base02)
|
||||
color: pill.active ? root.accentColor : (pill._hovered ? S.Theme.base03 : S.Theme.base02)
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: 150
|
||||
|
|
@ -92,7 +93,7 @@ Row {
|
|||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: pill.modelData.idx
|
||||
color: pill.active ? S.Theme.base00 : (root.parent?.accentColor ?? S.Theme.base05)
|
||||
color: pill.active ? S.Theme.base00 : root.accentColor
|
||||
font.pixelSize: S.Theme.fontSize - 2
|
||||
font.family: S.Theme.fontFamily
|
||||
font.bold: pill.active
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Bar 1.0 Bar.qml
|
|||
BarGroup 1.0 BarGroup.qml
|
||||
BarIcon 1.0 BarIcon.qml
|
||||
BarLabel 1.0 BarLabel.qml
|
||||
BarSection 1.0 BarSection.qml
|
||||
BarModule 1.0 BarModule.qml
|
||||
BatteryModule 1.0 BatteryModule.qml
|
||||
BluetoothModule 1.0 BluetoothModule.qml
|
||||
ClockModule 1.0 ClockModule.qml
|
||||
|
|
@ -21,11 +21,8 @@ NetworkModule 1.0 NetworkModule.qml
|
|||
NotifCard 1.0 NotifCard.qml
|
||||
NotifPopup 1.0 NotifPopup.qml
|
||||
NotificationsModule 1.0 NotificationsModule.qml
|
||||
OsdSection 1.0 OsdSection.qml
|
||||
OverviewBackdrop 1.0 OverviewBackdrop.qml
|
||||
PinnableSection 1.0 PinnableSection.qml
|
||||
PopupBackground 1.0 PopupBackground.qml
|
||||
PowerMenu 1.0 PowerMenu.qml
|
||||
PowerModule 1.0 PowerModule.qml
|
||||
PowerProfileModule 1.0 PowerProfileModule.qml
|
||||
PrivacyModule 1.0 PrivacyModule.qml
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue