Replace NTextInput with NComboBox for font settings

FontService: use Qt.fontFamilies to grab available fonts and split Mono
fonts
NComboBox: allow height changes
GeneralTab: replace NTextInput with NComboBox
This commit is contained in:
Ly-sec 2025-09-02 20:07:10 +02:00
parent 8d05cb9f3b
commit 520da3e915
4 changed files with 191 additions and 19 deletions

View file

@ -107,6 +107,9 @@ Singleton {
// Kickoff Matugen service // Kickoff Matugen service
MatugenService.init() MatugenService.init()
// Kickoff Font service
FontService.init()
Qt.callLater(function () { Qt.callLater(function () {
validateMonitorConfigurations() validateMonitorConfigurations()
}) })

View file

@ -158,36 +158,39 @@ ColumnLayout {
spacing: Style.marginL * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
NTextInput { NComboBox {
label: "Default Font" label: "Default Font"
description: "Main font used throughout the interface." description: "Main font used throughout the interface."
text: Settings.data.ui.fontDefault model: FontService.availableFonts
placeholderText: "Roboto" currentKey: Settings.data.ui.fontDefault
Layout.fillWidth: true placeholder: "Select default font..."
onEditingFinished: { popupHeight: 420 * scaling
Settings.data.ui.fontDefault = text onSelected: function (key) {
Settings.data.ui.fontDefault = key
} }
} }
NTextInput { NComboBox {
label: "Fixed Width Font" label: "Fixed Width Font"
description: "Monospace font used for terminal and code display." description: "Monospace font used for terminal and code display."
text: Settings.data.ui.fontFixed model: FontService.monospaceFonts
placeholderText: "DejaVu Sans Mono" currentKey: Settings.data.ui.fontFixed
Layout.fillWidth: true placeholder: "Select monospace font..."
onEditingFinished: { popupHeight: 320 * scaling
Settings.data.ui.fontFixed = text onSelected: function (key) {
Settings.data.ui.fontFixed = key
} }
} }
NTextInput { NComboBox {
label: "Billboard Font" label: "Billboard Font"
description: "Large font used for clocks and prominent displays." description: "Large font used for clocks and prominent displays."
text: Settings.data.ui.fontBillboard model: FontService.displayFonts
placeholderText: "Inter" currentKey: Settings.data.ui.fontBillboard
Layout.fillWidth: true placeholder: "Select display font..."
onEditingFinished: { popupHeight: 320 * scaling
Settings.data.ui.fontBillboard = text onSelected: function (key) {
Settings.data.ui.fontBillboard = key
} }
} }
} }

165
Services/FontService.qml Normal file
View file

