aboutsummaryrefslogtreecommitdiffstats
path: root/appearance/IconsTab.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-16 13:46:37 +0200
committerDanilo M. <danix@danix.xyz>2026-09-16 13:46:37 +0200
commit516e44c6c87825bfc985cbe6a1e73fce3fa7762a (patch)
treeeca6909bf1897c7701d0ba2b99200b91c499f9fe /appearance/IconsTab.qml
parent4d63b1d50a593ea2a2475d79bf17d67240eff969 (diff)
downloadquickshell-516e44c6c87825bfc985cbe6a1e73fce3fa7762a.tar.gz
quickshell-516e44c6c87825bfc985cbe6a1e73fce3fa7762a.zip
feat(appearance): polish the Icons tab previews and apply
Cursor previews were extracted only on hover, so the row stayed blank until each card was visited. They are now batched on open: one python pass extracts four shapes per theme (arrow, hand, text, horizontal resize) through the theme's aliases and every storage form it may use (hyprcursor .hlc, a shape directory SVG, or a legacy Xcursor binary via xcur2png). The card rows are Flows so they wrap instead of running off the right edge, and the tab scrolls. Cards widened and names wrap to two lines. A size selector (16-64) applies the pointer size live, to gsettings, the GTK settings.ini files and environment.lua. gsettings alone does not reliably switch icon/cursor here: both gtk-3.0 and gtk-4.0 settings.ini carry the names, so apply now rewrites gtk-icon-theme-name / gtk-cursor-theme-name / gtk-cursor-theme-size there too. The symlinked `default` cursor alias is skipped in the scan and the current value resolved through it, so the real theme carries the current marker. The tab row is centred against the panel so it reads as drawer chrome rather than the first line of a tab's content.
Diffstat (limited to 'appearance/IconsTab.qml')
-rw-r--r--appearance/IconsTab.qml177
1 files changed, 105 insertions, 72 deletions
diff --git a/appearance/IconsTab.qml b/appearance/IconsTab.qml
index 92c3f74..b2c7ee4 100644
--- a/appearance/IconsTab.qml
+++ b/appearance/IconsTab.qml
@@ -13,32 +13,33 @@ import Quickshell
import Quickshell.Widgets
import QtQuick
-Column {
- spacing: 12
+Flickable {
+ contentHeight: col.implicitHeight
+ clip: true
- 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 }
- }
+ property string liveCursorCursor: ""
- Flickable {
- id: iconList
+ Column {
+ id: col
width: parent.width
- height: 150
- contentWidth: iconRow.implicitWidth
- contentHeight: height
- clip: true
- flickableDirection: Flickable.HorizontalFlick
+ spacing: 12
- Row {
- id: iconRow
+ 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 }
+ }
+
+ // Flow, not a horizontal strip: the cards wrap onto further rows rather
+ // than running off the right edge, and the tab scrolls vertically.
+ Flow {
+ width: parent.width
spacing: 10
Repeater {
model: Icons.iconThemes
@@ -46,7 +47,7 @@ Column {
id: iconCard
required property string modelData
readonly property bool current: Icons.currentIcon === modelData
- width: 150; height: 120; radius: 10
+ width: 230; height: 150; 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"
@@ -58,10 +59,12 @@ Column {
}
Column {
+ width: parent.width - 16
anchors.centerIn: parent
- spacing: 6
+ spacing: 10
Row {
- spacing: 6
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: 8
Repeater {
model: ["folder", "text-x-generic", "image-x-generic", "network-wireless"]
IconImage {
@@ -73,45 +76,62 @@ Column {
// /org/gtk gresource paths Qt cannot open.
source: glyph.indexOf("/") === 0 && glyph.indexOf("/org/gtk/") !== 0
? "file://" + glyph : ""
- implicitSize: 26
+ implicitSize: 44
}
}
}
Text {
- anchors.horizontalCenter: parent.horizontalCenter
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
text: iconCard.current ? modelData + " current" : modelData
color: Theme.text
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ wrapMode: Text.WrapAnywhere
+ maximumLineCount: 2
+ elide: Text.ElideRight
}
}
}
}
}
- }
-
- 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
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: "Cursor theme"
+ color: Theme.subtext
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2; bold: true }
+ }
+ Item { width: 12; height: 1 }
+ Text {
+ anchors.verticalCenter: parent.verticalCenter
+ text: "Size"
+ color: Theme.subtext
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ }
+ Repeater {
+ model: [16, 24, 32, 48, 64]
+ Tab {
+ required property var modelData
+ anchors.verticalCenter: parent.verticalCenter
+ text: String(modelData)
+ selected: Icons.cursorSize === modelData
+ onClicked: Icons.setCursorSize(modelData)
+ }
+ }
+ }
+
+ Flow {
+ width: parent.width
spacing: 10
Repeater {
model: Icons.cursorThemes
Rectangle {
+ id: cursorCard
required property string modelData
readonly property bool current: Icons.currentCursor === modelData
- width: 120; height: 120; radius: 10
+ width: 230; height: 150; 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"
@@ -119,13 +139,15 @@ Column {
HoverHandler {
onHoveredChanged: {
if (hovered) {
- Icons.previewCursor(modelData);
- // Live preview: the real cursor, reverted on leave.
+ // Previews are already batched on open; hovering
+ // only switches the real cursor and reverts it.
liveCursorCursor = modelData;
- Quickshell.execDetached(["hyprctl", "setcursor", modelData, "24"]);
+ Quickshell.execDetached(["hyprctl", "setcursor", modelData,
+ String(Icons.cursorSize)]);
} else if (liveCursorCursor === modelData) {
liveCursorCursor = "";
- Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, "24"]);
+ Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor,
+ String(Icons.cursorSize)]);
}
}
}
@@ -136,49 +158,60 @@ Column {
}
Column {
+ width: parent.width - 16
anchors.centerIn: parent
- spacing: 6
- Image {
+ spacing: 10
+ Row {
anchors.horizontalCenter: parent.horizontalCenter
- width: 40; height: 40
- asynchronous: true
- source: Icons.cursorPreview[modelData] ? "file://" + Icons.cursorPreview[modelData] : ""
- fillMode: Image.PreserveAspectFit
+ spacing: 8
+ Repeater {
+ // The cases that tell themes apart: arrow, hand,
+ // text and horizontal resize.
+ model: ["left_ptr", "hand2", "xterm", "resize"]
+ Image {
+ required property string modelData
+ readonly property string p: (Icons.cursorPreview[cursorCard.modelData] ?? {})[modelData] ?? ""
+ width: 44; height: 44
+ asynchronous: true
+ source: p !== "" ? "file://" + p : ""
+ 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
+ width: parent.width
horizontalAlignment: Text.AlignHCenter
+ text: cursorCard.current ? modelData + " current" : modelData
+ color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ wrapMode: Text.WrapAnywhere
+ maximumLineCount: 2
+ elide: Text.ElideRight
}
}
}
}
}
- }
- Text {
- width: parent.width
- wrapMode: Text.Wrap
- visible: Icons.notice !== ""
- text: Icons.notice
- color: Theme.green
- font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 3 }
+ 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"]);
+ Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, String(Icons.cursorSize)]);
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"]);
+ Quickshell.execDetached(["hyprctl", "setcursor", Icons.currentCursor, String(Icons.cursorSize)]);
liveCursorCursor = "";
}
}