mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
dock: create an initial basic dock
This commit is contained in:
@@ -17,6 +17,7 @@ Singleton {
|
||||
property real topBarTransparency: 0.75
|
||||
property real topBarWidgetTransparency: 0.85
|
||||
property real popupTransparency: 0.92
|
||||
property real dockTransparency: 1.0
|
||||
property var appUsageRanking: {}
|
||||
property bool use24HourClock: true
|
||||
property bool useFahrenheit: false
|
||||
@@ -81,6 +82,9 @@ Singleton {
|
||||
property int fontWeight: Font.Normal
|
||||
property bool gtkThemingEnabled: false
|
||||
property bool qtThemingEnabled: false
|
||||
property bool showDock: false
|
||||
property var pinnedApps: []
|
||||
property bool dockAutoHide: false
|
||||
|
||||
readonly property string defaultFontFamily: "Inter Variable"
|
||||
readonly property string defaultMonoFontFamily: "Fira Code"
|
||||
@@ -137,6 +141,7 @@ Singleton {
|
||||
topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75;
|
||||
topBarWidgetTransparency = settings.topBarWidgetTransparency !== undefined ? (settings.topBarWidgetTransparency > 1 ? settings.topBarWidgetTransparency / 100 : settings.topBarWidgetTransparency) : 0.85;
|
||||
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92;
|
||||
dockTransparency = settings.dockTransparency !== undefined ? (settings.dockTransparency > 1 ? settings.dockTransparency / 100 : settings.dockTransparency) : 1.0;
|
||||
appUsageRanking = settings.appUsageRanking || {};
|
||||
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
|
||||
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
|
||||
@@ -193,6 +198,9 @@ Singleton {
|
||||
fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal;
|
||||
gtkThemingEnabled = settings.gtkThemingEnabled !== undefined ? settings.gtkThemingEnabled : false;
|
||||
qtThemingEnabled = settings.qtThemingEnabled !== undefined ? settings.qtThemingEnabled : false;
|
||||
showDock = settings.showDock !== undefined ? settings.showDock : false;
|
||||
pinnedApps = settings.pinnedApps !== undefined ? settings.pinnedApps : [];
|
||||
dockAutoHide = settings.dockAutoHide !== undefined ? settings.dockAutoHide : false;
|
||||
applyStoredTheme();
|
||||
detectAvailableIconThemes();
|
||||
detectQtTools();
|
||||
@@ -214,6 +222,7 @@ Singleton {
|
||||
"topBarTransparency": topBarTransparency,
|
||||
"topBarWidgetTransparency": topBarWidgetTransparency,
|
||||
"popupTransparency": popupTransparency,
|
||||
"dockTransparency": dockTransparency,
|
||||
"appUsageRanking": appUsageRanking,
|
||||
"use24HourClock": use24HourClock,
|
||||
"useFahrenheit": useFahrenheit,
|
||||
@@ -255,7 +264,10 @@ Singleton {
|
||||
"monoFontFamily": monoFontFamily,
|
||||
"fontWeight": fontWeight,
|
||||
"gtkThemingEnabled": gtkThemingEnabled,
|
||||
"qtThemingEnabled": qtThemingEnabled
|
||||
"qtThemingEnabled": qtThemingEnabled,
|
||||
"showDock": showDock,
|
||||
"pinnedApps": pinnedApps,
|
||||
"dockAutoHide": dockAutoHide
|
||||
}, null, 2));
|
||||
}
|
||||
|
||||
@@ -312,6 +324,11 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setDockTransparency(transparency) {
|
||||
dockTransparency = transparency;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function addAppUsage(app) {
|
||||
if (!app)
|
||||
return;
|
||||
@@ -785,6 +802,42 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setShowDock(enabled) {
|
||||
showDock = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setPinnedApps(apps) {
|
||||
pinnedApps = apps;
|
||||
pinnedAppsChanged(); // Explicitly emit the signal
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function addPinnedApp(appId) {
|
||||
if (!appId) return;
|
||||
var currentPinned = [...pinnedApps];
|
||||
if (currentPinned.indexOf(appId) === -1) {
|
||||
currentPinned.push(appId);
|
||||
setPinnedApps(currentPinned);
|
||||
}
|
||||
}
|
||||
|
||||
function removePinnedApp(appId) {
|
||||
if (!appId) return;
|
||||
var currentPinned = pinnedApps.filter(id => id !== appId);
|
||||
setPinnedApps(currentPinned);
|
||||
}
|
||||
|
||||
function isPinnedApp(appId) {
|
||||
return appId && pinnedApps.indexOf(appId) !== -1;
|
||||
}
|
||||
|
||||
|
||||
function setDockAutoHide(enabled) {
|
||||
dockAutoHide = enabled;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function _shq(s) {
|
||||
return "'" + String(s).replace(/'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
@@ -301,6 +301,9 @@ DankModal {
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
@@ -328,6 +331,9 @@ DankModal {
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData)
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
@@ -340,5 +346,185 @@ DankModal {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: contextMenu
|
||||
|
||||
property var currentApp: null
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app
|
||||
|
||||
if (!contextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay)
|
||||
contextMenu.parent = Overlay.overlay;
|
||||
|
||||
const menuWidth = 180;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
const screenWidth = Screen.width;
|
||||
const screenHeight = Screen.height;
|
||||
let finalX = x;
|
||||
let finalY = y;
|
||||
|
||||
if (x + menuWidth > screenWidth - 20)
|
||||
finalX = x - menuWidth;
|
||||
|
||||
if (y + menuHeight > screenHeight - 20)
|
||||
finalY = y - menuHeight;
|
||||
|
||||
contextMenu.x = Math.max(20, finalX);
|
||||
contextMenu.y = Math.max(20, finalY);
|
||||
open();
|
||||
}
|
||||
|
||||
width: 180
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
modal: false
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
onClosed: {
|
||||
closePolicy = Popup.CloseOnEscape;
|
||||
}
|
||||
onOpened: {
|
||||
outsideClickTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: outsideClickTimer
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
contextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside;
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: pinMouseArea.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
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) return "push_pin"
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
return Prefs.isPinnedApp(appId) ? "keep_off" : "push_pin"
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) return "Pin to Dock"
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
return Prefs.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock"
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry) return
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || ""
|
||||
if (Prefs.isPinnedApp(appId)) {
|
||||
Prefs.removePinnedApp(appId)
|
||||
} else {
|
||||
Prefs.addPinnedApp(appId)
|
||||
}
|
||||
contextMenu.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: launchMouseArea.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
|
||||
|
||||
DankIcon {
|
||||
name: "launch"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Launch"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launchMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (contextMenu.currentApp) {
|
||||
appLauncher.launchApp(contextMenu.currentApp)
|
||||
}
|
||||
contextMenu.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -353,6 +353,9 @@ PanelWindow {
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData);
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
@@ -376,6 +379,9 @@ PanelWindow {
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onItemRightClicked: function(index, modelData, mouseX, mouseY) {
|
||||
contextMenu.show(mouseX, mouseY, modelData);
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
@@ -391,4 +397,199 @@ PanelWindow {
|
||||
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: contextMenu
|
||||
|
||||
property var currentApp: null
|
||||
|
||||
function show(x, y, app) {
|
||||
currentApp = app;
|
||||
if (!contextMenu.parent && typeof Overlay !== "undefined" && Overlay.overlay)
|
||||
contextMenu.parent = Overlay.overlay;
|
||||
|
||||
const menuWidth = 180;
|
||||
const menuHeight = menuColumn.implicitHeight + Theme.spacingS * 2;
|
||||
const screenWidth = Screen.width;
|
||||
const screenHeight = Screen.height;
|
||||
let finalX = x;
|
||||
let finalY = y;
|
||||
if (x + menuWidth > screenWidth - 20)
|
||||
finalX = x - menuWidth;
|
||||
|
||||
if (y + menuHeight > screenHeight - 20)
|
||||
finalY = y - menuHeight;
|
||||
|
||||
contextMenu.x = Math.max(20, finalX);
|
||||
contextMenu.y = Math.max(20, finalY);
|
||||
open();
|
||||
}
|
||||
|
||||
width: 180
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
modal: false
|
||||
closePolicy: Popup.CloseOnEscape
|
||||
onClosed: {
|
||||
closePolicy = Popup.CloseOnEscape;
|
||||
}
|
||||
onOpened: {
|
||||
outsideClickTimer.start();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: outsideClickTimer
|
||||
|
||||
interval: 100
|
||||
onTriggered: {
|
||||
contextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside;
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: pinMouseArea.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
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
return "push_pin";
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "";
|
||||
return Prefs.isPinnedApp(appId) ? "keep_off" : "push_pin";
|
||||
}
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
return "Pin to Dock";
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "";
|
||||
return Prefs.isPinnedApp(appId) ? "Unpin from Dock" : "Pin to Dock";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!contextMenu.currentApp || !contextMenu.currentApp.desktopEntry)
|
||||
return ;
|
||||
|
||||
var appId = contextMenu.currentApp.desktopEntry.id || contextMenu.currentApp.desktopEntry.execString || "";
|
||||
if (Prefs.isPinnedApp(appId))
|
||||
Prefs.removePinnedApp(appId);
|
||||
else
|
||||
Prefs.addPinnedApp(appId);
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
height: 5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "transparent"
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: launchMouseArea.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
|
||||
|
||||
DankIcon {
|
||||
name: "launch"
|
||||
size: Theme.iconSize - 2
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Launch"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: launchMouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (contextMenu.currentApp)
|
||||
appLauncher.launchApp(contextMenu.currentApp);
|
||||
|
||||
contextMenu.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
188
Modules/Dock/Dock.qml
Normal file
188
Modules/Dock/Dock.qml
Normal file
@@ -0,0 +1,188 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: dock
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||
|
||||
property var modelData
|
||||
property var contextMenu
|
||||
property var windowsMenu
|
||||
property bool autoHide: Prefs.dockAutoHide
|
||||
property real backgroundTransparency: Prefs.dockTransparency
|
||||
|
||||
|
||||
property bool contextMenuOpen: (contextMenu && contextMenu.visible && contextMenu.screen === modelData) || (windowsMenu && windowsMenu.visible && windowsMenu.screen === modelData)
|
||||
property bool windowIsFullscreen: {
|
||||
if (!NiriService.focusedWindowId || !NiriService.niriAvailable) return false
|
||||
var focusedWindow = NiriService.windows.find(w => w.id === NiriService.focusedWindowId)
|
||||
if (!focusedWindow) return false
|
||||
var fullscreenApps = ["vlc", "mpv", "kodi", "steam", "lutris", "wine", "dosbox"]
|
||||
return fullscreenApps.some(app => focusedWindow.app_id && focusedWindow.app_id.toLowerCase().includes(app))
|
||||
}
|
||||
property bool reveal: (!autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen) && !windowIsFullscreen
|
||||
|
||||
Connections {
|
||||
target: Prefs
|
||||
function onDockTransparencyChanged() {
|
||||
dock.backgroundTransparency = Prefs.dockTransparency;
|
||||
}
|
||||
}
|
||||
|
||||
screen: modelData
|
||||
visible: Prefs.showDock
|
||||
color: "transparent"
|
||||
|
||||
anchors {
|
||||
bottom: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
|
||||
margins {
|
||||
left: 0
|
||||
right: 0
|
||||
}
|
||||
|
||||
implicitHeight: 100
|
||||
exclusiveZone: autoHide ? -1 : 65 - 16
|
||||
|
||||
mask: Region {
|
||||
item: dockMouseArea
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: dockMouseArea
|
||||
height: dock.reveal ? 65 : 12
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
implicitWidth: dock.reveal ? dockBackground.width + 32 : (dockBackground.width + 32)
|
||||
hoverEnabled: true
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Item {
|
||||
id: dockContainer
|
||||
anchors.fill: parent
|
||||
|
||||
transform: Translate {
|
||||
id: dockSlide
|
||||
y: dock.reveal ? 0 : 60
|
||||
|
||||
Behavior on y {
|
||||
NumberAnimation {
|
||||
duration: 200
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: dockBackground
|
||||
objectName: "dockBackground"
|
||||
anchors {
|
||||
top: parent.top
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
width: dockApps.implicitWidth + 12
|
||||
height: parent.height - 8
|
||||
|
||||
anchors.topMargin: 4
|
||||
anchors.bottomMargin: 1
|
||||
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, backgroundTransparency)
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
layer.enabled: true
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(Theme.surfaceTint.r, Theme.surfaceTint.g, Theme.surfaceTint.b, 0.04)
|
||||
radius: parent.radius
|
||||
}
|
||||
|
||||
DockApps {
|
||||
id: dockApps
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.topMargin: 4
|
||||
anchors.bottomMargin: 4
|
||||
|
||||
contextMenu: dock.contextMenu
|
||||
windowsMenu: dock.windowsMenu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: appTooltip
|
||||
|
||||
property var hoveredButton: {
|
||||
if (!dockApps.children[0]) return null
|
||||
var row = dockApps.children[0]
|
||||
var repeater = null
|
||||
for (var i = 0; i < row.children.length; i++) {
|
||||
var child = row.children[i]
|
||||
if (child && typeof child.count !== "undefined" && typeof child.itemAt === "function") {
|
||||
repeater = child
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!repeater || !repeater.itemAt) return null
|
||||
for (var i = 0; i < repeater.count; i++) {
|
||||
var item = repeater.itemAt(i)
|
||||
if (item && item.dockButton && item.dockButton.showTooltip) {
|
||||
return item.dockButton
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
property string tooltipText: hoveredButton ? hoveredButton.tooltipText : ""
|
||||
|
||||
visible: hoveredButton !== null && tooltipText !== ""
|
||||
width: tooltipLabel.implicitWidth + 24
|
||||
height: tooltipLabel.implicitHeight + 12
|
||||
|
||||
color: Theme.surfaceContainer
|
||||
radius: Theme.cornerRadius
|
||||
border.width: 1
|
||||
border.color: Theme.outlineMedium
|
||||
|
||||
y: -height - 8
|
||||
x: hoveredButton ? hoveredButton.mapToItem(dockContainer, hoveredButton.width/2, 0).x - width/2 : 0
|
||||
|
||||
StyledText {
|
||||
id: tooltipLabel
|
||||
anchors.centerIn: parent
|
||||
text: appTooltip.tooltipText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
289
Modules/Dock/DockAppButton.qml
Normal file
289
Modules/Dock/DockAppButton.qml
Normal file
@@ -0,0 +1,289 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var appData
|
||||
property var contextMenu: null
|
||||
property var windowsMenu: null
|
||||
property var dockApps: null
|
||||
property int index: -1
|
||||
property bool longPressing: false
|
||||
property bool dragging: false
|
||||
property point dragStartPos: Qt.point(0, 0)
|
||||
property point dragOffset: Qt.point(0, 0)
|
||||
property int targetIndex: -1
|
||||
property int originalIndex: -1
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
property bool isHovered: mouseArea.containsMouse && !dragging
|
||||
|
||||
transform: Translate {
|
||||
id: translateY
|
||||
y: 0
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: bounceAnimation
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: translateY
|
||||
property: "y"
|
||||
to: -10
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedAccel
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
target: translateY
|
||||
property: "y"
|
||||
to: -8
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
NumberAnimation {
|
||||
id: exitAnimation
|
||||
running: false
|
||||
target: translateY
|
||||
property: "y"
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
|
||||
onIsHoveredChanged: {
|
||||
if (isHovered) {
|
||||
exitAnimation.stop()
|
||||
if (!bounceAnimation.running) {
|
||||
bounceAnimation.restart()
|
||||
}
|
||||
} else {
|
||||
bounceAnimation.stop()
|
||||
exitAnimation.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3)
|
||||
border.width: 2
|
||||
border.color: Theme.primary
|
||||
visible: dragging
|
||||
z: -1
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: longPressTimer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (appData && appData.isPinned) {
|
||||
longPressing = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: -20
|
||||
hoverEnabled: true
|
||||
cursorShape: longPressing ? Qt.DragMoveCursor : Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
|
||||
|
||||
|
||||
onPressed: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton && appData && appData.isPinned) {
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
||||
longPressTimer.start()
|
||||
}
|
||||
}
|
||||
|
||||
onReleased: (mouse) => {
|
||||
longPressTimer.stop()
|
||||
if (longPressing) {
|
||||
if (dragging && targetIndex >= 0 && targetIndex !== originalIndex && dockApps) {
|
||||
dockApps.movePinnedApp(originalIndex, targetIndex)
|
||||
}
|
||||
|
||||
longPressing = false
|
||||
dragging = false
|
||||
dragOffset = Qt.point(0, 0)
|
||||
targetIndex = -1
|
||||
originalIndex = -1
|
||||
}
|
||||
}
|
||||
|
||||
onPositionChanged: (mouse) => {
|
||||
if (longPressing && !dragging) {
|
||||
var distance = Math.sqrt(Math.pow(mouse.x - dragStartPos.x, 2) + Math.pow(mouse.y - dragStartPos.y, 2))
|
||||
if (distance > 5) {
|
||||
dragging = true
|
||||
targetIndex = index
|
||||
originalIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
dragOffset = Qt.point(mouse.x - dragStartPos.x, mouse.y - dragStartPos.y)
|
||||
|
||||
if (dockApps) {
|
||||
var threshold = 40
|
||||
var newTargetIndex = targetIndex
|
||||
|
||||
if (dragOffset.x > threshold && targetIndex < dockApps.pinnedAppCount - 1) {
|
||||
newTargetIndex = targetIndex + 1
|
||||
} else if (dragOffset.x < -threshold && targetIndex > 0) {
|
||||
newTargetIndex = targetIndex - 1
|
||||
}
|
||||
|
||||
if (newTargetIndex !== targetIndex) {
|
||||
targetIndex = newTargetIndex
|
||||
dragStartPos = Qt.point(mouse.x, mouse.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: (mouse) => {
|
||||
if (!appData || longPressing) return
|
||||
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
var windowCount = appData.windows ? appData.windows.count : 0
|
||||
|
||||
if (windowCount === 0) {
|
||||
if (appData && appData.appId) {
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry) {
|
||||
Prefs.addAppUsage({
|
||||
id: appData.appId,
|
||||
name: desktopEntry.name || appData.appId,
|
||||
icon: desktopEntry.icon || "",
|
||||
exec: desktopEntry.exec || "",
|
||||
comment: desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
||||
}
|
||||
} else if (windowCount === 1) {
|
||||
var window = appData.windows.get(0)
|
||||
NiriService.focusWindow(window.id)
|
||||
} else {
|
||||
windowsMenu.showForButton(root, appData, 40)
|
||||
}
|
||||
} else if (mouse.button === Qt.MiddleButton) {
|
||||
if (appData && appData.appId) {
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry) {
|
||||
Prefs.addAppUsage({
|
||||
id: appData.appId,
|
||||
name: desktopEntry.name || appData.appId,
|
||||
icon: desktopEntry.icon || "",
|
||||
exec: desktopEntry.exec || "",
|
||||
comment: desktopEntry.comment || ""
|
||||
})
|
||||
}
|
||||
Quickshell.execDetached(["gtk-launch", appData.appId])
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
if (contextMenu) {
|
||||
contextMenu.showForButton(root, appData, 40)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property bool showTooltip: mouseArea.containsMouse && !dragging
|
||||
property string tooltipText: {
|
||||
if (!appData || !appData.appId) return ""
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
return desktopEntry && desktopEntry.name ? desktopEntry.name : appData.appId
|
||||
}
|
||||
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
width: 40
|
||||
height: 40
|
||||
anchors.centerIn: parent
|
||||
source: {
|
||||
if (!appData || !appData.appId) return ""
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry && desktopEntry.icon) {
|
||||
var iconPath = Quickshell.iconPath(desktopEntry.icon, Prefs.iconTheme === "System Default" ? "" : Prefs.iconTheme)
|
||||
return iconPath
|
||||
}
|
||||
return ""
|
||||
}
|
||||
smooth: true
|
||||
mipmap: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
implicitSize: 40
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 40
|
||||
height: 40
|
||||
anchors.centerIn: parent
|
||||
visible: !iconImg.visible
|
||||
color: Theme.surfaceLight
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.width: 1
|
||||
border.color: Theme.primarySelected
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: {
|
||||
if (!appData || !appData.appId) return "?"
|
||||
var desktopEntry = DesktopEntries.byId(appData.appId)
|
||||
if (desktopEntry && desktopEntry.name) {
|
||||
return desktopEntry.name.charAt(0).toUpperCase()
|
||||
}
|
||||
return appData.appId.charAt(0).toUpperCase()
|
||||
}
|
||||
font.pixelSize: 14
|
||||
color: Theme.primary
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -2
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: appData && appData.windows ? Math.min(appData.windows.count, 4) : 0
|
||||
|
||||
Rectangle {
|
||||
width: appData && appData.windows && appData.windows.count <= 3 ? 5 : 3
|
||||
height: 2
|
||||
radius: 1
|
||||
color: {
|
||||
if (!appData || !appData.windows || appData.windows.count === 0) return "transparent"
|
||||
var window = appData.windows.get(index)
|
||||
return window && window.id == NiriService.focusedWindowId ? Theme.primary :
|
||||
Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Modules/Dock/DockApps.qml
Normal file
164
Modules/Dock/DockApps.qml
Normal file
@@ -0,0 +1,164 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var contextMenu: null
|
||||
property var windowsMenu: null
|
||||
property bool requestDockShow: false
|
||||
property int pinnedAppCount: 0
|
||||
|
||||
implicitWidth: row.width
|
||||
implicitHeight: row.height
|
||||
|
||||
function movePinnedApp(fromIndex, toIndex) {
|
||||
if (fromIndex === toIndex) return
|
||||
|
||||
var currentPinned = [...Prefs.pinnedApps]
|
||||
if (fromIndex < 0 || fromIndex >= currentPinned.length || toIndex < 0 || toIndex >= currentPinned.length) return
|
||||
|
||||
var movedApp = currentPinned.splice(fromIndex, 1)[0]
|
||||
currentPinned.splice(toIndex, 0, movedApp)
|
||||
|
||||
Prefs.setPinnedApps(currentPinned)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
spacing: 2
|
||||
anchors.centerIn: parent
|
||||
height: 40
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
model: ListModel {
|
||||
id: dockModel
|
||||
|
||||
Component.onCompleted: updateModel()
|
||||
|
||||
function updateModel() {
|
||||
clear()
|
||||
|
||||
var items = []
|
||||
var runningApps = NiriService.getRunningAppIds()
|
||||
var pinnedApps = [...Prefs.pinnedApps]
|
||||
var addedApps = new Set()
|
||||
|
||||
pinnedApps.forEach(appId => {
|
||||
var lowerAppId = appId.toLowerCase()
|
||||
if (!addedApps.has(lowerAppId)) {
|
||||
var windows = NiriService.getWindowsByAppId(appId)
|
||||
items.push({
|
||||
appId: appId,
|
||||
windows: windows,
|
||||
isPinned: true,
|
||||
isRunning: windows.length > 0
|
||||
})
|
||||
addedApps.add(lowerAppId)
|
||||
}
|
||||
})
|
||||
root.pinnedAppCount = pinnedApps.length
|
||||
var unpinnedApps = []
|
||||
runningApps.forEach(appId => {
|
||||
var lowerAppId = appId.toLowerCase()
|
||||
if (!addedApps.has(lowerAppId)) {
|
||||
unpinnedApps.push(appId)
|
||||
addedApps.add(lowerAppId)
|
||||
}
|
||||
})
|
||||
var appUsageRanking = Prefs.appUsageRanking || {}
|
||||
var rankedApps = []
|
||||
|
||||
for (var appId in appUsageRanking) {
|
||||
var lowerAppId = appId.toLowerCase()
|
||||
if (!addedApps.has(lowerAppId)) {
|
||||
rankedApps.push({
|
||||
appId: appId,
|
||||
lastUsed: appUsageRanking[appId].lastUsed || 0,
|
||||
usageCount: appUsageRanking[appId].usageCount || 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rankedApps.sort((a, b) => b.lastUsed - a.lastUsed)
|
||||
|
||||
var recentToAdd = Math.min(3, rankedApps.length)
|
||||
for (var i = 0; i < recentToAdd; i++) {
|
||||
var appId = rankedApps[i].appId
|
||||
var lowerAppId = appId.toLowerCase()
|
||||
unpinnedApps.push(appId)
|
||||
addedApps.add(lowerAppId)
|
||||
}
|
||||
if (pinnedApps.length > 0 && unpinnedApps.length > 0) {
|
||||
items.push({
|
||||
appId: "__SEPARATOR__",
|
||||
windows: [],
|
||||
isPinned: false,
|
||||
isRunning: false
|
||||
})
|
||||
}
|
||||
unpinnedApps.forEach(appId => {
|
||||
var windows = NiriService.getWindowsByAppId(appId)
|
||||
items.push({
|
||||
appId: appId,
|
||||
windows: windows,
|
||||
isPinned: false,
|
||||
isRunning: windows.length > 0
|
||||
})
|
||||
})
|
||||
items.forEach(item => {
|
||||
append(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: delegateItem
|
||||
property alias dockButton: button
|
||||
|
||||
width: model.appId === "__SEPARATOR__" ? 16 : 40
|
||||
height: 40
|
||||
|
||||
Rectangle {
|
||||
visible: model.appId === "__SEPARATOR__"
|
||||
width: 2
|
||||
height: 20
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
radius: 1
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
DockAppButton {
|
||||
id: button
|
||||
visible: model.appId !== "__SEPARATOR__"
|
||||
anchors.centerIn: parent
|
||||
|
||||
width: 40
|
||||
height: 40
|
||||
|
||||
appData: model
|
||||
contextMenu: root.contextMenu
|
||||
windowsMenu: root.windowsMenu
|
||||
dockApps: root
|
||||
index: model.index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: NiriService
|
||||
function onWindowsChanged() { dockModel.updateModel() }
|
||||
function onWindowOpenedOrChanged() { dockModel.updateModel() }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Prefs
|
||||
function onPinnedAppsChanged() { dockModel.updateModel() }
|
||||
}
|
||||
}
|
||||
283
Modules/Dock/DockContextMenu.qml
Normal file
283
Modules/Dock/DockContextMenu.qml
Normal file
@@ -0,0 +1,283 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool showContextMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showContextMenu = true
|
||||
}
|
||||
function close() { showContextMenu = false }
|
||||
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showContextMenu
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
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 - 100)
|
||||
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible) updatePosition()
|
||||
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
var dockWindow = anchorItem.Window.window
|
||||
if (!dockWindow) {
|
||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
||||
|
||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
||||
|
||||
function findDockBackground(item) {
|
||||
if (item.objectName === "dockBackground") {
|
||||
return item
|
||||
}
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var found = findDockBackground(item.children[i])
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var dockBackground = findDockBackground(dockWindow.contentItem)
|
||||
if (dockBackground) {
|
||||
actualDockHeight = dockBackground.height
|
||||
}
|
||||
|
||||
var dockBottomMargin = 16 // The dock has bottom margin
|
||||
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
||||
|
||||
var dockContentWidth = dockWindow.width
|
||||
var screenWidth = root.screen.width
|
||||
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
||||
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width/2
|
||||
|
||||
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
|
||||
width: Math.min(400, Math.max(200, menuColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, menuColumn.implicitHeight + Theme.spacingS * 2)
|
||||
|
||||
x: {
|
||||
var left = 10
|
||||
var right = root.width - width - 10
|
||||
var want = root.anchorPos.x - width/2
|
||||
return Math.max(left, Math.min(right, want))
|
||||
}
|
||||
y: Math.max(10, root.anchorPos.y - height + 30)
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
opacity: showContextMenu ? 1 : 0
|
||||
scale: showContextMenu ? 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
|
||||
}
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: pinArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.appData && root.appData.isPinned ? "Unpin from Dock" : "Pin to Dock"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!root.appData) return
|
||||
if (root.appData.isPinned) {
|
||||
Prefs.removePinnedApp(root.appData.appId)
|
||||
} else {
|
||||
Prefs.addPinnedApp(root.appData.appId)
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 0
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.appData && root.appData.windows ? root.appData.windows : null
|
||||
|
||||
Rectangle {
|
||||
required property var model
|
||||
width: menuColumn.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: model.title || "Untitled Window"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: windowArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NiriService.focusWindow(model.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 1
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
visible: root.appData && root.appData.windows && root.appData.windows.count > 1
|
||||
width: parent.width
|
||||
height: 28
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: closeAllArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Close All Windows"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: closeAllArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeAllArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (!root.appData || !root.appData.windows) return
|
||||
for (var i = 0; i < root.appData.windows.count; i++) {
|
||||
var window = root.appData.windows.get(i)
|
||||
NiriService.closeWindow(window.id)
|
||||
}
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
200
Modules/Dock/DockWindowsMenu.qml
Normal file
200
Modules/Dock/DockWindowsMenu.qml
Normal file
@@ -0,0 +1,200 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool showWindowsMenu: false
|
||||
property var appData: null
|
||||
property var anchorItem: null
|
||||
property real dockVisibleHeight: 40
|
||||
property int margin: 10
|
||||
|
||||
function showForButton(button, data, dockHeight) {
|
||||
anchorItem = button
|
||||
appData = data
|
||||
dockVisibleHeight = dockHeight || 40
|
||||
|
||||
var dockWindow = button.Window.window
|
||||
if (dockWindow) {
|
||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||
var s = Quickshell.screens[i]
|
||||
if (dockWindow.x >= s.x && dockWindow.x < s.x + s.width) {
|
||||
root.screen = s
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showWindowsMenu = true
|
||||
}
|
||||
|
||||
function close() { showWindowsMenu = false }
|
||||
|
||||
screen: Quickshell.screens[0]
|
||||
|
||||
visible: showWindowsMenu
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
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 - 100)
|
||||
|
||||
onAnchorItemChanged: updatePosition()
|
||||
onVisibleChanged: if (visible) updatePosition()
|
||||
|
||||
function updatePosition() {
|
||||
if (!anchorItem) {
|
||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
var dockWindow = anchorItem.Window.window
|
||||
if (!dockWindow) {
|
||||
anchorPos = Qt.point(screen.width/2, screen.height - 100)
|
||||
return
|
||||
}
|
||||
|
||||
var buttonPosInDock = anchorItem.mapToItem(dockWindow.contentItem, 0, 0)
|
||||
|
||||
var actualDockHeight = root.dockVisibleHeight // fallback
|
||||
|
||||
function findDockBackground(item) {
|
||||
if (item.objectName === "dockBackground") {
|
||||
return item
|
||||
}
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var found = findDockBackground(item.children[i])
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var dockBackground = findDockBackground(dockWindow.contentItem)
|
||||
if (dockBackground) {
|
||||
actualDockHeight = dockBackground.height
|
||||
}
|
||||
|
||||
var dockBottomMargin = 16 // The dock has bottom margin
|
||||
var buttonScreenY = root.screen.height - actualDockHeight - dockBottomMargin - 20
|
||||
|
||||
var dockContentWidth = dockWindow.width
|
||||
var screenWidth = root.screen.width
|
||||
var dockLeftMargin = Math.round((screenWidth - dockContentWidth) / 2)
|
||||
var buttonScreenX = dockLeftMargin + buttonPosInDock.x + anchorItem.width/2
|
||||
|
||||
anchorPos = Qt.point(buttonScreenX, buttonScreenY)
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: menuContainer
|
||||
|
||||
width: Math.min(600, Math.max(250, windowColumn.implicitWidth + Theme.spacingS * 2))
|
||||
height: Math.max(60, windowColumn.implicitHeight + Theme.spacingS * 2)
|
||||
|
||||
x: {
|
||||
var left = 10
|
||||
var right = root.width - width - 10
|
||||
var want = root.anchorPos.x - width/2
|
||||
return Math.max(left, Math.min(right, want))
|
||||
}
|
||||
y: Math.max(10, root.anchorPos.y - height + 30)
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
opacity: showWindowsMenu ? 1 : 0
|
||||
scale: showWindowsMenu ? 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
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
id: windowColumn
|
||||
width: parent.width - Theme.spacingS * 2
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
spacing: 1
|
||||
|
||||
Repeater {
|
||||
model: root.appData && root.appData.windows ? root.appData.windows : null
|
||||
|
||||
Rectangle {
|
||||
required property var model
|
||||
width: windowColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: windowArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: model.title || "Untitled Window"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: model.is_focused ? Theme.primary : Theme.surfaceText
|
||||
font.weight: model.is_focused ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: windowArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
NiriService.focusWindow(model.id)
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
hoverEnabled: false
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
onClicked: {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,107 @@ ScrollView {
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: dockSection.implicitHeight + Theme.spacingL * 2
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: 1
|
||||
|
||||
Column {
|
||||
id: dockSection
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "dock_to_bottom"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "Dock"
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Show Dock"
|
||||
description: "Display a dock at the bottom of the screen with pinned and running applications"
|
||||
checked: Prefs.showDock
|
||||
onToggled: (checked) => {
|
||||
Prefs.setShowDock(checked)
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
width: parent.width
|
||||
text: "Auto-hide Dock"
|
||||
description: "Hide the dock when not in use and reveal it when hovering near the bottom of the screen"
|
||||
checked: Prefs.dockAutoHide
|
||||
visible: Prefs.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
onToggled: (checked) => {
|
||||
Prefs.setDockAutoHide(checked)
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
visible: Prefs.showDock
|
||||
opacity: visible ? 1 : 0
|
||||
|
||||
StyledText {
|
||||
text: "Dock Transparency"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
DankSlider {
|
||||
width: parent.width
|
||||
height: 24
|
||||
value: Math.round(Prefs.dockTransparency * 100)
|
||||
minimum: 0
|
||||
maximum: 100
|
||||
unit: ""
|
||||
showValue: true
|
||||
onSliderValueChanged: (newValue) => {
|
||||
Prefs.setDockTransparency(newValue / 100);
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.mediumDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
|
||||
|
||||
@@ -50,6 +50,16 @@ PanelWindow {
|
||||
|
||||
target: Prefs
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.screen
|
||||
function onGeometryChanged() {
|
||||
// Re-layout center widgets when screen geometry changes
|
||||
if (centerSection && centerSection.width > 0) {
|
||||
Qt.callLater(centerSection.updateLayout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: notificationHistory
|
||||
@@ -262,6 +272,12 @@ PanelWindow {
|
||||
property real spacing: Theme.spacingS
|
||||
|
||||
function updateLayout() {
|
||||
// Defer layout if dimensions are invalid
|
||||
if (width <= 0 || height <= 0 || !visible) {
|
||||
Qt.callLater(updateLayout);
|
||||
return;
|
||||
}
|
||||
|
||||
centerWidgets = [];
|
||||
totalWidgets = 0;
|
||||
totalWidth = 0;
|
||||
@@ -280,7 +296,7 @@ PanelWindow {
|
||||
}
|
||||
|
||||
function positionWidgets() {
|
||||
if (totalWidgets === 0)
|
||||
if (totalWidgets === 0 || width <= 0)
|
||||
return ;
|
||||
|
||||
let parentCenterX = width / 2;
|
||||
@@ -335,6 +351,18 @@ PanelWindow {
|
||||
Qt.callLater(updateLayout);
|
||||
});
|
||||
}
|
||||
|
||||
onWidthChanged: {
|
||||
if (width > 0) {
|
||||
Qt.callLater(updateLayout);
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible && width > 0) {
|
||||
Qt.callLater(updateLayout);
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: centerRepeater
|
||||
|
||||
@@ -343,4 +343,35 @@ Singleton {
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
function focusWindow(windowId) {
|
||||
if (!niriAvailable) return false
|
||||
|
||||
console.log("NiriService: Focusing window with command:", ["niri", "msg", "action", "focus-window", "--id", windowId.toString()])
|
||||
Quickshell.execDetached(["niri", "msg", "action", "focus-window", "--id", windowId.toString()])
|
||||
return true
|
||||
}
|
||||
|
||||
function closeWindow(windowId) {
|
||||
if (!niriAvailable) return false
|
||||
|
||||
console.log("NiriService: Closing window with command:", ["niri", "msg", "action", "close-window", "--id", windowId.toString()])
|
||||
Quickshell.execDetached(["niri", "msg", "action", "close-window", "--id", windowId.toString()])
|
||||
return true
|
||||
}
|
||||
|
||||
function getWindowsByAppId(appId) {
|
||||
if (!appId) return []
|
||||
return windows.filter(w => w.app_id && w.app_id.toLowerCase() === appId.toLowerCase())
|
||||
}
|
||||
|
||||
function getRunningAppIds() {
|
||||
var appIds = new Set()
|
||||
windows.forEach(w => {
|
||||
if (w.app_id) {
|
||||
appIds.add(w.app_id.toLowerCase())
|
||||
}
|
||||
})
|
||||
return Array.from(appIds)
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ GridView {
|
||||
signal keyboardNavigationReset()
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemHovered(int index)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= gridView.count)
|
||||
@@ -148,6 +149,7 @@ GridView {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection && !keyboardNavigationActive)
|
||||
@@ -158,8 +160,13 @@ GridView {
|
||||
onPositionChanged: {
|
||||
keyboardNavigationReset();
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
itemClicked(index, model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var globalPos = mapToGlobal(mouse.x, mouse.y);
|
||||
itemRightClicked(index, model, globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ ListView {
|
||||
signal keyboardNavigationReset()
|
||||
signal itemClicked(int index, var modelData)
|
||||
signal itemHovered(int index)
|
||||
signal itemRightClicked(int index, var modelData, real mouseX, real mouseY)
|
||||
|
||||
function ensureVisible(index) {
|
||||
if (index < 0 || index >= count)
|
||||
@@ -147,6 +148,7 @@ ListView {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection && !keyboardNavigationActive)
|
||||
@@ -157,8 +159,13 @@ ListView {
|
||||
onPositionChanged: {
|
||||
keyboardNavigationReset();
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
onClicked: (mouse) => {
|
||||
if (mouse.button === Qt.LeftButton) {
|
||||
itemClicked(index, model);
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
var globalPos = mapToGlobal(mouse.x, mouse.y);
|
||||
itemRightClicked(index, model, globalPos.x, globalPos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
shell.qml
20
shell.qml
@@ -15,6 +15,7 @@ import qs.Modules.Notifications.Popup
|
||||
import qs.Modules.ProcessList
|
||||
import qs.Modules.Settings
|
||||
import qs.Modules.TopBar
|
||||
import qs.Modules.Dock
|
||||
import qs.Services
|
||||
|
||||
ShellRoot {
|
||||
@@ -38,6 +39,17 @@ ShellRoot {
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Dock {
|
||||
modelData: item
|
||||
contextMenu: dockContextMenu
|
||||
windowsMenu: dockWindowsMenu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
CentcomPopout {
|
||||
id: centcomPopout
|
||||
}
|
||||
@@ -46,6 +58,14 @@ ShellRoot {
|
||||
id: systemTrayContextMenu
|
||||
}
|
||||
|
||||
DockContextMenu {
|
||||
id: dockContextMenu
|
||||
}
|
||||
|
||||
DockWindowsMenu {
|
||||
id: dockWindowsMenu
|
||||
}
|
||||
|
||||
NotificationCenterPopout {
|
||||
id: notificationCenter
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user