controlcenter: launcher panel, can hide/show applications
This commit is contained in:
parent
7ad940ea9f
commit
97d461871a
4 changed files with 460 additions and 1 deletions
|
|
@ -186,6 +186,11 @@ Item {
|
|||
icon: "task_alt"
|
||||
label: "taskbar"
|
||||
}
|
||||
|
||||
NavItem {
|
||||
icon: "apps"
|
||||
label: "launcher"
|
||||
}
|
||||
}
|
||||
|
||||
component NavItem: Item {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import "network"
|
|||
import "audio"
|
||||
import "appearance"
|
||||
import "taskbar"
|
||||
import "launcher"
|
||||
import qs.components
|
||||
import qs.services
|
||||
import qs.config
|
||||
|
|
@ -68,6 +69,13 @@ ClippingRectangle {
|
|||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
index: 6
|
||||
sourceComponent: LauncherPane {
|
||||
session: root.session
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
Anim {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import Quickshell.Bluetooth
|
|||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
readonly property list<string> panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar"]
|
||||
readonly property list<string> panes: ["ethernet", "wireless", "bluetooth", "audio", "appearance", "taskbar", "launcher"]
|
||||
|
||||
required property var root
|
||||
property bool floating: false
|
||||
|
|
|
|||
446
modules/controlcenter/launcher/LauncherPane.qml
Normal file
446
modules/controlcenter/launcher/LauncherPane.qml
Normal file
|
|
@ -0,0 +1,446 @@
|
|||
pragma ComponentBehavior: Bound
|
||||
|
||||
import ".."
|
||||
import "../../launcher/services"
|
||||
import qs.components
|
||||
import qs.components.controls
|
||||
import qs.components.effects
|
||||
import qs.components.containers
|
||||
import qs.services
|
||||
import qs.config
|
||||
import qs.utils
|
||||
import Caelestia
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
RowLayout {
|
||||
id: root
|
||||
|
||||
required property Session session
|
||||
|
||||
property var selectedApp: null
|
||||
property bool showDebugInfo: false
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: 0
|
||||
|
||||
FileView {
|
||||
id: configFile
|
||||
|
||||
path: `${Paths.config}/shell.json`
|
||||
watchChanges: true
|
||||
|
||||
onLoaded: {
|
||||
try {
|
||||
const config = JSON.parse(text());
|
||||
updateToggleState();
|
||||
} catch (e) {
|
||||
console.error("Failed to parse config:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateToggleState() {
|
||||
if (!root.selectedApp || !configFile.loaded) {
|
||||
hideFromLauncherSwitch.checked = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(configFile.text());
|
||||
const appId = root.selectedApp.id || root.selectedApp.entry?.id;
|
||||
|
||||
if (config.launcher && config.launcher.hiddenApps) {
|
||||
hideFromLauncherSwitch.checked = config.launcher.hiddenApps.includes(appId);
|
||||
} else {
|
||||
hideFromLauncherSwitch.checked = false;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to update toggle state:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function saveHiddenApps() {
|
||||
if (!configFile.loaded || !root.selectedApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = JSON.parse(configFile.text());
|
||||
const appId = root.selectedApp.id || root.selectedApp.entry?.id;
|
||||
|
||||
if (!config.launcher) config.launcher = {};
|
||||
if (!config.launcher.hiddenApps) config.launcher.hiddenApps = [];
|
||||
|
||||
const hiddenApps = config.launcher.hiddenApps;
|
||||
const isHidden = hideFromLauncherSwitch.checked;
|
||||
|
||||
if (isHidden) {
|
||||
// Add to hiddenApps if not already there
|
||||
if (!hiddenApps.includes(appId)) {
|
||||
hiddenApps.push(appId);
|
||||
}
|
||||
} else {
|
||||
// Remove from hiddenApps
|
||||
const index = hiddenApps.indexOf(appId);
|
||||
if (index !== -1) {
|
||||
hiddenApps.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const jsonString = JSON.stringify(config, null, 4);
|
||||
configFile.setText(jsonString);
|
||||
} catch (e) {
|
||||
console.error("Failed to save config:", e);
|
||||
}
|
||||
}
|
||||
|
||||
onSelectedAppChanged: {
|
||||
updateToggleState();
|
||||
}
|
||||
|
||||
AppDb {
|
||||
id: allAppsDb
|
||||
|
||||
path: `${Paths.state}/apps.sqlite`
|
||||
entries: DesktopEntries.applications.values // No filter - show all apps
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: Math.floor(parent.width * 0.4)
|
||||
Layout.minimumWidth: 420
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.large + Appearance.padding.normal
|
||||
anchors.leftMargin: Appearance.padding.large
|
||||
anchors.rightMargin: Appearance.padding.large + Appearance.padding.normal / 2
|
||||
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
RowLayout {
|
||||
spacing: Appearance.spacing.smaller
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Settings")
|
||||
font.pointSize: Appearance.font.size.large
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
text: qsTr("Applications (%1)").arg(allAppsDb.apps.length)
|
||||
font.pointSize: Appearance.font.size.larger
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("All applications available in the launcher")
|
||||
color: Colours.palette.m3outline
|
||||
}
|
||||
|
||||
StyledListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
|
||||
model: allAppsDb.apps
|
||||
spacing: Appearance.spacing.small / 2
|
||||
clip: true
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: parent
|
||||
}
|
||||
|
||||
delegate: StyledRect {
|
||||
required property var modelData
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
readonly property bool isSelected: root.selectedApp === modelData
|
||||
|
||||
color: Qt.alpha(Colours.tPalette.m3surfaceContainer, isSelected ? Colours.tPalette.m3surfaceContainer.a : 0)
|
||||
radius: Appearance.rounding.normal
|
||||
border.width: isSelected ? 1 : 0
|
||||
border.color: Colours.palette.m3primary
|
||||
|
||||
StateLayer {
|
||||
function onClicked(): void {
|
||||
root.selectedApp = modelData;
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.normal
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
IconImage {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
implicitSize: 32
|
||||
source: {
|
||||
const entry = modelData.entry;
|
||||
return entry ? Quickshell.iconPath(entry.icon, "image-missing") : "image-missing";
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.name || modelData.entry?.name || qsTr("Unknown")
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
}
|
||||
}
|
||||
|
||||
implicitHeight: 40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InnerBorder {
|
||||
leftThickness: 0
|
||||
rightThickness: Appearance.padding.normal / 2
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.large * 2
|
||||
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
Item {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: iconLoader.implicitWidth
|
||||
implicitHeight: iconLoader.implicitHeight
|
||||
|
||||
Loader {
|
||||
id: iconLoader
|
||||
sourceComponent: root.selectedApp ? appIconComponent : defaultIconComponent
|
||||
}
|
||||
|
||||
Component {
|
||||
id: appIconComponent
|
||||
IconImage {
|
||||
implicitSize: Appearance.font.size.extraLarge * 3 * 2
|
||||
source: {
|
||||
if (!root.selectedApp) return "image-missing";
|
||||
const entry = root.selectedApp.entry;
|
||||
if (entry && entry.icon) {
|
||||
return Quickshell.iconPath(entry.icon, "image-missing");
|
||||
}
|
||||
return "image-missing";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: defaultIconComponent
|
||||
MaterialIcon {
|
||||
text: "apps"
|
||||
font.pointSize: Appearance.font.size.extraLarge * 3
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: root.selectedApp ? (root.selectedApp.name || root.selectedApp.entry?.name || qsTr("Application Details")) : qsTr("Launcher Applications")
|
||||
font.pointSize: Appearance.font.size.large
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.topMargin: Appearance.spacing.large
|
||||
|
||||
StyledFlickable {
|
||||
anchors.fill: parent
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: debugLayout.implicitHeight
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: parent
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: debugLayout
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
implicitHeight: hideToggleRow.implicitHeight + Appearance.padding.large * 2
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
RowLayout {
|
||||
id: hideToggleRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Hide from launcher")
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
}
|
||||
|
||||
StyledSwitch {
|
||||
id: hideFromLauncherSwitch
|
||||
checked: false
|
||||
enabled: root.selectedApp !== null && configFile.loaded
|
||||
onToggled: {
|
||||
root.saveHiddenApps();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
implicitHeight: debugToggleRow.implicitHeight + Appearance.padding.large * 2
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
|
||||
RowLayout {
|
||||
id: debugToggleRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.margins: Appearance.padding.large
|
||||
spacing: Appearance.spacing.normal
|
||||
|
||||
StyledText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Show debug information")
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
}
|
||||
|
||||
StyledSwitch {
|
||||
id: showDebugInfoSwitch
|
||||
checked: root.showDebugInfo
|
||||
onToggled: {
|
||||
root.showDebugInfo = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Appearance.spacing.normal
|
||||
Layout.preferredHeight: 300
|
||||
visible: root.showDebugInfo
|
||||
color: Colours.tPalette.m3surfaceContainer
|
||||
radius: Appearance.rounding.normal
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Appearance.padding.normal
|
||||
spacing: Appearance.spacing.small
|
||||
|
||||
StyledText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Debug Info - All Available Properties")
|
||||
font.pointSize: Appearance.font.size.normal
|
||||
font.weight: 500
|
||||
}
|
||||
|
||||
StyledFlickable {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
contentHeight: debugText.implicitHeight
|
||||
clip: true
|
||||
|
||||
StyledScrollBar.vertical: StyledScrollBar {
|
||||
flickable: parent
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: debugText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
width: parent.width
|
||||
|
||||
text: {
|
||||
if (!root.selectedApp) return "No app selected";
|
||||
|
||||
let debug = "";
|
||||
const app = root.selectedApp;
|
||||
const entry = app.entry;
|
||||
|
||||
debug += "=== App Properties ===\n";
|
||||
for (let prop in app) {
|
||||
try {
|
||||
const value = app[prop];
|
||||
debug += prop + ": " + (value !== null && value !== undefined ? String(value) : "null/undefined") + "\n";
|
||||
} catch (e) {
|
||||
debug += prop + ": [error accessing]\n";
|
||||
}
|
||||
}
|
||||
|
||||
debug += "\n=== Entry Properties ===\n";
|
||||
if (entry) {
|
||||
for (let prop in entry) {
|
||||
try {
|
||||
const value = entry[prop];
|
||||
debug += prop + ": " + (value !== null && value !== undefined ? String(value) : "null/undefined") + "\n";
|
||||
} catch (e) {
|
||||
debug += prop + ": [error accessing]\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debug += "entry is null\n";
|
||||
}
|
||||
|
||||
return debug;
|
||||
}
|
||||
font.pointSize: Appearance.font.size.small
|
||||
color: Colours.palette.m3onSurfaceVariant
|
||||
wrapMode: TextEdit.Wrap
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InnerBorder {
|
||||
leftThickness: Appearance.padding.normal / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue