1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-07 05:55:37 -05:00

launcher: sort by usage frequency

This commit is contained in:
bbedward
2025-07-24 18:36:23 -04:00
parent 99f17de065
commit 366930fc03
13 changed files with 557 additions and 258 deletions

View File

@@ -15,7 +15,7 @@ Singleton {
property bool isLightMode: false property bool isLightMode: false
property real topBarTransparency: 0.75 property real topBarTransparency: 0.75
property real popupTransparency: 0.92 property real popupTransparency: 0.92
property var recentlyUsedApps: [] property var appUsageRanking: {}
property bool use24HourClock: true property bool use24HourClock: true
property bool useFahrenheit: false property bool useFahrenheit: false
property bool nightModeEnabled: false property bool nightModeEnabled: false
@@ -58,7 +58,7 @@ Singleton {
isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false; isLightMode = settings.isLightMode !== undefined ? settings.isLightMode : false;
topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75; topBarTransparency = settings.topBarTransparency !== undefined ? (settings.topBarTransparency > 1 ? settings.topBarTransparency / 100 : settings.topBarTransparency) : 0.75;
popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92; popupTransparency = settings.popupTransparency !== undefined ? (settings.popupTransparency > 1 ? settings.popupTransparency / 100 : settings.popupTransparency) : 0.92;
recentlyUsedApps = settings.recentlyUsedApps || []; appUsageRanking = settings.appUsageRanking || {};
use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true; use24HourClock = settings.use24HourClock !== undefined ? settings.use24HourClock : true;
useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false; useFahrenheit = settings.useFahrenheit !== undefined ? settings.useFahrenheit : false;
nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false; nightModeEnabled = settings.nightModeEnabled !== undefined ? settings.nightModeEnabled : false;
@@ -104,7 +104,7 @@ Singleton {
"isLightMode": isLightMode, "isLightMode": isLightMode,
"topBarTransparency": topBarTransparency, "topBarTransparency": topBarTransparency,
"popupTransparency": popupTransparency, "popupTransparency": popupTransparency,
"recentlyUsedApps": recentlyUsedApps, "appUsageRanking": appUsageRanking,
"use24HourClock": use24HourClock, "use24HourClock": use24HourClock,
"useFahrenheit": useFahrenheit, "useFahrenheit": useFahrenheit,
"nightModeEnabled": nightModeEnabled, "nightModeEnabled": nightModeEnabled,
@@ -179,56 +179,77 @@ Singleton {
saveSettings(); saveSettings();
} }
function addRecentApp(app) { function addAppUsage(app) {
if (!app) if (!app)
return; return;
var execProp = app.execString || app.exec || ""; var appId = app.id || (app.execString || app.exec || "");
if (!execProp) if (!appId)
return; return;
var existingIndex = -1; var currentRanking = Object.assign({}, appUsageRanking);
for (var i = 0; i < recentlyUsedApps.length; i++) {
if (recentlyUsedApps[i].exec === execProp) { if (currentRanking[appId]) {
existingIndex = i; currentRanking[appId].usageCount = (currentRanking[appId].usageCount || 1) + 1;
break; currentRanking[appId].lastUsed = Date.now();
} currentRanking[appId].icon = app.icon || currentRanking[appId].icon || "application-x-executable";
} currentRanking[appId].name = app.name || currentRanking[appId].name || "";
if (existingIndex >= 0) {
// App exists, increment usage count
recentlyUsedApps[existingIndex].usageCount = (recentlyUsedApps[existingIndex].usageCount || 1) + 1;
recentlyUsedApps[existingIndex].lastUsed = Date.now();
} else { } else {
// New app, create entry currentRanking[appId] = {
var appData = {
"name": app.name || "", "name": app.name || "",
"exec": execProp, "exec": app.execString || app.exec || "",
"icon": app.icon || "application-x-executable", "icon": app.icon || "application-x-executable",
"comment": app.comment || "", "comment": app.comment || "",
"usageCount": 1, "usageCount": 1,
"lastUsed": Date.now() "lastUsed": Date.now()
}; };
recentlyUsedApps.push(appData);
} }
// Sort by usage count (descending), then alphabetically by name
var sortedApps = recentlyUsedApps.sort(function(a, b) {
if (a.usageCount !== b.usageCount)
return b.usageCount - a.usageCount;
// Higher usage count first appUsageRanking = currentRanking;
return a.name.localeCompare(b.name);
});
// Limit to 10 apps
if (sortedApps.length > 10)
sortedApps = sortedApps.slice(0, 10);
// Reassign to trigger property change signal
recentlyUsedApps = sortedApps;
saveSettings(); saveSettings();
} }
function getRecentApps() { function getAppUsageRanking() {
return recentlyUsedApps; return appUsageRanking;
}
function getRankedApps() {
var apps = [];
for (var appId in appUsageRanking) {
var appData = appUsageRanking[appId];
apps.push({
id: appId,
name: appData.name,
exec: appData.exec,
icon: appData.icon,
comment: appData.comment,
usageCount: appData.usageCount,
lastUsed: appData.lastUsed
});
}
return apps.sort(function(a, b) {
if (a.usageCount !== b.usageCount)
return b.usageCount - a.usageCount;
return a.name.localeCompare(b.name);
});
}
function cleanupAppUsageRanking(availableAppIds) {
var currentRanking = Object.assign({}, appUsageRanking);
var hasChanges = false;
for (var appId in currentRanking) {
if (availableAppIds.indexOf(appId) === -1) {
delete currentRanking[appId];
hasChanges = true;
}
}
if (hasChanges) {
appUsageRanking = currentRanking;
saveSettings();
}
} }
// New preference setters // New preference setters

View File

@@ -550,8 +550,8 @@ DankModal {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
iconName: "dangerous" iconName: "close"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 6
iconColor: Theme.error iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: { onClicked: {

View File

@@ -20,7 +20,7 @@ DankModal {
} }
// DankModal configuration // DankModal configuration
visible: settingsVisible visible: settingsVisible
width: 650 width: 750
height: 750 height: 750
keyboardFocus: "ondemand" keyboardFocus: "ondemand"
onBackgroundClicked: { onBackgroundClicked: {
@@ -92,6 +92,7 @@ DankModal {
{ text: "Personalization", icon: "person" }, { text: "Personalization", icon: "person" },
{ text: "Time & Weather", icon: "schedule" }, { text: "Time & Weather", icon: "schedule" },
{ text: "Widgets", icon: "widgets" }, { text: "Widgets", icon: "widgets" },
{ text: "Launcher", icon: "apps" },
{ text: "Appearance", icon: "palette" } { text: "Appearance", icon: "palette" }
] ]
} }
@@ -128,7 +129,7 @@ DankModal {
} }
} }
// System Tab // Widgets Tab
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: settingsTabBar.currentIndex === 2 active: settingsTabBar.currentIndex === 2
@@ -139,12 +140,23 @@ DankModal {
} }
} }
// Appearance Tab // Launcher Tab
Loader { Loader {
anchors.fill: parent anchors.fill: parent
active: settingsTabBar.currentIndex === 3 active: settingsTabBar.currentIndex === 3
visible: active visible: active
asynchronous: true asynchronous: true
sourceComponent: Component {
LauncherTab {}
}
}
// Appearance Tab
Loader {
anchors.fill: parent
active: settingsTabBar.currentIndex === 4
visible: active
asynchronous: true
sourceComponent: Component { sourceComponent: Component {
AppearanceTab {} AppearanceTab {}
} }

View File

@@ -93,6 +93,7 @@ DankModal {
content: Component { content: Component {
Item { Item {
id: spotlightKeyHandler
anchors.fill: parent anchors.fill: parent
focus: true focus: true
// Handle keyboard shortcuts // Handle keyboard shortcuts
@@ -115,9 +116,9 @@ DankModal {
} 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 (!searchField.activeFocus && event.text && event.text.length > 0 && event.text.match(/[a-zA-Z0-9\\s]/)) {
searchField.text = event.text;
searchField.forceActiveFocus(); searchField.forceActiveFocus();
searchField.insertText(event.text);
event.accepted = true; event.accepted = true;
} }
} }
@@ -162,6 +163,8 @@ DankModal {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: spotlightOpen enabled: spotlightOpen
placeholderText: "Search applications..." placeholderText: "Search applications..."
ignoreLeftRightKeys: true
keyForwardTargets: [spotlightKeyHandler]
text: appLauncher.searchQuery text: appLauncher.searchQuery
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text; appLauncher.searchQuery = text;
@@ -170,14 +173,14 @@ DankModal {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
hide(); hide();
event.accepted = true; event.accepted = true;
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.searchQuery.length > 0) { } else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
// Launch first app when typing in search field if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) {
if (appLauncher.model.count > 0) appLauncher.launchSelected();
} else if (appLauncher.model.count > 0) {
appLauncher.launchApp(appLauncher.model.get(0)); appLauncher.launchApp(appLauncher.model.get(0));
}
event.accepted = true; 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) && appLauncher.searchQuery.length === 0)) { } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((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; event.accepted = false;
} }
} }

