1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 08:22:51 -05:00

refactor: perf improvement stopping singletons with ref

Also switch to scale+opacity anims with custom curve
This commit is contained in:
bbedward
2025-07-23 16:54:19 -04:00
parent 2c57487046
commit 4f63d5899b
21 changed files with 677 additions and 672 deletions

View File

@@ -7,11 +7,16 @@ import Quickshell
Singleton { Singleton {
id: root id: root
readonly property int durShort: 160 readonly property int durShort: 200
readonly property int durMed: 220 readonly property int durMed: 400
readonly property int durLong: 320 readonly property int durLong: 600
readonly property int slidePx: 100 readonly property int slidePx: 80
// Material Design 3 motion curves
readonly property var emphasized: [0.05, 0, 2/15, 0.06, 1/6, 0.4, 5/24, 0.82, 0.25, 1, 1, 1]
readonly property var emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]
readonly property var standard: [0.2, 0, 0, 1, 1, 1]
readonly property QtObject direction: QtObject { readonly property QtObject direction: QtObject {
readonly property int fromLeft: 0 readonly property int fromLeft: 0
@@ -20,30 +25,26 @@ Singleton {
} }
readonly property Component slideInLeft: Transition { readonly property Component slideInLeft: Transition {
NumberAnimation { properties: "x"; from: -Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutCubic } NumberAnimation { properties: "x"; from: -Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutQuart }
NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durShort }
} }
readonly property Component slideOutLeft: Transition { readonly property Component slideOutLeft: Transition {
NumberAnimation { properties: "x"; to: -Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InCubic } NumberAnimation { properties: "x"; to: -Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InQuart }
NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort }
} }
readonly property Component slideInRight: Transition { readonly property Component slideInRight: Transition {
NumberAnimation { properties: "x"; from: Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutCubic } NumberAnimation { properties: "x"; from: Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutQuart }
NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durShort }
} }
readonly property Component slideOutRight: Transition { readonly property Component slideOutRight: Transition {
NumberAnimation { properties: "x"; to: Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InCubic } NumberAnimation { properties: "x"; to: Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InQuart }
NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort }
} }
readonly property Component fadeIn: Transition { readonly property Component fadeIn: Transition {
NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durShort; easing.type: Easing.OutCubic } NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durMed; easing.type: Easing.OutQuart }
} }
readonly property Component fadeOut: Transition { readonly property Component fadeOut: Transition {
NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort; easing.type: Easing.InCubic } NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort; easing.type: Easing.InQuart }
} }
} }

9
Common/Ref.qml Normal file
View File

@@ -0,0 +1,9 @@
import Quickshell
import QtQuick
QtObject {
required property Singleton service
Component.onCompleted: service.refCount++
Component.onDestruction: service.refCount--
}

View File

@@ -19,8 +19,6 @@ DankModal {
function show() { function show() {
processListModal.visible = true; processListModal.visible = true;
ProcessMonitorService.updateSystemInfo();
ProcessMonitorService.updateProcessList();
SystemMonitorService.enableDetailedMonitoring(true); SystemMonitorService.enableDetailedMonitoring(true);
SystemMonitorService.updateSystemInfo(); SystemMonitorService.updateSystemInfo();
UserInfoService.getUptime(); UserInfoService.getUptime();
@@ -46,10 +44,11 @@ DankModal {
cornerRadius: Theme.cornerRadiusXLarge cornerRadius: Theme.cornerRadiusXLarge
enableShadow: true enableShadow: true
onVisibleChanged: { Ref {
ProcessMonitorService.enableMonitoring(visible); service: ProcessMonitorService
} }
onBackgroundClicked: hide() onBackgroundClicked: hide()
content: Component { content: Component {

View File

@@ -71,7 +71,9 @@ DankModal {
gridColumns: 4 gridColumns: 4
onAppLaunched: hide() onAppLaunched: hide()
onViewModeSelected: Prefs.setSpotlightModalViewMode(mode) onViewModeSelected: function(mode) {
Prefs.setSpotlightModalViewMode(mode);
}
} }
content: Component { content: Component {
@@ -265,12 +267,16 @@ DankModal {
iconSize: 40 iconSize: 40
showDescription: true showDescription: true
hoverUpdatesSelection: false hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
appLauncher.selectedIndex = index; appLauncher.selectedIndex = index;
} }
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
} }
// Grid view // Grid view
@@ -288,12 +294,16 @@ DankModal {
maxIconSize: 48 maxIconSize: 48
currentIndex: appLauncher.selectedIndex currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
appLauncher.selectedIndex = index; appLauncher.selectedIndex = index;
} }
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
} }
} }
} }