@ -0,0 +1,165 @@
pragma Singleton
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Commons
Singleton {
id: root
property ListModel availableFonts: ListModel {}
property ListModel monospaceFonts: ListModel {}
property ListModel displayFonts: ListModel {}
property bool fontsLoaded: false
Component.onCompleted: {
// Fonts will be loaded when init() is called
}
function init() {
Logger.log("FontService", "Service started")
loadSystemFonts()
}
function loadSystemFonts() {
Logger.log("FontService", "Loading system fonts...")
var fontFamilies = Qt.fontFamilies()
availableFonts.clear()
monospaceFonts.clear()
displayFonts.clear()
for (var i = 0; i < fontFamilies.length; i++) {
var fontName = fontFamilies[i]
if (fontName && fontName.trim() !== "") {
availableFonts.append({
"key": fontName,
"name": fontName
})
if (isMonospaceFont(fontName)) {
monospaceFonts.append({
"key": fontName,
"name": fontName
})
}
if (isDisplayFont(fontName)) {
displayFonts.append({
"key": fontName,
"name": fontName
})
}
}
}
sortModel(availableFonts)
sortModel(monospaceFonts)
sortModel(displayFonts)
if (monospaceFonts.count === 0) {
Logger.log("FontService", "No monospace fonts detected, adding fallbacks")
addFallbackFonts(
monospaceFonts,
["DejaVu Sans Mono", "Liberation Mono", "Courier New", "Courier", "Monaco", "Consolas", "Lucida Console", "Monaco", "Andale Mono"])
}
if (displayFonts.count === 0) {
Logger.log("FontService", "No display fonts detected, adding fallbacks")
addFallbackFonts(
displayFonts,
["Inter", "Roboto", "Open Sans", "Arial", "Helvetica", "Verdana", "Segoe UI", "SF Pro Display", "Ubuntu", "Noto Sans"])
}
fontsLoaded = true
Logger.log("FontService", "Loaded", availableFonts.count, "fonts (", monospaceFonts.count, "monospace,",
displayFonts.count, "display)")
}
function isMonospaceFont(fontName) {
var patterns = ["mono", "monospace", "fixed", "console", "terminal", "typewriter", "courier", "dejavu", "liberation", "source code", "fira code", "jetbrains", "cascadia", "hack", "inconsolata", "roboto mono", "ubuntu mono", "menlo", "consolas", "monaco", "andale mono"]
var lowerFontName = fontName.toLowerCase()
for (var i = 0; i < patterns.length; i++) {
if (lowerFontName.includes(patterns[i]))
return true
}
var commonFonts = ["DejaVu Sans Mono", "Liberation Mono", "Source Code Pro", "Fira Code", "JetBrains Mono", "Cascadia Code", "Hack", "Inconsolata", "Roboto Mono", "Ubuntu Mono", "Menlo", "Consolas", "Monaco", "Andale Mono", "Courier New", "Courier", "Lucida Console", "Monaco", "MS Gothic", "MS Mincho"]
return commonFonts.includes(fontName)
}
function isDisplayFont(fontName) {
var patterns = ["display", "headline", "title", "hero", "showcase", "brand", "inter", "roboto", "open sans", "lato", "montserrat", "poppins", "raleway", "nunito", "source sans", "ubuntu", "noto sans", "work sans", "dm sans", "manrope", "plus jakarta", "figtree"]
var lowerFontName = fontName.toLowerCase()
for (var i = 0; i < patterns.length; i++) {
if (lowerFontName.includes(patterns[i]))
return true
}
var commonFonts = ["Inter", "Roboto", "Open Sans", "Lato", "Montserrat", "Poppins", "Raleway", "Nunito", "Source Sans Pro", "Ubuntu", "Noto Sans", "Work Sans", "DM Sans", "Manrope", "Plus Jakarta Sans", "Figtree", "SF Pro Display", "Segoe UI", "Arial", "Helvetica", "Verdana"]
return commonFonts.includes(fontName)
}
function sortModel(model) {
var fontsArray = []
for (var i = 0; i < model.count; i++) {
fontsArray.push({
"key": model.get(i).key,
"name": model.get(i).name
})
}
fontsArray.sort(function (a, b) {
return a.name.localeCompare(b.name)
})
model.clear()
for (var j = 0; j < fontsArray.length; j++) {
model.append(fontsArray[j])
}
}
function addFallbackFonts(model, fallbackFonts) {
for (var i = 0; i < fallbackFonts.length; i++) {
var fontName = fallbackFonts[i]
var exists = false
for (var j = 0; j < model.count; j++) {
if (model.get(j).name === fontName) {
exists = true
break
}
}
if (!exists) {
model.append({
"key": fontName,
"name": fontName
})
}
}
sortModel(model)
}
function searchFonts(query) {
if (!query || query.trim() === "")
return availableFonts
var results = []
var lowerQuery = query.toLowerCase()
for (var i = 0; i < availableFonts.count; i++) {
var font = availableFonts.get(i)
if (font.name.toLowerCase().includes(lowerQuery)) {
results.push(font)
}
}
return results
}
}

View file

@ -10,6 +10,7 @@ RowLayout {
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
property real preferredWidth: 320 * scaling property real preferredWidth: 320 * scaling
property real popupHeight: 160 * scaling
property string label: "" property string label: ""
property string description: "" property string description: ""
@ -86,7 +87,7 @@ RowLayout {
popup: Popup { popup: Popup {
y: combo.height y: combo.height
width: combo.width width: combo.width
implicitHeight: Math.min(160 * scaling, contentItem.implicitHeight + Style.marginM * scaling * 2) implicitHeight: Math.min(root.popupHeight, contentItem.implicitHeight + Style.marginM * scaling * 2)
padding: Style.marginM * scaling padding: Style.marginM * scaling
contentItem: ListView { contentItem: ListView {