1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

update workspace indicators and qmlformat

This commit is contained in:
bbedward
2025-07-28 13:16:27 -04:00
parent 4581544585
commit b1db088828
16 changed files with 476 additions and 478 deletions

View File

@@ -76,7 +76,7 @@ Item {
} }
} }
} }
if (searchQuery.length === 0) { if (searchQuery.length === 0)
apps = apps.sort(function(a, b) { apps = apps.sort(function(a, b) {
var aId = a.id || (a.execString || a.exec || ""); var aId = a.id || (a.execString || a.exec || "");
var bId = b.id || (b.execString || b.exec || ""); var bId = b.id || (b.execString || b.exec || "");
@@ -84,9 +84,10 @@ Item {
var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0; var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0;
if (aUsage !== bUsage) if (aUsage !== bUsage)
return bUsage - aUsage; return bUsage - aUsage;
return (a.name || "").localeCompare(b.name || ""); return (a.name || "").localeCompare(b.name || "");
}); });
}
// Convert to model format and populate // Convert to model format and populate
apps.forEach((app) => { apps.forEach((app) => {
if (app) if (app)

View File

@@ -51,6 +51,8 @@ PanelWindow {
} }
Rectangle { Rectangle {
// Animation finished, now we can safely resize
id: mainContainer id: mainContainer
readonly property real targetWidth: Math.min(Screen.width * 0.9, 600) readonly property real targetWidth: Math.min(Screen.width * 0.9, 600)
@@ -98,8 +100,6 @@ PanelWindow {
y: Theme.barHeight + 4 y: Theme.barHeight + 4
// Only resize after animation is complete // Only resize after animation is complete
onOpacityChanged: { onOpacityChanged: {
// Animation finished, now we can safely resize
if (opacity === 1) if (opacity === 1)
Qt.callLater(() => { Qt.callLater(() => {
height = calculateHeight(); height = calculateHeight();

View File

@@ -97,9 +97,9 @@ ScrollView {
} }
onValueChanged: (value) => { onValueChanged: (value) => {
Prefs.setIconTheme(value); Prefs.setIconTheme(value);
if (value !== "System Default" && !Prefs.qt5ctAvailable && !Prefs.qt6ctAvailable) { if (value !== "System Default" && !Prefs.qt5ctAvailable && !Prefs.qt6ctAvailable)
ToastService.showWarning("qt5ct or qt6ct not found - Qt app themes may not update without these tools"); ToastService.showWarning("qt5ct or qt6ct not found - Qt app themes may not update without these tools");
}
} }
} }
@@ -108,9 +108,9 @@ ScrollView {
text: "Font Family" text: "Font Family"
description: "Select system font family" description: "Select system font family"
currentValue: { currentValue: {
if (Prefs.fontFamily === Prefs.defaultFontFamily) { if (Prefs.fontFamily === Prefs.defaultFontFamily)
return "Default"; return "Default";
}
return Prefs.fontFamily || "Default"; return Prefs.fontFamily || "Default";
} }
enableFuzzySearch: true enableFuzzySearch: true
@@ -119,47 +119,35 @@ ScrollView {
options: { options: {
var fonts = ["Default"]; var fonts = ["Default"];
var availableFonts = Qt.fontFamilies(); var availableFonts = Qt.fontFamilies();
var rootFamilies = []; var rootFamilies = [];
var seenFamilies = new Set(); var seenFamilies = new Set();
// Filter to root family names by removing common weight/style suffixes // Filter to root family names by removing common weight/style suffixes
for (var i = 0; i < availableFonts.length; i++) { for (var i = 0; i < availableFonts.length; i++) {
var fontName = availableFonts[i]; var fontName = availableFonts[i];
// Skip fonts beginning with . (like .AppleSystem) // Skip fonts beginning with . (like .AppleSystem)
if (fontName.startsWith(".")) { if (fontName.startsWith("."))
continue; continue;
}
// Skip the default font since we already added it as recommended // Skip the default font since we already added it as recommended
if (fontName === Prefs.defaultFontFamily) { if (fontName === Prefs.defaultFontFamily)
continue; continue;
}
var rootName = fontName.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i, function(match, suffix) {
var rootName = fontName // Keep these suffixes as they're part of the family name
.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "") return match;
.replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "") }).trim();
.replace(/ (UI|Display|Text|Mono|Sans|Serif)$/i, function(match, suffix) {
// Keep these suffixes as they're part of the family name
return match;
})
.trim();
if (!seenFamilies.has(rootName) && rootName !== "") { if (!seenFamilies.has(rootName) && rootName !== "") {
seenFamilies.add(rootName); seenFamilies.add(rootName);
rootFamilies.push(rootName); rootFamilies.push(rootName);
} }
} }
return fonts.concat(rootFamilies.sort()); return fonts.concat(rootFamilies.sort());
} }
onValueChanged: (value) => { onValueChanged: (value) => {
if (value === "Default") { if (value === "Default")
Prefs.setFontFamily(Prefs.defaultFontFamily); Prefs.setFontFamily(Prefs.defaultFontFamily);
} else { else
Prefs.setFontFamily(value); Prefs.setFontFamily(value);
}
} }
} }
@@ -168,33 +156,63 @@ ScrollView {
text: "Font Weight" text: "Font Weight"
description: "Select font weight" description: "Select font weight"
currentValue: { currentValue: {
switch(Prefs.fontWeight) { switch (Prefs.fontWeight) {
case Font.Thin: return "Thin"; case Font.Thin:
case Font.ExtraLight: return "Extra Light"; return "Thin";
case Font.Light: return "Light"; case Font.ExtraLight:
case Font.Normal: return "Regular"; return "Extra Light";
case Font.Medium: return "Medium"; case Font.Light:
case Font.DemiBold: return "Demi Bold"; return "Light";
case Font.Bold: return "Bold"; case Font.Normal:
case Font.ExtraBold: return "Extra Bold"; return "Regular";
case Font.Black: return "Black"; case Font.Medium:
default: return "Regular"; return "Medium";
case Font.DemiBold:
return "Demi Bold";
case Font.Bold:
return "Bold";
case Font.ExtraBold:
return "Extra Bold";
case Font.Black:
return "Black";
default:
return "Regular";
} }
} }
options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"] options: ["Thin", "Extra Light", "Light", "Regular", "Medium", "Demi Bold", "Bold", "Extra Bold", "Black"]
onValueChanged: (value) => { onValueChanged: (value) => {
var weight; var weight;
switch(value) { switch (value) {
case "Thin": weight = Font.Thin; break; case "Thin":
case "Extra Light": weight = Font.ExtraLight; break; weight = Font.Thin;
case "Light": weight = Font.Light; break; break;
case "Regular": weight = Font.Normal; break; case "Extra Light":
case "Medium": weight = Font.Medium; break; weight = Font.ExtraLight;
case "Demi Bold": weight = Font.DemiBold; break; break;
case "Bold": weight = Font.Bold; break; case "Light":
case "Extra Bold": weight = Font.ExtraBold; break; weight = Font.Light;
case "Black": weight = Font.Black; break; break;
default: weight = Font.Normal; break; case "Regular":
weight = Font.Normal;
break;
case "Medium":
weight = Font.Medium;
break;
case "Demi Bold":
weight = Font.DemiBold;
break;
case "Bold":
weight = Font.Bold;
break;
case "Extra Bold":
weight = Font.ExtraBold;
break;
case "Black":
weight = Font.Black;
break;
default:
weight = Font.Normal;
break;
} }
Prefs.setFontWeight(weight); Prefs.setFontWeight(weight);
} }
@@ -205,9 +223,9 @@ ScrollView {
text: "Monospace Font" text: "Monospace Font"
description: "Select monospace font for process list and technical displays" description: "Select monospace font for process list and technical displays"
currentValue: { currentValue: {
if (Prefs.monoFontFamily === Prefs.defaultMonoFontFamily) { if (Prefs.monoFontFamily === Prefs.defaultMonoFontFamily)
return "Default"; return "Default";
}
return Prefs.monoFontFamily || "Default"; return Prefs.monoFontFamily || "Default";
} }
enableFuzzySearch: true enableFuzzySearch: true
@@ -216,62 +234,41 @@ ScrollView {
options: { options: {
var fonts = ["Default"]; var fonts = ["Default"];
var availableFonts = Qt.fontFamilies(); var availableFonts = Qt.fontFamilies();
var monoFamilies = []; var monoFamilies = [];
var seenFamilies = new Set(); var seenFamilies = new Set();
// Filter to likely monospace fonts // Filter to likely monospace fonts
for (var i = 0; i < availableFonts.length; i++) { for (var i = 0; i < availableFonts.length; i++) {
var fontName = availableFonts[i]; var fontName = availableFonts[i];
// Skip fonts beginning with . // Skip fonts beginning with .
if (fontName.startsWith(".")) { if (fontName.startsWith("."))
continue; continue;
}
// Skip the default mono font since we already added it as recommended // Skip the default mono font since we already added it as recommended
if (fontName === Prefs.defaultMonoFontFamily) { if (fontName === Prefs.defaultMonoFontFamily)
continue; continue;
}
// Look for common monospace indicators // Look for common monospace indicators
var lowerName = fontName.toLowerCase(); var lowerName = fontName.toLowerCase();
if (lowerName.includes("mono") || if (lowerName.includes("mono") || lowerName.includes("code") || lowerName.includes("console") || lowerName.includes("terminal") || lowerName.includes("courier") || lowerName.includes("dejavu sans mono") || lowerName.includes("jetbrains") || lowerName.includes("fira") || lowerName.includes("hack") || lowerName.includes("source code") || lowerName.includes("ubuntu mono") || lowerName.includes("cascadia")) {
lowerName.includes("code") || var rootName = fontName.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "").replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "").trim();
lowerName.includes("console") ||
lowerName.includes("terminal") ||
lowerName.includes("courier") ||
lowerName.includes("dejavu sans mono") ||
lowerName.includes("jetbrains") ||
lowerName.includes("fira") ||
lowerName.includes("hack") ||
lowerName.includes("source code") ||
lowerName.includes("ubuntu mono") ||
lowerName.includes("cascadia")) {
var rootName = fontName
.replace(/ (Thin|Extra Light|Light|Regular|Medium|Semi Bold|Demi Bold|Bold|Extra Bold|Black|Heavy)$/i, "")
.replace(/ (Italic|Oblique|Condensed|Extended|Narrow|Wide)$/i, "")
.trim();
if (!seenFamilies.has(rootName) && rootName !== "") { if (!seenFamilies.has(rootName) && rootName !== "") {
seenFamilies.add(rootName); seenFamilies.add(rootName);
monoFamilies.push(rootName); monoFamilies.push(rootName);
} }
} }
} }
return fonts.concat(monoFamilies.sort()); return fonts.concat(monoFamilies.sort());
} }
onValueChanged: (value) => { onValueChanged: (value) => {
if (value === "Default") { if (value === "Default")
Prefs.setMonoFontFamily(Prefs.defaultMonoFontFamily); Prefs.setMonoFontFamily(Prefs.defaultMonoFontFamily);
} else { else
Prefs.setMonoFontFamily(value); Prefs.setMonoFontFamily(value);
}
} }
} }
} }
} }
// Transparency Settings Section // Transparency Settings Section

View File

@@ -133,6 +133,7 @@ ScrollView {
Prefs.setWeatherLocation(displayName, coordinates); Prefs.setWeatherLocation(displayName, coordinates);
} }
} }
} }
} }

View File

@@ -18,7 +18,6 @@ Rectangle {
const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover; const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
Component.onCompleted: { Component.onCompleted: {
root.currentDate = systemClock.date; root.currentDate = systemClock.date;
} }

View File

@@ -19,7 +19,6 @@ Rectangle {
const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover; const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
Component.onCompleted: { Component.onCompleted: {
SysMonitorService.addRef(); SysMonitorService.addRef();
} }

View File

@@ -17,9 +17,9 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
// Only show background when there's content to display // Only show background when there's content to display
if (!FocusedWindowService.focusedAppName && !FocusedWindowService.focusedWindowTitle) { if (!FocusedWindowService.focusedAppName && !FocusedWindowService.focusedWindowTitle)
return "transparent"; return "transparent";
}
const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover; const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }

View File

@@ -22,7 +22,6 @@ Rectangle {
const baseColor = Theme.surfaceTextHover; const baseColor = Theme.surfaceTextHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
states: [ states: [
State { State {
name: "shown" name: "shown"
@@ -115,7 +114,6 @@ Rectangle {
title = activePlayer.trackTitle || "Unknown Track"; title = activePlayer.trackTitle || "Unknown Track";
subtitle = activePlayer.trackArtist || ""; subtitle = activePlayer.trackArtist || "";
} }
return subtitle.length > 0 ? title + " • " + subtitle : title; return subtitle.length > 0 ? title + " • " + subtitle : title;
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
@@ -251,4 +249,4 @@ Rectangle {
} }
} }

View File

@@ -19,7 +19,6 @@ Rectangle {
const baseColor = ramArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover; const baseColor = ramArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }
Component.onCompleted: { Component.onCompleted: {
SysMonitorService.addRef(); SysMonitorService.addRef();
} }

View File

@@ -12,9 +12,9 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
// Only show background when there are system tray items to display // Only show background when there are system tray items to display
if (systemTrayRow.children.length === 0) { if (systemTrayRow.children.length === 0)
return "transparent"; return "transparent";
}
const baseColor = Theme.secondaryHover; const baseColor = Theme.secondaryHover;
return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency);
} }