View File

@@ -14,21 +14,14 @@ PanelWindow {
id: appDrawerPopout id: appDrawerPopout
property bool isVisible: false property bool isVisible: false
property bool showCategories: false
function show() { function show() {
appDrawerPopout.isVisible = true; appDrawerPopout.isVisible = true;
searchField.enabled = true;
appLauncher.searchQuery = ""; appLauncher.searchQuery = "";
} }
function hide() { function hide() {
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
appDrawerPopout.isVisible = false; appDrawerPopout.isVisible = false;
searchField.text = "";
showCategories = false;
} }
function toggle() { function toggle() {
@@ -62,74 +55,65 @@ PanelWindow {
gridColumns: 4 gridColumns: 4
onAppLaunched: appDrawerPopout.hide() onAppLaunched: appDrawerPopout.hide()
onViewModeSelected: Prefs.setAppLauncherViewMode(mode) onViewModeSelected: function(mode) {
Prefs.setAppLauncherViewMode(mode);
}
} }
// Background click to close (no visual background) // Background click to close (no visual background)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
enabled: appDrawerPopout.isVisible enabled: appDrawerPopout.isVisible
onClicked: appDrawerPopout.hide() onClicked: function(mouse) {
// Only close if click is outside the launcher panel
var localPos = mapToItem(launcherLoader, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > launcherLoader.width ||
localPos.y < 0 || localPos.y > launcherLoader.height) {
appDrawerPopout.hide();
}
}
} }
Component { // Main launcher panel with asynchronous loading
id: iconComponent Loader {
id: launcherLoader
Item {
property var appData: parent.modelData || {
}
IconImage {
id: iconImg
anchors.fill: parent
source: (appData && appData.icon) ? Quickshell.iconPath(appData.icon, "") : ""
smooth: true
asynchronous: true asynchronous: true
visible: status === Image.Ready active: appDrawerPopout.isVisible
}
Rectangle {
anchors.fill: parent
visible: !iconImg.visible
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
radius: Theme.cornerRadiusLarge
border.width: 1
border.color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
Text {
anchors.centerIn: parent
text: (appData && appData.name && appData.name.length > 0) ? appData.name.charAt(0).toUpperCase() : "A"
font.pixelSize: 28
color: Theme.primary
font.weight: Font.Bold
}
}
}
}
// Main launcher panel with enhanced design
Rectangle {
id: launcherPanel
width: 520 width: 520
height: 600 height: 600
color: Theme.popupBackground() x: Theme.spacingL
radius: Theme.cornerRadiusXLarge
opacity: appDrawerPopout.isVisible ? 1 : 0
x: appDrawerPopout.isVisible ? Theme.spacingL : Theme.spacingL - Anims.slidePx
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
Behavior on x { // GPU-accelerated scale + opacity animation
opacity: appDrawerPopout.isVisible ? 1 : 0
scale: appDrawerPopout.isVisible ? 1 : 0.9
Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Anims.durMed duration: Anims.durMed
easing.type: Easing.OutCubic easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
} }
} }
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
sourceComponent: Rectangle {
id: launcherPanel
color: Theme.popupBackground()
radius: Theme.cornerRadiusXLarge
// Remove layer rendering for better performance
antialiasing: true
smooth: true
// Material 3 elevation with multiple layers // Material 3 elevation with multiple layers
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
@@ -167,8 +151,8 @@ PanelWindow {
Component.onCompleted: { Component.onCompleted: {
if (appDrawerPopout.isVisible) if (appDrawerPopout.isVisible)
forceActiveFocus(); forceActiveFocus();
} }
// Handle keyboard shortcuts // Handle keyboard shortcuts
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
@@ -189,7 +173,7 @@ PanelWindow {
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
appLauncher.launchSelected(); appLauncher.launchSelected();
event.accepted = true; event.accepted = true;
} else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\s]/)) { } else if (event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
// User started typing, focus search field and pass the character // User started typing, focus search field and pass the character
searchField.forceActiveFocus(); searchField.forceActiveFocus();
searchField.text = event.text; searchField.text = event.text;
@@ -198,8 +182,10 @@ PanelWindow {
} }
Column { Column {
anchors.fill: parent width: parent.width - Theme.spacingXL * 2
anchors.margins: Theme.spacingXL height: parent.height - Theme.spacingXL * 2
x: Theme.spacingXL
y: Theme.spacingXL
spacing: Theme.spacingL spacing: Theme.spacingL
// Header section // Header section
@@ -228,7 +214,6 @@ PanelWindow {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
} }
// Enhanced search field // Enhanced search field
@@ -274,7 +259,6 @@ PanelWindow {
target: appDrawerPopout target: appDrawerPopout
} }
} }
// Category filter and view mode controls // Category filter and view mode controls
@@ -284,54 +268,21 @@ PanelWindow {
spacing: Theme.spacingM spacing: Theme.spacingM
visible: searchField.text.length === 0 visible: searchField.text.length === 0
// Category filter // Category filter using DankDropdown
Rectangle { Item {
width: 200 width: 200
height: 36 height: 36
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.4)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
Row { DankDropdown {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: "category"
size: 18
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
Text {
text: appLauncher.selectedCategory
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
font.weight: Font.Medium
}
}
DankIcon {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
name: showCategories ? "expand_less" : "expand_more"
size: 18
color: Theme.surfaceVariantText
}
MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true text: ""
cursorShape: Qt.PointingHandCursor currentValue: appLauncher.selectedCategory
onClicked: showCategories = !showCategories options: appLauncher.categories
optionIcons: appLauncher.categoryIcons
onValueChanged: function(value) {
appLauncher.setCategory(value);
}
} }
} }
Item { Item {
@@ -371,9 +322,7 @@ PanelWindow {
appLauncher.setViewMode("grid"); appLauncher.setViewMode("grid");
} }
} }
} }
} }
// App grid/list container // App grid/list container
@@ -403,12 +352,16 @@ PanelWindow {
iconSize: 56 iconSize: 56
showDescription: true showDescription: true
hoverUpdatesSelection: false hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
appLauncher.selectedIndex = index; appLauncher.selectedIndex = index;
} }
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
}
} }
// Grid view // Grid view
@@ -422,131 +375,21 @@ PanelWindow {
adaptiveColumns: false adaptiveColumns: false
currentIndex: appLauncher.selectedIndex currentIndex: appLauncher.selectedIndex
hoverUpdatesSelection: false hoverUpdatesSelection: false
keyboardNavigationActive: appLauncher.keyboardNavigationActive
onItemClicked: function(index, modelData) { onItemClicked: function(index, modelData) {
appLauncher.launchApp(modelData); appLauncher.launchApp(modelData);
} }
onItemHovered: function(index) { onItemHovered: function(index) {
appLauncher.selectedIndex = index; appLauncher.selectedIndex = index;
} }
onKeyboardNavigationReset: {
appLauncher.keyboardNavigationActive = false;
} }
}
// Category dropdown overlay - now positioned absolutely
Rectangle {
id: categoryDropdown
width: 200
height: Math.min(250, categories.length * 40 + Theme.spacingM * 2)
radius: Theme.cornerRadiusLarge
color: Theme.contentBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1
visible: showCategories
z: 1000
// Position it below the category button
anchors.top: parent.top
anchors.topMargin: 140 + (searchField.text.length === 0 ? 0 : -40)
anchors.left: parent.left
// Drop shadow
Rectangle {
anchors.fill: parent
anchors.margins: -4
color: "transparent"
radius: parent.radius + 4
z: -1
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 0
shadowBlur: 0.25 // radius/32
shadowColor: Qt.rgba(0, 0, 0, 0.2)
shadowOpacity: 0.2
}
}
ScrollView {
anchors.fill: parent
anchors.margins: Theme.spacingS
clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ListView {
// Make mouse wheel scrolling more responsive
property real wheelStepSize: 60
model: appLauncher.categories
spacing: 4
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
onWheel: (wheel) => {
var delta = wheel.angleDelta.y;
var steps = delta / 120; // Standard wheel step
parent.contentY -= steps * parent.wheelStepSize;
// Ensure we stay within bounds
if (parent.contentY < 0)
parent.contentY = 0;
else if (parent.contentY > parent.contentHeight - parent.height)
parent.contentY = Math.max(0, parent.contentHeight - parent.height);
}
}
delegate: Rectangle {
width: ListView.view.width
height: 36
radius: Theme.cornerRadiusSmall
color: catArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
Text {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
text: modelData
font.pixelSize: Theme.fontSizeMedium
color: appLauncher.selectedCategory === modelData ? Theme.primary : Theme.surfaceText
font.weight: appLauncher.selectedCategory === modelData ? Font.Medium : Font.Normal
}
MouseArea {
id: catArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
appLauncher.setCategory(modelData);
showCategories = false;
} }
} }
} }
}
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.OutCubic
} }
} }
} }
} }

