This commit is contained in:
Ly-sec
2025-09-28 18:42:59 +02:00
18 changed files with 260 additions and 139 deletions
+3 -5
View File
@@ -49,9 +49,7 @@ Item {
Connections {
target: root
function onTooltipTextChanged() {
if (PanelService.tooltip.visible) {
PanelService.tooltip.updateText(root.tooltipText)
}
TooltipService.updateText(root.tooltipText)
}
}
@@ -221,7 +219,7 @@ Item {
onEntered: {
hovered = true
root.entered()
PanelService.tooltip.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
if (disableOpen || forceClose) {
return
}
@@ -235,7 +233,7 @@ Item {
if (!forceOpen && !forceClose) {
hide()
}
PanelService.tooltip.hide()
TooltipService.hide()
}
onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) {
+3 -5
View File
@@ -61,9 +61,7 @@ Item {
Connections {
target: root
function onTooltipTextChanged() {
if (PanelService.tooltip.visible) {
PanelService.tooltip.updateText(root.tooltipText)
}
TooltipService.updateText(root.tooltipText)
}
}
@@ -262,7 +260,7 @@ Item {
onEntered: {
hovered = true
root.entered()
PanelService.tooltip.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
TooltipService.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
if (disableOpen || forceClose) {
return
}
@@ -276,7 +274,7 @@ Item {
if (!forceOpen && !forceClose) {
hide()
}
PanelService.tooltip.hide()
TooltipService.hide()
}
onClicked: function (mouse) {
if (mouse.button === Qt.LeftButton) {
+2 -2
View File
@@ -329,11 +329,11 @@ Item {
acceptedButtons: Qt.LeftButton
onEntered: {
if ((windowTitle !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
PanelService.tooltip.show(root, windowTitle, BarService.getTooltipDirection())
TooltipService.show(root, windowTitle, BarService.getTooltipDirection())
}
}
onExited: {
PanelService.tooltip.hide()
TooltipService.hide()
}
}
}
+3 -3
View File
@@ -114,14 +114,14 @@ Rectangle {
hoverEnabled: true
onEntered: {
if (!PanelService.getPanel("calendarPanel")?.active) {
PanelService.tooltip.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
TooltipService.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
}
}
onExited: {
PanelService.tooltip.hide()
TooltipService.hide()
}
onClicked: {
PanelService.tooltip.hide()
TooltipService.hide()
PanelService.getPanel("calendarPanel")?.toggle(this)
}
}
+2 -2
View File
@@ -376,11 +376,11 @@ Item {
onEntered: {
if ((tooltipText !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
PanelService.tooltip.show(root, tooltipText, BarService.getTooltipDirection())
TooltipService.show(root, tooltipText, BarService.getTooltipDirection())
}
}
onExited: {
PanelService.tooltip.hide()
TooltipService.hide()
}
}
}
+2 -2
View File
@@ -98,8 +98,8 @@ Rectangle {
}
}
}
onEntered: PanelService.tooltip.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
onExited: PanelService.tooltip.hide()
onEntered: TooltipService.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
onExited: TooltipService.hide()
}
}
}
+6 -3
View File
@@ -102,7 +102,7 @@ Rectangle {
modelData.secondaryActivate && modelData.secondaryActivate()
} else if (mouse.button === Qt.RightButton) {
trayTooltip.hide()
TooltipService.hideImmediately()
// Close the menu if it was visible
if (trayPanel && trayPanel.visible) {
@@ -135,8 +135,11 @@ Rectangle {
}
}
}
onEntered: PanelService.tooltip.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
onExited: PanelService.tooltip.hide()
onEntered: {
trayPanel.close()
TooltipService.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
}
onExited: TooltipService.hide()
}
}
}
+3 -3
View File
@@ -474,7 +474,7 @@ Variants {
anyAppHovered = true
const appName = appButton.appTitle || appButton.appId || "Unknown"
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
PanelService.tooltip.show(appButton, tooltipText, "top")
TooltipService.show(appButton, tooltipText, "top")
if (autoHide) {
showTimer.stop()
hideTimer.stop()
@@ -484,7 +484,7 @@ Variants {
onExited: {
anyAppHovered = false
PanelService.tooltip.hide()
TooltipService.hide()
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
hideTimer.restart()
}
@@ -500,7 +500,7 @@ Variants {
// Close any other existing context menu first
root.closeAllContextMenus()
// Hide tooltip when showing context menu
PanelService.tooltip.hide()
TooltipService.hide()
contextMenu.show(appButton, modelData.toplevel || modelData)
return
}
+90 -81
View File
@@ -14,9 +14,12 @@ PopupWindow {
property var toplevel: null
property Item anchorItem: null
property real scaling: 1.0
property bool hovered: menuMouseArea.containsMouse || activateMouseArea.containsMouse || pinMouseArea.containsMouse || closeMouseArea.containsMouse
property bool hovered: menuMouseArea.containsMouse
property var onAppClosed: null // Callback function for when an app is closed
// Track which menu item is hovered
property int hoveredItem: -1 // -1: none, 0: focus, 1: pin, 2: close
signal requestClose
implicitWidth: 140 * scaling
@@ -70,25 +73,64 @@ PopupWindow {
visible = false
}
// Close menu when clicking on background, track hover for the whole menu area
MouseArea {
id: menuMouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: function (mouse) {
if (mouse.button === Qt.RightButton) {
root.hide() // Close on right-click
} else {
root.hide() // Close when clicking on the background (outside menu content)
// Helper function to determine which menu item is under the mouse
function getHoveredItem(mouseY) {
const itemHeight = 32 * scaling
const startY = Style.marginM * scaling
const relativeY = mouseY - startY
if (relativeY < 0)
return -1
const itemIndex = Math.floor(relativeY / itemHeight)
return itemIndex >= 0 && itemIndex < 3 ? itemIndex : -1
}
// Handle menu item clicks
function handleItemClick(itemIndex) {
switch (itemIndex) {
case 0:
// Focus
if (root.toplevel?.activate) {
root.toplevel.activate()
}
root.requestClose()
break
case 1:
// Pin/Unpin
if (root.toplevel?.appId) {
root.toggleAppPin(root.toplevel.appId)
}
root.requestClose()
break
case 2:
// Close
// Check if toplevel is still valid before trying to close it
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
if (isValidToplevel && root.toplevel.close) {
root.toplevel.close()
// Trigger immediate dock update callback if provided
if (root.onAppClosed && typeof root.onAppClosed === "function") {
Qt.callLater(root.onAppClosed)
}
} else {
Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
}
root.hide()
root.requestClose()
break
}
}
Shortcut {
sequences: ["Escape"]
enabled: root.visible
onActivated: root.hide()
Timer {
id: closeTimer
interval: 500
repeat: false
running: false
onTriggered: {
root.hide()
}
}
Rectangle {
@@ -98,12 +140,32 @@ PopupWindow {
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
// Prevent clicks inside the menu from closing it
// Single MouseArea to handle both auto-close and menu interactions
MouseArea {
id: menuMouseArea
anchors.fill: parent
onClicked: {
hoverEnabled: true
cursorShape: root.hoveredItem >= 0 ? Qt.PointingHandCursor : Qt.ArrowCursor
} // Do nothing, just consume the click
onEntered: {
closeTimer.stop()
}
onExited: {
root.hoveredItem = -1
closeTimer.start()
}
onPositionChanged: mouse => {
root.hoveredItem = root.getHoveredItem(mouse.y)
}
onClicked: mouse => {
const clickedItem = root.getHoveredItem(mouse.y)
if (clickedItem >= 0) {
root.handleItemClick(clickedItem)
}
}
}
Column {
@@ -116,7 +178,7 @@ PopupWindow {
Rectangle {
width: parent.width
height: 32 * scaling
color: activateMouseArea.containsMouse ? Color.mTertiary : Color.transparent
color: root.hoveredItem === 0 ? Color.mTertiary : Color.transparent
radius: Style.radiusXS * scaling
Row {
@@ -128,38 +190,24 @@ PopupWindow {
NIcon {
icon: "eye"
font.pointSize: Style.fontSizeL * scaling
color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
NText {
text: I18n.tr("dock.menu.focus")
font.pointSize: Style.fontSizeS * scaling
color: activateMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 0 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: activateMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.toplevel?.activate) {
root.toplevel.activate()
}
root.requestClose()
}
}
}
// Pin/Unpin item
Rectangle {
width: parent.width
height: 32 * scaling
color: pinMouseArea.containsMouse ? Color.mTertiary : Color.transparent
color: root.hoveredItem === 1 ? Color.mTertiary : Color.transparent
radius: Style.radiusXS * scaling
Row {
@@ -175,7 +223,7 @@ PopupWindow {
return root.isAppPinned(root.toplevel.appId) ? "unpin" : "pin"
}
font.pointSize: Style.fontSizeL * scaling
color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
@@ -186,32 +234,17 @@ PopupWindow {
return root.isAppPinned(root.toplevel.appId) ? I18n.tr("dock.menu.unpin") : I18n.tr("dock.menu.pin")
}
font.pointSize: Style.fontSizeS * scaling
color: pinMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 1 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: pinMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.toplevel?.appId) {
root.toggleAppPin(root.toplevel.appId)
}
//root.hide()
root.requestClose()
}
}
}
// Close item
Rectangle {
width: parent.width
height: 32 * scaling
color: closeMouseArea.containsMouse ? Color.mTertiary : Color.transparent
color: root.hoveredItem === 2 ? Color.mTertiary : Color.transparent
radius: Style.radiusXS * scaling
Row {
@@ -223,41 +256,17 @@ PopupWindow {
NIcon {
icon: "close"
font.pointSize: Style.fontSizeL * scaling
color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
NText {
text: I18n.tr("dock.menu.close")
font.pointSize: Style.fontSizeS * scaling
color: closeMouseArea.containsMouse ? Color.mOnTertiary : Color.mOnSurfaceVariant
color: root.hoveredItem === 2 ? Color.mOnTertiary : Color.mOnSurfaceVariant
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: closeMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
// Check if toplevel is still valid before trying to close it
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
if (isValidToplevel && root.toplevel.close) {
root.toplevel.close()
// Trigger immediate dock update callback if provided
if (root.onAppClosed && typeof root.onAppClosed === "function") {
Qt.callLater(root.onAppClosed)
}
} else {
Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
}
root.hide()
root.requestClose()
}
}
}
}
}
+9 -6
View File
@@ -894,7 +894,7 @@ Loader {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: Style.marginM * scaling
radius: Style.radiusM * scaling
radius: Style.radiusS * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
@@ -905,7 +905,8 @@ Loader {
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: I18n.tr("lock-screen.shut-down")
font.pointSize: Style.fontSizeM * scaling
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
@@ -945,7 +946,7 @@ Loader {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: Style.marginM * scaling
radius: Style.radiusM * scaling
radius: Style.radiusS * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
@@ -956,7 +957,8 @@ Loader {
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: I18n.tr("lock-screen.restart")
font.pointSize: Style.fontSizeM * scaling
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
@@ -997,7 +999,7 @@ Loader {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.top
anchors.bottomMargin: Style.marginM * scaling
radius: Style.radiusM * scaling
radius: Style.radiusS * scaling
color: Color.mSurface
border.color: Color.mOutline
border.width: Math.max(1, Style.borderS * scaling)
@@ -1008,7 +1010,8 @@ Loader {
anchors.margins: Style.marginM * scaling
anchors.fill: parent
text: I18n.tr("lock-screen.suspend")
font.pointSize: Style.fontSizeM * scaling
font.pointSize: Style.fontSizeS * scaling
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
+8 -4
View File
@@ -150,6 +150,7 @@ ColumnLayout {
// AudioService Devices
ColumnLayout {
spacing: Style.marginS * scaling
Layout.fillWidth: true
NHeader {
label: I18n.tr("settings.audio.devices.section.label")
@@ -175,14 +176,15 @@ ColumnLayout {
Repeater {
model: AudioService.sinks
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sinks
required property PwNode modelData
text: modelData.description
checked: AudioService.sink?.id === modelData.id
onClicked: {
AudioService.setAudioSink(modelData)
localVolume = AudioService.volume
}
text: modelData.description
Layout.fillWidth: true
}
}
}
@@ -204,12 +206,14 @@ ColumnLayout {
Repeater {
model: AudioService.sources
//Layout.fillWidth: true
NRadioButton {
required property PwNode modelData
ButtonGroup.group: sources
required property PwNode modelData
text: modelData.description
checked: AudioService.source?.id === modelData.id
onClicked: AudioService.setAudioSource(modelData)
text: modelData.description
Layout.fillWidth: true
}
}
}
+7 -8
View File
@@ -14,7 +14,7 @@ PopupWindow {
property int padding: Style.marginM
property int delay: 0
property int hideDelay: 0
property int maxWidth: 340
property int maxWidth: 320
property real scaling: 1.0
property int animationDuration: Style.animationFast
property real animationScale: 0.85
@@ -25,7 +25,7 @@ PopupWindow {
property real anchorY: 0
property bool isPositioned: false
property bool pendingShow: false
property bool animatingOut: false
property bool animatingOut: true
visible: false
color: Color.transparent
@@ -110,11 +110,7 @@ PopupWindow {
if (!target || !tipText || tipText === "")
return
if (showDelay !== undefined) {
delay = showDelay
} else {
delay = Style.tooltipDelay
}
delay = showDelay
// Stop any running timers and animations
hideTimer.stop()
@@ -144,8 +140,9 @@ PopupWindow {
// Function to position and display the tooltip
function positionAndShow() {
if (!targetItem || !pendingShow)
if (!targetItem || !targetItem.parent || !pendingShow) {
return
}
// Get screen dimensions - try multiple methods
var screenWidth = Screen.width
@@ -393,6 +390,8 @@ PopupWindow {
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
wrapMode: Text.WordWrap
width: root.maxWidth
}
}
}
-3
View File
@@ -10,9 +10,6 @@ Singleton {
// This is not a panel...
property var lockScreen: null
// A ref. to our global tooltip
property var tooltip: null
// Panels
property var registeredPanels: ({})
property var openedPanel: null
+114
View File
@@ -0,0 +1,114 @@
pragma Singleton
import QtQuick
import Quickshell
import qs.Commons
import qs.Modules.Tooltip
Singleton {
id: root
property var activeTooltip: null
property var pendingTooltip: null // Track tooltip being created
property Component tooltipComponent: Component {
Tooltip {}
}
function show(target, text, direction, delay) {
// Don't create if no text
if (!target || !text) {
Logger.log("Tooltip", "No target or text")
return
}
// If we have a pending tooltip for a different target, cancel it
if (pendingTooltip && pendingTooltip.targetItem !== target) {
pendingTooltip.hideImmediately()
pendingTooltip.destroy()
pendingTooltip = null
}
// If we have an active tooltip for a different target, hide it
if (activeTooltip && activeTooltip.targetItem !== target) {
activeTooltip.hideImmediately()
// Don't destroy immediately - let it clean itself up
activeTooltip = null
}
// If we already have a tooltip for this target, just update it
if (activeTooltip && activeTooltip.targetItem === target) {
activeTooltip.updateText(text)
return activeTooltip
}
// Create new tooltip instance
const newTooltip = tooltipComponent.createObject(null)
if (newTooltip) {
// Track as pending until it's visible
pendingTooltip = newTooltip
// Connect cleanup when tooltip hides
newTooltip.visibleChanged.connect(() => {
if (!newTooltip.visible) {
// Clean up after a delay to avoid interfering with new tooltips
Qt.callLater(() => {
if (newTooltip && !newTooltip.visible) {
if (activeTooltip === newTooltip) {
activeTooltip = null
}
if (pendingTooltip === newTooltip) {
pendingTooltip = null
}
newTooltip.destroy()
}
})
} else {
// Tooltip is now visible, move from pending to active
if (pendingTooltip === newTooltip) {
activeTooltip = newTooltip
pendingTooltip = null
}
}
})
// Show the tooltip
newTooltip.show(target, text, direction || "auto", delay || Style.tooltipDelay)
return newTooltip
} else {
Logger.error("Tooltip", "Failed to create tooltip instance")
}
return null
}
function hide() {
if (pendingTooltip) {
pendingTooltip.hide()
}
if (activeTooltip) {
activeTooltip.hide()
}
}
function hideImmediately() {
if (pendingTooltip) {
pendingTooltip.hideImmediately()
pendingTooltip.destroy()
pendingTooltip = null
}
if (activeTooltip) {
activeTooltip.hideImmediately()
activeTooltip.destroy()
activeTooltip = null
}
}
function updateText(newText) {
if (activeTooltip) {
activeTooltip.updateText(newText)
}
}
}
+4 -4
View File
@@ -139,18 +139,18 @@ Rectangle {
onEntered: {
root.hovered = true
if (tooltipText) {
PanelService.tooltip.show(root, root.tooltipText)
TooltipService.show(root, root.tooltipText)
}
}
onExited: {
root.hovered = false
if (tooltipText) {
PanelService.tooltip.hide()
TooltipService.hide()
}
}
onPressed: mouse => {
if (tooltipText) {
PanelService.tooltip.hide()
TooltipService.hide()
}
if (mouse.button === Qt.LeftButton) {
root.clicked()
@@ -164,7 +164,7 @@ Rectangle {
onCanceled: {
root.hovered = false
if (tooltipText) {
PanelService.tooltip.hide()
TooltipService.hide()
}
}
}
+3 -3
View File
@@ -73,20 +73,20 @@ Rectangle {
onEntered: {
hovering = root.enabled ? true : false
if (tooltipText) {
PanelService.tooltip.show(parent, tooltipText, tooltipDirection)
TooltipService.show(parent, tooltipText, tooltipDirection)
}
root.entered()
}
onExited: {
hovering = false
if (tooltipText) {
PanelService.tooltip.hide()
TooltipService.hide()
}
root.exited()
}
onClicked: function (mouse) {
if (tooltipText) {
PanelService.tooltip.hide()
TooltipService.hide()
}
if (!root.enabled && !allowClickWhenDisabled) {
return
+1
View File
@@ -44,6 +44,7 @@ RadioButton {
font.pointSize: Style.fontSizeM * scaling
anchors.verticalCenter: parent.verticalCenter
anchors.left: outerCircle.right
anchors.right: parent.right
anchors.leftMargin: Style.marginS * scaling
}
}
-5
View File
@@ -51,10 +51,6 @@ ShellRoot {
Bar {}
Dock {}
Tooltip {
id: globalTooltip
}
Notification {
id: notification
}
@@ -120,7 +116,6 @@ ShellRoot {
Component.onCompleted: {
// Save a ref. to our lockScreen so we can access it easily
PanelService.lockScreen = lockScreen
PanelService.tooltip = globalTooltip
BarWidgetRegistry.init()
}