View File

@@ -145,6 +145,7 @@ PanelWindow {
// Content with focus management // Content with focus management
Item { Item {
id: keyHandler
anchors.fill: parent anchors.fill: parent
focus: true focus: true
Component.onCompleted: { Component.onCompleted: {
@@ -172,10 +173,10 @@ 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 (!searchField.activeFocus && 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.insertText(event.text);
event.accepted = true; event.accepted = true;
} }
} }
@@ -233,17 +234,21 @@ PanelWindow {
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
enabled: appDrawerPopout.isVisible enabled: appDrawerPopout.isVisible
placeholderText: "Search applications..." placeholderText: "Search applications..."
ignoreLeftRightKeys: true
keyForwardTargets: [keyHandler]
onTextEdited: { onTextEdited: {
appLauncher.searchQuery = text; appLauncher.searchQuery = text;
} }
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && appLauncher.model.count && text.length > 0) { if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
// Launch first app when typing in search field if (appLauncher.keyboardNavigationActive && appLauncher.model.count > 0) {
appLauncher.launchSelected();
} else if (appLauncher.model.count > 0) {
var firstApp = appLauncher.model.get(0); var firstApp = appLauncher.model.get(0);
appLauncher.launchApp(firstApp); appLauncher.launchApp(firstApp);
}
event.accepted = true; 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)) { } else if (event.key === Qt.Key_Down || event.key === Qt.Key_Up || event.key === Qt.Key_Left || event.key === Qt.Key_Right || ((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; event.accepted = false;
} }
} }