View File

@@ -17,6 +17,7 @@ Item {
property int gridColumns: 4 property int gridColumns: 4
property bool debounceSearch: true property bool debounceSearch: true
property int debounceInterval: 50 property int debounceInterval: 50
property bool keyboardNavigationActive: false
// Categories (computed from AppSearchService) // Categories (computed from AppSearchService)
property var categories: { property var categories: {
@@ -29,6 +30,9 @@ Item {
})); }));
} }
// Category icons (computed from AppSearchService)
property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
// Recent apps helper // Recent apps helper
property var recentApps: Prefs.recentlyUsedApps.map(recentApp => { property var recentApps: Prefs.recentlyUsedApps.map(recentApp => {
var app = AppSearchService.getAppByExec(recentApp.exec); var app = AppSearchService.getAppByExec(recentApp.exec);
@@ -129,6 +133,7 @@ Item {
// Keyboard navigation functions // Keyboard navigation functions
function selectNext() { function selectNext() {
if (filteredModel.count > 0) { if (filteredModel.count > 0) {
keyboardNavigationActive = true;
if (viewMode === "grid") { if (viewMode === "grid") {
var newIndex = Math.min(selectedIndex + gridColumns, filteredModel.count - 1); var newIndex = Math.min(selectedIndex + gridColumns, filteredModel.count - 1);
selectedIndex = newIndex; selectedIndex = newIndex;
@@ -140,6 +145,7 @@ Item {
function selectPrevious() { function selectPrevious() {
if (filteredModel.count > 0) { if (filteredModel.count > 0) {
keyboardNavigationActive = true;
if (viewMode === "grid") { if (viewMode === "grid") {
var newIndex = Math.max(selectedIndex - gridColumns, 0); var newIndex = Math.max(selectedIndex - gridColumns, 0);
selectedIndex = newIndex; selectedIndex = newIndex;
@@ -151,12 +157,14 @@ Item {
function selectNextInRow() { function selectNextInRow() {
if (filteredModel.count > 0 && viewMode === "grid") { if (filteredModel.count > 0 && viewMode === "grid") {
keyboardNavigationActive = true;
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1); selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
} }
} }
function selectPreviousInRow() { function selectPreviousInRow() {
if (filteredModel.count > 0 && viewMode === "grid") { if (filteredModel.count > 0 && viewMode === "grid") {
keyboardNavigationActive = true;
selectedIndex = Math.max(selectedIndex - 1, 0); selectedIndex = Math.max(selectedIndex - 1, 0);
} }
} }

View File

@@ -87,13 +87,23 @@ PanelWindow {
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
opacity: calendarVisible ? 1 : 0 opacity: calendarVisible ? 1 : 0
scale: calendarVisible ? 1 : 0.9
x: (Screen.width - targetWidth) / 2 x: (Screen.width - targetWidth) / 2
y: Theme.barHeight + 4 y: Theme.barHeight + 4
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
duration: Anims.durShort duration: Anims.durMed
easing.type: Easing.OutCubic easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
} }
} }
@@ -241,9 +251,14 @@ PanelWindow {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
enabled: calendarVisible enabled: calendarVisible
onClicked: { onClicked: function(mouse) {
// Only close if click is outside the main container
var localPos = mapToItem(mainContainer, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > mainContainer.width ||
localPos.y < 0 || localPos.y > mainContainer.height) {
calendarVisible = false; calendarVisible = false;
} }
} }
}
} }

View File

@@ -317,8 +317,8 @@ Rectangle {
x: 0 x: 0
y: 0 y: 0
width: mediaPlayerWidget.width width: mediaPlayer.width
height: mediaPlayerWidget.height height: mediaPlayer.height
enabled: progressMouseArea.isSeeking enabled: progressMouseArea.isSeeking
visible: false visible: false
preventStealing: true preventStealing: true

View File

@@ -16,6 +16,10 @@ Rectangle {
border.width: 1 border.width: 1
layer.enabled: true layer.enabled: true
Ref {
service: WeatherService
}
// Placeholder when no weather - centered in entire widget // Placeholder when no weather - centered in entire widget
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent

View File

@@ -47,25 +47,46 @@ PanelWindow {
bottom: true bottom: true
} }
Rectangle { Loader {
id: contentLoader
asynchronous: true
active: controlCenterVisible
readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2) readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2)
width: targetWidth width: targetWidth
height: root.powerOptionsExpanded ? 570 : 500 height: root.powerOptionsExpanded ? 570 : 500
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
// GPU-accelerated scale + opacity animation
opacity: controlCenterVisible ? 1 : 0
scale: controlCenterVisible ? 1 : 0.9
Behavior on opacity {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
sourceComponent: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: controlCenterVisible ? 1 : 0
property real normalX: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
x: controlCenterVisible ? normalX : normalX + Anims.slidePx
Behavior on x { // Remove layer rendering for better performance
NumberAnimation { antialiasing: true
duration: Anims.durMed smooth: true
easing.type: Easing.OutCubic
}
}
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@@ -653,25 +674,22 @@ PanelWindow {
duration: Theme.shortDuration // Faster for height changes duration: Theme.shortDuration // Faster for height changes
easing.type: Theme.standardEasing easing.type: Theme.standardEasing
} }
}
Behavior on opacity {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.OutCubic
} }
} }
} }
// Click outside to close // Click outside to close
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
z: -1 z: -1
onClicked: { onClicked: function(mouse) {
// Only close if click is outside the content loader
var localPos = mapToItem(contentLoader, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > contentLoader.width ||
localPos.y < 0 || localPos.y > contentLoader.height) {
controlCenterVisible = false; controlCenterVisible = false;
} }
} }
}
} }

View File

@@ -119,7 +119,7 @@ Column {
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : [] model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
Rectangle { Rectangle {
width: parent.width width: spanningNetworksColumn.width
height: 38 height: 38
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: networkArea2.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: networkArea2.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : modelData.connected ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"

View File

@@ -23,8 +23,6 @@ PanelWindow {
function show() { function show() {
isVisible = true; isVisible = true;
ProcessMonitorService.updateSystemInfo();
ProcessMonitorService.updateProcessList();
} }
function toggle() { function toggle() {
@@ -35,9 +33,11 @@ PanelWindow {
} }
visible: isVisible visible: isVisible
onIsVisibleChanged: {
ProcessMonitorService.enableMonitoring(isVisible); Ref {
service: ProcessMonitorService
} }
implicitWidth: 600 implicitWidth: 600
implicitHeight: 600 implicitHeight: 600
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
@@ -54,40 +54,60 @@ PanelWindow {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: processListPopout.hide() onClicked: function(mouse) {
// Only close if click is outside the content loader
var localPos = mapToItem(contentLoader, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > contentLoader.width ||
localPos.y < 0 || localPos.y > contentLoader.height) {
processListPopout.hide();
}
}
} }
Rectangle { Loader {
id: dropdownContent id: contentLoader
asynchronous: true
active: processListPopout.isVisible
readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2) readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2)
readonly property real targetHeight: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2) readonly property real targetHeight: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2)
width: targetWidth width: targetWidth
height: targetHeight height: targetHeight
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
// GPU-accelerated scale + opacity animation
opacity: processListPopout.isVisible ? 1 : 0
scale: processListPopout.isVisible ? 1 : 0.9
Behavior on opacity {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
sourceComponent: Rectangle {
id: dropdownContent
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: Theme.popupBackground() color: Theme.popupBackground()
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
clip: true clip: true
opacity: processListPopout.isVisible ? 1 : 0
layer.enabled: true
property real normalX: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
x: processListPopout.isVisible ? normalX : normalX + Anims.slidePx
Behavior on x { // Remove layer rendering for better performance
NumberAnimation { antialiasing: true
duration: Anims.durMed smooth: true
easing.type: Easing.OutCubic
}
}
MouseArea {
anchors.fill: parent
onClicked: {
}
}
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
@@ -108,30 +128,12 @@ PanelWindow {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
contextMenu: processContextMenuWindow contextMenu: processContextMenuWindow
processContextMenuWindow: processContextMenuWindow
} }
} }
layer.effect: MultiEffect {
shadowEnabled: true
shadowHorizontalOffset: 0
shadowVerticalOffset: 8
shadowBlur: 1
shadowColor: Qt.rgba(0, 0, 0, 0.15)
shadowOpacity: processListPopout.isVisible ? 0.15 : 0
} }
Behavior on opacity {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.OutCubic
}
}
} }
ProcessContextMenu { ProcessContextMenu {
id: processContextMenuWindow id: processContextMenuWindow
} }
} }