View File

@@ -133,9 +133,9 @@ Rectangle {
visible: Prefs.showWorkspaceIndex visible: Prefs.showWorkspaceIndex
anchors.centerIn: parent anchors.centerIn: parent
text: isPlaceholder ? sequentialNumber : sequentialNumber text: isPlaceholder ? sequentialNumber : sequentialNumber
color: isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceText color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeSmall
font.bold: isActive && !isPlaceholder font.weight: isActive && !isPlaceholder ? Font.DemiBold : Font.Normal
} }
Behavior on width { Behavior on width {

View File

@@ -9,10 +9,8 @@ import qs.Widgets
PanelWindow { PanelWindow {
id: root id: root
property var modelData property var modelData
screen: modelData
property bool volumePopupVisible: false property bool volumePopupVisible: false
function show() { function show() {
@@ -26,6 +24,7 @@ PanelWindow {
} }
screen: modelData
visible: volumePopupVisible visible: volumePopupVisible
WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1 WlrLayershell.exclusiveZone: -1

View File

@@ -1,8 +1,8 @@
import "../Common/fuzzysort.js" as FuzzySort
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import qs.Common import qs.Common
import qs.Widgets import qs.Widgets
import "../Common/fuzzysort.js" as FuzzySort
Rectangle { Rectangle {
id: root id: root
@@ -14,7 +14,7 @@ Rectangle {
property var optionIcons: [] // Array of icon names corresponding to options property var optionIcons: [] // Array of icon names corresponding to options
property bool forceRecreate: false property bool forceRecreate: false
property bool enableFuzzySearch: false property bool enableFuzzySearch: false
property int popupWidthOffset: 0 // How much wider the popup should be than the button property int popupWidthOffset: 0 // How much wider the popup should be than the button
property int maxPopupHeight: 400 property int maxPopupHeight: 400
signal valueChanged(string value) signal valueChanged(string value)
@@ -23,35 +23,32 @@ Rectangle {
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.surfaceHover color: Theme.surfaceHover
Component.onCompleted: { Component.onCompleted: {
// Force a small delay to ensure proper initialization // Force a small delay to ensure proper initialization
forceRecreateTimer.start(); forceRecreateTimer.start();
} }
Timer {
id: forceRecreateTimer
interval: 50
repeat: false
onTriggered: {
root.forceRecreate = !root.forceRecreate;
}
}
Component.onDestruction: { Component.onDestruction: {
var popup = popupLoader.item; var popup = popupLoader.item;
if (popup && popup.visible) { if (popup && popup.visible)
popup.close(); popup.close();
}
} }
onVisibleChanged: { onVisibleChanged: {
var popup = popupLoader.item; var popup = popupLoader.item;
if (!visible && popup && popup.visible) if (!visible && popup && popup.visible)
popup.close(); popup.close();
else if (visible) { else if (visible)
// Force recreate popup when component becomes visible // Force recreate popup when component becomes visible
forceRecreateTimer.start(); forceRecreateTimer.start();
}
Timer {
id: forceRecreateTimer
interval: 50
repeat: false
onTriggered: {
root.forceRecreate = !root.forceRecreate;
} }
} }
@@ -160,14 +157,16 @@ Rectangle {
Loader { Loader {
id: popupLoader id: popupLoader
active: true
property bool recreateFlag: root.forceRecreate property bool recreateFlag: root.forceRecreate
active: true
onRecreateFlagChanged: { onRecreateFlagChanged: {
// Force recreation by toggling active // Force recreation by toggling active
active = false; active = false;
active = true; active = true;
} }
sourceComponent: Component { sourceComponent: Component {
Popup { Popup {
id: dropdownMenu id: dropdownMenu
@@ -176,194 +175,201 @@ Rectangle {
property var filteredOptions: [] property var filteredOptions: []
property int selectedIndex: -1 property int selectedIndex: -1
parent: Overlay.overlay
width: dropdown.width + root.popupWidthOffset
height: Math.min(root.maxPopupHeight,
(root.enableFuzzySearch ? 48 : 0) +
Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
searchQuery = ""
updateFilteredOptions()
if (root.enableFuzzySearch && searchField.visible) {
searchField.forceActiveFocus()
}
}
function updateFilteredOptions() { function updateFilteredOptions() {
if (!root.enableFuzzySearch || searchQuery.length === 0) { if (!root.enableFuzzySearch || searchQuery.length === 0) {
filteredOptions = root.options filteredOptions = root.options;
} else { } else {
var results = FuzzySort.go(searchQuery, root.options, { var results = FuzzySort.go(searchQuery, root.options, {
limit: 50, "limit": 50,
threshold: -10000 "threshold": -10000
}) });
filteredOptions = results.map(function(result) { filteredOptions = results.map(function(result) {
return result.target return result.target;
}) });
} }
selectedIndex = -1 selectedIndex = -1;
} }
function selectNext() { function selectNext() {
if (filteredOptions.length > 0) { if (filteredOptions.length > 0) {
selectedIndex = (selectedIndex + 1) % filteredOptions.length selectedIndex = (selectedIndex + 1) % filteredOptions.length;
listView.positionViewAtIndex(selectedIndex, ListView.Contain) listView.positionViewAtIndex(selectedIndex, ListView.Contain);
} }
} }
function selectPrevious() { function selectPrevious() {
if (filteredOptions.length > 0) { if (filteredOptions.length > 0) {
selectedIndex = selectedIndex <= 0 ? filteredOptions.length - 1 : selectedIndex - 1 selectedIndex = selectedIndex <= 0 ? filteredOptions.length - 1 : selectedIndex - 1;
listView.positionViewAtIndex(selectedIndex, ListView.Contain) listView.positionViewAtIndex(selectedIndex, ListView.Contain);
} }
} }
function selectCurrent() { function selectCurrent() {
if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) { if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) {
root.currentValue = filteredOptions[selectedIndex] root.currentValue = filteredOptions[selectedIndex];
root.valueChanged(filteredOptions[selectedIndex]) root.valueChanged(filteredOptions[selectedIndex]);
dropdownMenu.close() dropdownMenu.close();
} }
} }
background: Rectangle { parent: Overlay.overlay
color: "transparent" width: dropdown.width + root.popupWidthOffset
} height: Math.min(root.maxPopupHeight, (root.enableFuzzySearch ? 48 : 0) + Math.min(filteredOptions.length, 10) * 36 + 16)
padding: 0
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: {
searchQuery = "";
updateFilteredOptions();
if (root.enableFuzzySearch && searchField.visible)
searchField.forceActiveFocus();
contentItem: Rectangle { }
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
border.color: Theme.primarySelected
border.width: 1
radius: Theme.cornerRadiusSmall
Column { background: Rectangle {
anchors.fill: parent color: "transparent"
anchors.margins: Theme.spacingS }
// Search field contentItem: Rectangle {
Rectangle { color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
id: searchContainer border.color: Theme.primarySelected
width: parent.width border.width: 1
height: 36
visible: root.enableFuzzySearch
radius: Theme.cornerRadiusSmall radius: Theme.cornerRadiusSmall
color: Theme.surfaceVariantAlpha
DankTextField { Column {
id: searchField
anchors.fill: parent anchors.fill: parent
anchors.margins: 1 anchors.margins: Theme.spacingS
placeholderText: "Search..."
text: dropdownMenu.searchQuery // Search field
topPadding: Theme.spacingS Rectangle {
bottomPadding: Theme.spacingS id: searchContainer
onTextChanged: {
dropdownMenu.searchQuery = text width: parent.width
dropdownMenu.updateFilteredOptions() height: 36
visible: root.enableFuzzySearch
radius: Theme.cornerRadiusSmall
color: Theme.surfaceVariantAlpha
DankTextField {
id: searchField
anchors.fill: parent
anchors.margins: 1
placeholderText: "Search..."
text: dropdownMenu.searchQuery
topPadding: Theme.spacingS
bottomPadding: Theme.spacingS
onTextChanged: {
dropdownMenu.searchQuery = text;
dropdownMenu.updateFilteredOptions();
}
Keys.onDownPressed: dropdownMenu.selectNext()
Keys.onUpPressed: dropdownMenu.selectPrevious()
Keys.onReturnPressed: dropdownMenu.selectCurrent()
Keys.onEnterPressed: dropdownMenu.selectCurrent()
}
}
Item {
width: 1
height: Theme.spacingXS
visible: root.enableFuzzySearch
}
ListView {
id: listView
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true
model: dropdownMenu.filteredOptions
spacing: 2
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted)
dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY, parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: Rectangle {
property bool isSelected: dropdownMenu.selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData)
width: ListView.view.width
height: 32
radius: Theme.cornerRadiusSmall
color: isSelected ? Theme.primaryHover : optionArea.containsMouse ? Theme.primaryHoverLight : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? root.optionIcons[optionIndex] : ""
size: 18
color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText
visible: name !== ""
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData
font.pixelSize: Theme.fontSizeMedium
color: isCurrentValue ? Theme.primary : Theme.surfaceText
font.weight: isCurrentValue ? Font.Medium : Font.Normal
width: parent.parent.width - parent.x - Theme.spacingS
elide: Text.ElideRight
}
}
MouseArea {
id: optionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.currentValue = modelData;
root.valueChanged(modelData);
dropdownMenu.close();
}
}
}
} }
Keys.onDownPressed: dropdownMenu.selectNext()
Keys.onUpPressed: dropdownMenu.selectPrevious()
Keys.onReturnPressed: dropdownMenu.selectCurrent()
Keys.onEnterPressed: dropdownMenu.selectCurrent()
} }
} }
Item {
width: 1
height: Theme.spacingXS
visible: root.enableFuzzySearch
}
ListView {
id: listView
width: parent.width
height: parent.height - (root.enableFuzzySearch ? searchContainer.height + Theme.spacingXS : 0)
clip: true
model: dropdownMenu.filteredOptions
spacing: 2
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * parent.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, parent.contentHeight - parent.height);
parent.contentY = Math.max(0, Math.min(maxY,
parent.contentY - dy * parent.wheelMultiplier));
ev.accepted = true;
}
}
delegate: Rectangle {
property bool isSelected: dropdownMenu.selectedIndex === index
property bool isCurrentValue: root.currentValue === modelData
property int optionIndex: root.options.indexOf(modelData)
width: ListView.view.width
height: 32
radius: Theme.cornerRadiusSmall
color: isSelected ? Theme.primaryHover :
optionArea.containsMouse ? Theme.primaryHoverLight : "transparent"
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: optionIndex >= 0 && root.optionIcons.length > optionIndex ?
root.optionIcons[optionIndex] : ""
size: 18
color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText
visible: name !== ""
}
StyledText {
anchors.verticalCenter: parent.verticalCenter
text: modelData
font.pixelSize: Theme.fontSizeMedium
color: isCurrentValue ? Theme.primary : Theme.surfaceText
font.weight: isCurrentValue ? Font.Medium : Font.Normal
width: parent.parent.width - parent.x - Theme.spacingS
elide: Text.ElideRight
}
}
MouseArea {
id: optionArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.currentValue = modelData
root.valueChanged(modelData)
dropdownMenu.close()
}
}
}
}
}
}
} }
} }
} }
} }

View File

@@ -6,6 +6,7 @@ import qs.Common
GridView { GridView {
id: gridView id: gridView
property int currentIndex: 0 property int currentIndex: 0
property int columns: 4 property int columns: 4
property bool adaptiveColumns: false property bool adaptiveColumns: false
@@ -17,6 +18,12 @@ GridView {
property int minIconSize: 32 property int minIconSize: 32
property bool hoverUpdatesSelection: true property bool hoverUpdatesSelection: true
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset() signal keyboardNavigationReset()
signal itemClicked(int index, var modelData) signal itemClicked(int index, var modelData)
@@ -41,34 +48,6 @@ GridView {
} }
clip: true clip: true
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * gridView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, gridView.contentHeight - gridView.height);
gridView.contentY = Math.max(0, Math.min(maxY,
gridView.contentY - dy * gridView.wheelMultiplier));
ev.accepted = true;
}
}
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
cellWidth: baseCellWidth cellWidth: baseCellWidth
cellHeight: baseCellHeight cellHeight: baseCellHeight
@@ -79,92 +58,113 @@ GridView {
flickDeceleration: 300 flickDeceleration: 300
maximumFlickVelocity: 30000 maximumFlickVelocity: 30000
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * gridView.wheelBaseStep;
if (ev.inverted)
dy = -dy;
const maxY = Math.max(0, gridView.contentHeight - gridView.height);
gridView.contentY = Math.max(0, Math.min(maxY, gridView.contentY - dy * gridView.wheelMultiplier));
ev.accepted = true;
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AsNeeded
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: Rectangle { delegate: Rectangle {
width: gridView.cellWidth - cellPadding width: gridView.cellWidth - cellPadding
height: gridView.cellHeight - cellPadding height: gridView.cellHeight - cellPadding
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: currentIndex === index ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: currentIndex === index ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium
border.width: currentIndex === index ? 2 : 1 border.width: currentIndex === index ? 2 : 1
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingS spacing: Theme.spacingS
Item { Item {
property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, gridView.cellWidth * iconSizeRatio)) property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, gridView.cellWidth * iconSizeRatio))
width: iconSize width: iconSize
height: iconSize height: iconSize
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
IconImage { IconImage {
id: iconImg id: iconImg
anchors.fill: parent
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !iconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadiusLarge
border.width: 1
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: Math.min(28, parent.width * 0.5)
color: Theme.primary
font.weight: Font.Bold
}
}
anchors.fill: parent
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
} }
StyledText { Rectangle {
anchors.horizontalCenter: parent.horizontalCenter anchors.fill: parent
width: gridView.cellWidth - 12 visible: !iconImg.visible
text: model.name || "" color: Theme.surfaceLight
font.pixelSize: Theme.fontSizeSmall radius: Theme.cornerRadiusLarge
color: Theme.surfaceText border.width: 1
font.weight: Font.Medium border.color: Theme.primarySelected
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter StyledText {
maximumLineCount: 2 anchors.centerIn: parent
wrapMode: Text.WordWrap text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: Math.min(28, parent.width * 0.5)
color: Theme.primary
font.weight: Font.Bold
}
} }
} }
MouseArea { StyledText {
id: mouseArea anchors.horizontalCenter: parent.horizontalCenter
width: gridView.cellWidth - 12
anchors.fill: parent text: model.name || ""
hoverEnabled: true font.pixelSize: Theme.fontSizeSmall
cursorShape: Qt.PointingHandCursor color: Theme.surfaceText
z: 10 font.weight: Font.Medium
onEntered: { elide: Text.ElideRight
if (hoverUpdatesSelection && !keyboardNavigationActive) horizontalAlignment: Text.AlignHCenter
currentIndex = index; maximumLineCount: 2
wrapMode: Text.WordWrap
itemHovered(index);
}
onPositionChanged: {
// Signal parent to reset keyboard navigation flag when mouse moves
keyboardNavigationReset();
}
onClicked: {
itemClicked(index, model);
}
} }
} }
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
z: 10
onEntered: {
if (hoverUpdatesSelection && !keyboardNavigationActive)
currentIndex = index;
itemHovered(index);
}
onPositionChanged: {
// Signal parent to reset keyboard navigation flag when mouse moves
keyboardNavigationReset();
}
onClicked: {
itemClicked(index, model);
}
}
}
} }

View File

@@ -6,6 +6,7 @@ import qs.Common
ListView { ListView {
id: listView id: listView
property int currentIndex: 0 property int currentIndex: 0
property int itemHeight: 72 property int itemHeight: 72
property int iconSize: 56 property int iconSize: 56
@@ -13,6 +14,8 @@ ListView {
property int itemSpacing: Theme.spacingS property int itemSpacing: Theme.spacingS
property bool hoverUpdatesSelection: true property bool hoverUpdatesSelection: true
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
signal keyboardNavigationReset() signal keyboardNavigationReset()
signal itemClicked(int index, var modelData) signal itemClicked(int index, var modelData)
@@ -37,29 +40,6 @@ ListView {
} }
clip: true clip: true
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AlwaysOn }
ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff }
property real wheelMultiplier: 1.8
property int wheelBaseStep: 160
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0
? ev.pixelDelta.y
: (ev.angleDelta.y / 120) * listView.wheelBaseStep;
if (ev.inverted) dy = -dy;
const maxY = Math.max(0, listView.contentHeight - listView.height);
listView.contentY = Math.max(0, Math.min(maxY,
listView.contentY - dy * listView.wheelMultiplier));
ev.accepted = true;
}
}
anchors.margins: itemSpacing anchors.margins: itemSpacing
spacing: itemSpacing spacing: itemSpacing
focus: true focus: true
@@ -67,103 +47,124 @@ ListView {
flickDeceleration: 600 flickDeceleration: 600
maximumFlickVelocity: 30000 maximumFlickVelocity: 30000
WheelHandler {
target: null
onWheel: (ev) => {
let dy = ev.pixelDelta.y !== 0 ? ev.pixelDelta.y : (ev.angleDelta.y / 120) * listView.wheelBaseStep;
if (ev.inverted)
dy = -dy;
const maxY = Math.max(0, listView.contentHeight - listView.height);
listView.contentY = Math.max(0, Math.min(maxY, listView.contentY - dy * listView.wheelMultiplier));
ev.accepted = true;
}
}
ScrollBar.vertical: ScrollBar {
policy: ScrollBar.AlwaysOn
}
ScrollBar.horizontal: ScrollBar {
policy: ScrollBar.AlwaysOff
}
delegate: Rectangle { delegate: Rectangle {
width: listView.width width: listView.width
height: itemHeight height: itemHeight
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
color: ListView.isCurrentItem ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03) color: ListView.isCurrentItem ? Theme.primaryPressed : mouseArea.containsMouse ? Theme.primaryHoverLight : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.03)
border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium
border.width: ListView.isCurrentItem ? 2 : 1 border.width: ListView.isCurrentItem ? 2 : 1
Row { Row {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
spacing: Theme.spacingL spacing: Theme.spacingL
Item { Item {
width: iconSize width: iconSize
height: iconSize height: iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
IconImage { IconImage {
id: iconImg id: iconImg
anchors.fill: parent
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !iconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadiusLarge
border.width: 1
border.color: Theme.primarySelected
StyledText {
anchors.centerIn: parent
text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: iconSize * 0.4
color: Theme.primary
font.weight: Font.Bold
}
}
anchors.fill: parent
source: (model.icon) ? Quickshell.iconPath(model.icon, "") : ""
smooth: true
asynchronous: true
visible: status === Image.Ready
} }
Column { Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.fill: parent
width: parent.width - iconSize - Theme.spacingL visible: !iconImg.visible
spacing: Theme.spacingXS color: Theme.surfaceLight
radius: Theme.cornerRadiusLarge
border.width: 1
border.color: Theme.primarySelected
StyledText { StyledText {
width: parent.width anchors.centerIn: parent
text: model.name || "" text: (model.name && model.name.length > 0) ? model.name.charAt(0).toUpperCase() : "A"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: iconSize * 0.4
color: Theme.surfaceText color: Theme.primary
font.weight: Font.Medium font.weight: Font.Bold
elide: Text.ElideRight
}
StyledText {
width: parent.width
text: model.comment || "Application"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
elide: Text.ElideRight
visible: showDescription && model.comment && model.comment.length > 0
} }
} }
} }
MouseArea { Column {
id: mouseArea anchors.verticalCenter: parent.verticalCenter
width: parent.width - iconSize - Theme.spacingL
spacing: Theme.spacingXS
anchors.fill: parent StyledText {
hoverEnabled: true width: parent.width
cursorShape: Qt.PointingHandCursor text: model.name || ""
z: 10 font.pixelSize: Theme.fontSizeLarge
onEntered: { color: Theme.surfaceText
if (hoverUpdatesSelection && !keyboardNavigationActive) font.weight: Font.Medium
listView.currentIndex = index; elide: Text.ElideRight
}
itemHovered(index); StyledText {
} width: parent.width
onPositionChanged: { text: model.comment || "Application"
// Signal parent to reset keyboard navigation flag when mouse moves font.pixelSize: Theme.fontSizeMedium
keyboardNavigationReset(); color: Theme.surfaceVariantText
} elide: Text.ElideRight
onClicked: { visible: showDescription && model.comment && model.comment.length > 0
itemClicked(index, model);
} }
} }
} }
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
z: 10
onEntered: {
if (hoverUpdatesSelection && !keyboardNavigationActive)
listView.currentIndex = index;
itemHovered(index);
}
onPositionChanged: {
// Signal parent to reset keyboard navigation flag when mouse moves
keyboardNavigationReset();
}
onClicked: {
itemClicked(index, model);
}
}
}
} }

View File

@@ -4,26 +4,24 @@ import qs.Services
Text { Text {
id: root id: root
property bool isMonospace: false property bool isMonospace: false
color: Theme.surfaceText color: Theme.surfaceText
font.pixelSize: Appearance.fontSize.normal font.pixelSize: Appearance.fontSize.normal
font.family: { font.family: {
var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily;
var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily;
// If user hasn't overridden the font and we're using the default // If user hasn't overridden the font and we're using the default
if (requestedFont === defaultFont) { if (requestedFont === defaultFont) {
var availableFonts = Qt.fontFamilies() var availableFonts = Qt.fontFamilies();
if (!availableFonts.includes(requestedFont)) { if (!availableFonts.includes(requestedFont))
// Use system default // Use system default
return isMonospace ? "Monospace" : "DejaVu Sans" return isMonospace ? "Monospace" : "DejaVu Sans";
}
} }
// Either user overrode it, or default font is available // Either user overrode it, or default font is available
return requestedFont return requestedFont;
} }
font.weight: Prefs.fontWeight font.weight: Prefs.fontWeight
wrapMode: Text.WordWrap wrapMode: Text.WordWrap