diff --git a/Common/Prefs.qml b/Common/Prefs.qml index 732e8c89..8dd51ae7 100644 --- a/Common/Prefs.qml +++ b/Common/Prefs.qml @@ -5,6 +5,7 @@ import QtCore import QtQuick import Quickshell import Quickshell.Io +import qs.Services Singleton { @@ -48,10 +49,42 @@ Singleton { property string wallpaperLastPath: "" property string profileLastPath: "" property bool doNotDisturb: false - property string fontFamily: "Noto Sans" - property string monoFontFamily: "JetBrains Mono" + property string fontFamily: "Inter Variable" + property string monoFontFamily: "Fira Code" property int fontWeight: Font.Normal + + readonly property string defaultFontFamily: "Inter Variable" + readonly property string defaultMonoFontFamily: "Fira Code" + readonly property string _homeUrl: StandardPaths.writableLocation(StandardPaths.HomeLocation) + + Timer { + id: fontCheckTimer + interval: 3000 + repeat: false + onTriggered: { + var availableFonts = Qt.fontFamilies() + var missingFonts = [] + + if (fontFamily === defaultFontFamily && !availableFonts.includes(defaultFontFamily)) { + missingFonts.push(defaultFontFamily) + } + if (monoFontFamily === defaultMonoFontFamily && !availableFonts.includes(defaultMonoFontFamily)) { + missingFonts.push(defaultMonoFontFamily) + } + + if (missingFonts.length > 0) { + var message = "Missing fonts: " + missingFonts.join(", ") + ". Using system defaults." + console.warn("Prefs: " + message) + ToastService.showWarning(message) + } + } + } + + Component.onCompleted: { + loadSettings(); + fontCheckTimer.start() + } function loadSettings() { parseSettings(settingsFile.text()); @@ -95,8 +128,8 @@ Singleton { wallpaperLastPath = settings.wallpaperLastPath !== undefined ? settings.wallpaperLastPath : ""; profileLastPath = settings.profileLastPath !== undefined ? settings.profileLastPath : ""; doNotDisturb = settings.doNotDisturb !== undefined ? settings.doNotDisturb : false; - fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : "Noto Sans"; - monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : "JetBrains Mono"; + fontFamily = settings.fontFamily !== undefined ? settings.fontFamily : defaultFontFamily; + monoFontFamily = settings.monoFontFamily !== undefined ? settings.monoFontFamily : defaultMonoFontFamily; fontWeight = settings.fontWeight !== undefined ? settings.fontWeight : Font.Normal; applyStoredTheme(); detectAvailableIconThemes(); @@ -543,9 +576,6 @@ Singleton { return "'" + String(s).replace(/'/g, "'\\''") + "'"; } - Component.onCompleted: loadSettings() - - FileView { id: settingsFile diff --git a/Modules/ProcessList/ProcessListView.qml b/Modules/ProcessList/ProcessListView.qml index a3fc52e3..abd7a4fd 100644 --- a/Modules/ProcessList/ProcessListView.qml +++ b/Modules/ProcessList/ProcessListView.qml @@ -211,18 +211,49 @@ Column { ListView { id: processListView + property real stableY: 0 + property bool isUserScrolling: false + property bool isScrollBarDragging: false + width: parent.width height: parent.height - columnHeaders.height clip: true spacing: 4 model: SysMonitorService.processes + boundsBehavior: Flickable.StopAtBounds + flickDeceleration: 1500 + maximumFlickVelocity: 2000 + + onMovementStarted: isUserScrolling = true + onMovementEnded: { + isUserScrolling = false + if (contentY > 40) { + stableY = contentY + } + } + + onContentYChanged: { + if (!isUserScrolling && !isScrollBarDragging && visible && stableY > 40 && Math.abs(contentY - stableY) > 10) { + contentY = stableY + } + } delegate: ProcessListItem { process: modelData contextMenu: root.contextMenu } - ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + ScrollBar.vertical: ScrollBar { + id: verticalScrollBar + policy: ScrollBar.AsNeeded + + onPressedChanged: { + processListView.isScrollBarDragging = pressed + if (!pressed && processListView.contentY > 40) { + processListView.stableY = processListView.contentY + } + } + } ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff } property real wheelMultiplier: 1.8 @@ -274,8 +305,11 @@ Column { } onModelChanged: { - if (model && model.length > 0) { - restoreAnchor(); + if (model && model.length > 0 && !isUserScrolling && stableY > 40) { + // Preserve scroll position when model updates + Qt.callLater(function() { + contentY = stableY + }) } } } diff --git a/Modules/ProcessList/SystemTab.qml b/Modules/ProcessList/SystemTab.qml index b01b78d8..2f5d1570 100644 --- a/Modules/ProcessList/SystemTab.qml +++ b/Modules/ProcessList/SystemTab.qml @@ -54,8 +54,10 @@ ScrollView { StyledText { text: SysMonitorService.hostname font.pixelSize: Theme.fontSizeXLarge + font.family: Prefs.monoFontFamily font.weight: Font.Light color: Theme.surfaceText + verticalAlignment: Text.AlignVCenter } StyledText { @@ -63,6 +65,7 @@ ScrollView { font.pixelSize: Theme.fontSizeMedium font.family: Prefs.monoFontFamily color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + verticalAlignment: Text.AlignVCenter } StyledText { @@ -70,6 +73,7 @@ ScrollView { font.pixelSize: Theme.fontSizeSmall font.family: Prefs.monoFontFamily color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + verticalAlignment: Text.AlignVCenter } StyledText { @@ -77,6 +81,7 @@ ScrollView { font.pixelSize: Theme.fontSizeSmall font.family: Prefs.monoFontFamily color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) + verticalAlignment: Text.AlignVCenter } } @@ -93,56 +98,141 @@ ScrollView { width: parent.width spacing: Theme.spacingXL - Column { + Rectangle { width: (parent.width - Theme.spacingXL) / 2 - spacing: Theme.spacingS + height: Math.max(hardwareColumn.implicitHeight, memoryColumn.implicitHeight) + Theme.spacingM + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.4) + border.width: 1 + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1) - StyledText { - text: SysMonitorService.cpuModel - font.pixelSize: Theme.fontSizeSmall - font.family: Prefs.monoFontFamily - font.weight: Font.Medium - color: Theme.surfaceText - width: parent.width - elide: Text.ElideRight + Column { + id: hardwareColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Theme.spacingM + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + DankIcon { + name: "memory" + size: Theme.iconSizeSmall + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: "Hardware" + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + font.weight: Font.Bold + color: Theme.primary + anchors.verticalCenter: parent.verticalCenter + } + } + + StyledText { + text: SysMonitorService.cpuModel + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + font.weight: Font.Medium + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + wrapMode: Text.Wrap + maximumLineCount: 2 + verticalAlignment: Text.AlignVCenter + } + + StyledText { + text: SysMonitorService.motherboard + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8) + width: parent.width + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + StyledText { + text: "BIOS " + SysMonitorService.biosVersion + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + width: parent.width + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } } - - StyledText { - text: SysMonitorService.motherboard - font.pixelSize: Theme.fontSizeSmall - font.family: Prefs.monoFontFamily - font.weight: Font.Medium - color: Theme.surfaceText - width: parent.width - elide: Text.ElideRight - } - } - Column { + Rectangle { width: (parent.width - Theme.spacingXL) / 2 - spacing: Theme.spacingS + height: Math.max(hardwareColumn.implicitHeight, memoryColumn.implicitHeight) + Theme.spacingM + radius: Theme.cornerRadius + color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.4) + border.width: 1 + border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1) - StyledText { - text: SysMonitorService.formatMemory(SysMonitorService.totalMemoryMB) + " Memory" - font.pixelSize: Theme.fontSizeSmall - font.family: Prefs.monoFontFamily - font.weight: Font.Medium - color: Theme.surfaceText - width: parent.width - elide: Text.ElideRight + Column { + id: memoryColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: Theme.spacingM + spacing: Theme.spacingXS + + Row { + width: parent.width + spacing: Theme.spacingS + + DankIcon { + name: "developer_board" + size: Theme.iconSizeSmall + color: Theme.secondary + anchors.verticalCenter: parent.verticalCenter + } + + StyledText { + text: "Memory" + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + font.weight: Font.Bold + color: Theme.secondary + anchors.verticalCenter: parent.verticalCenter + } + } + + StyledText { + text: SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB) + " Total" + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + font.weight: Font.Medium + color: Theme.surfaceText + width: parent.width + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + StyledText { + text: SysMonitorService.formatSystemMemory(SysMonitorService.usedMemoryKB) + " Used • " + SysMonitorService.formatSystemMemory(SysMonitorService.totalMemoryKB - SysMonitorService.usedMemoryKB) + " Available" + font.pixelSize: Theme.fontSizeSmall + font.family: Prefs.monoFontFamily + color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) + width: parent.width + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + Item { + width: parent.width + height: Theme.fontSizeSmall + Theme.spacingXS + } } - - StyledText { - text: "BIOS " + SysMonitorService.biosVersion - font.pixelSize: Theme.fontSizeSmall - font.family: Prefs.monoFontFamily - font.weight: Font.Medium - color: Theme.surfaceText - width: parent.width - elide: Text.ElideRight - } - } } @@ -181,6 +271,7 @@ ScrollView { StyledText { text: "Storage & Disks" font.pixelSize: Theme.fontSizeLarge + font.family: Prefs.monoFontFamily font.weight: Font.Bold color: Theme.surfaceText anchors.verticalCenter: parent.verticalCenter @@ -206,6 +297,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.25 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } StyledText { @@ -216,6 +308,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.2 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } StyledText { @@ -226,6 +319,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.15 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } StyledText { @@ -236,6 +330,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.15 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } StyledText { @@ -246,6 +341,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.15 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } StyledText { @@ -256,6 +352,7 @@ ScrollView { color: Theme.surfaceText width: parent.width * 0.1 elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter } } @@ -290,6 +387,7 @@ ScrollView { width: parent.width * 0.25 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } StyledText { @@ -300,6 +398,7 @@ ScrollView { width: parent.width * 0.2 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } StyledText { @@ -310,6 +409,7 @@ ScrollView { width: parent.width * 0.15 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } StyledText { @@ -320,6 +420,7 @@ ScrollView { width: parent.width * 0.15 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } StyledText { @@ -330,6 +431,7 @@ ScrollView { width: parent.width * 0.15 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } StyledText { @@ -349,6 +451,7 @@ ScrollView { width: parent.width * 0.1 elide: Text.ElideRight anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter } } diff --git a/Modules/Settings/AppearanceTab.qml b/Modules/Settings/AppearanceTab.qml index f5f8ce0a..c187f96b 100644 --- a/Modules/Settings/AppearanceTab.qml +++ b/Modules/Settings/AppearanceTab.qml @@ -100,10 +100,22 @@ ScrollView { width: parent.width text: "Font Family" description: "Select system font family" - currentValue: Prefs.fontFamily || "System Default" + currentValue: { + if (Prefs.fontFamily === Prefs.defaultFontFamily) { + return "Default"; + } + return Prefs.fontFamily || "System Default"; + } + enableFuzzySearch: true + popupWidthOffset: 100 + maxPopupHeight: 400 options: { var fonts = ["System Default"]; var availableFonts = Qt.fontFamilies(); + + // Add default font at the top + fonts.push("Default"); + var rootFamilies = []; var seenFamilies = new Set(); @@ -116,6 +128,11 @@ ScrollView { continue; } + // Skip the default font since we already added it as recommended + 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, "") @@ -134,7 +151,13 @@ ScrollView { return fonts.concat(rootFamilies.sort()); } onValueChanged: (value) => { - Prefs.setFontFamily(value === "System Default" ? "Noto Sans" : value); + if (value === "System Default") { + Prefs.setFontFamily(Prefs.defaultFontFamily); + } else if (value === "Default") { + Prefs.setFontFamily(Prefs.defaultFontFamily); + } else { + Prefs.setFontFamily(value); + } } } @@ -179,10 +202,22 @@ ScrollView { width: parent.width text: "Monospace Font" description: "Select monospace font for process list and technical displays" - currentValue: Prefs.monoFontFamily || "System Default" + currentValue: { + if (Prefs.monoFontFamily === Prefs.defaultMonoFontFamily) { + return "Default"; + } + return Prefs.monoFontFamily || "System Default"; + } + enableFuzzySearch: true + popupWidthOffset: 100 + maxPopupHeight: 400 options: { var fonts = ["System Default"]; var availableFonts = Qt.fontFamilies(); + + // Add default mono font at the top + fonts.push("Default"); + var monoFamilies = []; var seenFamilies = new Set(); @@ -195,6 +230,11 @@ ScrollView { continue; } + // Skip the default mono font since we already added it as recommended + if (fontName === Prefs.defaultMonoFontFamily) { + continue; + } + // Look for common monospace indicators var lowerName = fontName.toLowerCase(); if (lowerName.includes("mono") || @@ -225,7 +265,13 @@ ScrollView { return fonts.concat(monoFamilies.sort()); } onValueChanged: (value) => { - Prefs.setMonoFontFamily(value === "System Default" ? "JetBrains Mono" : value); + if (value === "System Default") { + Prefs.setMonoFontFamily(Prefs.defaultMonoFontFamily); + } else if (value === "Default") { + Prefs.setMonoFontFamily(Prefs.defaultMonoFontFamily); + } else { + Prefs.setMonoFontFamily(value); + } } } } diff --git a/Modules/TopBar/Clock.qml b/Modules/TopBar/Clock.qml index d0496f54..1c0736d6 100644 --- a/Modules/TopBar/Clock.qml +++ b/Modules/TopBar/Clock.qml @@ -38,7 +38,7 @@ Rectangle { StyledText { text: "•" - font.pixelSize: Theme.fontSizeMedium + font.pixelSize: Theme.fontSizeSmall color: Theme.outlineButton anchors.verticalCenter: parent.verticalCenter visible: !compactMode diff --git a/Modules/TopBar/Media.qml b/Modules/TopBar/Media.qml index f3347bf5..01313c4b 100644 --- a/Modules/TopBar/Media.qml +++ b/Modules/TopBar/Media.qml @@ -115,11 +115,6 @@ Rectangle { title = activePlayer.trackTitle || "Unknown Track"; subtitle = activePlayer.trackArtist || ""; } - if (title.length > 20) - title = title.substring(0, 20) + "..."; - - if (subtitle.length > 22) - subtitle = subtitle.substring(0, 22) + "..."; return subtitle.length > 0 ? title + " • " + subtitle : title; } @@ -127,6 +122,8 @@ Rectangle { color: Theme.surfaceText font.weight: Font.Medium elide: Text.ElideRight + wrapMode: Text.NoWrap + maximumLineCount: 1 MouseArea { anchors.fill: parent diff --git a/README.md b/README.md index 6a382136..dedbcca5 100644 --- a/README.md +++ b/README.md @@ -17,12 +17,14 @@ This shell kinda depends on [Niri](https://github.com/YaLTeR/niri), but only for mkdir -p ~/.local/share/fonts && curl -L "https://github.com/google/material-design-icons/raw/master/variablefont/MaterialSymbolsRounded%5BFILL%2CGRAD%2Copsz%2Cwght%5D.ttf" -o ~/.local/share/fonts/MaterialSymbolsRounded.ttf && fc-cache -f # Can also be installed from AUR on arch linux, paru -S ttf-material-symbols-variable-git -# 2 --- Noto Sans (recommended font) -mkdir -p ~/.local/share/fonts && curl -L "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf" -o ~/.local/share/fonts/NotoSans-Regular.ttf && curl -L "https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSans/NotoSans-Bold.ttf" -o ~/.local/share/fonts/NotoSans-Bold.ttf && fc-cache -f +# 2 --- Inter Variable (recommended font) +mkdir -p ~/.local/share/fonts && curl -L "https://github.com/rsms/inter/releases/download/v4.0/Inter-4.0.zip" -o /tmp/Inter.zip && unzip -j /tmp/Inter.zip "InterVariable.ttf" "InterVariable-Italic.ttf" -d ~/.local/share/fonts/ && rm /tmp/Inter.zip && fc-cache -f +# Can also be installed on arch linux, pacman -S inter-fonts -# 3 --- JetBrains Mono (recommended font) -mkdir -p ~/.local/share/fonts && curl -L "https://download-cdn.jetbrains.com/fonts/JetBrainsMono-2.304.zip" -o /tmp/JetBrainsMono.zip && unzip -j /tmp/JetBrainsMono.zip "fonts/ttf/*.ttf" -d ~/.local/share/fonts/ && rm /tmp/JetBrainsMono.zip && fc-cache -f +# 3 --- Fira Code (recommended monospace font) +mkdir -p ~/.local/share/fonts && curl -L "https://github.com/tonsky/FiraCode/releases/download/6.2/Fira_Code_v6.2.zip" -o /tmp/FiraCode.zip && unzip -j /tmp/FiraCode.zip "ttf/*.ttf" -d ~/.local/share/fonts/ && rm /tmp/FiraCode.zip && fc-cache -f +# Can also be installed on arch linux, pacman -S ttf-fira-code # 4 --- QuickShell (recommended to use a git build) @@ -42,7 +44,7 @@ paru -S quickshell-git ```bash # Arch -paru -S ttf-material-symbols-variable-git matugen cliphist cava wl-clipboard ddcutil +paru -S ttf-material-symbols-variable-git inter-font ttf-fira-code matugen cliphist cava wl-clipboard ddcutil ``` **Note on networking:** This shell requires NetworkManager for WiFi functionality. diff --git a/Widgets/DankDropdown.qml b/Widgets/DankDropdown.qml index 109fe438..03d9ed22 100644 --- a/Widgets/DankDropdown.qml +++ b/Widgets/DankDropdown.qml @@ -2,6 +2,7 @@ import QtQuick import QtQuick.Controls import qs.Common import qs.Widgets +import "../Common/fuzzysort.js" as FuzzySort Rectangle { id: root @@ -12,6 +13,9 @@ Rectangle { property var options: [] 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 maxPopupHeight: 400 signal valueChanged(string value) @@ -102,7 +106,8 @@ Rectangle { popup.close(); } else if (popup) { var pos = dropdown.mapToItem(Overlay.overlay, 0, dropdown.height + 4); - popup.x = pos.x; + // Center the wider popup over the dropdown button + popup.x = pos.x - (root.popupWidthOffset / 2); popup.y = pos.y; popup.open(); } @@ -167,13 +172,64 @@ Rectangle { Popup { id: dropdownMenu + property string searchQuery: "" + property var filteredOptions: [] + property int selectedIndex: -1 + parent: Overlay.overlay - width: 180 - height: Math.min(200, root.options.length * 36 + 16) + 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 + } else { + var results = FuzzySort.go(searchQuery, root.options, { + limit: 50, + threshold: -10000 + }) + filteredOptions = results.map(function(result) { + return result.target + }) + } + selectedIndex = -1 + } + + function selectNext() { + if (filteredOptions.length > 0) { + 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) + } + } + + function selectCurrent() { + if (selectedIndex >= 0 && selectedIndex < filteredOptions.length) { + root.currentValue = filteredOptions[selectedIndex] + root.valueChanged(filteredOptions[selectedIndex]) + dropdownMenu.close() + } + } + background: Rectangle { color: "transparent" } @@ -184,15 +240,53 @@ Rectangle { border.width: 1 radius: Theme.cornerRadiusSmall - ListView { + Column { anchors.fill: parent anchors.margins: Theme.spacingS - clip: true - model: root.options - spacing: 2 - ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } - ScrollBar.horizontal: ScrollBar { policy: ScrollBar.AlwaysOff } + // 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 + 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 + 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 @@ -213,11 +307,16 @@ Rectangle { } } - delegate: Rectangle { + 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: optionArea.containsMouse ? Theme.primaryHoverLight : "transparent" + color: isSelected ? Theme.primaryHover : + optionArea.containsMouse ? Theme.primaryHoverLight : "transparent" Row { anchors.left: parent.left @@ -226,9 +325,10 @@ Rectangle { spacing: Theme.spacingS DankIcon { - name: root.optionIcons.length > index ? root.optionIcons[index] : "" + name: optionIndex >= 0 && root.optionIcons.length > optionIndex ? + root.optionIcons[optionIndex] : "" size: 18 - color: root.currentValue === modelData ? Theme.primary : Theme.surfaceVariantText + color: isCurrentValue ? Theme.primary : Theme.surfaceVariantText visible: name !== "" } @@ -236,10 +336,11 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter text: modelData font.pixelSize: Theme.fontSizeMedium - color: root.currentValue === modelData ? Theme.primary : Theme.surfaceText - font.weight: root.currentValue === modelData ? Font.Medium : Font.Normal + 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 { @@ -249,17 +350,14 @@ Rectangle { hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: { - root.currentValue = modelData; - root.valueChanged(modelData); - var popup = popupLoader.item; - if (popup) popup.close(); + root.currentValue = modelData + root.valueChanged(modelData) + dropdownMenu.close() } } - + } } - } - } } diff --git a/Widgets/StyledText.qml b/Widgets/StyledText.qml index 3ee5be40..84455e8c 100644 --- a/Widgets/StyledText.qml +++ b/Widgets/StyledText.qml @@ -1,17 +1,34 @@ import QtQuick import qs.Common +import qs.Services Text { id: root + + property bool isMonospace: false color: Theme.surfaceText font.pixelSize: Appearance.fontSize.normal - font.family: Prefs.fontFamily + font.family: { + 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)) { + // Use system default + return isMonospace ? "Monospace" : "DejaVu Sans" + } + } + + // Either user overrode it, or default font is available + return requestedFont + } font.weight: Prefs.fontWeight wrapMode: Text.WordWrap elide: Text.ElideRight verticalAlignment: Text.AlignVCenter - // Font rendering improvements for crisp text renderType: Text.NativeRendering textFormat: Text.PlainText antialiasing: true