View File

@@ -48,33 +48,58 @@ PanelWindow {
// Click outside to dismiss overlay // Click outside to dismiss overlay
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: function(mouse) {
// Only close if click is outside the content loader
var localPos = mapToItem(contentLoader, mouse.x, mouse.y);
if (localPos.x < 0 || localPos.x > contentLoader.width ||
localPos.y < 0 || localPos.y > contentLoader.height) {
batteryPopupVisible = false; batteryPopupVisible = false;
} }
} }
}
Loader {
id: contentLoader
asynchronous: true
active: batteryPopupVisible
Rectangle {
readonly property real targetWidth: Math.min(380, Screen.width - Theme.spacingL * 2) readonly property real targetWidth: Math.min(380, Screen.width - Theme.spacingL * 2)
readonly property real targetHeight: Math.min(450, Screen.height - Theme.barHeight - Theme.spacingS * 2) readonly property real targetHeight: Math.min(450, Screen.height - Theme.barHeight - Theme.spacingS * 2)
width: targetWidth width: targetWidth
height: targetHeight height: targetHeight
property real normalX: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL) x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
x: batteryPopupVisible ? normalX : normalX + Anims.slidePx
y: Theme.barHeight + Theme.spacingS y: Theme.barHeight + Theme.spacingS
// GPU-accelerated scale + opacity animation
opacity: batteryPopupVisible ? 1 : 0
scale: batteryPopupVisible ? 1 : 0.9
Behavior on opacity {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
Behavior on scale {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.BezierSpline
easing.bezierCurve: Anims.emphasized
}
}
sourceComponent: Rectangle {
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 1 border.width: 1
opacity: batteryPopupVisible ? 1 : 0
// Prevent click-through to background // Remove layer rendering for better performance
MouseArea { antialiasing: true
// Consume the click to prevent it from reaching the background smooth: true
anchors.fill: parent
onClicked: {
}
}
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
@@ -461,21 +486,9 @@ PanelWindow {
} }
Behavior on opacity {
NumberAnimation {
duration: Anims.durShort
easing.type: Easing.OutCubic
}
}
Behavior on x {
NumberAnimation {
duration: Anims.durMed
easing.type: Easing.OutCubic
} }
} }
}
} }

