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:
@@ -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
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() {
|
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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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"])
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user