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 QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import "." as M
|
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
M.HoverPanel {
|
Column {
|
||||||
id: menuWindow
|
id: root
|
||||||
|
|
||||||
popupMode: true
|
|
||||||
contentWidth: 180
|
|
||||||
|
|
||||||
|
required property color accentColor
|
||||||
signal runCommand(var cmd)
|
signal runCommand(var cmd)
|
||||||
|
signal dismiss
|
||||||
|
|
||||||
readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== ""
|
readonly property bool _isNiri: Quickshell.env("NIRI_SOCKET") !== ""
|
||||||
|
|
||||||
// Confirmation state: null = normal menu, object = pending confirm
|
|
||||||
property var _confirmItem: null
|
property var _confirmItem: null
|
||||||
|
|
||||||
function _run(cmd) {
|
function _run(cmd) {
|
||||||
|
|
@ -35,8 +32,8 @@ M.HoverPanel {
|
||||||
|
|
||||||
// Normal menu entries
|
// Normal menu entries
|
||||||
Column {
|
Column {
|
||||||
visible: !menuWindow._confirmItem
|
visible: !root._confirmItem
|
||||||
width: menuWindow.contentWidth
|
width: root.width
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: [
|
model: [
|
||||||
|
|
@ -57,7 +54,7 @@ M.HoverPanel {
|
||||||
{
|
{
|
||||||
label: "Logout",
|
label: "Logout",
|
||||||
icon: "\uF2F5",
|
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,
|
color: S.Theme.base0A,
|
||||||
confirm: false
|
confirm: false
|
||||||
},
|
},
|
||||||
|
|
@ -83,7 +80,7 @@ M.HoverPanel {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
width: menuWindow.contentWidth
|
width: root.width
|
||||||
height: 32
|
height: 32
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
|
@ -121,7 +118,7 @@ M.HoverPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: menuWindow._requestAction(entry.modelData)
|
onTapped: root._requestAction(entry.modelData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +126,8 @@ M.HoverPanel {
|
||||||
|
|
||||||
// Confirmation view
|
// Confirmation view
|
||||||
Column {
|
Column {
|
||||||
visible: !!menuWindow._confirmItem
|
visible: !!root._confirmItem
|
||||||
width: menuWindow.contentWidth
|
width: root.width
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
@ -139,8 +136,8 @@ M.HoverPanel {
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: menuWindow._confirmItem ? menuWindow._confirmItem.label + "?" : ""
|
text: root._confirmItem ? root._confirmItem.label + "?" : ""
|
||||||
color: menuWindow._confirmItem ? menuWindow._confirmItem.color : S.Theme.base05
|
color: root._confirmItem ? root._confirmItem.color : S.Theme.base05
|
||||||
font.pixelSize: S.Theme.fontSize
|
font.pixelSize: S.Theme.fontSize
|
||||||
font.family: S.Theme.fontFamily
|
font.family: S.Theme.fontFamily
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
@ -151,7 +148,6 @@ M.HoverPanel {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: 8
|
spacing: 8
|
||||||
|
|
||||||
// Cancel
|
|
||||||
Item {
|
Item {
|
||||||
width: 72
|
width: 72
|
||||||
height: 28
|
height: 28
|
||||||
|
|
@ -178,11 +174,10 @@ M.HoverPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: menuWindow._cancelConfirm()
|
onTapped: root._cancelConfirm()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm
|
|
||||||
Item {
|
Item {
|
||||||
width: 72
|
width: 72
|
||||||
height: 28
|
height: 28
|
||||||
|
|
@ -190,20 +185,20 @@ M.HoverPanel {
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: {
|
color: {
|
||||||
if (!menuWindow._confirmItem)
|
if (!root._confirmItem)
|
||||||
return S.Theme.base02;
|
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);
|
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
|
radius: S.Theme.radius
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: menuWindow._confirmItem ? menuWindow._confirmItem.color : S.Theme.base03
|
border.color: root._confirmItem ? root._confirmItem.color : S.Theme.base03
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Confirm"
|
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.pixelSize: S.Theme.fontSize
|
||||||
font.family: S.Theme.fontFamily
|
font.family: S.Theme.fontFamily
|
||||||
font.bold: true
|
font.bold: true
|
||||||
|
|
@ -216,8 +211,8 @@ M.HoverPanel {
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
onTapped: {
|
onTapped: {
|
||||||
if (menuWindow._confirmItem)
|
if (root._confirmItem)
|
||||||
menuWindow._run(menuWindow._confirmItem.cmd);
|
root._run(root._confirmItem.cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ MemoryApplet 1.0 MemoryApplet.qml
|
||||||
MprisApplet 1.0 MprisApplet.qml
|
MprisApplet 1.0 MprisApplet.qml
|
||||||
NetworkApplet 1.0 NetworkApplet.qml
|
NetworkApplet 1.0 NetworkApplet.qml
|
||||||
NotifApplet 1.0 NotifApplet.qml
|
NotifApplet 1.0 NotifApplet.qml
|
||||||
|
PowerApplet 1.0 PowerApplet.qml
|
||||||
Separator 1.0 Separator.qml
|
Separator 1.0 Separator.qml
|
||||||
SparklineCanvas 1.0 SparklineCanvas.qml
|
SparklineCanvas 1.0 SparklineCanvas.qml
|
||||||
TemperatureApplet 1.0 TemperatureApplet.qml
|
TemperatureApplet 1.0 TemperatureApplet.qml
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,12 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.OsdSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
opacity: S.Modules.backlight.enable && S.BacklightService.available ? 1 : 0
|
opacity: S.Modules.backlight.enable && S.BacklightService.available ? 1 : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: "Brightness: " + percent + "%"
|
||||||
|
|
||||||
property int percent: S.BacklightService.percent
|
property int percent: S.BacklightService.percent
|
||||||
property bool _percentInit: false
|
property bool _percentInit: false
|
||||||
|
|
@ -27,24 +27,6 @@ M.OsdSection {
|
||||||
onWheel: event => S.BacklightService.adjust(event.angleDelta.y)
|
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 {
|
M.BarIcon {
|
||||||
icon: "\uF185"
|
icon: "\uF185"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
@ -54,4 +36,23 @@ M.OsdSection {
|
||||||
minText: "100%"
|
minText: "100%"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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
|
rightEdge: true
|
||||||
M.BatteryModule {}
|
M.BatteryModule {}
|
||||||
M.PowerModule {
|
M.PowerModule {
|
||||||
bar: bar
|
|
||||||
visible: S.Modules.power.enable
|
visible: S.Modules.power.enable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,13 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
|
||||||
import "." as M
|
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
|
// Icon element with crossfade animation on icon change.
|
||||||
|
// Pure visual component - tooltip handling lives in the parent BarModule.
|
||||||
Text {
|
Text {
|
||||||
id: root
|
id: root
|
||||||
property string icon: ""
|
property string icon: ""
|
||||||
property string tooltip: ""
|
|
||||||
property string minIcon: ""
|
property string minIcon: ""
|
||||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||||
property bool _hovered: false
|
|
||||||
property string _displayIcon: icon
|
property string _displayIcon: icon
|
||||||
property string _pendingIcon: ""
|
property string _pendingIcon: ""
|
||||||
|
|
||||||
|
|
@ -54,23 +52,4 @@ Text {
|
||||||
font.pixelSize: root.font.pixelSize
|
font.pixelSize: root.font.pixelSize
|
||||||
font.family: root.font.family
|
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 QtQuick
|
||||||
import Quickshell
|
|
||||||
import "." as M
|
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
|
// Label element with minimum-width support via minText.
|
||||||
|
// Pure visual component - tooltip handling lives in the parent BarModule.
|
||||||
Text {
|
Text {
|
||||||
id: root
|
id: root
|
||||||
property string label: ""
|
property string label: ""
|
||||||
property string tooltip: ""
|
|
||||||
property string minText: ""
|
property string minText: ""
|
||||||
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
property color accentColor: parent?.accentColor ?? S.Theme.base05
|
||||||
property bool _hovered: false
|
|
||||||
|
|
||||||
text: label
|
text: label
|
||||||
width: minText ? Math.max(implicitWidth, _minMetrics.width) : implicitWidth
|
width: minText ? Math.max(implicitWidth, _minMetrics.width) : implicitWidth
|
||||||
|
|
@ -25,23 +23,4 @@ Text {
|
||||||
font.pixelSize: root.font.pixelSize
|
font.pixelSize: root.font.pixelSize
|
||||||
font.family: root.font.family
|
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 "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
opacity: S.Modules.battery.enable && S.BatteryService.available ? 1 : 0
|
opacity: S.Modules.battery.enable && S.BatteryService.available ? 1 : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: "Battery: " + Math.round(S.BatteryService.percent) + "%" + (S.BatteryService.charging ? " (charging)" : "")
|
||||||
|
|
||||||
property real _blinkOpacity: 1
|
property real _blinkOpacity: 1
|
||||||
|
|
||||||
|
|
@ -18,7 +18,6 @@ M.PinnableSection {
|
||||||
minOpacity: 0.45
|
minOpacity: 0.45
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bar widgets
|
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: {
|
icon: {
|
||||||
if (S.BatteryService.charging)
|
if (S.BatteryService.charging)
|
||||||
|
|
@ -30,9 +29,6 @@ M.PinnableSection {
|
||||||
opacity: root._blinkOpacity
|
opacity: root._blinkOpacity
|
||||||
font.pixelSize: S.Theme.fontSize + 2
|
font.pixelSize: S.Theme.fontSize + 2
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: Math.round(S.BatteryService.percent) + "%"
|
label: Math.round(S.BatteryService.percent) + "%"
|
||||||
|
|
@ -40,12 +36,8 @@ M.PinnableSection {
|
||||||
color: S.BatteryService.stateColor
|
color: S.BatteryService.stateColor
|
||||||
opacity: root._blinkOpacity
|
opacity: root._blinkOpacity
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hover panel
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
id: hoverPanel
|
id: hoverPanel
|
||||||
showPanel: root._showPanel
|
showPanel: root._showPanel
|
||||||
|
|
@ -55,6 +47,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-battery"
|
panelNamespace: "nova-battery"
|
||||||
panelTitle: "Battery"
|
panelTitle: "Battery"
|
||||||
contentWidth: 240
|
contentWidth: 240
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.BatteryApplet {
|
C.BatteryApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,28 +4,28 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0
|
opacity: S.Modules.bluetooth.enable && S.BluetoothService.state !== "unavailable" ? 1 : 0
|
||||||
visible: opacity > 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 {
|
M.BarIcon {
|
||||||
icon: "\uF294"
|
icon: "\uF294"
|
||||||
color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor
|
color: S.BluetoothService.state === "off" ? S.Theme.base04 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
visible: S.BluetoothService.state === "connected"
|
visible: S.BluetoothService.state === "connected"
|
||||||
label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "")
|
label: S.BluetoothService.device + (S.BluetoothService.batteryPct >= 0 ? " " + S.BluetoothService.batteryPct + "%" : "")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|
@ -44,6 +44,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-bluetooth"
|
panelNamespace: "nova-bluetooth"
|
||||||
panelTitle: "Bluetooth"
|
panelTitle: "Bluetooth"
|
||||||
contentWidth: 250
|
contentWidth: 250
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
titleActionsComponent: Component {
|
titleActionsComponent: Component {
|
||||||
Item {
|
Item {
|
||||||
width: 20
|
width: 20
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: Qt.formatDateTime(clock.date, "dddd, dd. MMMM yyyy")
|
||||||
|
|
||||||
SystemClock {
|
SystemClock {
|
||||||
id: clock
|
id: clock
|
||||||
|
|
@ -19,9 +19,6 @@ M.PinnableSection {
|
||||||
label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm")
|
label: Qt.formatDateTime(clock.date, "ddd, dd. MMM HH:mm")
|
||||||
minText: "Wed, 00. Sep 00:00"
|
minText: "Wed, 00. Sep 00:00"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -33,6 +30,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-clock"
|
panelNamespace: "nova-clock"
|
||||||
panelTitle: Qt.formatTime(clock.date, "HH:mm:ss")
|
panelTitle: Qt.formatTime(clock.date, "HH:mm:ss")
|
||||||
contentWidth: 220
|
contentWidth: 220
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.ClockApplet {
|
C.ClockApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
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 _cores: S.SystemStats.cpuCores
|
||||||
readonly property var _coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
|
readonly property var _coreMaxFreq: S.SystemStats.cpuCoreMaxFreq
|
||||||
|
|
@ -34,17 +34,11 @@ M.PinnableSection {
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: "\uF2DB"
|
icon: "\uF2DB"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: S.SystemStats.cpuUsage.toString().padStart(2) + "%@" + S.SystemStats.cpuFreqGhz.toFixed(2)
|
label: S.SystemStats.cpuUsage.toString().padStart(2) + "%@" + S.SystemStats.cpuFreqGhz.toFixed(2)
|
||||||
minText: "99%@9.99"
|
minText: "99%@9.99"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -56,6 +50,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-cpu"
|
panelNamespace: "nova-cpu"
|
||||||
panelTitle: "CPU"
|
panelTitle: "CPU"
|
||||||
contentWidth: 260
|
contentWidth: 260
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.CpuApplet {
|
C.CpuApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: "Disk: " + _rootPct + "% used"
|
||||||
|
|
||||||
property var _mounts: S.SystemStats.diskMounts
|
property var _mounts: S.SystemStats.diskMounts
|
||||||
property int _rootPct: S.SystemStats.diskRootPct
|
property int _rootPct: S.SystemStats.diskRootPct
|
||||||
|
|
@ -23,18 +23,12 @@ M.PinnableSection {
|
||||||
icon: "\uF0C9"
|
icon: "\uF0C9"
|
||||||
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: root._rootPct + "%"
|
label: root._rootPct + "%"
|
||||||
minText: "100%"
|
minText: "100%"
|
||||||
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
color: root._anyWarn ? S.Theme.base09 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -46,6 +40,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-disk"
|
panelNamespace: "nova-disk"
|
||||||
panelTitle: "Disk"
|
panelTitle: "Disk"
|
||||||
contentWidth: 260
|
contentWidth: 260
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.DiskApplet {
|
C.DiskApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,20 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
||||||
visible: S.Modules.gpu.enable && S.SystemStats.gpuAvailable
|
visible: S.Modules.gpu.enable && S.SystemStats.gpuAvailable
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: "GPU: " + S.SystemStats.gpuUsage + "%"
|
||||||
|
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: "\uEB4C"
|
icon: "\uEB4C"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: S.SystemStats.gpuUsage + "%"
|
label: S.SystemStats.gpuUsage + "%"
|
||||||
minText: "100%"
|
minText: "100%"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -35,6 +29,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-gpu"
|
panelNamespace: "nova-gpu"
|
||||||
panelTitle: "GPU"
|
panelTitle: "GPU"
|
||||||
contentWidth: 240
|
contentWidth: 240
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.GpuApplet {
|
C.GpuApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,19 @@ import Quickshell.Wayland
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
// Unified bar panel — fullscreen transparent window so content can resize
|
// Bar panel - fullscreen transparent window so content can resize freely
|
||||||
// freely without triggering Wayland surface resizes.
|
// without triggering Wayland surface resizes.
|
||||||
//
|
//
|
||||||
// Hover mode (popupMode: false, default):
|
// Parent drives visibility via showPanel. Click-outside or Esc dismisses
|
||||||
// Parent drives visibility via showPanel. Panel auto-closes when showPanel
|
// and emits dismissed(). Pass anchorItem for lazy position computation.
|
||||||
// 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).
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool popupMode: false
|
property bool showPanel: false
|
||||||
|
|
||||||
// Hover mode
|
|
||||||
property bool showPanel: true
|
|
||||||
property Item anchorItem: null
|
property Item anchorItem: null
|
||||||
property bool panelHovered: false
|
|
||||||
|
|
||||||
// Popup mode
|
|
||||||
property real anchorX: -1
|
property real anchorX: -1
|
||||||
signal dismissed
|
signal dismissed
|
||||||
|
|
||||||
// Shared
|
|
||||||
required property color accentColor
|
required property color accentColor
|
||||||
property string panelTitle: ""
|
property string panelTitle: ""
|
||||||
property Component titleActionsComponent: null
|
property Component titleActionsComponent: null
|
||||||
|
|
@ -43,22 +29,6 @@ PanelWindow {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
property bool _winVisible: false
|
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.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.exclusiveZone: 0
|
WlrLayershell.exclusiveZone: 0
|
||||||
|
|
@ -80,38 +50,29 @@ PanelWindow {
|
||||||
if (root.anchorItem) {
|
if (root.anchorItem) {
|
||||||
const pt = root.anchorItem.mapToGlobal(root.anchorItem.width / 2, 0);
|
const pt = root.anchorItem.mapToGlobal(root.anchorItem.width / 2, 0);
|
||||||
cx = pt.x - (scr?.x ?? 0);
|
cx = pt.x - (scr?.x ?? 0);
|
||||||
} else {
|
} else if (root.anchorX >= 0) {
|
||||||
cx = root.anchorX;
|
cx = root.anchorX;
|
||||||
|
} else {
|
||||||
|
cx = sw / 2;
|
||||||
}
|
}
|
||||||
panelContainer.x = Math.max(0, Math.min(Math.round(cx - root.contentWidth / 2), sw - root.contentWidth));
|
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
|
// Grace period: after _show(), suppress click-outside dismiss briefly so
|
||||||
// to route wl_pointer.enter to the new overlay surface (cursor may be stationary).
|
// Niri has time to route wl_pointer.enter to the new overlay surface.
|
||||||
// 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.
|
|
||||||
property bool _grace: false
|
property bool _grace: false
|
||||||
Timer {
|
Timer {
|
||||||
id: _graceTimer
|
id: _graceTimer
|
||||||
interval: root.popupMode ? 1500 : 400
|
interval: 400
|
||||||
onTriggered: {
|
onTriggered: root._grace = false
|
||||||
root._grace = false;
|
|
||||||
if (!root.showPanel && !root._pinned)
|
|
||||||
root.dismiss();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content-change grace: call keepOpen(ms) when panel content is about to
|
// Content-change grace: call keepOpen(ms) when panel content is about to
|
||||||
// resize/rebuild (session switch, device list change, etc.) to prevent the
|
// resize/rebuild (session switch, device list change, etc.).
|
||||||
// hover-drop-on-resize from closing the panel.
|
|
||||||
property bool _contentBusy: false
|
property bool _contentBusy: false
|
||||||
Timer {
|
Timer {
|
||||||
id: _contentBusyTimer
|
id: _contentBusyTimer
|
||||||
onTriggered: {
|
onTriggered: root._contentBusy = false
|
||||||
root._contentBusy = false;
|
|
||||||
if (!root.showPanel && !root._grace && !root._pinned)
|
|
||||||
_hideTimer.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function keepOpen(ms) {
|
function keepOpen(ms) {
|
||||||
_contentBusy = true;
|
_contentBusy = true;
|
||||||
|
|
@ -121,13 +82,7 @@ PanelWindow {
|
||||||
|
|
||||||
function _show() {
|
function _show() {
|
||||||
_updatePosition();
|
_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) {
|
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.y = -(panelContainer.height > 0 ? panelContainer.height : 400);
|
||||||
panelContainer.opacity = 0;
|
panelContainer.opacity = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -144,11 +99,11 @@ PanelWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function dismiss() {
|
function dismiss() {
|
||||||
_pinned = false;
|
if (!_winVisible)
|
||||||
|
return;
|
||||||
showAnim.stop();
|
showAnim.stop();
|
||||||
if (S.Theme.reducedMotion) {
|
if (S.Theme.reducedMotion) {
|
||||||
_winVisible = false;
|
_winVisible = false;
|
||||||
if (popupMode)
|
|
||||||
dismissed();
|
dismissed();
|
||||||
} else {
|
} else {
|
||||||
hideAnim.start();
|
hideAnim.start();
|
||||||
|
|
@ -157,30 +112,12 @@ PanelWindow {
|
||||||
_graceTimer.stop();
|
_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: {
|
onShowPanelChanged: {
|
||||||
if (root.popupMode)
|
|
||||||
return;
|
|
||||||
if (showPanel) {
|
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)
|
if (!_winVisible || hideAnim.running)
|
||||||
_show();
|
_show();
|
||||||
} else {
|
} else {
|
||||||
_hideTimer.restart();
|
dismiss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,17 +157,15 @@ PanelWindow {
|
||||||
}
|
}
|
||||||
onFinished: {
|
onFinished: {
|
||||||
root._winVisible = false;
|
root._winVisible = false;
|
||||||
if (root.popupMode)
|
|
||||||
root.dismissed();
|
root.dismissed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Popup mode: click-outside dismiss.
|
// Click-outside dismiss.
|
||||||
// TapHandler fires for all taps; position check skips taps inside panelContainer.
|
// 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 {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: root.popupMode
|
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
enabled: !root._grace
|
enabled: !root._grace
|
||||||
|
|
@ -243,6 +178,13 @@ PanelWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Esc dismiss
|
||||||
|
Shortcut {
|
||||||
|
sequence: "Escape"
|
||||||
|
enabled: root._winVisible
|
||||||
|
onActivated: root.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
M.PopupBackground {
|
M.PopupBackground {
|
||||||
x: panelContainer.x
|
x: panelContainer.x
|
||||||
y: panelContainer.y
|
y: panelContainer.y
|
||||||
|
|
@ -260,52 +202,17 @@ PanelWindow {
|
||||||
height: _panelColumn.height
|
height: _panelColumn.height
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
enabled: !root.popupMode && !root._pinned
|
|
||||||
onHoveredChanged: if (!root.popupMode && !root._pinned)
|
|
||||||
root.panelHovered = hovered
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: _panelColumn
|
id: _panelColumn
|
||||||
width: root.contentWidth
|
width: root.contentWidth
|
||||||
|
|
||||||
// Header row: title + action buttons + pin — shown in hover mode always,
|
// Header row: title + action buttons
|
||||||
// and in popup mode when a title or actions are provided.
|
|
||||||
Item {
|
Item {
|
||||||
id: _headerItem
|
id: _headerItem
|
||||||
visible: !root.popupMode || root.panelTitle !== "" || root.titleActionsComponent !== null
|
visible: root.panelTitle !== "" || root.titleActionsComponent !== null
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 24
|
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 {
|
Text {
|
||||||
visible: root.panelTitle !== ""
|
visible: root.panelTitle !== ""
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
@ -318,52 +225,14 @@ PanelWindow {
|
||||||
font.family: S.Theme.fontFamily
|
font.family: S.Theme.fontFamily
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action buttons — anchored left of pin button slot
|
|
||||||
Loader {
|
Loader {
|
||||||
id: _titleActionsLoader
|
id: _titleActionsLoader
|
||||||
anchors.right: _pinBtn.left
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 4
|
anchors.rightMargin: 4
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
sourceComponent: root.titleActionsComponent
|
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 {
|
Rectangle {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
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 {
|
Rectangle {
|
||||||
x: panelContainer.x
|
x: panelContainer.x
|
||||||
y: panelContainer.y
|
y: panelContainer.y
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,15 @@ import Quickshell
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
M.BarIcon {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
color: S.IdleInhibitService.active ? S.Theme.base09 : root.accentColor
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
const parts = ["Idle inhibition: " + (S.IdleInhibitService.active ? "active" : "inactive")];
|
const parts = ["Idle inhibition: " + (S.IdleInhibitService.active ? "active" : "inactive")];
|
||||||
if (S.IdleInhibitService.inhibitors)
|
if (S.IdleInhibitService.inhibitors)
|
||||||
parts.push(S.IdleInhibitService.inhibitors);
|
parts.push(S.IdleInhibitService.inhibitors);
|
||||||
return parts.join("\n");
|
return parts.join("\n");
|
||||||
}
|
}
|
||||||
|
onTapped: S.IdleInhibitService.toggle()
|
||||||
icon: S.IdleInhibitService.active ? "\uF06E" : "\uF070"
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 5000
|
interval: 5000
|
||||||
|
|
@ -23,9 +21,9 @@ M.BarIcon {
|
||||||
onTriggered: S.IdleInhibitService.refreshInhibitors()
|
onTriggered: S.IdleInhibitService.refreshInhibitors()
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
M.BarIcon {
|
||||||
anchors.fill: parent
|
color: S.IdleInhibitService.active ? S.Theme.base09 : root.accentColor
|
||||||
cursorShape: Qt.PointingHandCursor
|
icon: S.IdleInhibitService.active ? "\uF06E" : "\uF070"
|
||||||
onClicked: S.IdleInhibitService.toggle()
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
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 int percent: S.SystemStats.memPercent
|
||||||
property real usedGb: S.SystemStats.memUsedGb
|
property real usedGb: S.SystemStats.memUsedGb
|
||||||
|
|
@ -25,17 +25,11 @@ M.PinnableSection {
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: "\uEFC5"
|
icon: "\uEFC5"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: root.percent + "%"
|
label: root.percent + "%"
|
||||||
minText: "100%"
|
minText: "100%"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -47,6 +41,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-memory"
|
panelNamespace: "nova-memory"
|
||||||
panelTitle: "Memory"
|
panelTitle: "Memory"
|
||||||
contentWidth: 240
|
contentWidth: 240
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.MemoryApplet {
|
C.MemoryApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
opacity: S.Modules.mpris.enable && player !== null ? 1 : 0
|
opacity: S.Modules.mpris.enable && player !== null ? 1 : 0
|
||||||
visible: opacity > 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 var _players: S.MprisService.players
|
||||||
readonly property MprisPlayer player: S.MprisService.player
|
readonly property MprisPlayer player: S.MprisService.player
|
||||||
|
|
@ -19,7 +19,6 @@ M.PinnableSection {
|
||||||
property string _cachedArt: ""
|
property string _cachedArt: ""
|
||||||
property string _artTrack: ""
|
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 _artUrl: player?.trackArtUrl ?? ""
|
||||||
readonly property string _currentTrack: player?.trackTitle ?? ""
|
readonly property string _currentTrack: player?.trackTitle ?? ""
|
||||||
on_ArtUrlChanged: if (_artUrl)
|
on_ArtUrlChanged: if (_artUrl)
|
||||||
|
|
@ -74,22 +73,12 @@ M.PinnableSection {
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: root.playing ? "\uF04B" : (root.player?.playbackState === MprisPlaybackState.Paused ? "\uDB80\uDFE4" : "\uDB81\uDCDB")
|
icon: root.playing ? "\uF04B" : (root.player?.playbackState === MprisPlaybackState.Paused ? "\uDB80\uDFE4" : "\uDB81\uDCDB")
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: root.player?.trackTitle || root.player?.identity || ""
|
label: root.player?.trackTitle || root.player?.identity || ""
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
width: Math.min(implicitWidth, 200)
|
width: Math.min(implicitWidth, 200)
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -101,6 +90,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-mpris"
|
panelNamespace: "nova-mpris"
|
||||||
panelTitle: "Now Playing"
|
panelTitle: "Now Playing"
|
||||||
contentWidth: 280
|
contentWidth: 280
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.MprisApplet {
|
C.MprisApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,18 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
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
|
readonly property string state: S.NetworkService.state
|
||||||
|
|
||||||
|
|
@ -23,18 +31,12 @@ M.PinnableSection {
|
||||||
}
|
}
|
||||||
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
visible: root.state === "wifi"
|
visible: root.state === "wifi"
|
||||||
label: S.NetworkService.essid
|
label: S.NetworkService.essid
|
||||||
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
color: root.state === "disconnected" ? S.Theme.base08 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|
@ -53,6 +55,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-network"
|
panelNamespace: "nova-network"
|
||||||
panelTitle: "Wi-Fi"
|
panelTitle: "Wi-Fi"
|
||||||
contentWidth: 250
|
contentWidth: 250
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
titleActionsComponent: Component {
|
titleActionsComponent: Component {
|
||||||
Item {
|
Item {
|
||||||
width: 20
|
width: 20
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,10 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
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")
|
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
|
color: S.NotifService.dnd ? S.Theme.base04 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
id: countLabel
|
id: countLabel
|
||||||
label: S.NotifService.count > 0 ? String(S.NotifService.count) + (root.hasUrgent ? "!" : "") : ""
|
label: S.NotifService.count > 0 ? String(S.NotifService.count) + (root.hasUrgent ? "!" : "") : ""
|
||||||
color: root.hasUrgent ? S.Theme.base08 : root.accentColor
|
color: root.hasUrgent ? S.Theme.base08 : root.accentColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
|
|
||||||
transform: Scale {
|
transform: Scale {
|
||||||
id: countScale
|
id: countScale
|
||||||
|
|
@ -83,6 +77,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-notifications"
|
panelNamespace: "nova-notifications"
|
||||||
panelTitle: "Notifications"
|
panelTitle: "Notifications"
|
||||||
contentWidth: 350
|
contentWidth: 350
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
titleActionsComponent: Component {
|
titleActionsComponent: Component {
|
||||||
Row {
|
Row {
|
||||||
spacing: 8
|
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 Quickshell.Io
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
import "../applets" as C
|
||||||
|
|
||||||
M.BarIcon {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
icon: "\uF011"
|
|
||||||
tooltip: "Power menu"
|
tooltip: "Power menu"
|
||||||
|
|
||||||
required property var bar
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: runner
|
id: runner
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
M.BarIcon {
|
||||||
anchors.fill: parent
|
icon: "\uF011"
|
||||||
cursorShape: Qt.PointingHandCursor
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
onClicked: {
|
|
||||||
menuLoader.active = !menuLoader.active;
|
|
||||||
M.TooltipState.visible = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
M.HoverPanel {
|
||||||
id: menuLoader
|
id: hoverPanel
|
||||||
active: false
|
showPanel: root._showPanel
|
||||||
M.PowerMenu {
|
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
|
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 => {
|
onRunCommand: cmd => {
|
||||||
runner.command = cmd;
|
runner.command = cmd;
|
||||||
runner.running = true;
|
runner.running = true;
|
||||||
}
|
}
|
||||||
|
onDismiss: root.dismissPanel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,17 @@ import QtQuick
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
M.BarIcon {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
tooltip: "Power profile: " + (S.PowerProfileService.profile || "unknown")
|
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
|
color: S.PowerProfileService.profile === "performance" ? S.Theme.base09 : S.PowerProfileService.profile === "power-saver" ? S.Theme.base0B : root.accentColor
|
||||||
|
|
||||||
icon: {
|
icon: {
|
||||||
if (S.PowerProfileService.profile === "performance")
|
if (S.PowerProfileService.profile === "performance")
|
||||||
return "\uF0E7";
|
return "\uF0E7";
|
||||||
|
|
@ -17,14 +22,6 @@ M.BarIcon {
|
||||||
return "\uF24E";
|
return "\uF24E";
|
||||||
return "\uF0E7";
|
return "\uF0E7";
|
||||||
}
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
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]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ import Quickshell.Services.Pipewire
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
Row {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
|
cursorShape: Qt.ArrowCursor
|
||||||
|
|
||||||
// Only detect active client streams, not hardware sources/devices
|
|
||||||
readonly property bool _videoCapture: {
|
readonly property bool _videoCapture: {
|
||||||
if (!Pipewire.nodes)
|
if (!Pipewire.nodes)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,15 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: Math.max(1, S.Theme.moduleSpacing - 2)
|
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 _warm: S.Modules.temperature.warm || 80
|
||||||
readonly property int _hot: S.Modules.temperature.hot || 90
|
readonly property int _hot: S.Modules.temperature.hot || 90
|
||||||
readonly property string _deviceFilter: S.Modules.temperature.device || ""
|
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: {
|
readonly property int _temp: {
|
||||||
if (_deviceFilter !== "") {
|
if (_deviceFilter !== "") {
|
||||||
const dev = S.SystemStats.tempDevices.find(d => d.name === _deviceFilter);
|
const dev = S.SystemStats.tempDevices.find(d => d.name === _deviceFilter);
|
||||||
|
|
@ -34,18 +33,12 @@ M.PinnableSection {
|
||||||
icon: "\uF2C9"
|
icon: "\uF2C9"
|
||||||
color: root._stateColor
|
color: root._stateColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
M.BarLabel {
|
M.BarLabel {
|
||||||
label: root._temp + "\u00B0C"
|
label: root._temp + "\u00B0C"
|
||||||
minText: "100\u00B0C"
|
minText: "100\u00B0C"
|
||||||
color: root._stateColor
|
color: root._stateColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -57,6 +50,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-temperature"
|
panelNamespace: "nova-temperature"
|
||||||
panelTitle: "Temperature"
|
panelTitle: "Temperature"
|
||||||
contentWidth: 220
|
contentWidth: 220
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.TemperatureApplet {
|
C.TemperatureApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import "../services" as S
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
id: menuWindow
|
id: menuWindow
|
||||||
|
|
||||||
popupMode: true
|
showPanel: true
|
||||||
|
|
||||||
required property var handle
|
required property var handle
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@ import Quickshell.Services.SystemTray
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
RowLayout {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing + 2
|
spacing: S.Theme.moduleSpacing + 2
|
||||||
visible: S.Modules.tray.enable && _trayRepeater.count > 0
|
visible: S.Modules.tray.enable && _trayRepeater.count > 0
|
||||||
|
cursorShape: Qt.ArrowCursor
|
||||||
|
|
||||||
required property var bar
|
required property var bar
|
||||||
property var _activeMenu: null
|
property var _activeMenu: null
|
||||||
|
|
@ -69,7 +70,7 @@ RowLayout {
|
||||||
M.ThemedIcon {
|
M.ThemedIcon {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: iconItem.modelData.icon
|
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.text = tip;
|
||||||
M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
M.TooltipState.itemX = iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
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;
|
M.TooltipState.visible = true;
|
||||||
} else if (!hovered) {
|
} else if (!hovered) {
|
||||||
M.TooltipState.visible = false;
|
M.TooltipState.visible = false;
|
||||||
|
|
@ -114,7 +115,7 @@ RowLayout {
|
||||||
id: menuLoader
|
id: menuLoader
|
||||||
active: false
|
active: false
|
||||||
M.TrayMenu {
|
M.TrayMenu {
|
||||||
accentColor: root.parent?.accentColor ?? S.Theme.base05
|
accentColor: root.accentColor
|
||||||
handle: iconItem.modelData.menu
|
handle: iconItem.modelData.menu
|
||||||
screen: root.bar.screen
|
screen: root.bar.screen
|
||||||
anchorX: iconItem.mapToGlobal(iconItem.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
|
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 "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.OsdSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: "Volume: " + Math.round(volume * 100) + "%" + (muted ? " (muted)" : "")
|
||||||
|
|
||||||
PwObjectTracker {
|
PwObjectTracker {
|
||||||
objects: [Pipewire.defaultAudioSink, ...root._streamList]
|
objects: [Pipewire.defaultAudioSink, ...root._streamList]
|
||||||
|
|
@ -63,24 +63,12 @@ M.OsdSection {
|
||||||
minIcon: "\uF028"
|
minIcon: "\uF028"
|
||||||
color: root._volumeColor
|
color: root._volumeColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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 {
|
M.BarLabel {
|
||||||
label: Math.round(root.volume * 100) + "%"
|
label: Math.round(root.volume * 100) + "%"
|
||||||
minText: "100%"
|
minText: "100%"
|
||||||
color: root._volumeColor
|
color: root._volumeColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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 {
|
WheelHandler {
|
||||||
|
|
@ -100,6 +88,7 @@ M.OsdSection {
|
||||||
panelNamespace: "nova-volume"
|
panelNamespace: "nova-volume"
|
||||||
panelTitle: "Sound"
|
panelTitle: "Sound"
|
||||||
contentWidth: 220
|
contentWidth: 220
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.VolumeApplet {
|
C.VolumeApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@ import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
import "../applets" as C
|
import "../applets" as C
|
||||||
|
|
||||||
M.PinnableSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
visible: S.Modules.weather.enable && S.WeatherService.available
|
visible: S.Modules.weather.enable && S.WeatherService.available
|
||||||
_panelHovered: hoverPanel.panelHovered
|
tooltip: S.WeatherService.summary || "Weather"
|
||||||
|
|
||||||
M.BarIcon {
|
M.BarIcon {
|
||||||
icon: S.WeatherService.icon
|
icon: S.WeatherService.icon
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
TapHandler {
|
|
||||||
onTapped: root._pinned = !root._pinned
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
M.HoverPanel {
|
M.HoverPanel {
|
||||||
|
|
@ -27,6 +24,7 @@ M.PinnableSection {
|
||||||
panelNamespace: "nova-weather"
|
panelNamespace: "nova-weather"
|
||||||
panelTitle: "Weather"
|
panelTitle: "Weather"
|
||||||
contentWidth: 280
|
contentWidth: 280
|
||||||
|
onDismissed: root.dismissPanel()
|
||||||
|
|
||||||
C.WeatherApplet {
|
C.WeatherApplet {
|
||||||
width: hoverPanel.contentWidth
|
width: hoverPanel.contentWidth
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ import Quickshell.Widgets
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
M.BarSection {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: S.Theme.moduleSpacing
|
spacing: S.Theme.moduleSpacing
|
||||||
tooltip: S.NiriIpc.focusedAppId ? S.NiriIpc.focusedAppId + "\n" + S.NiriIpc.focusedTitle : S.NiriIpc.focusedTitle
|
tooltip: S.NiriIpc.focusedAppId ? S.NiriIpc.focusedAppId + "\n" + S.NiriIpc.focusedTitle : S.NiriIpc.focusedTitle
|
||||||
|
cursorShape: Qt.ArrowCursor
|
||||||
|
|
||||||
readonly property string _iconSource: {
|
readonly property string _iconSource: {
|
||||||
if (!S.NiriIpc.focusedAppId)
|
if (!S.NiriIpc.focusedAppId)
|
||||||
|
|
@ -19,7 +20,7 @@ M.BarSection {
|
||||||
|
|
||||||
readonly property real _iconOffset: _icon.visible ? _icon.width + root.spacing : 0
|
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
|
readonly property real naturalWidth: _iconOffset + _label.implicitWidth
|
||||||
|
|
||||||
IconImage {
|
IconImage {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import Quickshell.Io
|
||||||
import "." as M
|
import "." as M
|
||||||
import "../services" as S
|
import "../services" as S
|
||||||
|
|
||||||
Row {
|
M.BarModule {
|
||||||
id: root
|
id: root
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
cursorShape: Qt.ArrowCursor
|
||||||
|
|
||||||
required property var bar
|
required property var bar
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@ Row {
|
||||||
M.TooltipState.text = name;
|
M.TooltipState.text = name;
|
||||||
M.TooltipState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
M.TooltipState.itemX = pill.mapToGlobal(pill.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0);
|
||||||
M.TooltipState.screen = QsWindow.window?.screen ?? null;
|
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;
|
M.TooltipState.visible = true;
|
||||||
} else {
|
} else {
|
||||||
M.TooltipState.visible = false;
|
M.TooltipState.visible = false;
|
||||||
|
|
@ -82,7 +83,7 @@ Row {
|
||||||
width: S.Theme.fontSize + 4
|
width: S.Theme.fontSize + 4
|
||||||
height: S.Theme.fontSize + 4
|
height: S.Theme.fontSize + 4
|
||||||
radius: width / 2
|
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 {
|
Behavior on color {
|
||||||
ColorAnimation {
|
ColorAnimation {
|
||||||
duration: 150
|
duration: 150
|
||||||
|
|
@ -92,7 +93,7 @@ Row {
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: pill.modelData.idx
|
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.pixelSize: S.Theme.fontSize - 2
|
||||||
font.family: S.Theme.fontFamily
|
font.family: S.Theme.fontFamily
|
||||||
font.bold: pill.active
|
font.bold: pill.active
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Bar 1.0 Bar.qml
|
||||||
BarGroup 1.0 BarGroup.qml
|
BarGroup 1.0 BarGroup.qml
|
||||||
BarIcon 1.0 BarIcon.qml
|
BarIcon 1.0 BarIcon.qml
|
||||||
BarLabel 1.0 BarLabel.qml
|
BarLabel 1.0 BarLabel.qml
|
||||||
BarSection 1.0 BarSection.qml
|
BarModule 1.0 BarModule.qml
|
||||||
BatteryModule 1.0 BatteryModule.qml
|
BatteryModule 1.0 BatteryModule.qml
|
||||||
BluetoothModule 1.0 BluetoothModule.qml
|
BluetoothModule 1.0 BluetoothModule.qml
|
||||||
ClockModule 1.0 ClockModule.qml
|
ClockModule 1.0 ClockModule.qml
|
||||||
|
|
@ -21,11 +21,8 @@ NetworkModule 1.0 NetworkModule.qml
|
||||||
NotifCard 1.0 NotifCard.qml
|
NotifCard 1.0 NotifCard.qml
|
||||||
NotifPopup 1.0 NotifPopup.qml
|
NotifPopup 1.0 NotifPopup.qml
|
||||||
NotificationsModule 1.0 NotificationsModule.qml
|
NotificationsModule 1.0 NotificationsModule.qml
|
||||||
OsdSection 1.0 OsdSection.qml
|
|
||||||
OverviewBackdrop 1.0 OverviewBackdrop.qml
|
OverviewBackdrop 1.0 OverviewBackdrop.qml
|
||||||
PinnableSection 1.0 PinnableSection.qml
|
|
||||||
PopupBackground 1.0 PopupBackground.qml
|
PopupBackground 1.0 PopupBackground.qml
|
||||||
PowerMenu 1.0 PowerMenu.qml
|
|
||||||
PowerModule 1.0 PowerModule.qml
|
PowerModule 1.0 PowerModule.qml
|
||||||
PowerProfileModule 1.0 PowerProfileModule.qml
|
PowerProfileModule 1.0 PowerProfileModule.qml
|
||||||
PrivacyModule 1.0 PrivacyModule.qml
|
PrivacyModule 1.0 PrivacyModule.qml
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue