Compare commits

..

3 commits

13 changed files with 717 additions and 887 deletions

View file

@ -1,7 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import "." as M import "." as M
M.BarSection { M.BarSection {
@ -12,9 +11,8 @@ M.BarSection {
tooltip: "" tooltip: ""
property int percent: 0 property int percent: 0
property bool _panelHovered: false
property bool _osdActive: false property bool _osdActive: false
readonly property bool _showPanel: root._hovered || _panelHovered || _osdActive readonly property bool _showPanel: root._hovered || hoverPanel.panelHovered || _osdActive
onPercentChanged: if (percent > 0) onPercentChanged: if (percent > 0)
_flashPanel() _flashPanel()
@ -97,103 +95,13 @@ M.BarSection {
onWheel: event => root.adjust(event.angleDelta.y) onWheel: event => root.adjust(event.angleDelta.y)
} }
PanelWindow { M.HoverPanel {
id: panel id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
visible: _winVisible accentColor: root.accentColor
color: "transparent" panelNamespace: "nova-backlight"
contentWidth: 200
property bool _winVisible: false
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: 0
WlrLayershell.namespace: "nova-backlight"
anchors.top: true
anchors.left: true
margins.top: 0
margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
implicitWidth: panelContent.width
implicitHeight: panelContent.height
Connections {
target: root
function on_ShowPanelChanged() {
if (root._showPanel) {
panel._winVisible = true;
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
}
ParallelAnimation {
id: showAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
}
ParallelAnimation {
id: hideAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false
}
HoverHandler {
onHoveredChanged: root._panelHovered = hovered
}
Rectangle {
x: panelContent.x
y: panelContent.y
width: panelContent.width
height: panelContent.height
color: M.Theme.base01
opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: root.accentColor
border.width: 1
}
Column {
id: panelContent
width: 200
opacity: 0
y: -height
Item { Item {
width: parent.width width: parent.width
@ -266,5 +174,4 @@ M.BarSection {
} }
} }
} }
}
} }

View file

@ -101,10 +101,10 @@ M.BarSection {
} }
} }
Loader { LazyLoader {
id: menuLoader id: menuLoader
active: false active: false
sourceComponent: M.BluetoothMenu { M.BluetoothMenu {
accentColor: root.accentColor accentColor: root.accentColor
screen: root.bar.screen screen: root.bar.screen
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)

View file

@ -83,16 +83,9 @@ PanelWindow {
opacity: 0 opacity: 0
y: -height y: -height
Rectangle { M.PopupBackground {
anchors.fill: parent anchors.fill: parent
color: M.Theme.base01 accentColor: M.FlyoutState.accentColor
opacity: Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: M.FlyoutState.accentColor
border.width: 1
} }
Text { Text {

115
modules/HoverPanel.qml Normal file
View file

@ -0,0 +1,115 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import "." as M
// Shared hover/OSD panel PanelWindow slides down from the bar on hover or
// external trigger. Parent module computes showPanel and reads panelHovered.
PanelWindow {
id: root
required property bool showPanel
required property real anchorX
required property color accentColor
property string panelNamespace: "nova-panel"
property real contentWidth: 220
property bool animateHeight: false
property bool panelHovered: false
default property alias content: panelContent.children
screen: QsWindow.window?.screen ?? null
visible: _winVisible
color: "transparent"
property bool _winVisible: false
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: 0
WlrLayershell.namespace: root.panelNamespace
anchors.top: true
anchors.left: true
margins.top: 0
margins.left: Math.max(0, Math.min(Math.round(anchorX - implicitWidth / 2), (screen?.width ?? 1920) - implicitWidth))
implicitWidth: panelContent.width
implicitHeight: panelContent.height
Behavior on implicitHeight {
enabled: root.animateHeight
NumberAnimation {
duration: 100
easing.type: Easing.OutCubic
}
}
onShowPanelChanged: {
if (showPanel) {
_winVisible = true;
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
ParallelAnimation {
id: showAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
}
ParallelAnimation {
id: hideAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: root._winVisible = false
}
HoverHandler {
onHoveredChanged: root.panelHovered = hovered
}
M.PopupBackground {
x: panelContent.x
y: panelContent.y
width: panelContent.width
height: panelContent.height
opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85)
accentColor: root.accentColor
}
Column {
id: panelContent
width: root.contentWidth
opacity: 0
y: -height
}
}

View file

@ -1,7 +1,6 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Wayland
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import "." as M import "." as M
@ -58,9 +57,8 @@ M.BarSection {
required property var bar required property var bar
property bool _panelHovered: false
property bool _pinned: false property bool _pinned: false
readonly property bool _anyHover: root._hovered || _panelHovered readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
readonly property bool _showPanel: _anyHover || _pinned readonly property bool _showPanel: _anyHover || _pinned
on_AnyHoverChanged: { on_AnyHoverChanged: {
@ -97,112 +95,14 @@ M.BarSection {
} }
} }
PanelWindow { M.HoverPanel {
id: panel id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
visible: _winVisible accentColor: root.accentColor
color: "transparent" panelNamespace: "nova-mpris"
contentWidth: 280
property bool _winVisible: false animateHeight: true
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: 0
WlrLayershell.namespace: "nova-mpris"
anchors.top: true
anchors.left: true
margins.top: 0
margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
implicitWidth: panelContent.width
implicitHeight: panelContent.height
Behavior on implicitHeight {
NumberAnimation {
duration: 100
easing.type: Easing.OutCubic
}
}
Connections {
target: root
function on_ShowPanelChanged() {
if (root._showPanel) {
panel._winVisible = true;
hideAnim.stop();
showAnim.start();
} else {
showAnim.stop();
hideAnim.start();
}
}
}
ParallelAnimation {
id: showAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
}
ParallelAnimation {
id: hideAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false
}
HoverHandler {
onHoveredChanged: root._panelHovered = hovered
}
Rectangle {
x: panelContent.x
y: panelContent.y
width: panelContent.width
height: panelContent.height
color: M.Theme.base01
opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: root.accentColor
border.width: 1
}
Column {
id: panelContent
width: 280
opacity: 0
y: -height
topPadding: 4
bottomPadding: 4
spacing: 2
// Album art // Album art
Item { Item {
@ -484,5 +384,4 @@ M.BarSection {
} }
} }
} }
}
} }

View file

@ -121,10 +121,10 @@ M.BarSection {
} }
} }
Loader { LazyLoader {
id: menuLoader id: menuLoader
active: false active: false
sourceComponent: M.NetworkMenu { M.NetworkMenu {
accentColor: root.accentColor accentColor: root.accentColor
screen: root.bar.screen screen: root.bar.screen
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)

View file

@ -77,10 +77,10 @@ M.BarSection {
onTapped: M.NotifService.toggleDnd() onTapped: M.NotifService.toggleDnd()
} }
Loader { LazyLoader {
id: centerLoader id: centerLoader
active: false active: false
sourceComponent: M.NotifCenter { M.NotifCenter {
accentColor: root.accentColor accentColor: root.accentColor
screen: root.bar.screen screen: root.bar.screen
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)

View file

@ -0,0 +1,15 @@
import QtQuick
import "." as M
Rectangle {
property color accentColor: M.Theme.base05
color: M.Theme.base01
opacity: Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: accentColor
border.width: 1
}

View file

@ -56,16 +56,9 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
} }
Rectangle { M.PopupBackground {
anchors.fill: parent anchors.fill: parent
color: M.Theme.base01 accentColor: root.accentColor
opacity: Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: root.accentColor
border.width: 1
} }
Column { Column {

View file

@ -23,11 +23,11 @@ M.BarIcon {
} }
} }
Loader { LazyLoader {
id: menuLoader id: menuLoader
active: false active: false
sourceComponent: M.PowerMenu { M.PowerMenu {
accentColor: parent?.accentColor ?? root.color accentColor: root.accentColor
screen: root.bar.screen screen: root.bar.screen
anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
onDismissed: menuLoader.active = false onDismissed: menuLoader.active = false

View file

@ -105,10 +105,10 @@ RowLayout {
} }
// Per-icon context menu window, created on demand // Per-icon context menu window, created on demand
Loader { LazyLoader {
id: menuLoader id: menuLoader
active: false active: false
sourceComponent: M.TrayMenu { M.TrayMenu {
accentColor: root.parent?.accentColor ?? M.Theme.base05 accentColor: root.parent?.accentColor ?? M.Theme.base05
handle: iconItem.modelData.menu handle: iconItem.modelData.menu
screen: root.bar.screen screen: root.bar.screen

View file

@ -1,6 +1,5 @@
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Wayland
import Quickshell.Services.Pipewire import Quickshell.Services.Pipewire
import "." as M import "." as M
@ -40,11 +39,13 @@ M.BarSection {
} }
property bool _expanded: false property bool _expanded: false
property bool _panelHovered: false
property bool _osdActive: false property bool _osdActive: false
readonly property bool _anyHover: root._hovered || _panelHovered readonly property bool _anyHover: root._hovered || hoverPanel.panelHovered
readonly property bool _showPanel: _anyHover || _expanded || _osdActive readonly property bool _showPanel: _anyHover || _expanded || _osdActive
on_ShowPanelChanged: if (!_showPanel)
_expanded = false
onVolumeChanged: _flashPanel() onVolumeChanged: _flashPanel()
onMutedChanged: _flashPanel() onMutedChanged: _flashPanel()
@ -104,107 +105,13 @@ M.BarSection {
} }
// Unified volume panel hover shows slider, click expands to show devices // Unified volume panel hover shows slider, click expands to show devices
PanelWindow { M.HoverPanel {
id: panel id: hoverPanel
showPanel: root._showPanel
screen: QsWindow.window?.screen ?? null anchorX: root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0)
visible: _winVisible accentColor: root.accentColor
color: "transparent" panelNamespace: "nova-volume"
contentWidth: 220
property bool _winVisible: false
WlrLayershell.layer: WlrLayer.Overlay
WlrLayershell.exclusiveZone: 0
WlrLayershell.namespace: "nova-volume"
anchors.top: true
anchors.left: true
margins.top: 0
margins.left: Math.max(0, Math.min(Math.round(root.mapToGlobal(root.width / 2, 0).x - (QsWindow.window?.screen?.x ?? 0) - implicitWidth / 2), (panel.screen?.width ?? 1920) - implicitWidth))
implicitWidth: panelContent.width
implicitHeight: panelContent.height
// Show/hide logic
Connections {
target: root
function on_ShowPanelChanged() {
if (root._showPanel) {
panel._winVisible = true;
hideAnim.stop();
showAnim.start();
} else {
root._expanded = false;
showAnim.stop();
hideAnim.start();
}
}
}
ParallelAnimation {
id: showAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 1
duration: 120
easing.type: Easing.OutCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: 0
duration: 150
easing.type: Easing.OutCubic
}
}
ParallelAnimation {
id: hideAnim
NumberAnimation {
target: panelContent
property: "opacity"
to: 0
duration: 150
easing.type: Easing.InCubic
}
NumberAnimation {
target: panelContent
property: "y"
to: -panelContent.height
duration: 150
easing.type: Easing.InCubic
}
onFinished: panel._winVisible = false
}
// Keep panel open when mouse is over it
HoverHandler {
onHoveredChanged: root._panelHovered = hovered
// Click inside panel doesn't dismiss
}
Rectangle {
x: panelContent.x
y: panelContent.y
width: panelContent.width
height: panelContent.height
color: M.Theme.base01
opacity: panelContent.opacity * Math.max(M.Theme.barOpacity, 0.85)
topLeftRadius: 0
topRightRadius: 0
bottomLeftRadius: M.Theme.radius
bottomRightRadius: M.Theme.radius
border.color: root.accentColor
border.width: 1
}
Column {
id: panelContent
width: 220
opacity: 0
y: -height
// Compact: slider row // Compact: slider row
Item { Item {
@ -514,5 +421,4 @@ M.BarSection {
} }
} }
} }
}
} }

View file

@ -13,6 +13,8 @@ Volume 1.0 Volume.qml
Tray 1.0 Tray.qml Tray 1.0 Tray.qml
TrayMenu 1.0 TrayMenu.qml TrayMenu 1.0 TrayMenu.qml
PopupPanel 1.0 PopupPanel.qml PopupPanel 1.0 PopupPanel.qml
PopupBackground 1.0 PopupBackground.qml
HoverPanel 1.0 HoverPanel.qml
PowerMenu 1.0 PowerMenu.qml PowerMenu 1.0 PowerMenu.qml
ScreenCorners 1.0 ScreenCorners.qml ScreenCorners 1.0 ScreenCorners.qml
ThemedIcon 1.0 ThemedIcon.qml ThemedIcon 1.0 ThemedIcon.qml