288 lines
8.1 KiB
QML
288 lines
8.1 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Widgets
|
|
import "../.."
|
|
|
|
Item {
|
|
id: root
|
|
width: parent ? parent.width : 1280
|
|
height: parent ? parent.height : 820
|
|
focus: true
|
|
|
|
/* ------------------------------
|
|
PIXEL CONSTANTS (DO NOT TOUCH)
|
|
------------------------------ */
|
|
|
|
property int menuLeft: 64
|
|
property int menuTop: 140
|
|
property int lineHeight: 38 + 40 + 1
|
|
|
|
property int nameFontSize: 32
|
|
property int stateFontSize: 28
|
|
|
|
property int stateColumnX: 824
|
|
|
|
property int soulOffsetX: -36 - 32
|
|
property int soulOffsetY: -26
|
|
|
|
/* ------------------------------ */
|
|
|
|
property int iconSize: 36
|
|
property int textStartX: 239
|
|
property int textRightPadding: 96
|
|
property int visibleRows: Math.max(1, Math.floor((height - menuTop - 64) / lineHeight))
|
|
property int scrollOffset: 0
|
|
|
|
property ShellStateManager manager: null
|
|
property int activeSelection: 0
|
|
property bool isSelected: true
|
|
|
|
ListModel {
|
|
id: appEntriesModel
|
|
}
|
|
|
|
function clamp(v, lo, hi) {
|
|
return Math.max(lo, Math.min(hi, v));
|
|
}
|
|
|
|
function menuLength() {
|
|
return appEntriesModel.count;
|
|
}
|
|
|
|
function menuAt(index) {
|
|
if (index < 0 || index >= appEntriesModel.count)
|
|
return null;
|
|
var row = appEntriesModel.get(index);
|
|
return row ? row.entry : null;
|
|
}
|
|
|
|
function wrapIndex(i) {
|
|
const length = menuLength();
|
|
if (length === 0)
|
|
return 0;
|
|
return (i + length) % length;
|
|
}
|
|
|
|
function clampSelection() {
|
|
const length = menuLength();
|
|
if (length === 0) {
|
|
activeSelection = 0;
|
|
return;
|
|
}
|
|
activeSelection = clamp(activeSelection, 0, length - 1);
|
|
}
|
|
|
|
function ensureVisible() {
|
|
const length = menuLength();
|
|
if (length === 0)
|
|
return;
|
|
if (activeSelection < scrollOffset)
|
|
scrollOffset = activeSelection;
|
|
else if (activeSelection >= scrollOffset + visibleRows)
|
|
scrollOffset = Math.max(0, activeSelection - visibleRows + 1);
|
|
}
|
|
|
|
function appName(entry) {
|
|
if (!entry)
|
|
return "";
|
|
if (entry.name && entry.name.length > 0)
|
|
return entry.name;
|
|
return entry.id || "";
|
|
}
|
|
|
|
function appUsageCount(entry) {
|
|
if (!entry || !manager || !manager.appUsageCounts)
|
|
return 0;
|
|
var key = entry.id || entry.name;
|
|
return manager.appUsageCounts[key] || 0;
|
|
}
|
|
|
|
function applicationsToArray() {
|
|
var apps = DesktopEntries.applications;
|
|
var list = [];
|
|
if (!apps)
|
|
return list;
|
|
if (apps.values && apps.values.length !== undefined) {
|
|
for (var i = 0; i < apps.values.length; i++) {
|
|
list.push(apps.values[i]);
|
|
}
|
|
} else if (apps.length !== undefined) {
|
|
for (var j = 0; j < apps.length; j++) {
|
|
list.push(apps[j]);
|
|
}
|
|
} else if (apps.count !== undefined && apps.get) {
|
|
for (var k = 0; k < apps.count; k++) {
|
|
list.push(apps.get(k));
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
function rebuildAppEntries() {
|
|
var list = applicationsToArray();
|
|
console.log("AppLauncher: rebuilding entries", list.length);
|
|
var filtered = [];
|
|
for (var i = 0; i < list.length; i++) {
|
|
var entry = list[i];
|
|
if (!entry)
|
|
continue;
|
|
if (entry.noDisplay)
|
|
continue;
|
|
filtered.push(entry);
|
|
}
|
|
console.log("AppLauncher: filtered entries", filtered.length);
|
|
filtered.sort(function (a, b) {
|
|
var usageA = appUsageCount(a);
|
|
var usageB = appUsageCount(b);
|
|
if (usageA !== usageB)
|
|
return usageB - usageA;
|
|
var nameA = appName(a);
|
|
var nameB = appName(b);
|
|
return nameA.localeCompare(nameB);
|
|
});
|
|
appEntriesModel.clear();
|
|
for (var i = 0; i < filtered.length; i++) {
|
|
appEntriesModel.append({
|
|
entry: filtered[i]
|
|
});
|
|
}
|
|
clampSelection();
|
|
ensureVisible();
|
|
}
|
|
|
|
Connections {
|
|
target: DesktopEntries
|
|
function onApplicationsChanged() {
|
|
rebuildAppEntries();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: manager
|
|
ignoreUnknownSignals: true
|
|
function onAppUsageCountsChanged() {
|
|
rebuildAppEntries();
|
|
}
|
|
function onAppLauncherOpened() {
|
|
activeSelection = 0;
|
|
isSelected = true;
|
|
clampSelection();
|
|
}
|
|
}
|
|
|
|
Text {
|
|
text: "APPS"
|
|
font.family: "8bitoperator JVE"
|
|
font.pixelSize: 71
|
|
renderType: Text.NativeRendering
|
|
font.hintingPreference: Font.PreferNoHinting
|
|
smooth: false
|
|
antialiasing: false
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
color: "#ffffff"
|
|
y: 32
|
|
}
|
|
|
|
Repeater {
|
|
model: appEntriesModel
|
|
|
|
delegate: Item {
|
|
width: root.width
|
|
height: lineHeight
|
|
x: 0
|
|
y: menuTop + (index - root.scrollOffset) * lineHeight
|
|
visible: index >= root.scrollOffset && index < root.scrollOffset + root.visibleRows
|
|
|
|
Text {
|
|
x: root.textStartX
|
|
y: 0
|
|
text: root.appName(model.entry)
|
|
width: root.width - root.textStartX - root.textRightPadding
|
|
font.family: "8bitoperator JVE"
|
|
font.pixelSize: 71
|
|
font.letterSpacing: 1
|
|
renderType: Text.NativeRendering
|
|
font.hintingPreference: Font.PreferNoHinting
|
|
smooth: false
|
|
antialiasing: false
|
|
wrapMode: Text.NoWrap
|
|
elide: Text.ElideRight
|
|
color: (root.activeSelection == index && root.isSelected == true) ? "#fefe00" : "#ffffff"
|
|
}
|
|
|
|
IconImage {
|
|
id: appIcon
|
|
x: 182
|
|
y: 8 + 14
|
|
implicitSize: root.iconSize
|
|
source: Quickshell.iconPath(model.entry && model.entry.icon ? model.entry.icon : "")
|
|
asynchronous: true
|
|
}
|
|
|
|
Image {
|
|
source: "../QuickSettings/soul.png"
|
|
width: 36
|
|
height: 36
|
|
x: appIcon.x
|
|
y: appIcon.y
|
|
opacity: 0.7
|
|
visible: root.activeSelection == index
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ------------------------------
|
|
INPUT HANDLING
|
|
------------------------------ */
|
|
|
|
function handleKey(key) {
|
|
switch (key) {
|
|
case Qt.Key_Up:
|
|
activeSelection = wrapIndex(activeSelection - 1);
|
|
ensureVisible();
|
|
return true;
|
|
case Qt.Key_Down:
|
|
activeSelection = wrapIndex(activeSelection + 1);
|
|
ensureVisible();
|
|
return true;
|
|
case Qt.Key_Z:
|
|
case Qt.Key_Return:
|
|
case Qt.Key_Enter:
|
|
{
|
|
var entry = menuAt(activeSelection);
|
|
if (entry) {
|
|
if (manager && manager.bumpAppUsage) {
|
|
manager.bumpAppUsage(entry.id || entry.name);
|
|
}
|
|
entry.execute();
|
|
}
|
|
if (manager && manager.closeShell) {
|
|
manager.closeShell();
|
|
}
|
|
return true;
|
|
}
|
|
case Qt.Key_X:
|
|
case Qt.Key_Shift:
|
|
case Qt.Key_Escape:
|
|
if (manager && manager.closeAppLauncher) {
|
|
manager.closeAppLauncher();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Component.onCompleted: {
|
|
activeSelection = 0;
|
|
isSelected = true;
|
|
scrollOffset = 0;
|
|
rebuildAppEntries();
|
|
Qt.callLater(rebuildAppEntries);
|
|
ShellInputManager.registerHandler("appLauncher", handleKey);
|
|
}
|
|
|
|
Component.onDestruction: {
|
|
ShellInputManager.unregisterHandler("appLauncher");
|
|
}
|
|
}
|