mirror of
https://github.com/zoriya/noctalia-shell.git
synced 2026-08-15 18:43:59 +00:00
Tooltip: Refactoring in a single global tooltip.
This commit is contained in:
@@ -46,6 +46,15 @@ Item {
|
||||
width: pillHeight + Math.max(0, pill.width - pillOverlap)
|
||||
height: pillHeight
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (PanelService.tooltip.visible) {
|
||||
PanelService.tooltip.updateText(root.tooltipText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pill
|
||||
width: revealed ? pillMaxWidth : 1
|
||||
@@ -195,14 +204,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
target: pill
|
||||
delay: Style.tooltipDelayLong
|
||||
text: root.tooltipText
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: Style.pillDelay
|
||||
@@ -220,7 +221,7 @@ Item {
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
tooltip.show()
|
||||
PanelService.tooltip.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
if (disableOpen || forceClose) {
|
||||
return
|
||||
}
|
||||
@@ -234,7 +235,7 @@ Item {
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
}
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
|
||||
@@ -58,6 +58,15 @@ Item {
|
||||
width: buttonSize
|
||||
height: revealed ? (buttonSize + maxPillHeight - pillOverlap) : buttonSize
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onTooltipTextChanged() {
|
||||
if (PanelService.tooltip.visible) {
|
||||
PanelService.tooltip.updateText(root.tooltipText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pill
|
||||
width: revealed ? maxPillWidth : 1
|
||||
@@ -236,15 +245,6 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
target: pill
|
||||
text: root.tooltipText
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
delay: Style.tooltipDelayLong
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: Style.pillDelay
|
||||
@@ -262,7 +262,7 @@ Item {
|
||||
onEntered: {
|
||||
hovered = true
|
||||
root.entered()
|
||||
tooltip.show()
|
||||
PanelService.tooltip.show(pill, root.tooltipText, BarService.getTooltipDirection(), Style.tooltipDelayLong)
|
||||
if (disableOpen || forceClose) {
|
||||
return
|
||||
}
|
||||
@@ -276,7 +276,7 @@ Item {
|
||||
if (!forceOpen && !forceClose) {
|
||||
hide()
|
||||
}
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
}
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
|
||||
@@ -328,29 +328,17 @@ Item {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onEntered: {
|
||||
if (barPosition === "left" || barPosition === "right") {
|
||||
tooltip.show()
|
||||
} else if ((tooltip.text !== "") && (scrollingMode === "never")) {
|
||||
tooltip.show()
|
||||
if ((windowTitle !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
|
||||
PanelService.tooltip.show(root, windowTitle, BarService.getTooltipDirection())
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: windowTitle
|
||||
target: (barPosition === "left" || barPosition === "right") ? verticalLayout : windowActiveRect
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
delay: Style.tooltipDelay
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: CompositorService
|
||||
function onActiveWindowChanged() {
|
||||
|
||||
@@ -19,9 +19,9 @@ NIconButton {
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
tooltipText: I18n.tr("tooltips.bluetooth-devices")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
icon: BluetoothService.enabled ? "bluetooth" : "bluetooth-off"
|
||||
onClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
||||
onRightClicked: PanelService.getPanel("bluetoothPanel")?.toggle(this)
|
||||
}
|
||||
|
||||
@@ -106,14 +106,6 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: I18n.tr("clock.tooltip")
|
||||
target: clockContainer
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clockMouseArea
|
||||
@@ -122,14 +114,14 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (!PanelService.getPanel("calendarPanel")?.active) {
|
||||
tooltip.show()
|
||||
PanelService.tooltip.show(root, I18n.tr("clock.tooltip"), BarService.getTooltipDirection())
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
}
|
||||
onClicked: {
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
PanelService.getPanel("calendarPanel")?.toggle(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,7 @@ NIconButton {
|
||||
// If we have a custom path or distro logo, don't use the theme icon.
|
||||
icon: (customIconPath === "" && !useDistroLogo) ? customIcon : ""
|
||||
tooltipText: I18n.tr("tooltips.open-control-center")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
baseSize: Style.capsuleHeight
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
|
||||
@@ -11,9 +11,7 @@ NIconButton {
|
||||
|
||||
icon: "dark-mode"
|
||||
tooltipText: Settings.data.colorSchemes.darkMode ? I18n.tr("tooltips.switch-to-light-mode") : I18n.tr("tooltips.switch-to-dark-mode")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
baseSize: Style.capsuleHeight
|
||||
colorBg: Settings.data.colorSchemes.darkMode ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
|
||||
|
||||
@@ -15,9 +15,7 @@ NIconButton {
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
icon: IdleInhibitorService.isInhibited ? "keep-awake-on" : "keep-awake-off"
|
||||
tooltipText: IdleInhibitorService.isInhibited ? I18n.tr("tooltips.disable-keep-awake") : I18n.tr("tooltips.enable-keep-awake")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
colorBg: IdleInhibitorService.isInhibited ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
colorFg: IdleInhibitorService.isInhibited ? Color.mOnPrimary : Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
|
||||
@@ -42,6 +42,21 @@ Item {
|
||||
// Fixed width - no expansion
|
||||
readonly property real widgetWidth: Math.max(1, screen.width * 0.06)
|
||||
|
||||
readonly property string tooltipText: {
|
||||
var title = getTitle()
|
||||
var controls = ""
|
||||
if (MediaService.canGoNext) {
|
||||
controls += "Right click for next.\n"
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
controls += "Middle click for previous."
|
||||
}
|
||||
if (controls !== "") {
|
||||
return title + "\n\n" + controls
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
implicitHeight: visible ? ((barPosition === "left" || barPosition === "right") ? calculatedVerticalHeight() : Math.round(Style.barHeight * scaling)) : 0
|
||||
implicitWidth: visible ? ((barPosition === "left" || barPosition === "right") ? Math.round(Style.baseWidgetSize * 0.8 * scaling) : (widgetWidth * scaling)) : 0
|
||||
|
||||
@@ -360,39 +375,14 @@ Item {
|
||||
}
|
||||
|
||||
onEntered: {
|
||||
if ((tooltip.text !== "") && (barPosition === "left" || barPosition === "right")) {
|
||||
tooltip.show()
|
||||
} else if ((tooltip.text !== "") && (scrollingMode === "never")) {
|
||||
tooltip.show()
|
||||
if ((tooltipText !== "") && (barPosition === "left" || barPosition === "right") || (scrollingMode === "never")) {
|
||||
PanelService.tooltip.show(root, tooltipText, BarService.getTooltipDirection())
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
tooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: tooltip
|
||||
text: {
|
||||
var title = getTitle()
|
||||
var controls = ""
|
||||
if (MediaService.canGoNext) {
|
||||
controls += "Right click for next.\n"
|
||||
}
|
||||
if (MediaService.canGoPrevious) {
|
||||
controls += "Middle click for previous."
|
||||
}
|
||||
if (controls !== "") {
|
||||
return title + "\n\n" + controls
|
||||
}
|
||||
return title
|
||||
}
|
||||
target: (barPosition === "left" || barPosition === "right") ? verticalLayout : mediaMini
|
||||
positionLeft: barPosition === "right"
|
||||
positionRight: barPosition === "left"
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
delay: Style.tooltipDelay
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,7 @@ NIconButton {
|
||||
|
||||
icon: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? "nightlight-forced" : "nightlight-on") : "nightlight-off"
|
||||
tooltipText: Settings.data.nightLight.enabled ? (Settings.data.nightLight.forced ? I18n.tr("tooltips.night-light-forced") : I18n.tr("tooltips.night-light-enabled")) : I18n.tr("tooltips.night-light-disabled")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
onClicked: {
|
||||
// Check if wlsunset is available before enabling night light
|
||||
if (!ProgramCheckerService.wlsunsetAvailable) {
|
||||
|
||||
@@ -53,9 +53,7 @@ NIconButton {
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
icon: Settings.data.notifications.doNotDisturb ? "bell-off" : "bell"
|
||||
tooltipText: Settings.data.notifications.doNotDisturb ? I18n.tr("tooltips.open-notification-history-disable-dnd") : I18n.tr("tooltips.open-notification-history-enable-dnd")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
|
||||
@@ -19,9 +19,7 @@ NIconButton {
|
||||
tooltipText: I18n.tr("tooltips.power-profile", {
|
||||
"profile": PowerProfileService.getName()
|
||||
})
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
colorBg: (PowerProfileService.profile === PowerProfile.Balanced) ? (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent) : Color.mPrimary
|
||||
colorFg: (PowerProfileService.profile === PowerProfile.Balanced) ? Color.mOnSurface : Color.mOnPrimary
|
||||
|
||||
@@ -12,9 +12,7 @@ NIconButton {
|
||||
|
||||
icon: "camera-video"
|
||||
tooltipText: ScreenRecorderService.isRecording ? I18n.tr("tooltips.click-to-stop-recording") : I18n.tr("tooltips.click-to-start-recording")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
baseSize: Style.capsuleHeight
|
||||
colorBg: ScreenRecorderService.isRecording ? Color.mPrimary : (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
|
||||
@@ -15,9 +15,7 @@ NIconButton {
|
||||
baseSize: Style.capsuleHeight
|
||||
icon: "power"
|
||||
tooltipText: I18n.tr("tooltips.session-menu")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
colorFg: Color.mError
|
||||
colorBorder: Color.transparent
|
||||
|
||||
@@ -98,17 +98,8 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: taskbarTooltip.show()
|
||||
onExited: taskbarTooltip.hide()
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: taskbarTooltip
|
||||
text: taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app."
|
||||
target: taskbarItem
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
positionLeft: Settings.data.bar.position === "right"
|
||||
positionRight: Settings.data.bar.position === "left"
|
||||
onEntered: PanelService.tooltip.show(taskbarItem, taskbarItem.modelData.title || taskbarItem.modelData.appId || "Unknown app.", BarService.getTooltipDirection())
|
||||
onExited: PanelService.tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,15 +135,8 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
onEntered: trayTooltip.show()
|
||||
onExited: trayTooltip.hide()
|
||||
}
|
||||
|
||||
NTooltip {
|
||||
id: trayTooltip
|
||||
target: trayIcon
|
||||
text: modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item"
|
||||
positionAbove: Settings.data.bar.position === "bottom"
|
||||
onEntered: PanelService.tooltip.show(trayIcon, modelData.tooltipTitle || modelData.name || modelData.id || "Tray Item", BarService.getTooltipDirection())
|
||||
onExited: PanelService.tooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,7 @@ NIconButton {
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
icon: "wallpaper-selector"
|
||||
tooltipText: I18n.tr("tooltips.open-wallpaper-selector")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
colorBg: (Settings.data.bar.showCapsule ? Color.mSurfaceVariant : Color.transparent)
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
|
||||
@@ -19,7 +19,8 @@ NIconButton {
|
||||
colorFg: Color.mOnSurface
|
||||
colorBorder: Color.transparent
|
||||
colorBorderHover: Color.transparent
|
||||
|
||||
tooltipText: I18n.tr("tooltips.manage-wifi")
|
||||
tooltipDirection: BarService.getTooltipDirection()
|
||||
icon: {
|
||||
try {
|
||||
if (NetworkService.ethernetConnected) {
|
||||
@@ -40,10 +41,6 @@ NIconButton {
|
||||
return "signal_wifi_bad"
|
||||
}
|
||||
}
|
||||
tooltipText: I18n.tr("tooltips.manage-wifi")
|
||||
tooltipPositionAbove: Settings.data.bar.position === "bottom"
|
||||
tooltipPositionLeft: Settings.data.bar.position === "right"
|
||||
tooltipPositionRight: Settings.data.bar.position === "left"
|
||||
onClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
||||
onRightClicked: PanelService.getPanel("wifiPanel")?.toggle(this)
|
||||
}
|
||||
|
||||
+4
-12
@@ -373,14 +373,6 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
id: appTooltip
|
||||
target: appButton
|
||||
positionAbove: true
|
||||
visible: false
|
||||
}
|
||||
|
||||
Image {
|
||||
id: appIcon
|
||||
width: iconSize
|
||||
@@ -481,8 +473,8 @@ Variants {
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
const appName = appButton.appTitle || appButton.appId || "Unknown"
|
||||
appTooltip.text = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
appTooltip.isVisible = true
|
||||
const tooltipText = appName.length > 40 ? appName.substring(0, 37) + "..." : appName
|
||||
PanelService.tooltip.show(appButton, tooltipText, "top")
|
||||
if (autoHide) {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
@@ -492,7 +484,7 @@ Variants {
|
||||
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
appTooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
@@ -508,7 +500,7 @@ Variants {
|
||||
// Close any other existing context menu first
|
||||
root.closeAllContextMenus()
|
||||
// Hide tooltip when showing context menu
|
||||
appTooltip.hide()
|
||||
PanelService.tooltip.hide()
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData)
|
||||
return
|
||||
}
|
||||
|
||||
+9
-9
@@ -410,10 +410,10 @@ Variants {
|
||||
// Make visible and animate in
|
||||
osdItem.visible = true
|
||||
// Use Qt.callLater to ensure the visible change is processed before animation
|
||||
Qt.callLater(function () {
|
||||
osdItem.opacity = 1
|
||||
osdItem.scale = 1.0
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
osdItem.opacity = 1
|
||||
osdItem.scale = 1.0
|
||||
})
|
||||
|
||||
// Start the auto-hide timer
|
||||
hideTimer.start()
|
||||
@@ -524,11 +524,11 @@ Variants {
|
||||
root.item.showOSD()
|
||||
} else {
|
||||
// If item not ready yet, wait for it
|
||||
Qt.callLater(function () {
|
||||
if (root.item) {
|
||||
root.item.showOSD()
|
||||
}
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
if (root.item) {
|
||||
root.item.showOSD()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ Popup {
|
||||
|
||||
NIconButton {
|
||||
icon: "close"
|
||||
tooltipText: "Close"
|
||||
onClicked: widgetSettings.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +93,11 @@ Item {
|
||||
// Activate the loader and show toast
|
||||
windowLoader.active = true
|
||||
// Need a small delay to ensure the window is created
|
||||
Qt.callLater(function () {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.showToast(data.message, data.description, data.type, data.duration)
|
||||
}
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
if (windowLoader.item) {
|
||||
windowLoader.item.showToast(data.message, data.description, data.type, data.duration)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onToastHidden() {
|
||||
|
||||
@@ -0,0 +1,414 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property string text: ""
|
||||
property string direction: "auto" // "auto", "left", "right", "top", "bottom"
|
||||
property int margin: Style.marginXS // distance from target
|
||||
property int padding: Style.marginM
|
||||
property int delay: 0
|
||||
property int hideDelay: 0
|
||||
property int maxWidth: 340
|
||||
property real scaling: 1.0
|
||||
property int animationDuration: Style.animationFast
|
||||
property real animationScale: 0.85
|
||||
|
||||
// Internal properties
|
||||
property var targetItem: null
|
||||
property real anchorX: 0
|
||||
property real anchorY: 0
|
||||
property bool isPositioned: false
|
||||
property bool pendingShow: false
|
||||
property bool animatingOut: false
|
||||
|
||||
visible: false
|
||||
color: Color.transparent
|
||||
|
||||
anchor.item: targetItem
|
||||
anchor.rect.x: anchorX
|
||||
anchor.rect.y: anchorY
|
||||
|
||||
implicitWidth: Math.min(tooltipText.implicitWidth + padding * 2 * scaling, maxWidth * scaling)
|
||||
implicitHeight: tooltipText.implicitHeight + padding * 2 * scaling
|
||||
|
||||
// Timer for showing tooltip after delay
|
||||
Timer {
|
||||
id: showTimer
|
||||
interval: root.delay
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.positionAndShow()
|
||||
}
|
||||
}
|
||||
|
||||
// Timer for hiding tooltip after delay
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: root.hideDelay
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
root.startHideAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
// Show animation
|
||||
ParallelAnimation {
|
||||
id: showAnimation
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "scale"
|
||||
from: root.animationScale
|
||||
to: 1.0
|
||||
duration: root.animationDuration
|
||||
easing.type: Easing.OutBack
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
// Hide animation
|
||||
ParallelAnimation {
|
||||
id: hideAnimation
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "opacity"
|
||||
from: 1.0
|
||||
to: 0.0
|
||||
duration: root.animationDuration * 0.75 // Slightly faster hide
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
|
||||
PropertyAnimation {
|
||||
target: tooltipContainer
|
||||
property: "scale"
|
||||
from: 1.0
|
||||
to: root.animationScale
|
||||
duration: root.animationDuration * 0.75
|
||||
easing.type: Easing.InCubic
|
||||
}
|
||||
|
||||
onFinished: {
|
||||
root.completeHide()
|
||||
}
|
||||
}
|
||||
|
||||
// Function to show tooltip
|
||||
function show(target, tipText, customDirection, showDelay) {
|
||||
if (!target || !tipText || tipText === "")
|
||||
return
|
||||
|
||||
if (showDelay !== undefined) {
|
||||
delay = showDelay
|
||||
} else {
|
||||
delay = Style.tooltipDelay
|
||||
}
|
||||
|
||||
// Stop any running timers and animations
|
||||
hideTimer.stop()
|
||||
showTimer.stop()
|
||||
hideAnimation.stop()
|
||||
animatingOut = false
|
||||
|
||||
// If we're already showing for a different target, hide immediately
|
||||
if (visible && targetItem !== target) {
|
||||
hideImmediately()
|
||||
}
|
||||
|
||||
// Set properties
|
||||
targetItem = target
|
||||
text = tipText
|
||||
pendingShow = true
|
||||
|
||||
if (customDirection !== undefined) {
|
||||
direction = customDirection
|
||||
} else {
|
||||
direction = "auto"
|
||||
}
|
||||
|
||||
// Start show timer
|
||||
showTimer.start()
|
||||
}
|
||||
|
||||
// Function to position and display the tooltip
|
||||
function positionAndShow() {
|
||||
if (!targetItem || !pendingShow)
|
||||
return
|
||||
|
||||
// Get screen dimensions - try multiple methods
|
||||
var screenWidth = Screen.width
|
||||
var screenHeight = Screen.height
|
||||
|
||||
// Try to get screen from target item
|
||||
if (targetItem) {
|
||||
if (targetItem.screen) {
|
||||
screenWidth = targetItem.screen.width
|
||||
screenHeight = targetItem.screen.height
|
||||
scaling = ScalingService.getScreenScale(targetItem.screen)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate tooltip dimensions
|
||||
const tipWidth = Math.min(tooltipText.implicitWidth + padding * 2 * scaling, maxWidth * scaling)
|
||||
const tipHeight = tooltipText.implicitHeight + padding * 2 * scaling
|
||||
|
||||
// Get target's global position
|
||||
const targetGlobal = targetItem.mapToGlobal(0, 0)
|
||||
const targetWidth = targetItem.width
|
||||
const targetHeight = targetItem.height
|
||||
|
||||
var newAnchorX = 0
|
||||
var newAnchorY = 0
|
||||
|
||||
if (direction === "auto") {
|
||||
// Calculate available space in each direction
|
||||
const spaceLeft = targetGlobal.x
|
||||
const spaceRight = screenWidth - (targetGlobal.x + targetWidth)
|
||||
const spaceTop = targetGlobal.y
|
||||
const spaceBottom = screenHeight - (targetGlobal.y + targetHeight)
|
||||
|
||||
// Try positions in order of available space
|
||||
const positions = [{
|
||||
"dir": "bottom",
|
||||
"space": spaceBottom,
|
||||
"x": (targetWidth - tipWidth) / 2,
|
||||
"y": targetHeight + margin * scaling,
|
||||
"fits": spaceBottom >= tipHeight + margin * scaling
|
||||
}, {
|
||||
"dir": "top",
|
||||
"space": spaceTop,
|
||||
"x": (targetWidth - tipWidth) / 2,
|
||||
"y": -tipHeight - margin * scaling,
|
||||
"fits": spaceTop >= tipHeight + margin * scaling
|
||||
}, {
|
||||
"dir": "right",
|
||||
"space": spaceRight,
|
||||
"x": targetWidth + margin * scaling,
|
||||
"y": (targetHeight - tipHeight) / 2,
|
||||
"fits": spaceRight >= tipWidth + margin * scaling
|
||||
}, {
|
||||
"dir": "left",
|
||||
"space": spaceLeft,
|
||||
"x": -tipWidth - margin * scaling,
|
||||
"y": (targetHeight - tipHeight) / 2,
|
||||
"fits": spaceLeft >= tipWidth + margin * scaling
|
||||
}]
|
||||
|
||||
// Find first position that fits
|
||||
var selectedPosition = null
|
||||
for (var i = 0; i < positions.length; i++) {
|
||||
if (positions[i].fits) {
|
||||
selectedPosition = positions[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If none fit perfectly
|
||||
if (!selectedPosition) {
|
||||
// Sort by available space and use position with most space
|
||||
positions.sort(function (a, b) {
|
||||
return b.space - a.space
|
||||
})
|
||||
selectedPosition = positions[0]
|
||||
}
|
||||
|
||||
newAnchorX = selectedPosition.x
|
||||
newAnchorY = selectedPosition.y
|
||||
|
||||
// Adjust horizontal position to keep tooltip on screen
|
||||
if (direction === "auto") {
|
||||
const globalX = targetGlobal.x + newAnchorX
|
||||
if (globalX < 0) {
|
||||
newAnchorX = -targetGlobal.x + margin * scaling
|
||||
} else if (globalX + tipWidth > screenWidth) {
|
||||
newAnchorX = screenWidth - targetGlobal.x - tipWidth - margin * scaling
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Manual direction positioning
|
||||
switch (direction) {
|
||||
case "left":
|
||||
newAnchorX = -tipWidth - margin * scaling
|
||||
newAnchorY = (targetHeight - tipHeight) / 2
|
||||
break
|
||||
case "right":
|
||||
newAnchorX = targetWidth + margin * scaling
|
||||
newAnchorY = (targetHeight - tipHeight) / 2
|
||||
break
|
||||
case "top":
|
||||
newAnchorX = (targetWidth - tipWidth) / 2
|
||||
newAnchorY = -tipHeight - margin * scaling
|
||||
break
|
||||
case "bottom":
|
||||
newAnchorX = (targetWidth - tipWidth) / 2
|
||||
newAnchorY = targetHeight + margin * scaling
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Apply position
|
||||
anchorX = newAnchorX
|
||||
anchorY = newAnchorY
|
||||
isPositioned = true
|
||||
pendingShow = false
|
||||
|
||||
// Show tooltip and start animation
|
||||
visible = true
|
||||
|
||||
// Initialize animation state
|
||||
tooltipContainer.opacity = 0.0
|
||||
tooltipContainer.scale = animationScale
|
||||
|
||||
// Start show animation
|
||||
showAnimation.start()
|
||||
|
||||
// Force anchor update after showing
|
||||
Qt.callLater(() => {
|
||||
if (root.anchor && root.visible) {
|
||||
root.anchor.updateAnchor()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Function to hide tooltip
|
||||
function hide() {
|
||||
// Stop show timer if it's running
|
||||
showTimer.stop()
|
||||
pendingShow = false
|
||||
|
||||
// Stop hide timer if it's running
|
||||
hideTimer.stop()
|
||||
|
||||
if (hideDelay > 0 && visible && !animatingOut) {
|
||||
hideTimer.start()
|
||||
} else {
|
||||
startHideAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
function startHideAnimation() {
|
||||
if (!visible || animatingOut)
|
||||
return
|
||||
|
||||
animatingOut = true
|
||||
showAnimation.stop() // Stop show animation if running
|
||||
hideAnimation.start()
|
||||
}
|
||||
|
||||
function completeHide() {
|
||||
// Hide the popup window
|
||||
visible = false
|
||||
animatingOut = false
|
||||
pendingShow = false
|
||||
|
||||
// Clear the text and reset state
|
||||
text = ""
|
||||
isPositioned = false
|
||||
|
||||
// Reset container state
|
||||
tooltipContainer.opacity = 1.0
|
||||
tooltipContainer.scale = 1.0
|
||||
}
|
||||
|
||||
// Quick hide without delay or animation
|
||||
function hideImmediately() {
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
showAnimation.stop()
|
||||
hideAnimation.stop()
|
||||
pendingShow = false
|
||||
animatingOut = false
|
||||
completeHide()
|
||||
}
|
||||
|
||||
// Update text function for binding support
|
||||
function updateText(newText) {
|
||||
if (visible && targetItem) {
|
||||
text = newText
|
||||
positionAndShow()
|
||||
}
|
||||
}
|
||||
|
||||
// Reset function to clean up state
|
||||
function reset() {
|
||||
// Stop all timers and animations
|
||||
showTimer.stop()
|
||||
hideTimer.stop()
|
||||
showAnimation.stop()
|
||||
hideAnimation.stop()
|
||||
|
||||
// Clear all state
|
||||
visible = false
|
||||
pendingShow = false
|
||||
animatingOut = false
|
||||
text = ""
|
||||
isPositioned = false
|
||||
|
||||
// Reset to defaults
|
||||
direction = "auto"
|
||||
delay = 0
|
||||
hideDelay = 0
|
||||
|
||||
// Reset container state
|
||||
tooltipContainer.opacity = 1.0
|
||||
tooltipContainer.scale = 1.0
|
||||
}
|
||||
|
||||
// Tooltip content container for animations
|
||||
Item {
|
||||
id: tooltipContainer
|
||||
anchors.fill: parent
|
||||
|
||||
// Animation properties
|
||||
opacity: 1.0
|
||||
scale: 1.0
|
||||
transformOrigin: Item.Center
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
radius: Style.radiusM * scaling
|
||||
|
||||
// Only show content when we have text
|
||||
visible: root.text !== ""
|
||||
|
||||
NText {
|
||||
id: tooltipText
|
||||
anchors.centerIn: parent
|
||||
anchors.margins: root.padding * root.scaling
|
||||
text: root.text
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurfaceVariant
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, root.maxWidth * root.scaling - root.padding * 2 * root.scaling)
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
reset()
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
reset()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user