// Choosing a font family. // // A closed dropdown: the button shows the current family rendered in its own // face, and opening it reveals a search field and the matches — each drawn IN // the font it names, because a list of family names set in the current font // tells you nothing about what you are choosing. // // Closed by default on purpose. This used to be an always-open search-and-list // that put twenty-four sample rows on the Typography tab before you asked for // any of them. import QtQuick import qs.config import qs.modules.clipboard Column { id: root spacing: 0 property var families: [] property string current: "" property string emptyText: "No fonts found" property bool open: false signal picked(string family) // Filtered rather than listing all 139: a scrolling wall of family names // inside a page that is already scrolling is worse than a search box. readonly property var matches: { const needle = filter.text.trim().toLowerCase(); const list = root.families.filter(family => needle === "" ? true : family.toLowerCase().indexOf(needle) >= 0); // Always show what is currently selected, even when it does not match. if (needle === "" && list.indexOf(root.current) >= 0) return [root.current].concat(list.filter(f => f !== root.current)).slice(0, 12); return list.slice(0, 12); } function choose(family: string): void { root.picked(family); root.open = false; filter.text = ""; } // ── The closed state: one button ──────────────────────────────────────── Rectangle { id: closedButton width: parent.width height: 38 radius: 10 color: buttonMouse.containsMouse || root.open || activeFocus ? Theme.alpha(Theme.fg, 0.09) : Theme.alpha(Theme.fg, 0.055) border.width: root.open || activeFocus ? 2 : 1 border.color: root.open || activeFocus ? Theme.alpha(Theme.accent, 0.55) : Theme.alpha(Theme.fg, 0.07) activeFocusOnTab: true Accessible.role: Accessible.ComboBox Accessible.name: (root.current !== "" ? root.current : "Choose a font") Keys.onReturnPressed: root.open = !root.open Keys.onSpacePressed: root.open = !root.open Text { anchors.left: parent.left anchors.leftMargin: 13 anchors.right: chevron.left anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter text: root.current !== "" ? root.current : "Choose a font" elide: Text.ElideRight // The current family draws itself — the closed state is a sample. font.family: root.current !== "" ? root.current : Theme.fontFamily font.pixelSize: Theme.fontSize color: root.current !== "" ? Theme.fg : Theme.fgMuted } Text { id: chevron anchors.right: parent.right anchors.rightMargin: 12 anchors.verticalCenter: parent.verticalCenter text: root.open ? "▴" : "▾" color: Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: 11 } MouseArea { id: buttonMouse anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: root.open = !root.open } } Item { width: 1; height: root.open ? 8 : 0 } // ── The open state: search plus matches ───────────────────────────────── Column { width: parent.width visible: root.open spacing: 0 SearchField { id: filter width: parent.width placeholder: "Search installed fonts" } Repeater { model: root.open ? root.matches : [] SettingRow { id: candidate required property var modelData required property int index readonly property bool selected: candidate.modelData === root.current label: candidate.modelData detail: candidate.selected ? "Currently in use" : "" controlWidth: 210 divider: candidate.index < root.matches.length - 1 activatable: !candidate.selected onActivated: root.choose(candidate.modelData) Text { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter width: 200 horizontalAlignment: Text.AlignRight elide: Text.ElideRight // The sample is drawn in the candidate family, which is the // entire reason this is a list rather than a text field. text: "Handgloves 0123" font.family: candidate.modelData font.pixelSize: Theme.fontSize + 1 color: candidate.selected ? Theme.accent : Theme.fgDim } } } SettingRow { width: parent.width visible: root.matches.length === 0 label: root.emptyText divider: false } } }