mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-25 14:02:53 -05:00
1059 lines
44 KiB
QML
1059 lines
44 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Services.SystemTray
|
|
import Quickshell.Wayland
|
|
import Quickshell.Widgets
|
|
import qs.Common
|
|
import qs.Widgets
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property bool isVertical: axis?.isVertical ?? false
|
|
property var axis: null
|
|
property var parentWindow: null
|
|
property var parentScreen: null
|
|
property real widgetThickness: 30
|
|
property real barThickness: 48
|
|
property bool isAtBottom: false
|
|
readonly property real horizontalPadding: SettingsData.dankBarNoBackground ? 2 : Theme.spacingS
|
|
readonly property var hiddenTrayIds: {
|
|
const envValue = Quickshell.env("DMS_HIDE_TRAYIDS") || ""
|
|
return envValue ? envValue.split(",").map(id => id.trim().toLowerCase()) : []
|
|
}
|
|
readonly property var allTrayItems: {
|
|
if (!hiddenTrayIds.length) {
|
|
return SystemTray.items.values
|
|
}
|
|
return SystemTray.items.values.filter(item => {
|
|
const itemId = item?.id || ""
|
|
return !hiddenTrayIds.includes(itemId.toLowerCase())
|
|
})
|
|
}
|
|
readonly property var mainBarItems: allTrayItems.filter(item => !SessionData.isHiddenTrayId(item?.id || ""))
|
|
readonly property int calculatedSize: {
|
|
if (allTrayItems.length === 0) return 0
|
|
const itemCount = mainBarItems.length + 1
|
|
return itemCount * 24 + horizontalPadding * 2
|
|
}
|
|
readonly property real visualWidth: isVertical ? widgetThickness : calculatedSize
|
|
readonly property real visualHeight: isVertical ? calculatedSize : widgetThickness
|
|
|
|
width: isVertical ? barThickness : visualWidth
|
|
height: isVertical ? visualHeight : barThickness
|
|
visible: allTrayItems.length > 0
|
|
|
|
property bool menuOpen: false
|
|
|
|
Rectangle {
|
|
id: visualBackground
|
|
width: root.visualWidth
|
|
height: root.visualHeight
|
|
anchors.centerIn: parent
|
|
radius: SettingsData.dankBarNoBackground ? 0 : Theme.cornerRadius
|
|
color: {
|
|
if (allTrayItems.length === 0) {
|
|
return "transparent";
|
|
}
|
|
|
|
if (SettingsData.dankBarNoBackground) {
|
|
return "transparent";
|
|
}
|
|
|
|
const baseColor = Theme.widgetBaseBackgroundColor;
|
|
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: layoutLoader
|
|
anchors.centerIn: parent
|
|
sourceComponent: root.isVertical ? columnComp : rowComp
|
|
}
|
|
|
|
Component {
|
|
id: rowComp
|
|
Row {
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: root.mainBarItems
|
|
|
|
delegate: Item {
|
|
id: delegateRoot
|
|
property var trayItem: modelData
|
|
property string iconSource: {
|
|
let icon = trayItem && trayItem.icon;
|
|
if (typeof icon === 'string' || icon instanceof String) {
|
|
if (icon === "") {
|
|
return "";
|
|
}
|
|
if (icon.includes("?path=")) {
|
|
const split = icon.split("?path=");
|
|
if (split.length !== 2) {
|
|
return icon;
|
|
}
|
|
|
|
const name = split[0];
|
|
const path = split[1];
|
|
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://${icon}`;
|
|
}
|
|
return icon;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
width: 24
|
|
height: root.barThickness
|
|
|
|
Rectangle {
|
|
id: visualContent
|
|
width: 24
|
|
height: 24
|
|
anchors.centerIn: parent
|
|
radius: Theme.cornerRadius
|
|
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
|
|
IconImage {
|
|
id: iconImg
|
|
anchors.centerIn: parent
|
|
width: Theme.barIconSize(root.barThickness)
|
|
height: Theme.barIconSize(root.barThickness)
|
|
source: delegateRoot.iconSource
|
|
asynchronous: true
|
|
smooth: true
|
|
mipmap: true
|
|
visible: status === Image.Ready
|
|
}
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: !iconImg.visible
|
|
text: {
|
|
const itemId = trayItem?.id || ""
|
|
if (!itemId) {
|
|
return "?"
|
|
}
|
|
return itemId.charAt(0).toUpperCase()
|
|
}
|
|
font.pixelSize: 10
|
|
color: Theme.surfaceText
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: trayItemArea
|
|
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: (mouse) => {
|
|
if (!delegateRoot.trayItem) {
|
|
return;
|
|
}
|
|
|
|
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
|
delegateRoot.trayItem.activate();
|
|
return ;
|
|
}
|
|
if (delegateRoot.trayItem.hasMenu) {
|
|
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: 24
|
|
height: root.barThickness
|
|
|
|
Rectangle {
|
|
id: caretButton
|
|
width: 24
|
|
height: 24
|
|
anchors.centerIn: parent
|
|
radius: Theme.cornerRadius
|
|
color: caretArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: root.menuOpen ? "expand_less" : "expand_more"
|
|
size: Theme.barIconSize(root.barThickness)
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
MouseArea {
|
|
id: caretArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.menuOpen = !root.menuOpen
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: columnComp
|
|
Column {
|
|
spacing: 0
|
|
|
|
Repeater {
|
|
model: root.mainBarItems
|
|
|
|
delegate: Item {
|
|
id: delegateRoot
|
|
property var trayItem: modelData
|
|
property string iconSource: {
|
|
let icon = trayItem && trayItem.icon;
|
|
if (typeof icon === 'string' || icon instanceof String) {
|
|
if (icon === "") {
|
|
return "";
|
|
}
|
|
if (icon.includes("?path=")) {
|
|
const split = icon.split("?path=");
|
|
if (split.length !== 2) {
|
|
return icon;
|
|
}
|
|
|
|
const name = split[0];
|
|
const path = split[1];
|
|
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://${icon}`;
|
|
}
|
|
return icon;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
width: root.barThickness
|
|
height: 24
|
|
|
|
Rectangle {
|
|
id: visualContent
|
|
width: 24
|
|
height: 24
|
|
anchors.centerIn: parent
|
|
radius: Theme.cornerRadius
|
|
color: trayItemArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
|
|
IconImage {
|
|
id: iconImg
|
|
anchors.centerIn: parent
|
|
width: Theme.barIconSize(root.barThickness)
|
|
height: Theme.barIconSize(root.barThickness)
|
|
source: delegateRoot.iconSource
|
|
asynchronous: true
|
|
smooth: true
|
|
mipmap: true
|
|
visible: status === Image.Ready
|
|
}
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
visible: !iconImg.visible
|
|
text: {
|
|
const itemId = trayItem?.id || ""
|
|
if (!itemId) {
|
|
return "?"
|
|
}
|
|
return itemId.charAt(0).toUpperCase()
|
|
}
|
|
font.pixelSize: 10
|
|
color: Theme.surfaceText
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: trayItemArea
|
|
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: (mouse) => {
|
|
if (!delegateRoot.trayItem) {
|
|
return;
|
|
}
|
|
|
|
if (mouse.button === Qt.LeftButton && !delegateRoot.trayItem.onlyMenu) {
|
|
delegateRoot.trayItem.activate();
|
|
return ;
|
|
}
|
|
if (delegateRoot.trayItem.hasMenu) {
|
|
root.showForTrayItem(delegateRoot.trayItem, visualContent, parentScreen, root.isAtBottom, root.isVertical, root.axis);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: root.barThickness
|
|
height: 24
|
|
|
|
Rectangle {
|
|
id: caretButtonVert
|
|
width: 24
|
|
height: 24
|
|
anchors.centerIn: parent
|
|
radius: Theme.cornerRadius
|
|
color: caretAreaVert.containsMouse ? Theme.primaryHover : "transparent"
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: {
|
|
const edge = root.axis?.edge
|
|
if (edge === "left") {
|
|
return root.menuOpen ? "chevron_left" : "chevron_right"
|
|
} else {
|
|
return root.menuOpen ? "chevron_right" : "chevron_left"
|
|
}
|
|
}
|
|
size: Theme.barIconSize(root.barThickness)
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
MouseArea {
|
|
id: caretAreaVert
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: root.menuOpen = !root.menuOpen
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PanelWindow {
|
|
id: overflowMenu
|
|
visible: root.menuOpen
|
|
screen: root.parentScreen
|
|
WlrLayershell.layer: WlrLayershell.Top
|
|
WlrLayershell.exclusiveZone: -1
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
WlrLayershell.namespace: "dms:tray-overflow-menu"
|
|
color: "transparent"
|
|
|
|
anchors {
|
|
top: true
|
|
left: true
|
|
right: true
|
|
bottom: true
|
|
}
|
|
|
|
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
|
|
|
onVisibleChanged: {
|
|
if (visible) updatePosition()
|
|
}
|
|
|
|
function updatePosition() {
|
|
if (!root.parentWindow) {
|
|
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
|
|
return
|
|
}
|
|
|
|
const globalPos = root.mapToGlobal(0, 0)
|
|
const screenX = screen.x || 0
|
|
const screenY = screen.y || 0
|
|
const relativeX = globalPos.x - screenX
|
|
const relativeY = globalPos.y - screenY
|
|
|
|
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
|
|
const effectiveBarThickness = Math.max(widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
|
|
|
|
if (root.isVertical) {
|
|
const edge = root.axis?.edge
|
|
let targetX = edge === "left"
|
|
? effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
|
|
: screen.width - (effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance)
|
|
anchorPos = Qt.point(targetX, relativeY + root.height / 2)
|
|
} else {
|
|
let targetY = root.isAtBottom
|
|
? screen.height - (effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance)
|
|
: effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
|
|
anchorPos = Qt.point(relativeX + root.width / 2, targetY)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: menuContainer
|
|
width: 250
|
|
height: Math.min(screen.height - 100, menuColumn.implicitHeight + Theme.spacingS * 2)
|
|
|
|
x: {
|
|
if (root.isVertical) {
|
|
const edge = root.axis?.edge
|
|
if (edge === "left") {
|
|
const targetX = overflowMenu.anchorPos.x
|
|
return Math.min(overflowMenu.screen.width - width - 10, targetX)
|
|
} else {
|
|
const targetX = overflowMenu.anchorPos.x - width
|
|
return Math.max(10, targetX)
|
|
}
|
|
} else {
|
|
const left = 10
|
|
const right = overflowMenu.width - width - 10
|
|
const want = overflowMenu.anchorPos.x - width / 2
|
|
return Math.max(left, Math.min(right, want))
|
|
}
|
|
}
|
|
|
|
y: {
|
|
if (root.isVertical) {
|
|
const top = 10
|
|
const bottom = overflowMenu.height - height - 10
|
|
const want = overflowMenu.anchorPos.y - height / 2
|
|
return Math.max(top, Math.min(bottom, want))
|
|
} else {
|
|
if (root.isAtBottom) {
|
|
const targetY = overflowMenu.anchorPos.y - height
|
|
return Math.max(10, targetY)
|
|
} else {
|
|
const targetY = overflowMenu.anchorPos.y
|
|
return Math.min(overflowMenu.screen.height - height - 10, targetY)
|
|
}
|
|
}
|
|
}
|
|
|
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
radius: Theme.cornerRadius
|
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
border.width: 1
|
|
|
|
opacity: root.menuOpen ? 1 : 0
|
|
scale: root.menuOpen ? 1 : 0.85
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.topMargin: 4
|
|
anchors.leftMargin: 2
|
|
anchors.rightMargin: -2
|
|
anchors.bottomMargin: -4
|
|
radius: parent.radius
|
|
color: Qt.rgba(0, 0, 0, 0.15)
|
|
z: parent.z - 1
|
|
}
|
|
|
|
DankFlickable {
|
|
id: scrollView
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingS
|
|
contentWidth: width
|
|
contentHeight: menuColumn.implicitHeight
|
|
clip: true
|
|
|
|
Column {
|
|
id: menuColumn
|
|
width: parent.width
|
|
spacing: 2
|
|
|
|
Repeater {
|
|
model: root.allTrayItems
|
|
|
|
delegate: Rectangle {
|
|
property var trayItem: modelData
|
|
property string iconSource: {
|
|
let icon = trayItem?.icon
|
|
if (typeof icon === 'string' || icon instanceof String) {
|
|
if (icon === "") return ""
|
|
if (icon.includes("?path=")) {
|
|
const split = icon.split("?path=")
|
|
if (split.length !== 2) return icon
|
|
const name = split[0]
|
|
const path = split[1]
|
|
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://${icon}`
|
|
}
|
|
return icon
|
|
}
|
|
return ""
|
|
}
|
|
|
|
width: menuColumn.width
|
|
height: 32
|
|
radius: Theme.cornerRadius
|
|
color: itemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingS
|
|
|
|
IconImage {
|
|
id: menuIconImg
|
|
width: 20
|
|
height: 20
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
source: parent.parent.iconSource
|
|
asynchronous: true
|
|
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)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: 24
|
|
height: 24
|
|
radius: Theme.cornerRadius
|
|
color: visibilityArea.containsMouse ? Theme.primaryHover : "transparent"
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: SessionData.isHiddenTrayId(trayItem?.id || "") ? "visibility_off" : "visibility"
|
|
size: 16
|
|
color: Theme.surfaceText
|
|
}
|
|
|
|
MouseArea {
|
|
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 {
|
|
id: itemArea
|
|
anchors.fill: parent
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
z: -1
|
|
onClicked: root.menuOpen = false
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: trayMenuComponent
|
|
|
|
Rectangle {
|
|
id: menuRoot
|
|
|
|
property var trayItem: null
|
|
property var anchorItem: null
|
|
property var parentScreen: null
|
|
property bool isAtBottom: false
|
|
property bool isVertical: false
|
|
property var axis: null
|
|
property bool showMenu: false
|
|
property var menuHandle: null
|
|
|
|
ListModel { id: entryStack }
|
|
function topEntry() {
|
|
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null
|
|
}
|
|
|
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
|
trayItem = item
|
|
anchorItem = anchor
|
|
parentScreen = screen
|
|
isAtBottom = atBottom
|
|
isVertical = vertical
|
|
axis = axisObj
|
|
menuHandle = item?.menu
|
|
|
|
if (parentScreen) {
|
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
|
const s = Quickshell.screens[i]
|
|
if (s === parentScreen) {
|
|
menuWindow.screen = s
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
showMenu = true
|
|
}
|
|
|
|
function close() {
|
|
showMenu = false
|
|
}
|
|
|
|
function showSubMenu(entry) {
|
|
if (!entry || !entry.hasChildren) return;
|
|
|
|
entryStack.append({ handle: entry });
|
|
|
|
const h = entry.menu || entry;
|
|
if (h && typeof h.updateLayout === "function") h.updateLayout();
|
|
|
|
submenuHydrator.menu = h;
|
|
submenuHydrator.open();
|
|
Qt.callLater(() => submenuHydrator.close());
|
|
}
|
|
|
|
function goBack() {
|
|
if (!entryStack.count) return;
|
|
entryStack.remove(entryStack.count - 1);
|
|
}
|
|
|
|
width: 0
|
|
height: 0
|
|
color: "transparent"
|
|
|
|
PanelWindow {
|
|
|
|
WlrLayershell.namespace: "dms:tray-menu-window"
|
|
|
|
id: menuWindow
|
|
visible: menuRoot.showMenu && (menuRoot.trayItem?.hasMenu ?? false)
|
|
WlrLayershell.layer: WlrLayershell.Top
|
|
WlrLayershell.exclusiveZone: -1
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
|
color: "transparent"
|
|
|
|
anchors {
|
|
top: true
|
|
left: true
|
|
right: true
|
|
bottom: true
|
|
}
|
|
|
|
property point anchorPos: Qt.point(screen.width / 2, screen.height / 2)
|
|
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
updatePosition()
|
|
}
|
|
}
|
|
|
|
function updatePosition() {
|
|
if (!menuRoot.anchorItem || !menuRoot.trayItem) {
|
|
anchorPos = Qt.point(screen.width / 2, screen.height / 2)
|
|
return
|
|
}
|
|
|
|
const globalPos = menuRoot.anchorItem.mapToGlobal(0, 0)
|
|
const screenX = screen.x || 0
|
|
const screenY = screen.y || 0
|
|
const relativeX = globalPos.x - screenX
|
|
const relativeY = globalPos.y - screenY
|
|
|
|
const widgetThickness = Math.max(20, 26 + SettingsData.dankBarInnerPadding * 0.6)
|
|
const effectiveBarThickness = Math.max(widgetThickness + SettingsData.dankBarInnerPadding + 4, Theme.barHeight - 4 - (8 - SettingsData.dankBarInnerPadding))
|
|
|
|
if (menuRoot.isVertical) {
|
|
const edge = menuRoot.axis?.edge
|
|
let targetX
|
|
if (edge === "left") {
|
|
targetX = effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
|
|
} else {
|
|
const popupX = effectiveBarThickness + SettingsData.dankBarSpacing + Theme.popupDistance
|
|
targetX = screen.width - popupX
|
|
}
|
|
anchorPos = Qt.point(targetX, relativeY + menuRoot.anchorItem.height / 2)
|
|
} else {
|
|
let targetY
|
|
if (menuRoot.isAtBottom) {
|
|
const popupY = effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
|
|
targetY = screen.height - popupY
|
|
} else {
|
|
targetY = effectiveBarThickness + SettingsData.dankBarSpacing + SettingsData.dankBarBottomGap + Theme.popupDistance
|
|
}
|
|
anchorPos = Qt.point(relativeX + menuRoot.anchorItem.width / 2, targetY)
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: menuContainer
|
|
|
|
width: Math.min(500, Math.max(250, menuColumn.implicitWidth + Theme.spacingS * 2))
|
|
height: Math.max(40, menuColumn.implicitHeight + Theme.spacingS * 2)
|
|
|
|
x: {
|
|
if (menuRoot.isVertical) {
|
|
const edge = menuRoot.axis?.edge
|
|
if (edge === "left") {
|
|
const targetX = menuWindow.anchorPos.x
|
|
return Math.min(menuWindow.screen.width - width - 10, targetX)
|
|
} else {
|
|
const targetX = menuWindow.anchorPos.x - width
|
|
return Math.max(10, targetX)
|
|
}
|
|
} else {
|
|
const left = 10
|
|
const right = menuWindow.width - width - 10
|
|
const want = menuWindow.anchorPos.x - width / 2
|
|
return Math.max(left, Math.min(right, want))
|
|
}
|
|
}
|
|
|
|
y: {
|
|
if (menuRoot.isVertical) {
|
|
const top = 10
|
|
const bottom = menuWindow.height - height - 10
|
|
const want = menuWindow.anchorPos.y - height / 2
|
|
return Math.max(top, Math.min(bottom, want))
|
|
} else {
|
|
if (menuRoot.isAtBottom) {
|
|
const targetY = menuWindow.anchorPos.y - height
|
|
return Math.max(10, targetY)
|
|
} else {
|
|
const targetY = menuWindow.anchorPos.y
|
|
return Math.min(menuWindow.screen.height - height - 10, targetY)
|
|
}
|
|
}
|
|
}
|
|
|
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
radius: Theme.cornerRadius
|
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
border.width: 1
|
|
|
|
opacity: menuRoot.showMenu ? 1 : 0
|
|
scale: menuRoot.showMenu ? 1 : 0.85
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
anchors.topMargin: 4
|
|
anchors.leftMargin: 2
|
|
anchors.rightMargin: -2
|
|
anchors.bottomMargin: -4
|
|
radius: parent.radius
|
|
color: Qt.rgba(0, 0, 0, 0.15)
|
|
z: parent.z - 1
|
|
}
|
|
|
|
QsMenuAnchor {
|
|
id: submenuHydrator
|
|
anchor.window: menuWindow
|
|
}
|
|
|
|
QsMenuOpener {
|
|
id: rootOpener
|
|
menu: menuRoot.menuHandle
|
|
}
|
|
|
|
QsMenuOpener {
|
|
id: subOpener
|
|
menu: {
|
|
const e = menuRoot.topEntry();
|
|
return e ? (e.menu || e) : null;
|
|
}
|
|
}
|
|
|
|
|
|
Column {
|
|
id: menuColumn
|
|
|
|
width: parent.width - Theme.spacingS * 2
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: Theme.spacingS
|
|
spacing: 1
|
|
|
|
Rectangle {
|
|
visible: entryStack.count > 0
|
|
width: parent.width
|
|
height: 28
|
|
radius: Theme.cornerRadius
|
|
color: backArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXS
|
|
|
|
DankIcon {
|
|
name: "arrow_back"
|
|
size: 16
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Back")
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.surfaceText
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: backArea
|
|
anchors.fill: parent
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: menuRoot.goBack()
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
visible: entryStack.count > 0
|
|
width: parent.width
|
|
height: 1
|
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
}
|
|
|
|
Repeater {
|
|
model: entryStack.count
|
|
? (subOpener.children ? subOpener.children
|
|
: (menuRoot.topEntry()?.children || []))
|
|
: rootOpener.children
|
|
|
|
Rectangle {
|
|
property var menuEntry: modelData
|
|
|
|
width: menuColumn.width
|
|
height: menuEntry?.isSeparator ? 1 : 28
|
|
radius: menuEntry?.isSeparator ? 0 : Theme.cornerRadius
|
|
color: {
|
|
if (menuEntry?.isSeparator) {
|
|
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"
|
|
}
|
|
|
|
MouseArea {
|
|
id: itemArea
|
|
anchors.fill: parent
|
|
enabled: !menuEntry?.isSeparator && (menuEntry?.enabled !== false)
|
|
cursorShape: Qt.PointingHandCursor
|
|
|
|
onClicked: {
|
|
if (!menuEntry || menuEntry.isSeparator) return;
|
|
|
|
if (menuEntry.hasChildren) {
|
|
menuRoot.showSubMenu(menuEntry);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingS
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingS
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXS
|
|
visible: !menuEntry?.isSeparator
|
|
|
|
Rectangle {
|
|
width: 16
|
|
height: 16
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: menuEntry?.buttonType !== undefined && menuEntry.buttonType !== 0
|
|
radius: menuEntry?.buttonType === 2 ? 8 : 2
|
|
border.width: 1
|
|
border.color: Theme.outline
|
|
color: "transparent"
|
|
|
|
Rectangle {
|
|
anchors.centerIn: parent
|
|
width: parent.width - 6
|
|
height: parent.height - 6
|
|
radius: parent.radius - 3
|
|
color: Theme.primary
|
|
visible: menuEntry?.checkState === 2
|
|
}
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "check"
|
|
size: 10
|
|
color: Theme.primaryText
|
|
visible: menuEntry?.buttonType === 1 && menuEntry?.checkState === 2
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: 16
|
|
height: 16
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
visible: menuEntry?.icon && menuEntry.icon !== ""
|
|
|
|
Image {
|
|
anchors.fill: parent
|
|
source: menuEntry?.icon || ""
|
|
sourceSize.width: 16
|
|
sourceSize.height: 16
|
|
fillMode: Image.PreserveAspectFit
|
|
smooth: true
|
|
}
|
|
}
|
|
|
|
StyledText {
|
|
text: menuEntry?.text || ""
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: (menuEntry?.enabled !== false) ? Theme.surfaceText : Theme.surfaceTextMedium
|
|
elide: Text.ElideRight
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
width: Math.max(150, parent.width - 64)
|
|
wrapMode: Text.NoWrap
|
|
}
|
|
|
|
Item {
|
|
width: 16
|
|
height: 16
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
DankIcon {
|
|
anchors.centerIn: parent
|
|
name: "chevron_right"
|
|
size: 14
|
|
color: Theme.surfaceText
|
|
visible: menuEntry?.hasChildren ?? false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Theme.mediumDuration
|
|
easing.type: Theme.emphasizedEasing
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
z: -1
|
|
onClicked: menuRoot.close()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
property var currentTrayMenu: null
|
|
|
|
Connections {
|
|
target: currentTrayMenu
|
|
enabled: currentTrayMenu !== null
|
|
function onShowMenuChanged() {
|
|
if (parentWindow && typeof parentWindow.systemTrayMenuOpen !== "undefined") {
|
|
parentWindow.systemTrayMenuOpen = currentTrayMenu.showMenu
|
|
}
|
|
}
|
|
}
|
|
|
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
|
if (parentWindow && typeof parentWindow.systemTrayMenuOpen !== "undefined") {
|
|
parentWindow.systemTrayMenuOpen = true
|
|
}
|
|
|
|
if (currentTrayMenu) {
|
|
currentTrayMenu.destroy()
|
|
}
|
|
currentTrayMenu = trayMenuComponent.createObject(null)
|
|
if (currentTrayMenu) {
|
|
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj)
|
|
}
|
|
}
|
|
|
|
}
|