View File

@@ -162,7 +162,7 @@ PanelWindow {
Media { Media {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: clockWidget.left anchors.right: clock.left
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
visible: Prefs.showMusic && MprisController.activePlayer visible: Prefs.showMusic && MprisController.activePlayer
onClicked: { onClicked: {

View File

@@ -14,6 +14,10 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: weatherArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) color: weatherArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
Ref {
service: WeatherService
}
Row { Row {
id: weatherRow id: weatherRow

View File

@@ -116,6 +116,25 @@ Singleton {
return Array.from(mappedCategories) return Array.from(mappedCategories)
} }
// Category icon mappings
property var categoryIcons: ({
"All": "apps",
"Recents": "history",
"Media": "music_video",
"Development": "code",
"Games": "sports_esports",
"Graphics": "photo_library",
"Internet": "web",
"Office": "content_paste",
"Settings": "settings",
"System": "host",
"Utilities": "build"
})
function getCategoryIcon(category) {
return categoryIcons[category] || "folder"
}
function getAllCategories() { function getAllCategories() {
var categories = new Set(["All"]) var categories = new Set(["All"])

View File

@@ -6,14 +6,13 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
Singleton { Singleton {
// console.log("ProcessMonitorService: Updated - CPU:", root.totalCpuUsage.toFixed(1) + "%", "Memory:", memoryPercent.toFixed(1) + "%", "History length:", root.cpuHistory.length)
id: root id: root
property int refCount
property var processes: [] property var processes: []
property bool isUpdating: false property bool isUpdating: false
property int processUpdateInterval: 3000 property int processUpdateInterval: 3000
property bool monitoringEnabled: false
property int totalMemoryKB: 0 property int totalMemoryKB: 0
property int usedMemoryKB: 0 property int usedMemoryKB: 0
property int totalSwapKB: 0 property int totalSwapKB: 0
@@ -43,47 +42,8 @@ Singleton {
property bool sortDescending: true property bool sortDescending: true
property int maxProcesses: 20 property int maxProcesses: 20
function updateSystemInfo() {
if (!systemInfoProcess.running && root.monitoringEnabled)
systemInfoProcess.running = true;
}
function enableMonitoring(enabled) {
console.log("ProcessMonitorService: Monitoring", enabled ? "enabled" : "disabled");
root.monitoringEnabled = enabled;
if (enabled) {
root.cpuHistory = [];
root.memoryHistory = [];
root.networkHistory = ({
"rx": [],
"tx": []
});
root.diskHistory = ({
"read": [],
"write": []
});
updateSystemInfo();
updateProcessList();
updateNetworkStats();
updateDiskStats();
}
}
function updateNetworkStats() {
if (!networkStatsProcess.running && root.monitoringEnabled)
networkStatsProcess.running = true;
}
function updateDiskStats() {
if (!diskStatsProcess.running && root.monitoringEnabled)
diskStatsProcess.running = true;
}
function updateProcessList() { function updateProcessList() {
if (!root.isUpdating && root.monitoringEnabled) { if (!root.isUpdating) {
root.isUpdating = true; root.isUpdating = true;
let sortOption = ""; let sortOption = "";
switch (root.sortBy) { switch (root.sortBy) {
@@ -315,10 +275,17 @@ Singleton {
} }
Component.onCompleted: { Timer {
console.log("ProcessMonitorService: Starting initialization..."); running: root.refCount > 0
interval: root.processUpdateInterval
repeat: true
triggeredOnStart: true
onTriggered: {
systemInfoProcess.running = true;
updateProcessList(); updateProcessList();
console.log("ProcessMonitorService: Initialization complete"); networkStatsProcess.running = true;
diskStatsProcess.running = true;
}
} }
Process { Process {
@@ -441,20 +408,4 @@ Singleton {
} }
} }
Timer {
id: processTimer
interval: root.processUpdateInterval
running: root.monitoringEnabled
repeat: true
onTriggered: {
if (root.monitoringEnabled) {
updateSystemInfo();
updateProcessList();
updateNetworkStats();
updateDiskStats();
}
}
}
} }

View File

@@ -9,6 +9,8 @@ import qs.Common
Singleton { Singleton {
id: root id: root
property int refCount: 0
property var weather: ({ property var weather: ({
available: false, available: false,
loading: true, loading: true,
@@ -95,7 +97,27 @@ Singleton {
return url return url
} }
function addRef() {
refCount++;
console.log("WeatherService: addRef, refCount now:", refCount);
if (refCount === 1 && !weather.available) {
// Start fetching when first consumer appears
fetchWeather();
}
}
function removeRef() {
refCount = Math.max(0, refCount - 1);
console.log("WeatherService: removeRef, refCount now:", refCount);
}
function fetchWeather() { function fetchWeather() {
// Only fetch if someone is consuming the data
if (root.refCount === 0) {
console.log("WeatherService: Skipping fetch - no consumers");
return;
}
if (weatherFetcher.running) { if (weatherFetcher.running) {
console.log("Weather fetch already in progress, skipping") console.log("Weather fetch already in progress, skipping")
return return
@@ -217,7 +239,7 @@ Singleton {
Timer { Timer {
id: updateTimer id: updateTimer
interval: root.updateInterval interval: root.updateInterval
running: true running: root.refCount > 0
repeat: true repeat: true
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {

View File

@@ -12,6 +12,7 @@ Rectangle {
property string description: "" property string description: ""
property string currentValue: "" property string currentValue: ""
property var options: [] property var options: []
property var optionIcons: [] // Array of icon names corresponding to options
signal valueChanged(string value) signal valueChanged(string value)
@@ -72,14 +73,34 @@ Rectangle {
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
Row {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
width: parent.width - 24
DankIcon {
name: {
var currentIndex = root.options.indexOf(root.currentValue);
return root.optionIcons.length > currentIndex && currentIndex >= 0 ? root.optionIcons[currentIndex] : "";
}
size: 18
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
visible: {
var currentIndex = root.options.indexOf(root.currentValue);
return root.optionIcons.length > currentIndex && currentIndex >= 0 && root.optionIcons[currentIndex] !== "";
}
}
Text { Text {
text: root.currentValue text: root.currentValue
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - 24 width: parent.parent.width - (visible ? 24 + Theme.spacingS : 24) - (parent.children[0].visible ? 18 + Theme.spacingS : 0)
elide: Text.ElideRight elide: Text.ElideRight
} }
}
DankIcon { DankIcon {
name: "expand_more" name: "expand_more"
@@ -170,15 +191,28 @@ Rectangle {
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: optionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent" color: optionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
Text { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: root.optionIcons.length > index ? root.optionIcons[index] : ""
size: 18
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
visible: root.optionIcons.length > index && root.optionIcons[index] !== ""
}
Text {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
text: modelData text: modelData
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText
font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal
} }
}
MouseArea { MouseArea {
id: optionArea id: optionArea

View File

@@ -19,10 +19,33 @@ ScrollView {
property int minIconSize: 32 property int minIconSize: 32
property real wheelStepSize: 60 property real wheelStepSize: 60
property bool hoverUpdatesSelection: true property bool hoverUpdatesSelection: true
property bool keyboardNavigationActive: false
signal keyboardNavigationReset()
signal itemClicked(int index, var modelData) signal itemClicked(int index, var modelData)
signal itemHovered(int index) signal itemHovered(int index)
// Ensure the current item is visible
function ensureVisible(index) {
if (index < 0 || index >= grid.count) return;
var itemY = Math.floor(index / grid.actualColumns) * grid.cellHeight;
var itemBottom = itemY + grid.cellHeight;
if (itemY < grid.contentY) {
grid.contentY = itemY;
} else if (itemBottom > grid.contentY + grid.height) {
grid.contentY = itemBottom - grid.height;
}
}
onCurrentIndexChanged: {
if (keyboardNavigationActive) {
ensureVisible(currentIndex);
}
}
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AsNeeded ScrollBar.vertical.policy: ScrollBar.AsNeeded
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
@@ -134,11 +157,15 @@ ScrollView {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
z: 10 z: 10
onEntered: { onEntered: {
if (hoverUpdatesSelection) if (hoverUpdatesSelection && !keyboardNavigationActive) {
currentIndex = index; currentIndex = index;
}
itemHovered(index); itemHovered(index);
} }
onPositionChanged: {
// Signal parent to reset keyboard navigation flag when mouse moves
keyboardNavigationReset();
}
onClicked: { onClicked: {
itemClicked(index, model); itemClicked(index, model);
} }

View File

@@ -15,10 +15,32 @@ ScrollView {
property bool showDescription: true property bool showDescription: true
property int itemSpacing: Theme.spacingS property int itemSpacing: Theme.spacingS
property bool hoverUpdatesSelection: true property bool hoverUpdatesSelection: true
property bool keyboardNavigationActive: false
signal keyboardNavigationReset()
signal itemClicked(int index, var modelData) signal itemClicked(int index, var modelData)
signal itemHovered(int index) signal itemHovered(int index)
// Ensure the current item is visible
function ensureVisible(index) {
if (index < 0 || index >= list.count) return;
var itemY = index * (itemHeight + itemSpacing);
var itemBottom = itemY + itemHeight;
if (itemY < list.contentY) {
list.contentY = itemY;
} else if (itemBottom > list.contentY + list.height) {
list.contentY = itemBottom - list.height;
}
}
onCurrentIndexChanged: {
if (keyboardNavigationActive) {
ensureVisible(currentIndex);
}
}
clip: true clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
@@ -134,11 +156,15 @@ ScrollView {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
z: 10 z: 10
onEntered: { onEntered: {
if (hoverUpdatesSelection) if (hoverUpdatesSelection && !keyboardNavigationActive) {
listView.currentIndex = index; listView.currentIndex = index;
}
itemHovered(index); itemHovered(index);
} }
onPositionChanged: {
// Signal parent to reset keyboard navigation flag when mouse moves
keyboardNavigationReset();
}
onClicked: { onClicked: {
itemClicked(index, model); itemClicked(index, model);
} }