Merge branch 'main' into file-dialog-builtin

This commit is contained in:
ItsLemmy
2025-09-22 11:54:44 -04:00
11 changed files with 608 additions and 77 deletions
+19 -16
View File
@@ -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}}
+5 -3
View File
@@ -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": {
+4 -3
View File
@@ -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
+10 -4
View File
@@ -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
}
+2 -1
View File
@@ -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
View File
@@ -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)
}
}
}
+261
View File
@@ -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()
}
}
}
}
}
}
+54 -28
View File
@@ -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 {
+2 -1
View File
@@ -103,7 +103,8 @@ Singleton {
"SidePanelToggle": {
"allowUserSettings": true,
"useDistroLogo": false,
"icon": "noctalia"
"icon": "noctalia",
"customIconPath": ""
},
"Volume": {
"allowUserSettings": true,