From b1db08882825a7b303861ad832b4631a257de939 Mon Sep 17 00:00:00 2001 From: bbedward Date: Mon, 28 Jul 2025 13:16:27 -0400 Subject: [PATCH] update workspace indicators and qmlformat --- Modules/AppDrawer/AppLauncher.qml | 5 +- Modules/CentcomCenter/CentcomPopout.qml | 4 +- Modules/Settings/AppearanceTab.qml | 159 ++++++----- Modules/Settings/TimeWeatherTab.qml | 1 + Modules/TopBar/Clock.qml | 1 - Modules/TopBar/CpuMonitor.qml | 1 - Modules/TopBar/FocusedApp.qml | 4 +- Modules/TopBar/Media.qml | 4 +- Modules/TopBar/RamMonitor.qml | 1 - Modules/TopBar/SystemTrayBar.qml | 4 +- Modules/TopBar/WorkspaceSwitcher.qml | 6 +- Modules/VolumePopup.qml | 5 +- Widgets/DankDropdown.qml | 348 ++++++++++++------------ Widgets/DankGridView.qml | 196 ++++++------- Widgets/DankListView.qml | 197 +++++++------- Widgets/StyledText.qml | 18 +- 16 files changed, 476 insertions(+), 478 deletions(-) diff --git a/Modules/AppDrawer/AppLauncher.qml b/Modules/AppDrawer/AppLauncher.qml index 7473b23a..2a7eb426 100644 --- a/Modules/AppDrawer/AppLauncher.qml +++ b/Modules/AppDrawer/AppLauncher.qml @@ -76,7 +76,7 @@ Item { } } } - if (searchQuery.length === 0) { + if (searchQuery.length === 0) apps = apps.sort(function(a, b) { var aId = a.id || (a.execString || a.exec || ""); var bId = b.id || (b.execString || b.exec || ""); @@ -84,9 +84,10 @@ Item { var bUsage = appUsageRanking[bId] ? appUsageRanking[bId].usageCount : 0; if (aUsage !== bUsage) return bUsage - aUsage; + return (a.name || "").localeCompare(b.name || ""); }); - } + // Convert to model format and populate apps.forEach((app) => { if (app) diff --git a/Modules/CentcomCenter/CentcomPopout.qml b/Modules/CentcomCenter/CentcomPopout.qml index 208b6803..10043b18 100644 --- a/Modules/CentcomCenter/CentcomPopout.qml +++ b/Modules/CentcomCenter/CentcomPopout.qml @@ -51,6 +51,8 @@ PanelWindow { } Rectangle { + // Animation finished, now we can safely resize + id: mainContainer readonly property real targetWidth: Math.min(Screen.width * 0.9, 600) @@ -98,8 +100,6 @@ PanelWindow { y: Theme.barHeight + 4 // Only resize after animation is complete onOpacityChanged: { - // Animation finished, now we can safely resize - if (opacity === 1) Qt.callLater(() => { height = calculateHeight(); diff --git a/Modules/Settings/AppearanceTab.qml b/Modules/Settings/AppearanceTab.qml index 3f5ed2c3..958a2d8e 100644 --- a/Modules/Settings/AppearanceTab.qml +++ b/Modules/Settings/AppearanceTab.qml @@ -97,9 +97,9 @@ ScrollView { } onValueChanged: (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"); - } + } } @@ -108,9 +108,9 @@ ScrollView { text: "Font Family" description: "Select system font family" currentValue: { - if (Prefs.fontFamily === Prefs.defaultFontFamily) { + if (Prefs.fontFamily === Prefs.defaultFontFamily) return "Default"; - } + return Prefs.fontFamily || "Default"; } enableFuzzySearch: true @@ -119,47 +119,35 @@ ScrollView { options: { var fonts = ["Default"]; var availableFonts = Qt.fontFamilies(); - var rootFamilies = []; var seenFamilies = new Set(); - // Filter to root family names by removing common weight/style suffixes for (var i = 0; i < availableFonts.length; i++) { var fontName = availableFonts[i]; - // Skip fonts beginning with . (like .AppleSystem) - if (fontName.startsWith(".")) { + if (fontName.startsWith(".")) continue; - } - + // Skip the default font since we already added it as recommended - if (fontName === Prefs.defaultFontFamily) { + if (fontName === Prefs.defaultFontFamily) 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) { - // Keep these suffixes as they're part of the family name - return match; - }) - .trim(); - + + 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) { + // Keep these suffixes as they're part of the family name + return match; + }).trim(); if (!seenFamilies.has(rootName) && rootName !== "") { seenFamilies.add(rootName); rootFamilies.push(rootName); } } - return fonts.concat(rootFamilies.sort()); } onValueChanged: (value) => { - if (value === "Default") { + if (value === "Default") Prefs.setFontFamily(Prefs.defaultFontFamily); - } else { + else Prefs.setFontFamily(value); - } } } @@ -168,33 +156,63 @@ ScrollView { text: "Font Weight" description: "Select font weight" currentValue: { - switch(Prefs.fontWeight) { - case Font.Thin: return "Thin"; - case Font.ExtraLight: return "Extra Light"; - case Font.Light: return "Light"; - case Font.Normal: return "Regular"; - case Font.Medium: 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"; + switch (Prefs.fontWeight) { + case Font.Thin: + return "Thin"; + case Font.ExtraLight: + return "Extra Light"; + case Font.Light: + return "Light"; + case Font.Normal: + return "Regular"; + case Font.Medium: + 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"] onValueChanged: (value) => { var weight; - switch(value) { - case "Thin": weight = Font.Thin; break; - case "Extra Light": weight = Font.ExtraLight; break; - case "Light": weight = Font.Light; 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; + switch (value) { + case "Thin": + weight = Font.Thin; + break; + case "Extra Light": + weight = Font.ExtraLight; + break; + case "Light": + weight = Font.Light; + 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); } @@ -205,9 +223,9 @@ ScrollView { text: "Monospace Font" description: "Select monospace font for process list and technical displays" currentValue: { - if (Prefs.monoFontFamily === Prefs.defaultMonoFontFamily) { + if (Prefs.monoFontFamily === Prefs.defaultMonoFontFamily) return "Default"; - } + return Prefs.monoFontFamily || "Default"; } enableFuzzySearch: true @@ -216,62 +234,41 @@ ScrollView { options: { var fonts = ["Default"]; var availableFonts = Qt.fontFamilies(); - var monoFamilies = []; var seenFamilies = new Set(); - // Filter to likely monospace fonts for (var i = 0; i < availableFonts.length; i++) { var fontName = availableFonts[i]; - // Skip fonts beginning with . - if (fontName.startsWith(".")) { + if (fontName.startsWith(".")) continue; - } - + // Skip the default mono font since we already added it as recommended - if (fontName === Prefs.defaultMonoFontFamily) { + if (fontName === Prefs.defaultMonoFontFamily) continue; - } - + // Look for common monospace indicators var lowerName = fontName.toLowerCase(); - 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")) { - - 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 (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")) { + 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 !== "") { seenFamilies.add(rootName); monoFamilies.push(rootName); } } } - return fonts.concat(monoFamilies.sort()); } onValueChanged: (value) => { - if (value === "Default") { + if (value === "Default") Prefs.setMonoFontFamily(Prefs.defaultMonoFontFamily); - } else { + else Prefs.setMonoFontFamily(value); - } } } + } + } // Transparency Settings Section diff --git a/Modules/Settings/TimeWeatherTab.qml b/Modules/Settings/TimeWeatherTab.qml index f8d9aa59..1f53b8b8 100644 --- a/Modules/Settings/TimeWeatherTab.qml +++ b/Modules/Settings/TimeWeatherTab.qml @@ -133,6 +133,7 @@ ScrollView { Prefs.setWeatherLocation(displayName, coordinates); } } + } } diff --git a/Modules/TopBar/Clock.qml b/Modules/TopBar/Clock.qml index 5e4b93de..80ade06c 100644 --- a/Modules/TopBar/Clock.qml +++ b/Modules/TopBar/Clock.qml @@ -18,7 +18,6 @@ Rectangle { const baseColor = clockMouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } - Component.onCompleted: { root.currentDate = systemClock.date; } diff --git a/Modules/TopBar/CpuMonitor.qml b/Modules/TopBar/CpuMonitor.qml index 53ab4708..01d884cf 100644 --- a/Modules/TopBar/CpuMonitor.qml +++ b/Modules/TopBar/CpuMonitor.qml @@ -19,7 +19,6 @@ Rectangle { const baseColor = cpuArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } - Component.onCompleted: { SysMonitorService.addRef(); } diff --git a/Modules/TopBar/FocusedApp.qml b/Modules/TopBar/FocusedApp.qml index 3177e50e..bcd50082 100644 --- a/Modules/TopBar/FocusedApp.qml +++ b/Modules/TopBar/FocusedApp.qml @@ -17,9 +17,9 @@ Rectangle { radius: Theme.cornerRadius color: { // Only show background when there's content to display - if (!FocusedWindowService.focusedAppName && !FocusedWindowService.focusedWindowTitle) { + if (!FocusedWindowService.focusedAppName && !FocusedWindowService.focusedWindowTitle) return "transparent"; - } + const baseColor = mouseArea.containsMouse ? Theme.primaryHover : Theme.surfaceTextHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } diff --git a/Modules/TopBar/Media.qml b/Modules/TopBar/Media.qml index 5c7a303f..cc955e21 100644 --- a/Modules/TopBar/Media.qml +++ b/Modules/TopBar/Media.qml @@ -22,7 +22,6 @@ Rectangle { const baseColor = Theme.surfaceTextHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } - states: [ State { name: "shown" @@ -115,7 +114,6 @@ Rectangle { title = activePlayer.trackTitle || "Unknown Track"; subtitle = activePlayer.trackArtist || ""; } - return subtitle.length > 0 ? title + " • " + subtitle : title; } font.pixelSize: Theme.fontSizeSmall @@ -251,4 +249,4 @@ Rectangle { } -} \ No newline at end of file +} diff --git a/Modules/TopBar/RamMonitor.qml b/Modules/TopBar/RamMonitor.qml index 21718dd7..cd81a4ea 100644 --- a/Modules/TopBar/RamMonitor.qml +++ b/Modules/TopBar/RamMonitor.qml @@ -19,7 +19,6 @@ Rectangle { const baseColor = ramArea.containsMouse ? Theme.primaryPressed : Theme.secondaryHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } - Component.onCompleted: { SysMonitorService.addRef(); } diff --git a/Modules/TopBar/SystemTrayBar.qml b/Modules/TopBar/SystemTrayBar.qml index 7597d747..449f88ce 100644 --- a/Modules/TopBar/SystemTrayBar.qml +++ b/Modules/TopBar/SystemTrayBar.qml @@ -12,9 +12,9 @@ Rectangle { radius: Theme.cornerRadius color: { // Only show background when there are system tray items to display - if (systemTrayRow.children.length === 0) { + if (systemTrayRow.children.length === 0) return "transparent"; - } + const baseColor = Theme.secondaryHover; return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * Theme.widgetTransparency); } diff --git a/Modules/TopBar/WorkspaceSwitcher.qml b/Modules/TopBar/WorkspaceSwitcher.qml index 913ea7d7..72b3f36b 100644 --- a/Modules/TopBar/WorkspaceSwitcher.qml +++ b/Modules/TopBar/WorkspaceSwitcher.qml @@ -133,9 +133,9 @@ Rectangle { visible: Prefs.showWorkspaceIndex anchors.centerIn: parent text: isPlaceholder ? sequentialNumber : sequentialNumber - color: isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceText - font.pixelSize: Theme.fontSizeMedium - font.bold: isActive && !isPlaceholder + color: isActive ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium + font.pixelSize: Theme.fontSizeSmall + font.weight: isActive && !isPlaceholder ? Font.DemiBold : Font.Normal } Behavior on width { diff --git a/Modules/VolumePopup.qml b/Modules/VolumePopup.qml index c153063b..3f0fc251 100644 --- a/Modules/VolumePopup.qml +++ b/Modules/VolumePopup.qml @@ -9,10 +9,8 @@ import qs.Widgets PanelWindow { id: root - + property var modelData - screen: modelData - property bool volumePopupVisible: false function show() { @@ -26,6 +24,7 @@ PanelWindow { } + screen: modelData visible: volumePopupVisible WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.exclusiveZone: -1 diff --git a/Widgets/DankDropdown.qml b/Widgets/DankDropdown.qml index d14c901c..f555af7d 100644 --- a/Widgets/DankDropdown.qml +++ b/Widgets/DankDropdown.qml @@ -1,8 +1,8 @@ +import "../Common/fuzzysort.js" as FuzzySort import QtQuick import QtQuick.Controls import qs.Common import qs.Widgets -import "../Common/fuzzysort.js" as FuzzySort Rectangle { id: root @@ -14,7 +14,7 @@ Rectangle { property var optionIcons: [] // Array of icon names corresponding to options property bool forceRecreate: 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 signal valueChanged(string value) @@ -23,35 +23,32 @@ Rectangle { height: 60 radius: Theme.cornerRadius color: Theme.surfaceHover - Component.onCompleted: { // Force a small delay to ensure proper initialization forceRecreateTimer.start(); } - - Timer { - id: forceRecreateTimer - interval: 50 - repeat: false - onTriggered: { - root.forceRecreate = !root.forceRecreate; - } - } - Component.onDestruction: { var popup = popupLoader.item; - if (popup && popup.visible) { + if (popup && popup.visible) popup.close(); - } + } - onVisibleChanged: { var popup = popupLoader.item; if (!visible && popup && popup.visible) popup.close(); - else if (visible) { + else if (visible) // Force recreate popup when component becomes visible forceRecreateTimer.start(); + } + + Timer { + id: forceRecreateTimer + + interval: 50 + repeat: false + onTriggered: { + root.forceRecreate = !root.forceRecreate; } } @@ -160,14 +157,16 @@ Rectangle { Loader { id: popupLoader - active: true + property bool recreateFlag: root.forceRecreate + + active: true onRecreateFlagChanged: { // Force recreation by toggling active active = false; active = true; } - + sourceComponent: Component { Popup { id: dropdownMenu @@ -176,194 +175,201 @@ Rectangle { property var filteredOptions: [] 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() { if (!root.enableFuzzySearch || searchQuery.length === 0) { - filteredOptions = root.options + filteredOptions = root.options; } else { var results = FuzzySort.go(searchQuery, root.options, { - limit: 50, - threshold: -10000 - }) + "limit": 50, + "threshold": -10000 + }); filteredOptions = results.map(function(result) { - return result.target - }) + return result.target; + }); } - selectedIndex = -1 + selectedIndex = -1; } function selectNext() { if (filteredOptions.length > 0) { - selectedIndex = (selectedIndex + 1) % filteredOptions.length - listView.positionViewAtIndex(selectedIndex, ListView.Contain) + selectedIndex = (selectedIndex + 1) % filteredOptions.length; + listView.positionViewAtIndex(selectedIndex, ListView.Contain); } } function selectPrevious() { if (filteredOptions.length > 0) { - selectedIndex = selectedIndex <= 0 ? filteredOptions.length - 1 : selectedIndex - 1 - listView.positionViewAtIndex(selectedIndex, ListView.Contain) + selectedIndex = selectedIndex <= 0 ? filteredOptions.length - 1 : selectedIndex - 1; + listView.positionViewAtIndex(selectedIndex, ListView.Contain); } } function selectCurrent() { if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) { - root.currentValue = filteredOptions[selectedIndex] - root.valueChanged(filteredOptions[selectedIndex]) - dropdownMenu.close() + root.currentValue = filteredOptions[selectedIndex]; + root.valueChanged(filteredOptions[selectedIndex]); + dropdownMenu.close(); } } - background: Rectangle { - color: "transparent" - } + 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(); - 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 { - anchors.fill: parent - anchors.margins: Theme.spacingS + background: Rectangle { + color: "transparent" + } - // Search field - Rectangle { - id: searchContainer - width: parent.width - height: 36 - visible: root.enableFuzzySearch + 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 - color: Theme.surfaceVariantAlpha - DankTextField { - id: searchField + Column { anchors.fill: parent - anchors.margins: 1 - placeholderText: "Search..." - text: dropdownMenu.searchQuery - topPadding: Theme.spacingS - bottomPadding: Theme.spacingS - onTextChanged: { - dropdownMenu.searchQuery = text - dropdownMenu.updateFilteredOptions() + anchors.margins: Theme.spacingS + + // Search field + Rectangle { + id: searchContainer + + width: parent.width + 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() - } - } - } - } - } - } - } + } + } } diff --git a/Widgets/DankGridView.qml b/Widgets/DankGridView.qml index cba362f3..c6d74756 100644 --- a/Widgets/DankGridView.qml +++ b/Widgets/DankGridView.qml @@ -6,6 +6,7 @@ import qs.Common GridView { id: gridView + property int currentIndex: 0 property int columns: 4 property bool adaptiveColumns: false @@ -17,6 +18,12 @@ GridView { property int minIconSize: 32 property bool hoverUpdatesSelection: true 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 itemClicked(int index, var modelData) @@ -41,34 +48,6 @@ GridView { } 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 cellWidth: baseCellWidth cellHeight: baseCellHeight @@ -79,92 +58,113 @@ GridView { flickDeceleration: 300 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 { width: gridView.cellWidth - cellPadding height: gridView.cellHeight - cellPadding - 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) - border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium - border.width: currentIndex === index ? 2 : 1 + 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) + border.color: currentIndex === index ? Theme.primarySelected : Theme.outlineMedium + border.width: currentIndex === index ? 2 : 1 - Column { - anchors.centerIn: parent - spacing: Theme.spacingS + Column { + anchors.centerIn: parent + spacing: Theme.spacingS - Item { - property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, gridView.cellWidth * iconSizeRatio)) + Item { + property int iconSize: Math.min(maxIconSize, Math.max(minIconSize, gridView.cellWidth * iconSizeRatio)) - width: iconSize - height: iconSize - anchors.horizontalCenter: parent.horizontalCenter + width: iconSize + height: iconSize + anchors.horizontalCenter: parent.horizontalCenter - IconImage { - 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 - } - - } + IconImage { + id: iconImg + anchors.fill: parent + source: (model.icon) ? Quickshell.iconPath(model.icon, "") : "" + smooth: true + asynchronous: true + visible: status === Image.Ready } - StyledText { - anchors.horizontalCenter: parent.horizontalCenter - width: gridView.cellWidth - 12 - text: model.name || "" - font.pixelSize: Theme.fontSizeSmall - color: Theme.surfaceText - font.weight: Font.Medium - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - maximumLineCount: 2 - wrapMode: Text.WordWrap + 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 + } + } } - 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); - } + StyledText { + anchors.horizontalCenter: parent.horizontalCenter + width: gridView.cellWidth - 12 + text: model.name || "" + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceText + font.weight: Font.Medium + elide: Text.ElideRight + horizontalAlignment: Text.AlignHCenter + maximumLineCount: 2 + wrapMode: Text.WordWrap } } + 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); + } + } + + } + } diff --git a/Widgets/DankListView.qml b/Widgets/DankListView.qml index bc09b519..5c3734de 100644 --- a/Widgets/DankListView.qml +++ b/Widgets/DankListView.qml @@ -6,6 +6,7 @@ import qs.Common ListView { id: listView + property int currentIndex: 0 property int itemHeight: 72 property int iconSize: 56 @@ -13,6 +14,8 @@ ListView { property int itemSpacing: Theme.spacingS property bool hoverUpdatesSelection: true property bool keyboardNavigationActive: false + property real wheelMultiplier: 1.8 + property int wheelBaseStep: 160 signal keyboardNavigationReset() signal itemClicked(int index, var modelData) @@ -37,29 +40,6 @@ ListView { } 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 spacing: itemSpacing focus: true @@ -67,103 +47,124 @@ ListView { flickDeceleration: 600 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 { width: listView.width height: itemHeight - 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) - border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium - border.width: ListView.isCurrentItem ? 2 : 1 + 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) + border.color: ListView.isCurrentItem ? Theme.primarySelected : Theme.outlineMedium + border.width: ListView.isCurrentItem ? 2 : 1 - Row { - anchors.fill: parent - anchors.margins: Theme.spacingM - spacing: Theme.spacingL + Row { + anchors.fill: parent + anchors.margins: Theme.spacingM + spacing: Theme.spacingL - Item { - width: iconSize - height: iconSize - anchors.verticalCenter: parent.verticalCenter + Item { + width: iconSize + height: iconSize + anchors.verticalCenter: parent.verticalCenter - IconImage { - 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 - } - - } + IconImage { + id: iconImg + anchors.fill: parent + source: (model.icon) ? Quickshell.iconPath(model.icon, "") : "" + smooth: true + asynchronous: true + visible: status === Image.Ready } - Column { - anchors.verticalCenter: parent.verticalCenter - width: parent.width - iconSize - Theme.spacingL - spacing: Theme.spacingXS + Rectangle { + anchors.fill: parent + visible: !iconImg.visible + color: Theme.surfaceLight + radius: Theme.cornerRadiusLarge + border.width: 1 + border.color: Theme.primarySelected StyledText { - width: parent.width - text: model.name || "" - font.pixelSize: Theme.fontSizeLarge - color: Theme.surfaceText - font.weight: Font.Medium - 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 + 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 } } } - MouseArea { - id: mouseArea + Column { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - iconSize - Theme.spacingL + spacing: Theme.spacingXS - anchors.fill: parent - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - z: 10 - onEntered: { - if (hoverUpdatesSelection && !keyboardNavigationActive) - listView.currentIndex = index; + StyledText { + width: parent.width + text: model.name || "" + font.pixelSize: Theme.fontSizeLarge + color: Theme.surfaceText + font.weight: Font.Medium + elide: Text.ElideRight + } - itemHovered(index); - } - onPositionChanged: { - // Signal parent to reset keyboard navigation flag when mouse moves - keyboardNavigationReset(); - } - onClicked: { - itemClicked(index, model); + 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 { + 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); + } + } + + } + } diff --git a/Widgets/StyledText.qml b/Widgets/StyledText.qml index d8a37c96..3387fc66 100644 --- a/Widgets/StyledText.qml +++ b/Widgets/StyledText.qml @@ -4,26 +4,24 @@ import qs.Services Text { id: root - + property bool isMonospace: false color: Theme.surfaceText font.pixelSize: Appearance.fontSize.normal font.family: { - var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily - var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily - + var requestedFont = isMonospace ? Prefs.monoFontFamily : Prefs.fontFamily; + var defaultFont = isMonospace ? Prefs.defaultMonoFontFamily : Prefs.defaultFontFamily; // If user hasn't overridden the font and we're using the default if (requestedFont === defaultFont) { - var availableFonts = Qt.fontFamilies() - if (!availableFonts.includes(requestedFont)) { + var availableFonts = Qt.fontFamilies(); + if (!availableFonts.includes(requestedFont)) // Use system default - return isMonospace ? "Monospace" : "DejaVu Sans" - } + return isMonospace ? "Monospace" : "DejaVu Sans"; + } - // Either user overrode it, or default font is available - return requestedFont + return requestedFont; } font.weight: Prefs.fontWeight wrapMode: Text.WordWrap