mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge branch 'main' into file-dialog-builtin
This commit is contained in:
@@ -1,22 +1,25 @@
|
||||
palette = 0={{colors.shadow.default.hex}}
|
||||
palette = 1={{colors.red.default.hex}}
|
||||
palette = 2={{colors.green_source.default.hex}}
|
||||
palette = 3={{colors.yellow_source.default.hex}}
|
||||
palette = 4={{colors.primary.default.hex}}
|
||||
palette = 5={{colors.purple_source.default.hex}}
|
||||
palette = 6={{colors.blue_source.default.hex}}
|
||||
palette = 7=#f0f0f0
|
||||
palette = 8={{colors.outline.default.hex}}
|
||||
palette = 9={{colors.red_value.default.hex}}
|
||||
palette = 10={{colors.green_value.default.hex}}
|
||||
palette = 11={{colors.yellow_value.default.hex}}
|
||||
palette = 12={{colors.blue_value.default.hex}}
|
||||
palette = 13={{colors.purple_value.default.hex}}
|
||||
palette = 14={{colors.blue_value.default.hex}}
|
||||
palette = 15={{colors.primary.default.hex}}
|
||||
palette = 0= {{colors.shadow.default.hex}}
|
||||
palette = 1= {{colors.error.default.hex}}
|
||||
palette = 2= {{colors.tertiary.default.hex}}
|
||||
palette = 3= {{colors.secondary.default.hex}}
|
||||
palette = 4= {{colors.primary.default.hex}}
|
||||
palette = 5= {{colors.primary.default.hex}}
|
||||
palette = 6= {{colors.secondary.default.hex}}
|
||||
palette = 7= {{colors.on_background.default.hex}}
|
||||
palette = 8= {{colors.outline.default.hex}}
|
||||
palette = 9= {{colors.secondary_fixed_dim.default.hex}}
|
||||
palette = 10= {{colors.tertiary_container.default.hex}}
|
||||
palette = 11= {{colors.surface_container.default.hex}}
|
||||
palette = 12= {{colors.primary_container.default.hex}}
|
||||
palette = 13= {{colors.on_primary_container.default.hex}}
|
||||
palette = 14= {{colors.surface_variant.default.hex}}
|
||||
palette = 15= {{colors.primary.default.hex}}
|
||||
|
||||
foreground={{colors.on_surface.default.hex}}
|
||||
background={{colors.surface.default.hex}}
|
||||
|
||||
cursor-color = {{colors.on_surface.default.hex}}
|
||||
cursor-text = {{colors.on_surface.default.hex}}
|
||||
|
||||
selection-background = {{colors.on_secondary.default.hex}}
|
||||
selection-foreground = {{colors.secondary_fixed_dim.default.hex}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"settingsVersion": 3,
|
||||
"settingsVersion": 4,
|
||||
"bar": {
|
||||
"position": "top",
|
||||
"backgroundOpacity": 1,
|
||||
@@ -116,7 +116,8 @@
|
||||
"exclusive": false,
|
||||
"backgroundOpacity": 1,
|
||||
"floatingRatio": 1,
|
||||
"monitors": []
|
||||
"monitors": [],
|
||||
"pinnedApps": []
|
||||
},
|
||||
"network": {
|
||||
"wifiEnabled": true,
|
||||
@@ -125,6 +126,7 @@
|
||||
"notifications": {
|
||||
"doNotDisturb": false,
|
||||
"monitors": [],
|
||||
"location": "top_right",
|
||||
"lastSeenTs": 0,
|
||||
"lowUrgencyDuration": 3,
|
||||
"normalUrgencyDuration": 8,
|
||||
@@ -149,7 +151,7 @@
|
||||
},
|
||||
"colorSchemes": {
|
||||
"useWallpaperColors": false,
|
||||
"predefinedScheme": "",
|
||||
"predefinedScheme": "Noctalia (default)",
|
||||
"darkMode": true
|
||||
},
|
||||
"matugen": {
|
||||
|
||||
@@ -113,7 +113,7 @@ Singleton {
|
||||
JsonAdapter {
|
||||
id: adapter
|
||||
|
||||
property int settingsVersion: 3
|
||||
property int settingsVersion: 4
|
||||
|
||||
// bar
|
||||
property JsonObject bar: JsonObject {
|
||||
@@ -233,6 +233,8 @@ Singleton {
|
||||
property real backgroundOpacity: 1.0
|
||||
property real floatingRatio: 1.0
|
||||
property list<string> monitors: []
|
||||
// Desktop entry IDs pinned to the dock (e.g., "org.kde.konsole", "firefox.desktop")
|
||||
property list<string> pinnedApps: []
|
||||
}
|
||||
|
||||
// network
|
||||
@@ -245,9 +247,8 @@ Singleton {
|
||||
property JsonObject notifications: JsonObject {
|
||||
property bool doNotDisturb: false
|
||||
property list<string> monitors: []
|
||||
// Last time the user opened the notification history (ms since e899999999999998poch)
|
||||
property string location: "top_right"
|
||||
property real lastSeenTs: 0
|
||||
// Duration settings for different urgency levels (in seconds)
|
||||
property int lowUrgencyDuration: 3
|
||||
property int normalUrgencyDuration: 8
|
||||
property int criticalUrgencyDuration: 15
|
||||
|
||||
@@ -31,8 +31,10 @@ NIconButton {
|
||||
|
||||
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
|
||||
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo !== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
readonly property string customIconPath: widgetSettings.customIconPath || ""
|
||||
|
||||
icon: useDistroLogo ? "" : customIcon
|
||||
// If we have a custom path or distro logo, don't use the theme icon.
|
||||
icon: (customIconPath === "" && !useDistroLogo) ? customIcon : ""
|
||||
tooltipText: "Open side panel"
|
||||
baseSize: Style.capsuleHeight
|
||||
compact: (Settings.data.bar.density === "compact")
|
||||
@@ -45,12 +47,16 @@ NIconButton {
|
||||
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle()
|
||||
|
||||
IconImage {
|
||||
id: logo
|
||||
id: customOrDistroLogo
|
||||
anchors.centerIn: parent
|
||||
width: root.width * 0.8
|
||||
height: width
|
||||
source: useDistroLogo ? DistroLogoService.osLogo : ""
|
||||
visible: useDistroLogo && source !== ""
|
||||
source: {
|
||||
if (customIconPath !== "") return customIconPath;
|
||||
if (useDistroLogo) return DistroLogoService.osLogo;
|
||||
return "";
|
||||
}
|
||||
visible: source !== ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ Rectangle {
|
||||
columnSpacing: Style.marginXXS * scaling
|
||||
|
||||
NText {
|
||||
text: isVertical ? `${SystemStatService.cpuTemp}°` : `${SystemStatService.cpuTemp}°C`
|
||||
text: `${SystemStatService.cpuTemp}°C`
|
||||
font.family: Settings.data.ui.fontFixed
|
||||
font.pointSize: textSize
|
||||
font.weight: Style.fontWeightMedium
|
||||
@@ -128,6 +128,7 @@ Rectangle {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Color.mPrimary
|
||||
scale: isVertical ? Math.min(1.0, cpuTempContent.width / implicitWidth) : 1.0
|
||||
Layout.row: isVertical ? 0 : 0
|
||||
Layout.column: isVertical ? 0 : 1
|
||||
}
|
||||
|
||||
+182
-16
@@ -13,6 +13,7 @@ Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Item {
|
||||
id: root
|
||||
required property ShellScreen modelData
|
||||
property real scaling: ScalingService.getScreenScale(modelData)
|
||||
|
||||
@@ -25,6 +26,45 @@ Variants {
|
||||
}
|
||||
}
|
||||
|
||||
// Update dock apps when toplevels change
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onValuesChanged() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Also listen to model changes (for ObjectModel)
|
||||
Connections {
|
||||
target: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
function onCountChanged() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Update dock apps when pinned apps change
|
||||
Connections {
|
||||
target: Settings.data.dock
|
||||
function onPinnedAppsChanged() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update when component is ready
|
||||
Component.onCompleted: {
|
||||
if (Settings.isLoaded && ToplevelManager) {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Update when Settings are loaded
|
||||
Connections {
|
||||
target: Settings
|
||||
function onSettingsLoaded() {
|
||||
updateDockApps()
|
||||
}
|
||||
}
|
||||
|
||||
// Shared properties between peek and dock windows
|
||||
readonly property bool autoHide: Settings.data.dock.autoHide
|
||||
readonly property int hideDelay: 500
|
||||
@@ -43,12 +83,72 @@ Variants {
|
||||
// Shared state between windows
|
||||
property bool dockHovered: false
|
||||
property bool anyAppHovered: false
|
||||
property bool menuHovered: false
|
||||
property bool hidden: autoHide
|
||||
property bool peekHovered: false
|
||||
|
||||
// Separate property to control Loader - stays true during animations
|
||||
property bool dockLoaded: !autoHide // Start loaded if autoHide is off
|
||||
|
||||
// Track the currently open context menu
|
||||
property var currentContextMenu: null
|
||||
|
||||
// Combined model of running apps and pinned apps
|
||||
property var dockApps: []
|
||||
|
||||
// Function to close any open context menu
|
||||
function closeAllContextMenus() {
|
||||
if (currentContextMenu && currentContextMenu.visible) {
|
||||
currentContextMenu.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update the combined dock apps model
|
||||
function updateDockApps() {
|
||||
const runningApps = ToplevelManager ? (ToplevelManager.toplevels.values || []) : []
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
const combined = []
|
||||
|
||||
// First, add pinned apps (both running and non-running) in their pinned order
|
||||
pinnedApps.forEach(pinnedAppId => {
|
||||
const runningApp = runningApps.find(toplevel => toplevel && toplevel.appId === pinnedAppId)
|
||||
if (runningApp) {
|
||||
// Pinned app that is currently running
|
||||
combined.push({
|
||||
"type": "pinned-running",
|
||||
"toplevel": runningApp,
|
||||
"appId": runningApp.appId,
|
||||
"title": runningApp.title
|
||||
})
|
||||
} else {
|
||||
// Pinned app that is not running
|
||||
combined.push({
|
||||
"type": "pinned",
|
||||
"toplevel": null,
|
||||
"appId": pinnedAppId,
|
||||
"title": pinnedAppId
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Then, add running apps that are not pinned
|
||||
runningApps.forEach(toplevel => {
|
||||
if (toplevel && toplevel.appId) {
|
||||
const isPinned = pinnedApps.includes(toplevel.appId)
|
||||
if (!isPinned) {
|
||||
combined.push({
|
||||
"type": "running",
|
||||
"toplevel": toplevel,
|
||||
"appId": toplevel.appId,
|
||||
"title": toplevel.title
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
dockApps = combined
|
||||
}
|
||||
|
||||
// Timer to unload dock after hide animation completes
|
||||
Timer {
|
||||
id: unloadTimer
|
||||
@@ -65,7 +165,7 @@ Variants {
|
||||
id: hideTimer
|
||||
interval: hideDelay
|
||||
onTriggered: {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered) {
|
||||
if (autoHide && !dockHovered && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hidden = true
|
||||
unloadTimer.restart() // Start unload timer when hiding
|
||||
}
|
||||
@@ -137,7 +237,7 @@ Variants {
|
||||
|
||||
onExited: {
|
||||
peekHovered = false
|
||||
if (!hidden && !dockHovered && !anyAppHovered) {
|
||||
if (!hidden && !dockHovered && !anyAppHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
@@ -147,7 +247,7 @@ Variants {
|
||||
|
||||
// DOCK WINDOW
|
||||
Loader {
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (ToplevelManager.toplevels.values.length > 0)
|
||||
active: Settings.isLoaded && modelData && Settings.data.dock.monitors.includes(modelData.name) && dockLoaded && ToplevelManager && (dockApps.length > 0)
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
id: dockWindow
|
||||
@@ -235,10 +335,15 @@ Variants {
|
||||
|
||||
onExited: {
|
||||
dockHovered = false
|
||||
if (autoHide && !anyAppHovered && !peekHovered) {
|
||||
if (autoHide && !anyAppHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
// Close any open context menu when clicking on the dock background
|
||||
closeAllContextMenus()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
@@ -247,10 +352,10 @@ Variants {
|
||||
height: parent.height - (Style.marginM * 2 * scaling)
|
||||
anchors.centerIn: parent
|
||||
|
||||
function getAppIcon(toplevel: Toplevel): string {
|
||||
if (!toplevel)
|
||||
function getAppIcon(appData): string {
|
||||
if (!appData || !appData.appId)
|
||||
return ""
|
||||
return AppIcons.iconForAppId(toplevel.appId?.toLowerCase())
|
||||
return AppIcons.iconForAppId(appData.appId?.toLowerCase())
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -260,7 +365,7 @@ Variants {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Repeater {
|
||||
model: ToplevelManager ? ToplevelManager.toplevels : null
|
||||
model: dockApps
|
||||
|
||||
delegate: Item {
|
||||
id: appButton
|
||||
@@ -268,10 +373,19 @@ Variants {
|
||||
Layout.preferredHeight: iconSize
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
|
||||
property bool isActive: ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData
|
||||
property bool isActive: modelData.toplevel && ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === modelData.toplevel
|
||||
property bool hovered: appMouseArea.containsMouse
|
||||
property string appId: modelData ? modelData.appId : ""
|
||||
property string appTitle: modelData ? modelData.title : ""
|
||||
property string appTitle: modelData ? (modelData.title || modelData.appId) : ""
|
||||
property bool isRunning: modelData && (modelData.type === "running" || modelData.type === "pinned-running")
|
||||
|
||||
// Listen for the toplevel being closed
|
||||
Connections {
|
||||
target: modelData?.toplevel
|
||||
function onClosed() {
|
||||
Qt.callLater(root.updateDockApps)
|
||||
}
|
||||
}
|
||||
|
||||
// Individual tooltip for this app
|
||||
NTooltip {
|
||||
@@ -296,6 +410,9 @@ Variants {
|
||||
fillMode: Image.PreserveAspectFit
|
||||
cache: true
|
||||
|
||||
// Dim pinned apps that aren't running
|
||||
opacity: appButton.isRunning ? 1.0 : 0.6
|
||||
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
@@ -305,6 +422,13 @@ Variants {
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back if no icon
|
||||
@@ -314,6 +438,7 @@ Variants {
|
||||
icon: "question-mark"
|
||||
font.pointSize: iconSize * 0.7
|
||||
color: appButton.isActive ? Color.mPrimary : Color.mOnSurfaceVariant
|
||||
opacity: appButton.isRunning ? 1.0 : 0.6
|
||||
scale: appButton.hovered ? 1.15 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
@@ -323,6 +448,29 @@ Variants {
|
||||
easing.overshoot: 1.2
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Style.animationFast
|
||||
easing.type: Easing.OutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context menu popup
|
||||
DockMenu {
|
||||
id: contextMenu
|
||||
scaling: root.scaling
|
||||
onHoveredChanged: menuHovered = hovered
|
||||
onRequestClose: contextMenu.hide()
|
||||
onAppClosed: root.updateDockApps // Force immediate dock update when app is closed
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.currentContextMenu = contextMenu
|
||||
} else if (root.currentContextMenu === contextMenu) {
|
||||
root.currentContextMenu = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
@@ -330,7 +478,7 @@ Variants {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton
|
||||
acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton
|
||||
|
||||
onEntered: {
|
||||
anyAppHovered = true
|
||||
@@ -347,17 +495,35 @@ Variants {
|
||||
onExited: {
|
||||
anyAppHovered = false
|
||||
appTooltip.hide()
|
||||
if (autoHide && !dockHovered && !peekHovered) {
|
||||
if (autoHide && !dockHovered && !peekHovered && !menuHovered) {
|
||||
hideTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
if (mouse.button === Qt.MiddleButton && modelData?.close) {
|
||||
modelData.close()
|
||||
// Close any existing context menu first
|
||||
if (mouse.button !== Qt.RightButton || root.currentContextMenu !== contextMenu) {
|
||||
root.closeAllContextMenus()
|
||||
}
|
||||
if (mouse.button === Qt.LeftButton && modelData?.activate) {
|
||||
modelData.activate()
|
||||
|
||||
// Check if toplevel is still valid (not a stale reference)
|
||||
const isValidToplevel = modelData?.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(modelData.toplevel)
|
||||
|
||||
if (mouse.button === Qt.MiddleButton && isValidToplevel && modelData.toplevel.close) {
|
||||
modelData.toplevel.close()
|
||||
Qt.callLater(root.updateDockApps) // Force immediate dock update
|
||||
} else if (mouse.button === Qt.LeftButton) {
|
||||
if (isValidToplevel && modelData.toplevel.activate) {
|
||||
// Running app - activate it
|
||||
modelData.toplevel.activate()
|
||||
} else if (modelData?.appId) {
|
||||
// Pinned app not running - launch it
|
||||
Quickshell.execDetached(["gtk-launch", modelData.appId])
|
||||
}
|
||||
} else if (mouse.button === Qt.RightButton) {
|
||||
// Hide tooltip when showing context menu
|
||||
appTooltip.hide()
|
||||
contextMenu.show(appButton, modelData.toplevel || modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Widgets
|
||||
import qs.Commons
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
PopupWindow {
|
||||
id: root
|
||||
|
||||
property var toplevel: null
|
||||
property Item anchorItem: null
|
||||
property real scaling: 1.0
|
||||
property bool hovered: menuMouseArea.containsMouse || activateMouseArea.containsMouse || pinMouseArea.containsMouse || closeMouseArea.containsMouse
|
||||
property var onAppClosed: null // Callback function for when an app is closed
|
||||
|
||||
signal requestClose
|
||||
|
||||
implicitWidth: 160 * scaling
|
||||
implicitHeight: contextMenuColumn.implicitHeight + (Style.marginM * scaling * 2)
|
||||
color: Color.transparent
|
||||
visible: false
|
||||
|
||||
// Helper functions for pin/unpin functionality
|
||||
function isAppPinned(appId) {
|
||||
if (!appId)
|
||||
return false
|
||||
const pinnedApps = Settings.data.dock.pinnedApps || []
|
||||
return pinnedApps.includes(appId)
|
||||
}
|
||||
|
||||
function toggleAppPin(appId) {
|
||||
if (!appId)
|
||||
return
|
||||
|
||||
let pinnedApps = (Settings.data.dock.pinnedApps || []).slice() // Create a copy
|
||||
const isPinned = pinnedApps.includes(appId)
|
||||
|
||||
if (isPinned) {
|
||||
// Unpin: remove from array
|
||||
pinnedApps = pinnedApps.filter(id => id !== appId)
|
||||
} else {
|
||||
// Pin: add to array
|
||||
pinnedApps.push(appId)
|
||||
}
|
||||
|
||||
// Update the settings
|
||||
Settings.data.dock.pinnedApps = pinnedApps
|
||||
}
|
||||
|
||||
anchor.item: anchorItem
|
||||
anchor.rect.x: anchorItem ? (anchorItem.width - implicitWidth) / 2 : 0
|
||||
anchor.rect.y: anchorItem ? -implicitHeight - (Style.marginM * scaling) : 0
|
||||
|
||||
function show(item, toplevelData) {
|
||||
if (!item) {
|
||||
Logger.warn("DockMenu", "anchorItem is undefined, won't show menu.")
|
||||
return
|
||||
}
|
||||
|
||||
anchorItem = item
|
||||
toplevel = toplevelData
|
||||
visible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible = false
|
||||
}
|
||||
|
||||
// Close menu when clicking on background, track hover for the whole menu area
|
||||
MouseArea {
|
||||
id: menuMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: root.hide() // Close when clicking on the background (outside menu content)
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequences: ["Escape"]
|
||||
enabled: root.visible
|
||||
onActivated: root.hide()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Color.mSurface
|
||||
radius: Style.radiusS * scaling
|
||||
border.color: Color.mOutline
|
||||
border.width: Math.max(1, Style.borderS * scaling)
|
||||
|
||||
// Prevent clicks inside the menu from closing it
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
|
||||
} // Do nothing, just consume the click
|
||||
}
|
||||
|
||||
Column {
|
||||
id: contextMenuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Style.marginM * scaling
|
||||
spacing: 0
|
||||
|
||||
// Activate/Focus item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: activateMouseArea.containsMouse ? Qt.alpha(Color.mSecondary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "eye"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!root.toplevel)
|
||||
return "Activate"
|
||||
// Check if this toplevel is active by comparing with ToplevelManager.activeToplevel
|
||||
const isActive = ToplevelManager.activeToplevel && ToplevelManager.activeToplevel === root.toplevel
|
||||
return isActive ? "Focus" : "Activate"
|
||||
}
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: activateMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (root.toplevel?.activate) {
|
||||
root.toplevel.activate()
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pin/Unpin item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: pinMouseArea.containsMouse ? Qt.alpha(Color.mTertiary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: {
|
||||
if (!root.toplevel)
|
||||
return "pin"
|
||||
return root.isAppPinned(root.toplevel.appId) ? "pinned-off" : "pin"
|
||||
}
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: {
|
||||
if (!root.toplevel)
|
||||
return "Pin"
|
||||
return root.isAppPinned(root.toplevel.appId) ? "Unpin" : "Pin"
|
||||
}
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: pinMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
if (root.toplevel?.appId) {
|
||||
root.toggleAppPin(root.toplevel.appId)
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close item
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32 * scaling
|
||||
color: closeMouseArea.containsMouse ? Qt.alpha(Color.mPrimary, 0.2) : Color.transparent
|
||||
radius: Style.radiusXS * scaling
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Style.marginS * scaling
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Style.marginS * scaling
|
||||
|
||||
NIcon {
|
||||
icon: "x"
|
||||
font.pointSize: Style.fontSizeL * scaling
|
||||
color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
NText {
|
||||
text: "Close"
|
||||
font.pointSize: Style.fontSizeS * scaling
|
||||
color: closeMouseArea.containsMouse ? Color.mPrimary : Color.mOnSurface
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closeMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
// Check if toplevel is still valid before trying to close it
|
||||
const isValidToplevel = root.toplevel && ToplevelManager && ToplevelManager.toplevels.values.includes(root.toplevel)
|
||||
|
||||
if (isValidToplevel && root.toplevel.close) {
|
||||
root.toplevel.close()
|
||||
// Trigger immediate dock update callback if provided
|
||||
if (root.onAppClosed && typeof root.onAppClosed === "function") {
|
||||
Qt.callLater(root.onAppClosed)
|
||||
}
|
||||
} else {
|
||||
Logger.warn("DockMenu", "Cannot close app - invalid toplevel reference")
|
||||
}
|
||||
root.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Variants {
|
||||
id: root
|
||||
|
||||
required property ShellScreen modelData
|
||||
readonly property real scaling: ScalingService.getScreenScale(modelData)
|
||||
property real scaling: ScalingService.getScreenScale(modelData)
|
||||
|
||||
// Access the notification model from the service - UPDATED NAME
|
||||
property ListModel notificationModel: NotificationService.activeList
|
||||
@@ -26,52 +26,75 @@ Variants {
|
||||
|
||||
visible: (notificationModel.count > 0)
|
||||
|
||||
Connections {
|
||||
target: ScalingService
|
||||
function onScaleChanged(screenName, scale) {
|
||||
if (root.modelData && screenName === root.modelData.name) {
|
||||
root.scaling = scale
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceComponent: PanelWindow {
|
||||
screen: modelData
|
||||
color: Color.transparent
|
||||
|
||||
// Position based on bar location - always at top
|
||||
anchors.top: true
|
||||
anchors.right: Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom"
|
||||
anchors.left: Settings.data.bar.position === "left"
|
||||
readonly property string location: (Settings.isLoaded && Settings.data && Settings.data.notifications && Settings.data.notifications.location) ? Settings.data.notifications.location : "top_right"
|
||||
readonly property bool isTop: (location === "top") || (location.length >= 3 && location.substring(0, 3) === "top")
|
||||
readonly property bool isBottom: (location === "bottom") || (location.length >= 6 && location.substring(0, 6) === "bottom")
|
||||
readonly property bool isLeft: location.indexOf("_left") >= 0
|
||||
readonly property bool isRight: location.indexOf("_right") >= 0
|
||||
readonly property bool isCentered: (location === "top" || location === "bottom")
|
||||
|
||||
// Anchor selection based on location (window edges)
|
||||
anchors.top: isTop
|
||||
anchors.bottom: isBottom
|
||||
anchors.left: isLeft
|
||||
anchors.right: isRight
|
||||
|
||||
// Margins depending on bar position and chosen location
|
||||
margins.top: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "top":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
return Style.marginM * scaling
|
||||
if (!(anchors.top))
|
||||
return 0
|
||||
var base = Style.marginM * scaling
|
||||
if (Settings.data.bar.position === "top") {
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
|
||||
return (Style.barHeight * scaling) + base + floatExtraV
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
margins.bottom: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "bottom":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
if (!(anchors.bottom))
|
||||
return 0
|
||||
var base = Style.marginM * scaling
|
||||
if (Settings.data.bar.position === "bottom") {
|
||||
var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL * scaling : 0
|
||||
return (Style.barHeight * scaling) + base + floatExtraV
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
margins.left: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "left":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
|
||||
default:
|
||||
if (!(anchors.left))
|
||||
return 0
|
||||
var base = Style.marginM * scaling
|
||||
if (Settings.data.bar.position === "left") {
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
|
||||
return (Style.barHeight * scaling) + base + floatExtraH
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
margins.right: {
|
||||
switch (Settings.data.bar.position) {
|
||||
case "right":
|
||||
return (Style.barHeight + Style.marginM) * scaling + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0)
|
||||
case "top":
|
||||
case "bottom":
|
||||
return Style.marginM * scaling
|
||||
default:
|
||||
if (!(anchors.right))
|
||||
return 0
|
||||
var base = Style.marginM * scaling
|
||||
if (Settings.data.bar.position === "right") {
|
||||
var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL * scaling : 0
|
||||
return (Style.barHeight * scaling) + base + floatExtraH
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
implicitWidth: 360 * scaling
|
||||
@@ -110,9 +133,12 @@ Variants {
|
||||
// Main notification container
|
||||
ColumnLayout {
|
||||
id: notificationStack
|
||||
anchors.top: parent.top
|
||||
anchors.right: (Settings.data.bar.position === "right" || Settings.data.bar.position === "top" || Settings.data.bar.position === "bottom") ? parent.right : undefined
|
||||
anchors.left: Settings.data.bar.position === "left" ? parent.left : undefined
|
||||
// Anchor the stack inside the window based on chosen location
|
||||
anchors.top: parent.isTop ? parent.top : undefined
|
||||
anchors.bottom: parent.isBottom ? parent.bottom : undefined
|
||||
anchors.left: parent.isLeft ? parent.left : undefined
|
||||
anchors.right: parent.isRight ? parent.right : undefined
|
||||
anchors.horizontalCenter: parent.isCentered ? parent.horizontalCenter : undefined
|
||||
spacing: Style.marginS * scaling
|
||||
width: 360 * scaling
|
||||
visible: true
|
||||
|
||||
@@ -16,18 +16,34 @@ ColumnLayout {
|
||||
// Local state
|
||||
property string valueIcon: widgetData.icon !== undefined ? widgetData.icon : widgetMetadata.icon
|
||||
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
|
||||
property string valueCustomIconPath: widgetData.customIconPath !== undefined ? widgetData.customIconPath : ""
|
||||
|
||||
function saveSettings() {
|
||||
var settings = Object.assign({}, widgetData || {})
|
||||
settings.icon = valueIcon
|
||||
settings.useDistroLogo = valueUseDistroLogo
|
||||
settings.customIconPath = valueCustomIconPath
|
||||
return settings
|
||||
}
|
||||
|
||||
NToggle {
|
||||
label: "Use distro logo instead of icon"
|
||||
checked: valueUseDistroLogo
|
||||
onToggled: checked => valueUseDistroLogo = checked
|
||||
onToggled: {
|
||||
valueUseDistroLogo = checked
|
||||
if (checked) {
|
||||
valueCustomIconPath = ""
|
||||
valueIcon = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NFilePicker {
|
||||
id: filePicker
|
||||
title: "Select a custom icon"
|
||||
onFileSelected: function (filePath) {
|
||||
valueCustomIconPath = "file://" + filePath
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -35,21 +51,35 @@ ColumnLayout {
|
||||
|
||||
NLabel {
|
||||
label: "Icon"
|
||||
description: "Select an icon from the library."
|
||||
description: "Select an icon from the library or a custom file."
|
||||
}
|
||||
|
||||
NImageCircled {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
imagePath: valueCustomIconPath
|
||||
visible: valueCustomIconPath !== ""
|
||||
width: Style.fontSizeXL * 2 * scaling
|
||||
height: Style.fontSizeXL * 2 * scaling
|
||||
}
|
||||
|
||||
NIcon {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
icon: valueIcon
|
||||
font.pointSize: Style.fontSizeXL * scaling
|
||||
visible: valueIcon !== ""
|
||||
visible: valueIcon !== "" && valueCustomIconPath === ""
|
||||
}
|
||||
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: "Browse"
|
||||
text: "Browse Library"
|
||||
onClicked: iconPicker.open()
|
||||
}
|
||||
|
||||
NButton {
|
||||
enabled: !valueUseDistroLogo
|
||||
text: "Browse File"
|
||||
onClicked: filePicker.open()
|
||||
}
|
||||
}
|
||||
|
||||
NIconPicker {
|
||||
@@ -57,6 +87,7 @@ ColumnLayout {
|
||||
initialIcon: valueIcon
|
||||
onIconSelected: function (iconName) {
|
||||
valueIcon = iconName
|
||||
valueCustomIconPath = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,39 @@ ColumnLayout {
|
||||
checked: Settings.data.notifications.doNotDisturb
|
||||
onToggled: checked => Settings.data.notifications.doNotDisturb = checked
|
||||
}
|
||||
|
||||
NComboBox {
|
||||
label: "Location"
|
||||
description: "Where notifications appear on screen."
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
key: "top"
|
||||
name: "Top"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_left"
|
||||
name: "Top left"
|
||||
}
|
||||
ListElement {
|
||||
key: "top_right"
|
||||
name: "Top right"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom"
|
||||
name: "Bottom"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_left"
|
||||
name: "Bottom left"
|
||||
}
|
||||
ListElement {
|
||||
key: "bottom_right"
|
||||
name: "Bottom right"
|
||||
}
|
||||
}
|
||||
currentKey: Settings.data.notifications.location || "top_right"
|
||||
onSelected: key => Settings.data.notifications.location = key
|
||||
}
|
||||
}
|
||||
|
||||
NDivider {
|
||||
|
||||
@@ -103,7 +103,8 @@ Singleton {
|
||||
"SidePanelToggle": {
|
||||
"allowUserSettings": true,
|
||||
"useDistroLogo": false,
|
||||
"icon": "noctalia"
|
||||
"icon": "noctalia",
|
||||
"customIconPath": ""
|
||||
},
|
||||
"Volume": {
|
||||
"allowUserSettings": true,
|
||||
|
||||
Reference in New Issue
Block a user