mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
refactor: perf improvement stopping singletons with ref
Also switch to scale+opacity anims with custom curve
This commit is contained in:
@@ -7,11 +7,16 @@ import Quickshell
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
readonly property int durShort: 160
|
||||
readonly property int durMed: 220
|
||||
readonly property int durLong: 320
|
||||
readonly property int durShort: 200
|
||||
readonly property int durMed: 400
|
||||
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 int fromLeft: 0
|
||||
@@ -20,30 +25,26 @@ Singleton {
|
||||
}
|
||||
|
||||
readonly property Component slideInLeft: Transition {
|
||||
NumberAnimation { properties: "x"; from: -Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutCubic }
|
||||
NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durShort }
|
||||
NumberAnimation { properties: "x"; from: -Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutQuart }
|
||||
}
|
||||
|
||||
readonly property Component slideOutLeft: Transition {
|
||||
NumberAnimation { properties: "x"; to: -Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InCubic }
|
||||
NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort }
|
||||
NumberAnimation { properties: "x"; to: -Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InQuart }
|
||||
}
|
||||
|
||||
readonly property Component slideInRight: Transition {
|
||||
NumberAnimation { properties: "x"; from: Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutCubic }
|
||||
NumberAnimation { properties: "opacity"; from: 0.0; to: 1.0; duration: Anims.durShort }
|
||||
NumberAnimation { properties: "x"; from: Anims.slidePx; to: 0; duration: Anims.durMed; easing.type: Easing.OutQuart }
|
||||
}
|
||||
|
||||
readonly property Component slideOutRight: Transition {
|
||||
NumberAnimation { properties: "x"; to: Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InCubic }
|
||||
NumberAnimation { properties: "opacity"; to: 0.0; duration: Anims.durShort }
|
||||
NumberAnimation { properties: "x"; to: Anims.slidePx; duration: Anims.durShort; easing.type: Easing.InQuart }
|
||||
}
|
||||
|
||||
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 {
|
||||
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
9
Common/Ref.qml
Normal file
@@ -0,0 +1,9 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
required property Singleton service
|
||||
|
||||
Component.onCompleted: service.refCount++
|
||||
Component.onDestruction: service.refCount--
|
||||
}
|
||||
@@ -19,8 +19,6 @@ DankModal {
|
||||
|
||||
function show() {
|
||||
processListModal.visible = true;
|
||||
ProcessMonitorService.updateSystemInfo();
|
||||
ProcessMonitorService.updateProcessList();
|
||||
SystemMonitorService.enableDetailedMonitoring(true);
|
||||
SystemMonitorService.updateSystemInfo();
|
||||
UserInfoService.getUptime();
|
||||
@@ -46,9 +44,10 @@ DankModal {
|
||||
cornerRadius: Theme.cornerRadiusXLarge
|
||||
enableShadow: true
|
||||
|
||||
onVisibleChanged: {
|
||||
ProcessMonitorService.enableMonitoring(visible);
|
||||
Ref {
|
||||
service: ProcessMonitorService
|
||||
}
|
||||
|
||||
|
||||
onBackgroundClicked: hide()
|
||||
|
||||
|
||||
@@ -71,7 +71,9 @@ DankModal {
|
||||
gridColumns: 4
|
||||
|
||||
onAppLaunched: hide()
|
||||
onViewModeSelected: Prefs.setSpotlightModalViewMode(mode)
|
||||
onViewModeSelected: function(mode) {
|
||||
Prefs.setSpotlightModalViewMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -265,12 +267,16 @@ DankModal {
|
||||
iconSize: 40
|
||||
showDescription: true
|
||||
hoverUpdatesSelection: false
|
||||
keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view
|
||||
@@ -288,12 +294,16 @@ DankModal {
|
||||
maxIconSize: 48
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
hoverUpdatesSelection: false
|
||||
keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,21 +14,14 @@ PanelWindow {
|
||||
id: appDrawerPopout
|
||||
|
||||
property bool isVisible: false
|
||||
property bool showCategories: false
|
||||
|
||||
|
||||
|
||||
function show() {
|
||||
appDrawerPopout.isVisible = true;
|
||||
searchField.enabled = true;
|
||||
appLauncher.searchQuery = "";
|
||||
}
|
||||
|
||||
function hide() {
|
||||
searchField.enabled = false; // Disable before hiding to prevent Wayland warnings
|
||||
appDrawerPopout.isVisible = false;
|
||||
searchField.text = "";
|
||||
showCategories = false;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
@@ -62,491 +55,341 @@ PanelWindow {
|
||||
gridColumns: 4
|
||||
|
||||
onAppLaunched: appDrawerPopout.hide()
|
||||
onViewModeSelected: Prefs.setAppLauncherViewMode(mode)
|
||||
onViewModeSelected: function(mode) {
|
||||
Prefs.setAppLauncherViewMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
// Background click to close (no visual background)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
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 {
|
||||
id: iconComponent
|
||||
|
||||
Item {
|
||||
property var appData: parent.modelData || {
|
||||
// Main launcher panel with asynchronous loading
|
||||
Loader {
|
||||
id: launcherLoader
|
||||
asynchronous: true
|
||||
active: appDrawerPopout.isVisible
|
||||
|
||||
width: 520
|
||||
height: 600
|
||||
x: Theme.spacingL
|
||||
y: Theme.barHeight + Theme.spacingXS
|
||||
|
||||
// GPU-accelerated scale + opacity animation
|
||||
opacity: appDrawerPopout.isVisible ? 1 : 0
|
||||
scale: appDrawerPopout.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: launcherPanel
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
|
||||
// Remove layer rendering for better performance
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
|
||||
IconImage {
|
||||
id: iconImg
|
||||
|
||||
// Material 3 elevation with multiple layers
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
source: (appData && appData.icon) ? Quickshell.iconPath(appData.icon, "") : ""
|
||||
smooth: true
|
||||
asynchronous: true
|
||||
visible: status === Image.Ready
|
||||
anchors.margins: -3
|
||||
color: "transparent"
|
||||
radius: parent.radius + 3
|
||||
border.color: Qt.rgba(0, 0, 0, 0.05)
|
||||
border.width: 1
|
||||
z: -3
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !iconImg.visible
|
||||
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
|
||||
radius: Theme.cornerRadiusLarge
|
||||
anchors.margins: -2
|
||||
color: "transparent"
|
||||
radius: parent.radius + 2
|
||||
border.color: Qt.rgba(0, 0, 0, 0.08)
|
||||
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
|
||||
}
|
||||
|
||||
z: -2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Main launcher panel with enhanced design
|
||||
Rectangle {
|
||||
id: launcherPanel
|
||||
|
||||
width: 520
|
||||
height: 600
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusXLarge
|
||||
opacity: appDrawerPopout.isVisible ? 1 : 0
|
||||
x: appDrawerPopout.isVisible ? Theme.spacingL : Theme.spacingL - Anims.slidePx
|
||||
y: Theme.barHeight + Theme.spacingXS
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Anims.durMed
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Material 3 elevation with multiple layers
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -3
|
||||
color: "transparent"
|
||||
radius: parent.radius + 3
|
||||
border.color: Qt.rgba(0, 0, 0, 0.05)
|
||||
border.width: 1
|
||||
z: -3
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -2
|
||||
color: "transparent"
|
||||
radius: parent.radius + 2
|
||||
border.color: Qt.rgba(0, 0, 0, 0.08)
|
||||
border.width: 1
|
||||
z: -2
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
radius: parent.radius
|
||||
z: -1
|
||||
}
|
||||
|
||||
// Content with focus management
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Component.onCompleted: {
|
||||
if (appDrawerPopout.isVisible)
|
||||
forceActiveFocus();
|
||||
|
||||
}
|
||||
// Handle keyboard shortcuts
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
appDrawerPopout.hide();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
appLauncher.selectNext();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
appLauncher.selectPrevious();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
appLauncher.launchSelected();
|
||||
event.accepted = true;
|
||||
} 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
|
||||
searchField.forceActiveFocus();
|
||||
searchField.text = event.text;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
color: "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
border.width: 1
|
||||
radius: parent.radius
|
||||
z: -1
|
||||
}
|
||||
|
||||
// Header section
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
// App launcher title
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Applications"
|
||||
font.pixelSize: Theme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
// Content with focus management
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
focus: true
|
||||
Component.onCompleted: {
|
||||
if (appDrawerPopout.isVisible)
|
||||
forceActiveFocus();
|
||||
}
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
Keys.onPressed: function(event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
appDrawerPopout.hide();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down) {
|
||||
appLauncher.selectNext();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up) {
|
||||
appLauncher.selectPrevious();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectNextInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") {
|
||||
appLauncher.selectPreviousInRow();
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
|
||||
appLauncher.launchSelected();
|
||||
event.accepted = true;
|
||||
} 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
|
||||
searchField.forceActiveFocus();
|
||||
searchField.text = event.text;
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 200
|
||||
height: 1
|
||||
}
|
||||
|
||||
// Quick stats
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: appLauncher.model.count + " apps"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Enhanced search field
|
||||
DankTextField {
|
||||
id: searchField
|
||||
Column {
|
||||
width: parent.width - Theme.spacingXL * 2
|
||||
height: parent.height - Theme.spacingXL * 2
|
||||
x: Theme.spacingXL
|
||||
y: Theme.spacingXL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
width: parent.width
|
||||
height: 52
|
||||
cornerRadius: Theme.cornerRadiusLarge
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
|
||||
normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: appDrawerPopout.isVisible
|
||||
placeholderText: "Search applications..."
|
||||
onTextEdited: {
|
||||
appLauncher.searchQuery = text;
|
||||
}
|
||||
Keys.onPressed: function(event) {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.model.count && text.length > 0) {
|
||||
// Launch first app when typing in search field
|
||||
var firstApp = appLauncher.model.get(0);
|
||||
appLauncher.launchApp(firstApp);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") || (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
// Pass navigation keys and enter (when not searching) to main handler
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
// Header section
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Connections {
|
||||
function onVisibleChanged() {
|
||||
if (appDrawerPopout.visible)
|
||||
searchField.forceActiveFocus();
|
||||
else
|
||||
searchField.clearFocus();
|
||||
}
|
||||
|
||||
target: appDrawerPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Category filter and view mode controls
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
spacing: Theme.spacingM
|
||||
visible: searchField.text.length === 0
|
||||
|
||||
// Category filter
|
||||
Rectangle {
|
||||
width: 200
|
||||
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 {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
// App launcher title
|
||||
Text {
|
||||
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
|
||||
}
|
||||
|
||||
text: "Applications"
|
||||
font.pixelSize: Theme.fontSizeLarge + 4
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
Item {
|
||||
width: parent.width - 200
|
||||
height: 1
|
||||
}
|
||||
|
||||
// Quick stats
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
name: showCategories ? "expand_less" : "expand_more"
|
||||
size: 18
|
||||
text: appLauncher.model.count + " apps"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: showCategories = !showCategories
|
||||
// Enhanced search field
|
||||
DankTextField {
|
||||
id: searchField
|
||||
|
||||
width: parent.width
|
||||
height: 52
|
||||
cornerRadius: Theme.cornerRadiusLarge
|
||||
backgroundColor: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, Theme.getContentBackgroundAlpha() * 0.7)
|
||||
normalBorderColor: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
focusedBorderColor: Theme.primary
|
||||
leftIconName: "search"
|
||||
leftIconSize: Theme.iconSize
|
||||
leftIconColor: Theme.surfaceVariantText
|
||||
leftIconFocusedColor: Theme.primary
|
||||
showClearButton: true
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
enabled: appDrawerPopout.isVisible
|
||||
placeholderText: "Search applications..."
|
||||
onTextEdited: {
|
||||
appLauncher.searchQuery = text;
|
||||
}
|
||||
Keys.onPressed: function(event) {
|
||||
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.model.count && text.length > 0) {
|
||||
// Launch first app when typing in search field
|
||||
var firstApp = appLauncher.model.get(0);
|
||||
appLauncher.launchApp(firstApp);
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || (event.key === Qt.Key_Left && appLauncher.viewMode === "grid") || (event.key === Qt.Key_Right && appLauncher.viewMode === "grid") || ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length === 0)) {
|
||||
// Pass navigation keys and enter (when not searching) to main handler
|
||||
event.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
function onVisibleChanged() {
|
||||
if (appDrawerPopout.visible)
|
||||
searchField.forceActiveFocus();
|
||||
else
|
||||
searchField.clearFocus();
|
||||
}
|
||||
|
||||
target: appDrawerPopout
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 300
|
||||
height: 1
|
||||
}
|
||||
|
||||
// View mode toggle
|
||||
// Category filter and view mode controls
|
||||
Row {
|
||||
spacing: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 40
|
||||
spacing: Theme.spacingM
|
||||
visible: searchField.text.length === 0
|
||||
|
||||
// List view button
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
circular: false
|
||||
iconName: "view_list"
|
||||
iconSize: 20
|
||||
iconColor: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
|
||||
hoverColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
backgroundColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("list");
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view button
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
circular: false
|
||||
iconName: "grid_view"
|
||||
iconSize: 20
|
||||
iconColor: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
|
||||
hoverColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
backgroundColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("grid");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// App grid/list container
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: {
|
||||
// Calculate more precise remaining height
|
||||
let usedHeight = 40 + Theme.spacingL;
|
||||
// Header
|
||||
usedHeight += 52 + Theme.spacingL;
|
||||
// Search container
|
||||
usedHeight += (searchField.text.length === 0 ? 40 + Theme.spacingL : 0);
|
||||
// Category/controls when visible
|
||||
return parent.height - usedHeight;
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
// List view
|
||||
DankListView {
|
||||
id: appList
|
||||
|
||||
anchors.fill: parent
|
||||
visible: appLauncher.viewMode === "list"
|
||||
model: appLauncher.model
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
itemHeight: 72
|
||||
iconSize: 56
|
||||
showDescription: true
|
||||
hoverUpdatesSelection: false
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view
|
||||
DankGridView {
|
||||
id: appGrid
|
||||
|
||||
anchors.fill: parent
|
||||
visible: appLauncher.viewMode === "grid"
|
||||
model: appLauncher.model
|
||||
columns: 4
|
||||
adaptiveColumns: false
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
hoverUpdatesSelection: false
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Category filter using DankDropdown
|
||||
Item {
|
||||
width: 200
|
||||
height: 36
|
||||
|
||||
DankDropdown {
|
||||
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);
|
||||
text: ""
|
||||
currentValue: appLauncher.selectedCategory
|
||||
options: appLauncher.categories
|
||||
optionIcons: appLauncher.categoryIcons
|
||||
onValueChanged: function(value) {
|
||||
appLauncher.setCategory(value);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width - 300
|
||||
height: 1
|
||||
}
|
||||
|
||||
// View mode toggle
|
||||
Row {
|
||||
spacing: 4
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
// List view button
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
circular: false
|
||||
iconName: "view_list"
|
||||
iconSize: 20
|
||||
iconColor: appLauncher.viewMode === "list" ? Theme.primary : Theme.surfaceText
|
||||
hoverColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
backgroundColor: appLauncher.viewMode === "list" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("list");
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view button
|
||||
DankActionButton {
|
||||
buttonSize: 36
|
||||
circular: false
|
||||
iconName: "grid_view"
|
||||
iconSize: 20
|
||||
iconColor: appLauncher.viewMode === "grid" ? Theme.primary : Theme.surfaceText
|
||||
hoverColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
|
||||
backgroundColor: appLauncher.viewMode === "grid" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
onClicked: {
|
||||
appLauncher.setViewMode("grid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// App grid/list container
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: {
|
||||
// Calculate more precise remaining height
|
||||
let usedHeight = 40 + Theme.spacingL;
|
||||
// Header
|
||||
usedHeight += 52 + Theme.spacingL;
|
||||
// Search container
|
||||
usedHeight += (searchField.text.length === 0 ? 40 + Theme.spacingL : 0);
|
||||
// Category/controls when visible
|
||||
return parent.height - usedHeight;
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
// List view
|
||||
DankListView {
|
||||
id: appList
|
||||
|
||||
anchors.fill: parent
|
||||
visible: appLauncher.viewMode === "list"
|
||||
model: appLauncher.model
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
itemHeight: 72
|
||||
iconSize: 56
|
||||
showDescription: true
|
||||
hoverUpdatesSelection: false
|
||||
keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid view
|
||||
DankGridView {
|
||||
id: appGrid
|
||||
|
||||
anchors.fill: parent
|
||||
visible: appLauncher.viewMode === "grid"
|
||||
model: appLauncher.model
|
||||
columns: 4
|
||||
adaptiveColumns: false
|
||||
currentIndex: appLauncher.selectedIndex
|
||||
hoverUpdatesSelection: false
|
||||
keyboardNavigationActive: appLauncher.keyboardNavigationActive
|
||||
onItemClicked: function(index, modelData) {
|
||||
appLauncher.launchApp(modelData);
|
||||
}
|
||||
onItemHovered: function(index) {
|
||||
appLauncher.selectedIndex = index;
|
||||
}
|
||||
onKeyboardNavigationReset: {
|
||||
appLauncher.keyboardNavigationActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ Item {
|
||||
property int gridColumns: 4
|
||||
property bool debounceSearch: true
|
||||
property int debounceInterval: 50
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
// Categories (computed from AppSearchService)
|
||||
property var categories: {
|
||||
@@ -28,6 +29,9 @@ Item {
|
||||
return cat !== "All";
|
||||
}));
|
||||
}
|
||||
|
||||
// Category icons (computed from AppSearchService)
|
||||
property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
|
||||
|
||||
// Recent apps helper
|
||||
property var recentApps: Prefs.recentlyUsedApps.map(recentApp => {
|
||||
@@ -129,6 +133,7 @@ Item {
|
||||
// Keyboard navigation functions
|
||||
function selectNext() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true;
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.min(selectedIndex + gridColumns, filteredModel.count - 1);
|
||||
selectedIndex = newIndex;
|
||||
@@ -140,6 +145,7 @@ Item {
|
||||
|
||||
function selectPrevious() {
|
||||
if (filteredModel.count > 0) {
|
||||
keyboardNavigationActive = true;
|
||||
if (viewMode === "grid") {
|
||||
var newIndex = Math.max(selectedIndex - gridColumns, 0);
|
||||
selectedIndex = newIndex;
|
||||
@@ -151,12 +157,14 @@ Item {
|
||||
|
||||
function selectNextInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.min(selectedIndex + 1, filteredModel.count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function selectPreviousInRow() {
|
||||
if (filteredModel.count > 0 && viewMode === "grid") {
|
||||
keyboardNavigationActive = true;
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +87,23 @@ PanelWindow {
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
opacity: calendarVisible ? 1 : 0
|
||||
scale: calendarVisible ? 1 : 0.9
|
||||
x: (Screen.width - targetWidth) / 2
|
||||
y: Theme.barHeight + 4
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.OutCubic
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,8 +251,13 @@ PanelWindow {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
enabled: calendarVisible
|
||||
onClicked: {
|
||||
calendarVisible = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -317,8 +317,8 @@ Rectangle {
|
||||
|
||||
x: 0
|
||||
y: 0
|
||||
width: mediaPlayerWidget.width
|
||||
height: mediaPlayerWidget.height
|
||||
width: mediaPlayer.width
|
||||
height: mediaPlayer.height
|
||||
enabled: progressMouseArea.isSeeking
|
||||
visible: false
|
||||
preventStealing: true
|
||||
|
||||
@@ -15,6 +15,10 @@ Rectangle {
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
layer.enabled: true
|
||||
|
||||
Ref {
|
||||
service: WeatherService
|
||||
}
|
||||
|
||||
// Placeholder when no weather - centered in entire widget
|
||||
Column {
|
||||
|
||||
@@ -47,25 +47,46 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Loader {
|
||||
id: contentLoader
|
||||
asynchronous: true
|
||||
active: controlCenterVisible
|
||||
|
||||
readonly property real targetWidth: Math.min(600, Screen.width - Theme.spacingL * 2)
|
||||
width: targetWidth
|
||||
height: root.powerOptionsExpanded ? 570 : 500
|
||||
y: Theme.barHeight + Theme.spacingXS
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
|
||||
|
||||
// GPU-accelerated scale + opacity animation
|
||||
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 {
|
||||
scale: controlCenterVisible ? 1 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
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 {
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
// Remove layer rendering for better performance
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
@@ -647,30 +668,27 @@ PanelWindow {
|
||||
|
||||
}
|
||||
|
||||
// Power menu height animation
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration // Faster for height changes
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.OutCubic
|
||||
// Power menu height animation
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration // Faster for height changes
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Click outside to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
onClicked: {
|
||||
controlCenterVisible = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ Column {
|
||||
model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? sortedWifiNetworks : []
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
width: spanningNetworksColumn.width
|
||||
height: 38
|
||||
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"
|
||||
|
||||
@@ -23,8 +23,6 @@ PanelWindow {
|
||||
|
||||
function show() {
|
||||
isVisible = true;
|
||||
ProcessMonitorService.updateSystemInfo();
|
||||
ProcessMonitorService.updateProcessList();
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
@@ -35,9 +33,11 @@ PanelWindow {
|
||||
}
|
||||
|
||||
visible: isVisible
|
||||
onIsVisibleChanged: {
|
||||
ProcessMonitorService.enableMonitoring(isVisible);
|
||||
|
||||
Ref {
|
||||
service: ProcessMonitorService
|
||||
}
|
||||
|
||||
implicitWidth: 600
|
||||
implicitHeight: 600
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
@@ -54,84 +54,86 @@ PanelWindow {
|
||||
|
||||
MouseArea {
|
||||
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 {
|
||||
id: dropdownContent
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
asynchronous: true
|
||||
active: processListPopout.isVisible
|
||||
|
||||
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)
|
||||
width: targetWidth
|
||||
height: targetHeight
|
||||
y: Theme.barHeight + Theme.spacingXS
|
||||
radius: Theme.cornerRadiusLarge
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
clip: true
|
||||
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
|
||||
|
||||
// GPU-accelerated scale + opacity animation
|
||||
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 {
|
||||
NumberAnimation {
|
||||
duration: Anims.durMed
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
SystemOverview {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
ProcessListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
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
|
||||
}
|
||||
|
||||
scale: processListPopout.isVisible ? 1 : 0.9
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.OutCubic
|
||||
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
|
||||
color: Theme.popupBackground()
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
// Remove layer rendering for better performance
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
SystemOverview {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
|
||||
}
|
||||
|
||||
ProcessListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
contextMenu: processContextMenuWindow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessContextMenu {
|
||||
id: processContextMenuWindow
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,33 +48,58 @@ PanelWindow {
|
||||
// Click outside to dismiss overlay
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
batteryPopupVisible = false;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Loader {
|
||||
id: contentLoader
|
||||
asynchronous: true
|
||||
active: batteryPopupVisible
|
||||
|
||||
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)
|
||||
width: targetWidth
|
||||
height: targetHeight
|
||||
property real normalX: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
|
||||
x: batteryPopupVisible ? normalX : normalX + Anims.slidePx
|
||||
x: Math.max(Theme.spacingL, Screen.width - targetWidth - Theme.spacingL)
|
||||
y: Theme.barHeight + Theme.spacingS
|
||||
color: Theme.popupBackground()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
// GPU-accelerated scale + opacity animation
|
||||
opacity: batteryPopupVisible ? 1 : 0
|
||||
|
||||
// Prevent click-through to background
|
||||
MouseArea {
|
||||
// Consume the click to prevent it from reaching the background
|
||||
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
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()
|
||||
radius: Theme.cornerRadiusLarge
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||
border.width: 1
|
||||
|
||||
// Remove layer rendering for better performance
|
||||
antialiasing: true
|
||||
smooth: true
|
||||
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
@@ -461,20 +486,8 @@ PanelWindow {
|
||||
|
||||
}
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Anims.durMed
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ PanelWindow {
|
||||
|
||||
Media {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: clockWidget.left
|
||||
anchors.right: clock.left
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
visible: Prefs.showMusic && MprisController.activePlayer
|
||||
onClicked: {
|
||||
|
||||
@@ -13,6 +13,10 @@ Rectangle {
|
||||
height: 30
|
||||
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)
|
||||
|
||||
Ref {
|
||||
service: WeatherService
|
||||
}
|
||||
|
||||
Row {
|
||||
id: weatherRow
|
||||
|
||||
@@ -116,6 +116,25 @@ Singleton {
|
||||
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() {
|
||||
var categories = new Set(["All"])
|
||||
|
||||
|
||||
@@ -6,14 +6,13 @@ import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
Singleton {
|
||||
// console.log("ProcessMonitorService: Updated - CPU:", root.totalCpuUsage.toFixed(1) + "%", "Memory:", memoryPercent.toFixed(1) + "%", "History length:", root.cpuHistory.length)
|
||||
|
||||
id: root
|
||||
|
||||
property int refCount
|
||||
|
||||
property var processes: []
|
||||
property bool isUpdating: false
|
||||
property int processUpdateInterval: 3000
|
||||
property bool monitoringEnabled: false
|
||||
property int totalMemoryKB: 0
|
||||
property int usedMemoryKB: 0
|
||||
property int totalSwapKB: 0
|
||||
@@ -43,47 +42,8 @@ Singleton {
|
||||
property bool sortDescending: true
|
||||
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() {
|
||||
if (!root.isUpdating && root.monitoringEnabled) {
|
||||
if (!root.isUpdating) {
|
||||
root.isUpdating = true;
|
||||
let sortOption = "";
|
||||
switch (root.sortBy) {
|
||||
@@ -315,10 +275,17 @@ Singleton {
|
||||
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("ProcessMonitorService: Starting initialization...");
|
||||
updateProcessList();
|
||||
console.log("ProcessMonitorService: Initialization complete");
|
||||
Timer {
|
||||
running: root.refCount > 0
|
||||
interval: root.processUpdateInterval
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
systemInfoProcess.running = true;
|
||||
updateProcessList();
|
||||
networkStatsProcess.running = true;
|
||||
diskStatsProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import qs.Common
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property int refCount: 0
|
||||
|
||||
property var weather: ({
|
||||
available: false,
|
||||
loading: true,
|
||||
@@ -95,7 +97,27 @@ Singleton {
|
||||
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() {
|
||||
// Only fetch if someone is consuming the data
|
||||
if (root.refCount === 0) {
|
||||
console.log("WeatherService: Skipping fetch - no consumers");
|
||||
return;
|
||||
}
|
||||
|
||||
if (weatherFetcher.running) {
|
||||
console.log("Weather fetch already in progress, skipping")
|
||||
return
|
||||
@@ -217,7 +239,7 @@ Singleton {
|
||||
Timer {
|
||||
id: updateTimer
|
||||
interval: root.updateInterval
|
||||
running: true
|
||||
running: root.refCount > 0
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
|
||||
@@ -12,6 +12,7 @@ Rectangle {
|
||||
property string description: ""
|
||||
property string currentValue: ""
|
||||
property var options: []
|
||||
property var optionIcons: [] // Array of icon names corresponding to options
|
||||
|
||||
signal valueChanged(string value)
|
||||
|
||||
@@ -72,13 +73,33 @@ Rectangle {
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
|
||||
Text {
|
||||
text: root.currentValue
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
Row {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width - 24
|
||||
elide: Text.ElideRight
|
||||
|
||||
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: root.currentValue
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.parent.width - (visible ? 24 + Theme.spacingS : 24) - (parent.children[0].visible ? 18 + Theme.spacingS : 0)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
@@ -170,14 +191,27 @@ Rectangle {
|
||||
radius: Theme.cornerRadiusSmall
|
||||
color: optionArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : "transparent"
|
||||
|
||||
Text {
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText
|
||||
font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal
|
||||
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
|
||||
text: modelData
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText
|
||||
font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -19,10 +19,33 @@ ScrollView {
|
||||
property int minIconSize: 32
|
||||
property real wheelStepSize: 60
|
||||
property bool hoverUpdatesSelection: true
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
signal keyboardNavigationReset()
|
||||
|
||||
signal itemClicked(int index, var modelData)
|
||||
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
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
@@ -134,11 +157,15 @@ ScrollView {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection)
|
||||
if (hoverUpdatesSelection && !keyboardNavigationActive) {
|
||||
currentIndex = index;
|
||||
|
||||
}
|
||||
itemHovered(index);
|
||||
}
|
||||
onPositionChanged: {
|
||||
// Signal parent to reset keyboard navigation flag when mouse moves
|
||||
keyboardNavigationReset();
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,32 @@ ScrollView {
|
||||
property bool showDescription: true
|
||||
property int itemSpacing: Theme.spacingS
|
||||
property bool hoverUpdatesSelection: true
|
||||
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
signal keyboardNavigationReset()
|
||||
signal itemClicked(int index, var modelData)
|
||||
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
|
||||
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
@@ -134,11 +156,15 @@ ScrollView {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
z: 10
|
||||
onEntered: {
|
||||
if (hoverUpdatesSelection)
|
||||
if (hoverUpdatesSelection && !keyboardNavigationActive) {
|
||||
listView.currentIndex = index;
|
||||
|
||||
}
|
||||
itemHovered(index);
|
||||
}
|
||||
onPositionChanged: {
|
||||
// Signal parent to reset keyboard navigation flag when mouse moves
|
||||
keyboardNavigationReset();
|
||||
}
|
||||
onClicked: {
|
||||
itemClicked(index, model);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user