mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-08 04:09:15 -04:00
d2905072c0
* feat(Autostart): add Autostart tab and application selection popup * fix(AutoStartTab): update systemdUserDir property to use XDG_CONFIG_HOME * fix(AutoStartTab): update autostartDir and systemdUserDir to use StandardPaths for config home * refactor(AutoStartTab): use FileView & FolderListModel * refactor(AutoStartTab): implement systemd override generation for autostart applications using FileView * feat(AutoStartTab): add systemd check to determine environment and update tray icon visibility * feat(SettingsSidebar, AutoStartTab, DesktopService): add autostart functionality and systemd checks * feat(AutoStartTab): add hidden property support for desktop entries and toggle functionality * feat(AutoStartTab): add initialize autostart directory and add toast if writer failed * add(AutoStartTab): logging for scoped log tracking ---------
333 lines
12 KiB
QML
333 lines
12 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import qs.Common
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
FloatingWindow {
|
|
id: root
|
|
|
|
property bool disablePopupTransparency: true
|
|
property string searchQuery: ""
|
|
property var filteredApps: []
|
|
property int selectedIndex: -1
|
|
property bool keyboardNavigationActive: false
|
|
property var appsModel: []
|
|
property var parentModal: null
|
|
parentWindow: parentModal
|
|
|
|
signal appSelected(string appId)
|
|
|
|
objectName: "appBrowserPopup"
|
|
title: I18n.tr("Select Application")
|
|
minimumSize: Qt.size(400, 350)
|
|
implicitWidth: 500
|
|
implicitHeight: 550
|
|
color: "transparent"
|
|
visible: false
|
|
|
|
WindowBlur {
|
|
targetWindow: root
|
|
blurX: 0
|
|
blurY: 0
|
|
blurWidth: root.visible ? root.width : 0
|
|
blurHeight: root.visible ? root.height : 0
|
|
blurRadius: Theme.cornerRadius
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Theme.cornerRadius
|
|
color: Theme.withAlpha(Theme.surfaceContainer, 0.95)
|
|
border.color: Theme.outlineMedium
|
|
border.width: Theme.layerOutlineWidth
|
|
}
|
|
|
|
FocusScope {
|
|
anchors.fill: parent
|
|
focus: true
|
|
|
|
Keys.onPressed: event => {
|
|
switch (event.key) {
|
|
case Qt.Key_Escape:
|
|
root.hide();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Down:
|
|
root.selectNext();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Up:
|
|
root.selectPrevious();
|
|
event.accepted = true;
|
|
return;
|
|
case Qt.Key_Return:
|
|
case Qt.Key_Enter:
|
|
if (root.keyboardNavigationActive) {
|
|
root.selectApp();
|
|
} else if (root.filteredApps.length > 0) {
|
|
root.selectAppByIndex(0);
|
|
}
|
|
event.accepted = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: 0
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: 48
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onPressed: windowControls.tryStartMove()
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, 0.5)
|
|
}
|
|
|
|
Row {
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: Theme.spacingL
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingM
|
|
|
|
DankIcon {
|
|
name: "add_circle"
|
|
size: Theme.iconSize
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
|
|
StyledText {
|
|
text: I18n.tr("Select Application")
|
|
font.pixelSize: Theme.fontSizeXLarge
|
|
color: Theme.surfaceText
|
|
font.weight: Font.Medium
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
Row {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.spacingM
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: Theme.spacingXS
|
|
|
|
DankActionButton {
|
|
iconName: "close"
|
|
iconSize: Theme.iconSize - 4
|
|
iconColor: Theme.surfaceText
|
|
onClicked: root.hide()
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: parent.width
|
|
height: parent.height - 48
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingL
|
|
spacing: Theme.spacingM
|
|
|
|
DankTextField {
|
|
id: searchField
|
|
width: parent.width
|
|
height: 48
|
|
cornerRadius: Theme.cornerRadius
|
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainerHigh, 0.8)
|
|
normalBorderColor: Theme.outlineMedium
|
|
focusedBorderColor: Theme.primary
|
|
leftIconName: "search"
|
|
leftIconSize: Theme.iconSize
|
|
leftIconColor: Theme.surfaceVariantText
|
|
leftIconFocusedColor: Theme.primary
|
|
showClearButton: true
|
|
textColor: Theme.surfaceText
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
placeholderText: I18n.tr("Search applications...")
|
|
text: root.searchQuery
|
|
onTextEdited: {
|
|
root.searchQuery = text;
|
|
root.updateFilteredApps();
|
|
}
|
|
}
|
|
|
|
DankListView {
|
|
id: appList
|
|
width: parent.width
|
|
height: parent.height - searchField.height - Theme.spacingM
|
|
spacing: Theme.spacingS
|
|
model: root.filteredApps
|
|
clip: true
|
|
|
|
delegate: Rectangle {
|
|
width: appList.width
|
|
height: 60
|
|
radius: Theme.cornerRadius
|
|
required property int index
|
|
required property var modelData
|
|
|
|
readonly property bool isSelected: root.keyboardNavigationActive && index === root.selectedIndex
|
|
|
|
color: isSelected ? Theme.withAlpha(Theme.primary, 0.16) : appArea.containsMouse ? Theme.withAlpha(Theme.primary, 0.08) : Theme.withAlpha(Theme.surfaceVariant, 0.3)
|
|
border.color: isSelected ? Theme.primary : Theme.outlineMedium
|
|
border.width: isSelected ? 2 : Theme.layerOutlineWidth
|
|
|
|
Row {
|
|
anchors.fill: parent
|
|
anchors.margins: Theme.spacingM
|
|
spacing: Theme.spacingM
|
|
|
|
Image {
|
|
width: 28
|
|
height: 28
|
|
source: Paths.resolveIconUrl(modelData.icon || "application-x-executable")
|
|
sourceSize.width: 28
|
|
sourceSize.height: 28
|
|
fillMode: Image.PreserveAspectFit
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
onStatusChanged: {
|
|
if (status === Image.Error)
|
|
source = "image://icon/application-x-executable";
|
|
}
|
|
}
|
|
|
|
Column {
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 2
|
|
width: parent.width - 28 - Theme.spacingM * 3 - 24
|
|
|
|
StyledText {
|
|
text: modelData.name || modelData.id || ""
|
|
font.pixelSize: Theme.fontSizeMedium
|
|
font.weight: Font.Medium
|
|
color: Theme.surfaceText
|
|
elide: Text.ElideRight
|
|
width: parent.width
|
|
}
|
|
|
|
StyledText {
|
|
text: modelData.comment || modelData.genericName || ""
|
|
font.pixelSize: Theme.fontSizeSmall
|
|
color: Theme.outline
|
|
elide: Text.ElideRight
|
|
width: parent.width
|
|
}
|
|
}
|
|
|
|
DankIcon {
|
|
name: "add"
|
|
size: Theme.iconSize - 4
|
|
color: Theme.primary
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: appArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
cursorShape: Qt.PointingHandCursor
|
|
onClicked: {
|
|
const appId = modelData.id || modelData.execString || "";
|
|
root.appSelected(appId);
|
|
root.hide();
|
|
}
|
|
}
|
|
|
|
Behavior on color {
|
|
ColorAnimation {
|
|
duration: Theme.shortDuration
|
|
easing.type: Theme.standardEasing
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FloatingWindowControls {
|
|
id: windowControls
|
|
targetWindow: root
|
|
}
|
|
}
|
|
|
|
function updateFilteredApps() {
|
|
const allApps = root.appsModel || [];
|
|
var filtered = [];
|
|
if (!searchQuery || searchQuery.length === 0) {
|
|
filtered = allApps.slice();
|
|
} else {
|
|
var query = searchQuery.toLowerCase();
|
|
for (var i = 0; i < allApps.length; i++) {
|
|
var app = allApps[i];
|
|
var name = (app.name || "").toLowerCase();
|
|
var id = (app.id || "").toLowerCase();
|
|
var comment = (app.comment || app.genericName || "").toLowerCase();
|
|
if (name.indexOf(query) !== -1 || id.indexOf(query) !== -1 || comment.indexOf(query) !== -1)
|
|
filtered.push(app);
|
|
}
|
|
}
|
|
filteredApps = filtered;
|
|
selectedIndex = -1;
|
|
keyboardNavigationActive = false;
|
|
}
|
|
|
|
function selectNext() {
|
|
if (filteredApps.length === 0) return;
|
|
keyboardNavigationActive = true;
|
|
selectedIndex = Math.min(selectedIndex + 1, filteredApps.length - 1);
|
|
}
|
|
|
|
function selectPrevious() {
|
|
if (filteredApps.length === 0) return;
|
|
keyboardNavigationActive = true;
|
|
selectedIndex = Math.max(selectedIndex - 1, -1);
|
|
if (selectedIndex === -1) keyboardNavigationActive = false;
|
|
}
|
|
|
|
function selectApp() {
|
|
if (selectedIndex < 0 || selectedIndex >= filteredApps.length) return;
|
|
selectAppByIndex(selectedIndex);
|
|
}
|
|
|
|
function selectAppByIndex(idx) {
|
|
const app = filteredApps[idx];
|
|
if (!app) return;
|
|
root.appSelected(app.id || app.execString || "");
|
|
hide();
|
|
}
|
|
|
|
function show() {
|
|
updateFilteredApps();
|
|
visible = true;
|
|
Qt.callLater(() => searchField.forceActiveFocus());
|
|
}
|
|
|
|
function hide() {
|
|
visible = false;
|
|
searchQuery = "";
|
|
filteredApps = [];
|
|
selectedIndex = -1;
|
|
keyboardNavigationActive = false;
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if (!visible) {
|
|
searchQuery = "";
|
|
filteredApps = [];
|
|
selectedIndex = -1;
|
|
keyboardNavigationActive = false;
|
|
}
|
|
}
|
|
}
|