Complete contextual desktop controls
This commit is contained in:
@@ -210,6 +210,12 @@ PanelWindow {
|
||||
DockBody {
|
||||
id: body
|
||||
|
||||
onContextMenuRequested: (anchorItem, entry) => {
|
||||
dockContextMenu.anchorItem = anchorItem;
|
||||
dockContextMenu.entry = entry;
|
||||
dockContextMenu.visible = true;
|
||||
}
|
||||
|
||||
vertical: root.vertical
|
||||
leftSide: root.position === "left"
|
||||
|
||||
@@ -260,4 +266,8 @@ PanelWindow {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DockContextMenu {
|
||||
id: dockContextMenu
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ Rectangle {
|
||||
|
||||
// The item the tooltip is currently describing, or null.
|
||||
property Item hoveredItem: null
|
||||
signal contextMenuRequested(Item anchorItem, var entry)
|
||||
|
||||
// Set by the Dock. A side dock runs the same strip down the screen instead
|
||||
// of across it.
|
||||
@@ -170,6 +171,7 @@ Rectangle {
|
||||
onEntered: root.hoveredItem = dockItem
|
||||
onExited: if (root.hoveredItem === dockItem)
|
||||
root.hoveredItem = null
|
||||
onContextMenuRequested: root.contextMenuRequested(dockItem, dockItem.entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// The dock's app menu. Desktop-entry actions stay first; the shell-owned
|
||||
// configuration route is deliberately last so it never displaces app actions.
|
||||
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.modules.bar
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property Item anchorItem: null
|
||||
property var entry: null
|
||||
|
||||
anchor.item: root.anchorItem
|
||||
anchor.edges: Edges.Top | Edges.Left
|
||||
anchor.gravity: Edges.Top | Edges.Right
|
||||
anchor.margins.bottom: 8
|
||||
|
||||
implicitWidth: Math.max(menu.implicitWidth + Theme.popoverPadding * 2, 240)
|
||||
implicitHeight: menu.implicitHeight + Theme.popoverPadding * 2
|
||||
color: "transparent"
|
||||
visible: false
|
||||
grabFocus: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.popoverRadius
|
||||
color: Theme.alpha(Theme.bgPopover, Theme.popoverAlpha)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.08)
|
||||
|
||||
PrismEdge {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 1
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
inset: parent.radius
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menu
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.popoverPadding
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
id: applicationActions
|
||||
model: root.entry ? root.entry.actions : []
|
||||
|
||||
delegate: TrayMenuRow {
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
label: modelData.name
|
||||
onActivated: {
|
||||
modelData.execute();
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
anchors.margins: 3
|
||||
visible: applicationActions.count > 0
|
||||
border.width: 0
|
||||
color: Theme.alpha(Theme.fg, 0.1)
|
||||
}
|
||||
|
||||
TrayMenuRow {
|
||||
width: parent.width
|
||||
label: "Dock settings"
|
||||
onActivated: {
|
||||
ShellState.openSettings("desktop");
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ Item {
|
||||
// Emitted so DockBody can drive the single shared tooltip.
|
||||
signal entered
|
||||
signal exited
|
||||
signal contextMenuRequested
|
||||
|
||||
// The icon may grow past the cell on hover; the cell itself stays a fixed
|
||||
// size so the row doesn't reflow.
|
||||
@@ -115,7 +116,7 @@ Item {
|
||||
id: mouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
|
||||
onEntered: root.entered()
|
||||
onExited: root.exited()
|
||||
@@ -126,6 +127,10 @@ Item {
|
||||
root.launch();
|
||||
return;
|
||||
}
|
||||
if (mev.button === Qt.RightButton) {
|
||||
root.contextMenuRequested();
|
||||
return;
|
||||
}
|
||||
if (root.running)
|
||||
root.focusNext();
|
||||
else
|
||||
|
||||
@@ -14,6 +14,7 @@ import Quickshell.Services.Notifications
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.modules.quicksettings
|
||||
import qs.modules.bar
|
||||
import qs.widgets
|
||||
|
||||
Rectangle {
|
||||
@@ -127,11 +128,44 @@ Rectangle {
|
||||
onClicked: root.dismissed()
|
||||
}
|
||||
|
||||
IconButton {
|
||||
id: settingsMenuButton
|
||||
anchors.right: closeButton.left
|
||||
anchors.rightMargin: 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 6
|
||||
size: 24
|
||||
iconSize: 14
|
||||
tint: Theme.fgDim
|
||||
icon: "view-more-symbolic"
|
||||
iconFallback: "open-menu-symbolic"
|
||||
onClicked: settingsMenu.visible = !settingsMenu.visible
|
||||
}
|
||||
|
||||
Popover {
|
||||
id: settingsMenu
|
||||
anchorItem: settingsMenuButton
|
||||
|
||||
Column {
|
||||
implicitWidth: notificationSettings.implicitWidth
|
||||
implicitHeight: notificationSettings.implicitHeight
|
||||
|
||||
TrayMenuRow {
|
||||
id: notificationSettings
|
||||
label: "Notification settings"
|
||||
onActivated: {
|
||||
ShellState.openSettings("notifications");
|
||||
settingsMenu.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: layout
|
||||
anchors.left: appIcon.visible ? appIcon.right : parent.left
|
||||
anchors.leftMargin: appIcon.visible ? 10 : 14
|
||||
anchors.right: image.visible ? image.left : closeButton.left
|
||||
anchors.right: image.visible ? image.left : settingsMenuButton.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 12
|
||||
|
||||
@@ -34,7 +34,14 @@ PanelWindow {
|
||||
implicitWidth: root.desiredWidth
|
||||
implicitHeight: 64
|
||||
color: "transparent"
|
||||
mask: Region {}
|
||||
mask: Region {
|
||||
item: inputMask
|
||||
}
|
||||
|
||||
Item {
|
||||
id: inputMask
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
WlrLayershell.namespace: "qs-popover-osd"
|
||||
WlrLayershell.layer: WlrLayer.Overlay
|
||||
@@ -145,6 +152,17 @@ PanelWindow {
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: event => {
|
||||
if (event.button !== Qt.RightButton)
|
||||
return;
|
||||
ShellState.openSettings("accessibility");
|
||||
OsdState.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
|
||||
@@ -176,33 +176,33 @@ Item {
|
||||
height: aspect > 0 ? width / aspect : parent.height
|
||||
opacity: hasContent ? 1 : 0
|
||||
|
||||
// The capture context isn't ready the instant the layer surface is
|
||||
// told to become visible — it needs the surface to actually map,
|
||||
// which takes a frame or two. Calling captureFrame() before then
|
||||
// logs "no recording context is ready" and yields nothing, so
|
||||
// retry a few times and then give up quietly (the caption and app
|
||||
// icon are still shown, so a missing thumbnail is cosmetic).
|
||||
property int captureAttempts: 0
|
||||
// A ScreencopyView cannot tell us that its recording context is
|
||||
// ready before a capture. The enclosing overview gets that
|
||||
// context only after its first rendered frame, so defer the first
|
||||
// one-shot capture to that event. Retrying before then only emits
|
||||
// "no recording context is ready" warnings; if a capture later
|
||||
// cannot produce content, the app icon remains the fallback.
|
||||
property bool recordingReady: false
|
||||
|
||||
function tryCapture(): void {
|
||||
captureAttempts = 0;
|
||||
captureRetry.restart();
|
||||
if (shot.hasContent)
|
||||
return;
|
||||
if (!shot.recordingReady) {
|
||||
frameReady.restart();
|
||||
return;
|
||||
}
|
||||
shot.captureFrame();
|
||||
}
|
||||
|
||||
// `shot`, not `parent`: Timer is a QtObject, so `parent` does not
|
||||
// resolve to the enclosing ScreencopyView.
|
||||
Timer {
|
||||
id: captureRetry
|
||||
interval: 80
|
||||
repeat: true
|
||||
FrameAnimation {
|
||||
id: frameReady
|
||||
running: false
|
||||
onTriggered: {
|
||||
if (shot.hasContent || shot.captureAttempts >= 6) {
|
||||
stop();
|
||||
running = false;
|
||||
if (shot.hasContent)
|
||||
return;
|
||||
}
|
||||
shot.captureAttempts++;
|
||||
shot.captureFrame();
|
||||
shot.recordingReady = true;
|
||||
shot.tryCapture();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user