mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 08:22:51 -05:00
systemtray: new tray detail menu
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Effects
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.SystemTray
|
import Quickshell.Services.SystemTray
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -45,6 +47,7 @@ Item {
|
|||||||
visible: allTrayItems.length > 0
|
visible: allTrayItems.length > 0
|
||||||
|
|
||||||
property bool menuOpen: false
|
property bool menuOpen: false
|
||||||
|
property bool overflowWasOpenBeforeTrayMenu: false
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: visualBackground
|
id: visualBackground
|
||||||
@@ -157,17 +160,17 @@ Item {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
if (!delegateRoot.trayItem) {
|
if (!delegateRoot.trayItem) return
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
||||||
delegateRoot.trayItem.activate();
|
delegateRoot.trayItem.activate()
|
||||||
return ;
|
return
|
||||||
}
|
|
||||||
if (delegateRoot.trayItem.hasMenu) {
|
|
||||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!delegateRoot.trayItem.hasMenu) return
|
||||||
|
|
||||||
|
root.overflowWasOpenBeforeTrayMenu = root.menuOpen
|
||||||
|
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,17 +292,17 @@ Item {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: (mouse) => {
|
onClicked: (mouse) => {
|
||||||
if (!delegateRoot.trayItem) {
|
if (!delegateRoot.trayItem) return
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
||||||
delegateRoot.trayItem.activate();
|
delegateRoot.trayItem.activate()
|
||||||
return ;
|
return
|
||||||
}
|
|
||||||
if (delegateRoot.trayItem.hasMenu) {
|
|
||||||
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!delegateRoot.trayItem.hasMenu) return
|
||||||
|
|
||||||
|
root.overflowWasOpenBeforeTrayMenu = root.menuOpen
|
||||||
|
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,7 +352,7 @@ Item {
|
|||||||
screen: root.parentScreen
|
screen: root.parentScreen
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||||
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
@@ -360,10 +363,26 @@ Item {
|
|||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property real dpr: (typeof CompositorService !== "undefined" && CompositorService.getScreenScale)
|
||||||
|
? CompositorService.getScreenScale(overflowMenu.screen)
|
||||||
|
: (screen?.devicePixelRatio || 1)
|
||||||
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) updatePosition()
|
if (visible) {
|
||||||
|
updatePosition()
|
||||||
|
Qt.callLater(() => overflowFocusScope.forceActiveFocus())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: overflowFocusScope
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
root.menuOpen = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePosition() {
|
function updatePosition() {
|
||||||
@@ -395,50 +414,71 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: menuContainer
|
id: menuContainer
|
||||||
width: 250
|
|
||||||
height: Math.min(screen.height - 100, menuColumn.implicitHeight + Theme.spacingS * 2)
|
|
||||||
|
|
||||||
x: {
|
readonly property real rawWidth: {
|
||||||
|
const itemCount = root.allTrayItems.length
|
||||||
|
const cols = Math.min(5, itemCount)
|
||||||
|
const itemSize = 28
|
||||||
|
const spacing = 2
|
||||||
|
return cols * itemSize + (cols - 1) * spacing + Theme.spacingS * 2
|
||||||
|
}
|
||||||
|
readonly property real rawHeight: {
|
||||||
|
const itemCount = root.allTrayItems.length
|
||||||
|
const cols = Math.min(5, itemCount)
|
||||||
|
const rows = Math.ceil(itemCount / cols)
|
||||||
|
const itemSize = 28
|
||||||
|
const spacing = 2
|
||||||
|
return rows * itemSize + (rows - 1) * spacing + Theme.spacingS * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real alignedWidth: Theme.px(rawWidth, overflowMenu.dpr)
|
||||||
|
readonly property real alignedHeight: Theme.px(rawHeight, overflowMenu.dpr)
|
||||||
|
|
||||||
|
width: alignedWidth
|
||||||
|
height: alignedHeight
|
||||||
|
|
||||||
|
x: Theme.snap((() => {
|
||||||
if (root.isVertical) {
|
if (root.isVertical) {
|
||||||
const edge = root.axis?.edge
|
const edge = root.axis?.edge
|
||||||
if (edge === "left") {
|
if (edge === "left") {
|
||||||
const targetX = overflowMenu.anchorPos.x
|
const targetX = overflowMenu.anchorPos.x
|
||||||
return Math.min(overflowMenu.screen.width - width - 10, targetX)
|
return Math.min(overflowMenu.screen.width - alignedWidth - 10, targetX)
|
||||||
} else {
|
} else {
|
||||||
const targetX = overflowMenu.anchorPos.x - width
|
const targetX = overflowMenu.anchorPos.x - alignedWidth
|
||||||
return Math.max(10, targetX)
|
return Math.max(10, targetX)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const left = 10
|
const left = 10
|
||||||
const right = overflowMenu.width - width - 10
|
const right = overflowMenu.width - alignedWidth - 10
|
||||||
const want = overflowMenu.anchorPos.x - width / 2
|
const want = overflowMenu.anchorPos.x - alignedWidth / 2
|
||||||
return Math.max(left, Math.min(right, want))
|
return Math.max(left, Math.min(right, want))
|
||||||
}
|
}
|
||||||
}
|
})(), overflowMenu.dpr)
|
||||||
|
|
||||||
y: {
|
y: Theme.snap((() => {
|
||||||
if (root.isVertical) {
|
if (root.isVertical) {
|
||||||
const top = 10
|
const top = 10
|
||||||
const bottom = overflowMenu.height - height - 10
|
const bottom = overflowMenu.height - alignedHeight - 10
|
||||||
const want = overflowMenu.anchorPos.y - height / 2
|
const want = overflowMenu.anchorPos.y - alignedHeight / 2
|
||||||
return Math.max(top, Math.min(bottom, want))
|
return Math.max(top, Math.min(bottom, want))
|
||||||
} else {
|
} else {
|
||||||
if (root.isAtBottom) {
|
if (root.isAtBottom) {
|
||||||
const targetY = overflowMenu.anchorPos.y - height
|
const targetY = overflowMenu.anchorPos.y - alignedHeight
|
||||||
return Math.max(10, targetY)
|
return Math.max(10, targetY)
|
||||||
} else {
|
} else {
|
||||||
const targetY = overflowMenu.anchorPos.y
|
const targetY = overflowMenu.anchorPos.y
|
||||||
return Math.min(overflowMenu.screen.height - height - 10, targetY)
|
return Math.min(overflowMenu.screen.height - alignedHeight - 10, targetY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})(), overflowMenu.dpr)
|
||||||
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
property real shadowBlurPx: 10
|
||||||
radius: Theme.cornerRadius
|
property real shadowSpreadPx: 0
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
property real shadowBaseAlpha: 0.60
|
||||||
border.width: 1
|
readonly property real popupSurfaceAlpha: Theme.popupTransparency
|
||||||
|
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||||
|
|
||||||
opacity: root.menuOpen ? 1 : 0
|
opacity: root.menuOpen ? 1 : 0
|
||||||
scale: root.menuOpen ? 1 : 0.85
|
scale: root.menuOpen ? 1 : 0.85
|
||||||
@@ -457,156 +497,122 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
|
id: bgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 4
|
layer.enabled: true
|
||||||
anchors.leftMargin: 2
|
layer.smooth: true
|
||||||
anchors.rightMargin: -2
|
layer.textureSize: Qt.size(Math.round(width * overflowMenu.dpr * 2), Math.round(height * overflowMenu.dpr * 2))
|
||||||
anchors.bottomMargin: -4
|
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||||
radius: parent.radius
|
layer.samples: 4
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
|
||||||
z: parent.z - 1
|
layer.effect: MultiEffect {
|
||||||
|
autoPaddingEnabled: true
|
||||||
|
shadowEnabled: true
|
||||||
|
blurEnabled: false
|
||||||
|
maskEnabled: false
|
||||||
|
property int blurMax: 64
|
||||||
|
shadowBlur: Math.max(0, Math.min(1, menuContainer.shadowBlurPx / blurMax))
|
||||||
|
shadowScale: 1 + (2 * menuContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
||||||
|
shadowColor: {
|
||||||
|
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
|
||||||
|
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
antialiasing: true
|
||||||
|
smooth: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankFlickable {
|
Grid {
|
||||||
id: scrollView
|
id: menuGrid
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
anchors.margins: Theme.spacingS
|
columns: Math.min(5, root.allTrayItems.length)
|
||||||
contentWidth: width
|
spacing: 2
|
||||||
contentHeight: menuColumn.implicitHeight
|
rowSpacing: 2
|
||||||
clip: true
|
|
||||||
|
|
||||||
Column {
|
Repeater {
|
||||||
id: menuColumn
|
model: root.allTrayItems
|
||||||
width: parent.width
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
Repeater {
|
delegate: Rectangle {
|
||||||
model: root.allTrayItems
|
property var trayItem: modelData
|
||||||
|
property string iconSource: {
|
||||||
delegate: Rectangle {
|
let icon = trayItem?.icon
|
||||||
property var trayItem: modelData
|
if (typeof icon === 'string' || icon instanceof String) {
|
||||||
property string iconSource: {
|
if (icon === "") return ""
|
||||||
let icon = trayItem?.icon
|
if (icon.includes("?path=")) {
|
||||||
if (typeof icon === 'string' || icon instanceof String) {
|
const split = icon.split("?path=")
|
||||||
if (icon === "") return ""
|
if (split.length !== 2) return icon
|
||||||
if (icon.includes("?path=")) {
|
const name = split[0]
|
||||||
const split = icon.split("?path=")
|
const path = split[1]
|
||||||
if (split.length !== 2) return icon
|
let fileName = name.substring(name.lastIndexOf("/") + 1)
|
||||||
const name = split[0]
|
if (fileName.startsWith("dropboxstatus")) {
|
||||||
const path = split[1]
|
fileName = `hicolor/16x16/status/${fileName}`
|
||||||
let fileName = name.substring(name.lastIndexOf("/") + 1)
|
|
||||||
if (fileName.startsWith("dropboxstatus")) {
|
|
||||||
fileName = `hicolor/16x16/status/${fileName}`
|
|
||||||
}
|
|
||||||
return `file://${path}/${fileName}`
|
|
||||||
}
|
}
|
||||||
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
return `file://${path}/${fileName}`
|
||||||
return `file://${icon}`
|
|
||||||
}
|
|
||||||
return icon
|
|
||||||
}
|
}
|
||||||
return ""
|
if (icon.startsWith("/") && !icon.startsWith("file://")) {
|
||||||
|
return `file://${icon}`
|
||||||
|
}
|
||||||
|
return icon
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
width: menuColumn.width
|
width: 28
|
||||||
height: 32
|
height: 28
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: itemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
color: itemArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
Row {
|
IconImage {
|
||||||
anchors.left: parent.left
|
id: menuIconImg
|
||||||
anchors.leftMargin: Theme.spacingS
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
width: Theme.barIconSize(root.barThickness)
|
||||||
spacing: Theme.spacingS
|
height: Theme.barIconSize(root.barThickness)
|
||||||
|
source: parent.iconSource
|
||||||
|
asynchronous: true
|
||||||
|
smooth: true
|
||||||
|
mipmap: true
|
||||||
|
visible: status === Image.Ready
|
||||||
|
}
|
||||||
|
|
||||||
IconImage {
|
Text {
|
||||||
id: menuIconImg
|
anchors.centerIn: parent
|
||||||
width: 20
|
visible: !menuIconImg.visible
|
||||||
height: 20
|
text: {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
const itemId = trayItem?.id || ""
|
||||||
source: parent.parent.iconSource
|
if (!itemId) return "?"
|
||||||
asynchronous: true
|
return itemId.charAt(0).toUpperCase()
|
||||||
smooth: true
|
|
||||||
mipmap: true
|
|
||||||
visible: status === Image.Ready
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
visible: !menuIconImg.visible
|
|
||||||
text: {
|
|
||||||
const itemId = trayItem?.id || ""
|
|
||||||
if (!itemId) return "?"
|
|
||||||
return itemId.charAt(0).toUpperCase()
|
|
||||||
}
|
|
||||||
font.pixelSize: 10
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: trayItem?.tooltip?.title || trayItem?.id || "Unknown"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
elide: Text.ElideRight
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: Math.min(implicitWidth, menuColumn.width - 80)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
font.pixelSize: 10
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
anchors.right: parent.right
|
id: itemArea
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.fill: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
hoverEnabled: true
|
||||||
width: 24
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
height: 24
|
cursorShape: Qt.PointingHandCursor
|
||||||
radius: Theme.cornerRadius
|
onClicked: (mouse) => {
|
||||||
color: visibilityArea.containsMouse ? Theme.primaryHover : "transparent"
|
if (!trayItem) return
|
||||||
|
|
||||||
DankIcon {
|
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
|
||||||
anchors.centerIn: parent
|
trayItem.activate()
|
||||||
name: SessionData.isHiddenTrayId(trayItem?.id || "") ? "visibility_off" : "visibility"
|
root.menuOpen = false
|
||||||
size: 16
|
return
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
if (!trayItem.hasMenu) return
|
||||||
id: visibilityArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
const itemId = trayItem?.id || ""
|
|
||||||
if (!itemId) return
|
|
||||||
if (SessionData.isHiddenTrayId(itemId)) {
|
|
||||||
SessionData.showTrayId(itemId)
|
|
||||||
} else {
|
|
||||||
SessionData.hideTrayId(itemId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
root.overflowWasOpenBeforeTrayMenu = true
|
||||||
id: itemArea
|
root.menuOpen = false
|
||||||
anchors.fill: parent
|
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
|
||||||
anchors.rightMargin: 32
|
|
||||||
hoverEnabled: true
|
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: (mouse) => {
|
|
||||||
if (!trayItem) return
|
|
||||||
|
|
||||||
if (mouse.button === Qt.LeftButton && !trayItem.onlyMenu) {
|
|
||||||
trayItem.activate()
|
|
||||||
root.menuOpen = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (trayItem.hasMenu) {
|
|
||||||
root.menuOpen = false
|
|
||||||
root.showForTrayItem(trayItem, parent, parentScreen, root.isAtBottom, root.isVertical, root.axis)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -665,6 +671,20 @@ Item {
|
|||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
showMenu = false
|
showMenu = false
|
||||||
|
if (root.overflowWasOpenBeforeTrayMenu) {
|
||||||
|
root.menuOpen = true
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (overflowMenu.visible && overflowFocusScope) {
|
||||||
|
overflowFocusScope.forceActiveFocus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
root.overflowWasOpenBeforeTrayMenu = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWithAction() {
|
||||||
|
root.overflowWasOpenBeforeTrayMenu = false
|
||||||
|
close()
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSubMenu(entry) {
|
function showSubMenu(entry) {
|
||||||
@@ -697,7 +717,7 @@ Item {
|
|||||||
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
||||||
WlrLayershell.layer: WlrLayershell.Top
|
WlrLayershell.layer: WlrLayershell.Top
|
||||||
WlrLayershell.exclusiveZone: -1
|
WlrLayershell.exclusiveZone: -1
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
@@ -707,11 +727,29 @@ Item {
|
|||||||
bottom: true
|
bottom: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property real dpr: (typeof CompositorService !== "undefined" && CompositorService.getScreenScale)
|
||||||
|
? CompositorService.getScreenScale(menuWindow.screen)
|
||||||
|
: (screen?.devicePixelRatio || 1)
|
||||||
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
updatePosition()
|
updatePosition()
|
||||||
|
Qt.callLater(() => menuFocusScope.forceActiveFocus())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: menuFocusScope
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onEscapePressed: {
|
||||||
|
if (entryStack.count > 0) {
|
||||||
|
menuRoot.goBack()
|
||||||
|
} else {
|
||||||
|
menuRoot.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,64 +782,104 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Item {
|
||||||
id: menuContainer
|
id: menuContainer
|
||||||
|
|
||||||
width: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
readonly property real rawWidth: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||||
height: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
readonly property real rawHeight: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||||
|
|
||||||
x: {
|
readonly property real alignedWidth: Theme.px(rawWidth, menuWindow.dpr)
|
||||||
|
readonly property real alignedHeight: Theme.px(rawHeight, menuWindow.dpr)
|
||||||
|
|
||||||
|
width: alignedWidth
|
||||||
|
height: alignedHeight
|
||||||
|
|
||||||
|
x: Theme.snap((() => {
|
||||||
if (menuRoot.isVertical) {
|
if (menuRoot.isVertical) {
|
||||||
const edge = menuRoot.axis?.edge
|
const edge = menuRoot.axis?.edge
|
||||||
if (edge === "left") {
|
if (edge === "left") {
|
||||||
const targetX = menuWindow.anchorPos.x
|
const targetX = menuWindow.anchorPos.x
|
||||||
return Math.min(menuWindow.screen.width - width - 10, targetX)
|
return Math.min(menuWindow.screen.width - alignedWidth - 10, targetX)
|
||||||
} else {
|
} else {
|
||||||
const targetX = menuWindow.anchorPos.x - width
|
const targetX = menuWindow.anchorPos.x - alignedWidth
|
||||||
return Math.max(10, targetX)
|
return Math.max(10, targetX)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const left = 10
|
const left = 10
|
||||||
const right = menuWindow.width - width - 10
|
const right = menuWindow.width - alignedWidth - 10
|
||||||
const want = menuWindow.anchorPos.x - width / 2
|
const want = menuWindow.anchorPos.x - alignedWidth / 2
|
||||||
return Math.max(left, Math.min(right, want))
|
return Math.max(left, Math.min(right, want))
|
||||||
}
|
}
|
||||||
}
|
})(), menuWindow.dpr)
|
||||||
|
|
||||||
y: {
|
y: Theme.snap((() => {
|
||||||
if (menuRoot.isVertical) {
|
if (menuRoot.isVertical) {
|
||||||
const top = 10
|
const top = 10
|
||||||
const bottom = menuWindow.height - height - 10
|
const bottom = menuWindow.height - alignedHeight - 10
|
||||||
const want = menuWindow.anchorPos.y - height / 2
|
const want = menuWindow.anchorPos.y - alignedHeight / 2
|
||||||
return Math.max(top, Math.min(bottom, want))
|
return Math.max(top, Math.min(bottom, want))
|
||||||
} else {
|
} else {
|
||||||
if (menuRoot.isAtBottom) {
|
if (menuRoot.isAtBottom) {
|
||||||
const targetY = menuWindow.anchorPos.y - height
|
const targetY = menuWindow.anchorPos.y - alignedHeight
|
||||||
return Math.max(10, targetY)
|
return Math.max(10, targetY)
|
||||||
} else {
|
} else {
|
||||||
const targetY = menuWindow.anchorPos.y
|
const targetY = menuWindow.anchorPos.y
|
||||||
return Math.min(menuWindow.screen.height - height - 10, targetY)
|
return Math.min(menuWindow.screen.height - alignedHeight - 10, targetY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})(), menuWindow.dpr)
|
||||||
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
property real shadowBlurPx: 10
|
||||||
radius: Theme.cornerRadius
|
property real shadowSpreadPx: 0
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
property real shadowBaseAlpha: 0.60
|
||||||
border.width: 1
|
readonly property real popupSurfaceAlpha: Theme.popupTransparency
|
||||||
|
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha))
|
||||||
|
|
||||||
opacity: menuRoot.showMenu ? 1 : 0
|
opacity: menuRoot.showMenu ? 1 : 0
|
||||||
scale: menuRoot.showMenu ? 1 : 0.85
|
scale: menuRoot.showMenu ? 1 : 0.85
|
||||||
|
|
||||||
Rectangle {
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on scale {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.mediumDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: menuBgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.topMargin: 4
|
layer.enabled: true
|
||||||
anchors.leftMargin: 2
|
layer.smooth: true
|
||||||
anchors.rightMargin: -2
|
layer.textureSize: Qt.size(Math.round(width * menuWindow.dpr), Math.round(height * menuWindow.dpr))
|
||||||
anchors.bottomMargin: -4
|
layer.textureMirroring: ShaderEffectSource.MirrorVertically
|
||||||
radius: parent.radius
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
layer.effect: MultiEffect {
|
||||||
z: parent.z - 1
|
autoPaddingEnabled: true
|
||||||
|
shadowEnabled: true
|
||||||
|
blurEnabled: false
|
||||||
|
maskEnabled: false
|
||||||
|
property int blurMax: 64
|
||||||
|
shadowBlur: Math.max(0, Math.min(1, menuContainer.shadowBlurPx / blurMax))
|
||||||
|
shadowScale: 1 + (2 * menuContainer.shadowSpreadPx) / Math.max(1, Math.min(menuBgShadowLayer.width, menuBgShadowLayer.height))
|
||||||
|
shadowColor: {
|
||||||
|
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
|
||||||
|
return Theme.withAlpha(baseColor, menuContainer.effectiveShadowAlpha)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
antialiasing: true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QsMenuAnchor {
|
QsMenuAnchor {
|
||||||
@@ -832,12 +910,90 @@ Item {
|
|||||||
anchors.topMargin: Theme.spacingS
|
anchors.topMargin: Theme.spacingS
|
||||||
spacing: 1
|
spacing: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: entryStack.count === 0
|
||||||
|
width: parent.width
|
||||||
|
height: 24
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: menuRoot.trayItem?.id || "Unknown"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: entryStack.count === 0
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: entryStack.count === 0
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: 0
|
||||||
|
color: visibilityToggleArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: SessionData.isHiddenTrayId(menuRoot.trayItem?.id || "") ? "visibility" : "visibility_off"
|
||||||
|
size: 16
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: SessionData.isHiddenTrayId(menuRoot.trayItem?.id || "") ? "Show in Tray" : "Hide from Tray"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: visibilityToggleArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
const itemId = menuRoot.trayItem?.id || ""
|
||||||
|
if (!itemId) return
|
||||||
|
|
||||||
|
if (SessionData.isHiddenTrayId(itemId)) {
|
||||||
|
SessionData.showTrayId(itemId)
|
||||||
|
} else {
|
||||||
|
SessionData.hideTrayId(itemId)
|
||||||
|
}
|
||||||
|
menuRoot.closeWithAction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: entryStack.count === 0
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: entryStack.count > 0
|
visible: entryStack.count > 0
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: 28
|
height: 28
|
||||||
radius: Theme.cornerRadius
|
radius: 0
|
||||||
color: backArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
color: backArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -863,6 +1019,7 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
id: backArea
|
id: backArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: menuRoot.goBack()
|
onClicked: menuRoot.goBack()
|
||||||
}
|
}
|
||||||
@@ -886,33 +1043,35 @@ Item {
|
|||||||
|
|
||||||
width: menuColumn.width
|
width: menuColumn.width
|
||||||
height: menuEntry?.isSeparator ? 1 : 28
|
height: menuEntry?.isSeparator ? 1 : 28
|
||||||
radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
|
radius: 0
|
||||||
color: {
|
color: {
|
||||||
if (menuEntry?.isSeparator) {
|
if (menuEntry?.isSeparator) {
|
||||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
}
|
}
|
||||||
return itemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
return itemArea.containsMouse ? Theme.primaryHover : Theme.withAlpha(Theme.surfaceContainer, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: itemArea
|
id: itemArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
|
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!menuEntry || menuEntry.isSeparator) return;
|
if (!menuEntry || menuEntry.isSeparator) return
|
||||||
|
|
||||||
if (menuEntry.hasChildren) {
|
if (menuEntry.hasChildren) {
|
||||||
menuRoot.showSubMenu(menuEntry);
|
menuRoot.showSubMenu(menuEntry)
|
||||||
} else {
|
return
|
||||||
if (typeof menuEntry.activate === "function") {
|
|
||||||
menuEntry.activate();
|
|
||||||
} else if (typeof menuEntry.triggered === "function") {
|
|
||||||
menuEntry.triggered();
|
|
||||||
}
|
|
||||||
Qt.createQmlObject('import QtQuick; Timer { interval: 80; running: true; repeat: false; onTriggered: menuRoot.close() }', menuRoot);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof menuEntry.activate === "function") {
|
||||||
|
menuEntry.activate()
|
||||||
|
} else if (typeof menuEntry.triggered === "function") {
|
||||||
|
menuEntry.triggered()
|
||||||
|
}
|
||||||
|
Qt.createQmlObject('import QtQuick; Timer { interval: 80; running: true; repeat: false; onTriggered: menuRoot.closeWithAction() }', menuRoot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -996,20 +1155,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Behavior on opacity {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on scale {
|
|
||||||
NumberAnimation {
|
|
||||||
duration: Theme.mediumDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -250,7 +250,10 @@ PanelWindow {
|
|||||||
property int blurMax: 64
|
property int blurMax: 64
|
||||||
shadowBlur: Math.max(0, Math.min(1, content.shadowBlurPx / blurMax))
|
shadowBlur: Math.max(0, Math.min(1, content.shadowBlurPx / blurMax))
|
||||||
shadowScale: 1 + (2 * content.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
shadowScale: 1 + (2 * content.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
||||||
shadowColor: Qt.rgba(0, 0, 0, content.effectiveShadowAlpha)
|
shadowColor: {
|
||||||
|
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
|
||||||
|
return Theme.withAlpha(baseColor, content.effectiveShadowAlpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Shape {
|
Shape {
|
||||||
|
|||||||
@@ -143,7 +143,10 @@ PanelWindow {
|
|||||||
property int blurMax: 64
|
property int blurMax: 64
|
||||||
shadowBlur: Math.max(0, Math.min(1, osdContainer.shadowBlurPx / blurMax))
|
shadowBlur: Math.max(0, Math.min(1, osdContainer.shadowBlurPx / blurMax))
|
||||||
shadowScale: 1 + (2 * osdContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
shadowScale: 1 + (2 * osdContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
||||||
shadowColor: Qt.rgba(0, 0, 0, osdContainer.effectiveShadowAlpha)
|
shadowColor: {
|
||||||
|
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
|
||||||
|
return Theme.withAlpha(baseColor, osdContainer.effectiveShadowAlpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankRectangle {
|
DankRectangle {
|
||||||
|
|||||||
@@ -224,7 +224,10 @@ PanelWindow {
|
|||||||
property int blurMax: 64
|
property int blurMax: 64
|
||||||
shadowBlur: Math.max(0, Math.min(1, contentWrapper.shadowBlurPx / blurMax))
|
shadowBlur: Math.max(0, Math.min(1, contentWrapper.shadowBlurPx / blurMax))
|
||||||
shadowScale: 1 + (2 * contentWrapper.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
shadowScale: 1 + (2 * contentWrapper.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
|
||||||
shadowColor: Qt.rgba(0, 0, 0, contentWrapper.effectiveShadowAlpha)
|
shadowColor: {
|
||||||
|
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
|
||||||
|
return Theme.withAlpha(baseColor, contentWrapper.effectiveShadowAlpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankRectangle {
|
DankRectangle {
|
||||||
|
|||||||
Reference in New Issue
Block a user