// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. import Quickshell import Quickshell.Widgets import QtQuick Column { spacing: 12 Text { text: Icons.scanning ? "scanning…" : `${Icons.iconThemes.length} icon themes · ${Icons.cursorThemes.length} cursor themes` color: Theme.overlay font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } Text { text: "Icon theme" color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } } Flickable { id: iconList width: parent.width height: 150 contentWidth: iconRow.implicitWidth contentHeight: height clip: true flickableDirection: Flickable.HorizontalFlick Row { id: iconRow spacing: 10 Repeater { model: Icons.iconThemes Rectangle { id: iconCard required property string modelData readonly property bool current: Icons.currentIcon === modelData width: 150; height: 120; radius: 10 color: current ? Qt.alpha(Theme.accent, 0.2) : Qt.alpha(Theme.surface, 0.35) border.width: current ? 2 : 1 border.color: current ? Theme.accent : "transparent" MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: Icons.applyIcon(modelData) } Column { anchors.centerIn: parent spacing: 6 Row { spacing: 6 Repeater { model: ["folder", "text-x-generic", "image-x-generic", "network-wireless"] IconImage { required property string modelData readonly property string glyph: (Icons.iconPreview[iconCard.modelData] ?? {})[modelData] ?? "" // Absolute paths must carry file://, or they // resolve against the config's qrc base and // fail; Gtk's builtin icons come back as // /org/gtk gresource paths Qt cannot open. source: glyph.indexOf("/") === 0 && glyph.indexOf("/org/gtk/") !== 0 ? "file://" + glyph : "" implicitSize: 26 } } } Text { anchors.horizontalCenter: parent.horizontalCenter text: iconCard.current ? modelData + " current" : modelData color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } } } } } } } Text { text: "Cursor theme" color: Theme.subtext font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true } } Flickable { width: parent.width height: 150 contentWidth: cursorRow.implicitWidth contentHeight: height clip: true flickableDirection: Flickable.HorizontalFlick Row { id: cursorRow spacing: 10 Repeater { model: Icons.cursorThemes Rectangle { required property string modelData readonly property bool current: Icons.currentCursor === modelData width: 120; height: 120; radius: 10 color: current ? Qt.alpha(Theme.accent, 0.2) : Qt.alpha(Theme.surface, 0.35) border.width: current ? 2 : 1 border.color: current ? Theme.accent : "transparent" HoverHandler { onHoveredChanged: { if (hovered) { Icons.previewCursor(modelData); // Live preview: the real cursor, reverted on leave. liveCursorCursor = modelData; Quickshell.execDetached(["hyprctl", "setcursor", modelData, "24"]); } else if (liveCursorCursor === modelData) { liveCursorCursor = ""; Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, "24"]); } } } MouseArea { anchors.fill: parent cursorShape: Qt.PointingHandCursor onClicked: Icons.applyCursor(modelData) } Column { anchors.centerIn: parent spacing: 6 Image { anchors.horizontalCenter: parent.horizontalCenter width: 40; height: 40 asynchronous: true source: Icons.cursorPreview[modelData] ? "file://" + Icons.cursorPreview[modelData] : "" fillMode: Image.PreserveAspectFit } Text { anchors.horizontalCenter: parent.horizontalCenter text: parent.parent.current ? modelData + " current" : modelData color: Theme.text font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } width: 110 elide: Text.ElideMiddle horizontalAlignment: Text.AlignHCenter } } } } } } Text { width: parent.width wrapMode: Text.Wrap visible: Icons.notice !== "" text: Icons.notice color: Theme.green font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 } } property string liveCursorCursor: "" onVisibleChanged: if (!visible && liveCursorCursor !== "") { Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, "24"]); liveCursorCursor = ""; } // A Loader destroys this item on tab switch or drawer close, and visible // does not necessarily change first, so revert the real cursor here too. Component.onDestruction: if (liveCursorCursor !== "") { Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, "24"]); liveCursorCursor = ""; } }