View File

@@ -24,7 +24,7 @@ Item {
var allCategories = AppSearchService.getAllCategories().filter(cat => { var allCategories = AppSearchService.getAllCategories().filter(cat => {
return cat !== "Education" && cat !== "Science"; return cat !== "Education" && cat !== "Science";
}); });
var result = ["All", "Recents"]; var result = ["All"];
return result.concat(allCategories.filter(cat => { return result.concat(allCategories.filter(cat => {
return cat !== "All"; return cat !== "All";
})); }));
@@ -33,13 +33,8 @@ Item {
// Category icons (computed from AppSearchService) // Category icons (computed from AppSearchService)
property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category)) property var categoryIcons: categories.map(category => AppSearchService.getCategoryIcon(category))
// Recent apps helper // App usage ranking helper
property var recentApps: Prefs.recentlyUsedApps.map(recentApp => { property var appUsageRanking: Prefs.appUsageRanking
var app = AppSearchService.getAppByExec(recentApp.exec);
return app && !app.noDisplay ? app : null;
}).filter(app => {
return app !== null;
})
// Signals // Signals
signal appLaunched(var app) signal appLaunched(var app)
@@ -70,10 +65,12 @@ Item {
} }
} }
onSelectedCategoryChanged: updateFilteredModel() onSelectedCategoryChanged: updateFilteredModel()
onAppUsageRankingChanged: updateFilteredModel()
function updateFilteredModel() { function updateFilteredModel() {
filteredModel.clear(); filteredModel.clear();
selectedIndex = 0; selectedIndex = 0;
keyboardNavigationActive = false;
var apps = []; var apps = [];
@@ -81,8 +78,6 @@ Item {
// Show apps from category // Show apps from category
if (selectedCategory === "All") { if (selectedCategory === "All") {
apps = AppSearchService.applications || []; apps = AppSearchService.applications || [];
} else if (selectedCategory === "Recents") {
apps = recentApps;
} else { } else {
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); var categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
apps = categoryApps.slice(0, maxResults); apps = categoryApps.slice(0, maxResults);
@@ -91,16 +86,6 @@ Item {
// Search with category filter // Search with category filter
if (selectedCategory === "All") { if (selectedCategory === "All") {
apps = AppSearchService.searchApplications(searchQuery); apps = AppSearchService.searchApplications(searchQuery);
} else if (selectedCategory === "Recents") {
if (recentApps.length > 0) {
var allSearchResults = AppSearchService.searchApplications(searchQuery);
var recentNames = new Set(recentApps.map(app => app.name));
apps = allSearchResults.filter(searchApp => {
return recentNames.has(searchApp.name);
});
} else {
apps = [];
}
} else { } else {
var categoryApps = AppSearchService.getAppsInCategory(selectedCategory); var categoryApps = AppSearchService.getAppsInCategory(selectedCategory);
if (categoryApps.length > 0) { if (categoryApps.length > 0) {
@@ -115,6 +100,21 @@ Item {
} }
} }
// Sort apps by usage ranking, then alphabetically
apps = apps.sort(function(a, b) {
var aId = a.id || (a.execString || a.exec || "");
var bId = b.id || (b.execString || b.exec || "");
var aUsage = appUsageRanking[aId] ? appUsageRanking[aId].usageCount : 0;
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
if (aUsage !== bUsage) {
return bUsage - aUsage; // Higher usage first
}
return (a.name || "").localeCompare(b.name || ""); // Alphabetical fallback
});
// Convert to model format and populate // Convert to model format and populate
apps.forEach(app => { apps.forEach(app => {
if (app) { if (app) {
@@ -178,16 +178,14 @@ Item {
} }
function launchApp(appData) { function launchApp(appData) {
if (appData.desktopEntry) { if (!appData) {
Prefs.addRecentApp(appData.desktopEntry); console.warn("AppLauncher: No app data provided");
appData.desktopEntry.execute(); return;
} else {
// Fallback to direct execution
var cleanExec = appData.exec.replace(/%[fFuU]/g, "").trim();
console.log("AppLauncher: Launching app directly:", cleanExec);
Quickshell.execDetached(["sh", "-c", cleanExec]);
} }
appData.desktopEntry.execute();
appLaunched(appData); appLaunched(appData);
Prefs.addAppUsage(appData.desktopEntry);
} }
// Category management // Category management

View File

@@ -58,9 +58,9 @@ Item {
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS
// Top row: All, Recents, Development, Graphics (4 items) // Top row: All, Development, Graphics, Games (4 items)
Row { Row {
property var topRowCategories: ["All", "Recents", "Development", "Graphics"] property var topRowCategories: ["All", "Development", "Graphics", "Games"]
width: parent.width width: parent.width
spacing: Theme.spacingS spacing: Theme.spacingS

View File

@@ -598,13 +598,13 @@ Item {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
width: Math.min(600, parent.width - Theme.spacingXL * 2) width: Math.min(parent.width - Theme.spacingXL * 2, implicitWidth)
text: randomFact text: randomFact
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: "white" color: "white"
opacity: 0.8 opacity: 0.8
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap wrapMode: Text.NoWrap
visible: randomFact !== "" visible: randomFact !== ""
} }
} }

View File

@@ -0,0 +1,399 @@
import QtQuick
import QtQuick.Controls
import Quickshell.Widgets
import qs.Common
import qs.Widgets
ScrollView {
id: launcherTab
contentWidth: availableWidth
contentHeight: column.implicitHeight + Theme.spacingXL
clip: true
Column {
id: column
width: parent.width
spacing: Theme.spacingXL
topPadding: Theme.spacingL
bottomPadding: Theme.spacingXL
StyledRect {
width: parent.width
height: appLauncherSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Column {
id: appLauncherSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "apps"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "App Launcher"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: "Use OS Logo for App Launcher"
description: "Display operating system logo instead of apps icon"
checked: Prefs.useOSLogo
onToggled: (checked) => {
return Prefs.setUseOSLogo(checked);
}
}
StyledRect {
width: parent.width
height: logoCustomization.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1
visible: Prefs.useOSLogo
opacity: visible ? 1 : 0
Column {
id: logoCustomization
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: "OS Logo Customization"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Color Override"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankTextField {
width: 160
height: 36
placeholderText: "#ffffff"
text: Prefs.osLogoColorOverride
maximumLength: 7
font.pixelSize: Theme.fontSizeMedium
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
onEditingFinished: {
var color = text.trim();
if (color === "" || /^#[0-9A-Fa-f]{6}$/.test(color))
Prefs.setOSLogoColorOverride(color);
else
text = Prefs.osLogoColorOverride;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Brightness"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
height: 24
minimum: 0
maximum: 100
value: Math.round(Prefs.osLogoBrightness * 100)
unit: ""
showValue: true
onSliderValueChanged: (newValue) => {
Prefs.setOSLogoBrightness(newValue / 100);
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Contrast"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
height: 24
minimum: 0
maximum: 200
value: Math.round(Prefs.osLogoContrast * 100)
unit: ""
showValue: true
onSliderValueChanged: (newValue) => {
Prefs.setOSLogoContrast(newValue / 100);
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}
}
StyledRect {
width: parent.width
height: recentlyUsedSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Column {
id: recentlyUsedSection
property var rankedAppsModel: {
var apps = [];
for (var appId in Prefs.appUsageRanking) {
var appData = Prefs.appUsageRanking[appId];
apps.push({
"id": appId,
"name": appData.name,
"exec": appData.exec,
"icon": appData.icon,
"comment": appData.comment,
"usageCount": appData.usageCount,
"lastUsed": appData.lastUsed
});
}
apps.sort(function(a, b) {
if (a.usageCount !== b.usageCount)
return b.usageCount - a.usageCount;
return a.name.localeCompare(b.name);
});
return apps.slice(0, 20);
}
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "history"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Recently Used Apps"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - parent.children[0].width - parent.children[1].width - clearAllButton.width - Theme.spacingM * 3
height: 1
}
DankActionButton {
id: clearAllButton
iconName: "delete_sweep"
iconSize: Theme.iconSize - 2
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
anchors.verticalCenter: parent.verticalCenter
onClicked: {
Prefs.appUsageRanking = {
};
Prefs.saveSettings();
}
}
}
StyledText {
width: parent.width
text: "Apps are ordered by usage frequency, then alphabetically."
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
}
Column {
id: rankedAppsList
width: parent.width
spacing: Theme.spacingS
Repeater {
model: recentlyUsedSection.rankedAppsModel
delegate: Rectangle {
width: rankedAppsList.width
height: 48
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
border.width: 1
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
StyledText {
text: (index + 1).toString()
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.primary
width: 20
anchors.verticalCenter: parent.verticalCenter
}
Image {
width: 24
height: 24
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
sourceSize.width: 24
sourceSize.height: 24
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
StyledText {
text: modelData.name || "Unknown App"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
}
StyledText {
text: {
if (!modelData.lastUsed) return "Never used";
var date = new Date(modelData.lastUsed);
var now = new Date();
var diffMs = now - date;
var diffMins = Math.floor(diffMs / (1000 * 60));
var diffHours = Math.floor(diffMs / (1000 * 60 * 60));
var diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffMins < 1) return "Last launched just now";
if (diffMins < 60) return "Last launched " + diffMins + " minute" + (diffMins === 1 ? "" : "s") + " ago";
if (diffHours < 24) return "Last launched " + diffHours + " hour" + (diffHours === 1 ? "" : "s") + " ago";
if (diffDays < 7) return "Last launched " + diffDays + " day" + (diffDays === 1 ? "" : "s") + " ago";
return "Last launched " + date.toLocaleDateString();
}
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
}
DankActionButton {
anchors.right: parent.right
anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
circular: true
iconName: "close"
iconSize: 16
iconColor: Theme.error
hoverColor: Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
onClicked: {
var currentRanking = Object.assign({}, Prefs.appUsageRanking);
delete currentRanking[modelData.id];
Prefs.appUsageRanking = currentRanking;
Prefs.saveSettings();
}
}
}
}
StyledText {
width: parent.width
text: recentlyUsedSection.rankedAppsModel.length === 0 ? "No apps have been launched yet." : ""
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
horizontalAlignment: Text.AlignHCenter
visible: recentlyUsedSection.rankedAppsModel.length === 0
}
}
}
}
}
}

View File

@@ -170,165 +170,5 @@ ScrollView {
} }
} }
} }
// App Launcher Section
StyledRect {
width: parent.width
height: appLauncherSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadiusLarge
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
border.width: 1
Column {
id: appLauncherSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "apps"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "App Launcher"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
DankToggle {
width: parent.width
text: "Use OS Logo for App Launcher"
description: "Display operating system logo instead of apps icon"
checked: Prefs.useOSLogo
onToggled: (checked) => {
return Prefs.setUseOSLogo(checked);
}
}
// OS Logo Customization - only visible when OS logo is enabled
StyledRect {
width: parent.width
height: logoCustomization.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.5)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
border.width: 1
visible: Prefs.useOSLogo
opacity: visible ? 1 : 0
Column {
id: logoCustomization
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: "OS Logo Customization"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: Font.Medium
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Color Override"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankTextField {
width: 160
height: 36
placeholderText: "#ffffff"
text: Prefs.osLogoColorOverride
maximumLength: 7
font.pixelSize: Theme.fontSizeMedium
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
onEditingFinished: {
var color = text.trim();
if (color === "" || /^#[0-9A-Fa-f]{6}$/.test(color))
Prefs.setOSLogoColorOverride(color);
else
text = Prefs.osLogoColorOverride;
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Brightness"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
height: 24
minimum: 0
maximum: 100
value: Math.round(Prefs.osLogoBrightness * 100)
unit: ""
showValue: true
onSliderValueChanged: (newValue) => {
Prefs.setOSLogoBrightness(newValue / 100);
}
}
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: "Contrast"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Medium
}
DankSlider {
width: parent.width
height: 24
minimum: 0
maximum: 200
value: Math.round(Prefs.osLogoContrast * 100)
unit: ""
showValue: true
onSliderValueChanged: (newValue) => {
Prefs.setOSLogoContrast(newValue / 100);
}
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
}
}
}
} }
} }

View File

@@ -116,20 +116,20 @@ PanelWindow {
// Use estimated fixed widths to break circular dependencies // Use estimated fixed widths to break circular dependencies
readonly property int launcherButtonWidth: 40 readonly property int launcherButtonWidth: 40
readonly property int workspaceSwitcherWidth: 120 // Approximate readonly property int workspaceSwitcherWidth: 120 // Approximate
readonly property int focusedAppMaxWidth: focusedApp.visible ? (topBarContent.spacingTight ? 288 : 456) : 0 readonly property int focusedAppMaxWidth: focusedApp.visible ? 456 : 0
readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth + focusedAppMaxWidth + (Theme.spacingXS * 2) readonly property int estimatedLeftSectionWidth: launcherButtonWidth + workspaceSwitcherWidth + focusedAppMaxWidth + (Theme.spacingXS * 2)
readonly property int rightSectionWidth: rightSection.width readonly property int rightSectionWidth: rightSection.width
readonly property int clockWidth: clock.width readonly property int clockWidth: clock.width
readonly property int mediaMaxWidth: media.visible ? 280 : 0 // Normal max width readonly property int mediaMaxWidth: media.visible ? 280 : 0 // Normal max width
readonly property int weatherWidth: weather.visible ? weather.width : 0 readonly property int weatherWidth: weather.visible ? weather.width : 0
readonly property bool validLayout: availableWidth > 100 && estimatedLeftSectionWidth > 0 && rightSectionWidth > 0 readonly property bool validLayout: availableWidth > 100 && estimatedLeftSectionWidth > 0 && rightSectionWidth > 0
readonly property int clockLeftEdge: validLayout ? (availableWidth - clockWidth) / 2 : 0 readonly property int clockLeftEdge: (availableWidth - clockWidth) / 2
readonly property int clockRightEdge: clockLeftEdge + clockWidth readonly property int clockRightEdge: clockLeftEdge + clockWidth
readonly property int leftSectionRightEdge: estimatedLeftSectionWidth readonly property int leftSectionRightEdge: estimatedLeftSectionWidth
readonly property int mediaLeftEdge: clockLeftEdge - mediaMaxWidth - Theme.spacingS readonly property int mediaLeftEdge: clockLeftEdge - mediaMaxWidth - Theme.spacingS
readonly property int rightSectionLeftEdge: availableWidth - rightSectionWidth readonly property int rightSectionLeftEdge: availableWidth - rightSectionWidth
readonly property int leftToClockGap: validLayout ? Math.max(0, clockLeftEdge - leftSectionRightEdge) : 1000 readonly property int leftToClockGap: Math.max(0, clockLeftEdge - leftSectionRightEdge)
readonly property int leftToMediaGap: (mediaMaxWidth > 0 && validLayout) ? Math.max(0, mediaLeftEdge - leftSectionRightEdge) : leftToClockGap readonly property int leftToMediaGap: mediaMaxWidth > 0 ? Math.max(0, mediaLeftEdge - leftSectionRightEdge) : leftToClockGap
readonly property int mediaToClockGap: mediaMaxWidth > 0 ? Theme.spacingS : 0 readonly property int mediaToClockGap: mediaMaxWidth > 0 ? Theme.spacingS : 0
readonly property int clockToRightGap: validLayout ? Math.max(0, rightSectionLeftEdge - clockRightEdge) : 1000 readonly property int clockToRightGap: validLayout ? Math.max(0, rightSectionLeftEdge - clockRightEdge) : 1000
readonly property bool spacingTight: validLayout && (leftToMediaGap < 150 || clockToRightGap < 100) readonly property bool spacingTight: validLayout && (leftToMediaGap < 150 || clockToRightGap < 100)

View File

@@ -119,7 +119,6 @@ Singleton {
// Category icon mappings // Category icon mappings
property var categoryIcons: ({ property var categoryIcons: ({
"All": "apps", "All": "apps",
"Recents": "history",
"Media": "music_video", "Media": "music_video",
"Development": "code", "Development": "code",
"Games": "sports_esports", "Games": "sports_esports",

View File

@@ -39,6 +39,10 @@ Rectangle {
property real topPadding: Theme.spacingM property real topPadding: Theme.spacingM
property real bottomPadding: Theme.spacingM property real bottomPadding: Theme.spacingM
// Behavior control
property bool ignoreLeftRightKeys: false
property var keyForwardTargets: []
// Signals // Signals
signal textEdited() signal textEdited()
signal editingFinished() signal editingFinished()
@@ -83,6 +87,10 @@ Rectangle {
textInput.cut(); textInput.cut();
} }
function insertText(str) {
textInput.insert(textInput.cursorPosition, str);
}
function clearFocus() { function clearFocus() {
textInput.focus = false; textInput.focus = false;
} }
@@ -119,13 +127,27 @@ Rectangle {
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
verticalAlignment: TextInput.AlignVCenter verticalAlignment: TextInput.AlignVCenter
selectByMouse: true selectByMouse: !root.ignoreLeftRightKeys
clip: true clip: true
onTextChanged: root.textEdited() onTextChanged: root.textEdited()
onEditingFinished: root.editingFinished() onEditingFinished: root.editingFinished()
onAccepted: root.accepted() onAccepted: root.accepted()
onActiveFocusChanged: root.focusStateChanged(activeFocus) onActiveFocusChanged: root.focusStateChanged(activeFocus)
Keys.forwardTo: root.ignoreLeftRightKeys ? root.keyForwardTargets : []
Keys.onLeftPressed: function(event) {
if (root.ignoreLeftRightKeys) {
event.accepted = true;
}
}
Keys.onRightPressed: function(event) {
if (root.ignoreLeftRightKeys) {
event.accepted = true;
}
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true