1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -05:00

Add support for material icons on launcher (#521)

- Add support for using material icons on launcher items (for plugins)
- Allow plugins to omit icons
- Update plugin docs
- add developer note so the next one working on launcher will not spend
  hours debugging the wrong place as I just did :)
This commit is contained in:
Bruno Cesar Rocha
2025-10-21 20:55:45 +01:00
committed by GitHub
parent 4e0c813db7
commit 92bcb83b16
6 changed files with 153 additions and 23 deletions

View File

@@ -7,6 +7,10 @@ import qs.Widgets
Rectangle {
id: resultsContainer
// DEVELOPER NOTE: This component renders the Spotlight launcher (accessed via Mod+Space).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modules/AppDrawer/AppLauncher.qml and vice versa.
property var appLauncher: null
property var contextMenu: null
@@ -90,19 +94,32 @@ Rectangle {
width: resultsList.iconSize
height: resultsList.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: resultsList.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: listIconImg
anchors.fill: parent
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !listIconImg.visible
visible: !parent.isMaterial && !listIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1
@@ -120,7 +137,7 @@ Rectangle {
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - resultsList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - resultsList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -255,20 +272,33 @@ Rectangle {
width: iconSize
height: iconSize
anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: gridIconImg
anchors.fill: parent
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
anchors.fill: parent
visible: !gridIconImg.visible
visible: !parent.isMaterial && !gridIconImg.visible
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 1

View File

@@ -404,16 +404,29 @@ DankPopout {
width: appList.iconSize
height: appList.iconSize
anchors.verticalCenter: parent.verticalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: appList.iconSize - Theme.spacingM
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: listIconImg
anchors.fill: parent
anchors.margins: Theme.spacingXS
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -421,7 +434,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingM
visible: !listIconImg.visible
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0
@@ -435,11 +448,12 @@ DankPopout {
font.weight: Font.Bold
}
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - appList.iconSize - Theme.spacingL
width: (model.icon !== undefined && model.icon !== "") ? (parent.width - appList.iconSize - Theme.spacingL) : parent.width
spacing: Theme.spacingXS
StyledText {
@@ -513,6 +527,7 @@ DankPopout {
property int baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
property int baseCellHeight: baseCellWidth + 20
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
property int remainingSpace: width - (actualColumns * cellWidth)
signal keyboardNavigationReset
@@ -578,6 +593,19 @@ DankPopout {
width: iconSize
height: iconSize
anchors.horizontalCenter: parent.horizontalCenter
visible: model.icon !== undefined && model.icon !== ""
property string iconValue: model.icon || ""
property bool isMaterial: iconValue.indexOf("material:") === 0
property string materialName: isMaterial ? iconValue.substring(9) : ""
DankIcon {
anchors.centerIn: parent
name: parent.materialName
size: parent.iconSize - Theme.spacingL
color: Theme.surfaceText
visible: parent.isMaterial
}
IconImage {
id: gridIconImg
@@ -586,10 +614,10 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
source: Quickshell.iconPath(model.icon, true)
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
smooth: true
asynchronous: true
visible: status === Image.Ready
visible: !parent.isMaterial && status === Image.Ready
}
Rectangle {
@@ -597,7 +625,7 @@ DankPopout {
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
anchors.bottomMargin: Theme.spacingS
visible: !gridIconImg.visible
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
color: Theme.surfaceLight
radius: Theme.cornerRadius
border.width: 0

View File

@@ -8,6 +8,10 @@ import qs.Widgets
Item {
id: root
// DEVELOPER NOTE: This component manages the AppDrawer launcher (accessed via DankBar icon).
// Changes to launcher behavior, especially item rendering, filtering, or model structure,
// likely require corresponding updates in Modals/Spotlight/SpotlightResults.qml and vice versa.
property string searchQuery: ""
property string selectedCategory: I18n.tr("All")
property string viewMode: "list" // "list" or "grid"
@@ -163,7 +167,7 @@ Item {
filteredModel.append({
"name": app.name || "",
"exec": app.execString || app.exec || app.action || "",
"icon": app.icon || "application-x-executable",
"icon": app.icon !== undefined ? app.icon : (isPluginItem ? "" : "application-x-executable"),
"comment": app.comment || "",
"categories": app.categories || [],
"isPlugin": isPluginItem,

View File

@@ -26,35 +26,35 @@ Item {
const baseItems = [
{
name: "Test Item 1",
icon: "lightbulb",
icon: "material:lightbulb",
comment: "This is a test item that shows a toast notification",
action: "toast:Test Item 1 executed!",
categories: ["LauncherExample"]
},
{
name: "Test Item 2",
icon: "star",
name: "Test Item 2",
icon: "material:star",
comment: "Another test item with different action",
action: "toast:Test Item 2 clicked!",
categories: ["LauncherExample"]
},
{
name: "Test Item 3",
icon: "favorite",
icon: "material:favorite",
comment: "Third test item for demonstration",
action: "toast:Test Item 3 activated!",
categories: ["LauncherExample"]
},
{
name: "Example Copy Action",
icon: "content_copy",
icon: "material:content_copy",
comment: "Demonstrates copying text to clipboard",
action: "copy:This text was copied by the launcher plugin!",
categories: ["LauncherExample"]
},
{
name: "Example Script Action",
icon: "terminal",
icon: "material:terminal",
comment: "Demonstrates running a simple command",
action: "script:echo 'Hello from launcher plugin!'",
categories: ["LauncherExample"]

View File

@@ -88,13 +88,41 @@ function executeItem(item): void
```javascript
{
name: "Item Name", // Display name
icon: "icon_name", // Material icon
icon: "icon_name", // Icon (optional, see Icon Types below)
comment: "Description", // Subtitle text
action: "type:data", // Action to execute
categories: ["PluginName"] // Category array
}
```
**Icon Types**:
The `icon` field supports three formats:
1. **Material Design Icons** - Use `material:` prefix:
```javascript
icon: "material:lightbulb" // Material Symbols Rounded font
```
Examples: `material:star`, `material:favorite`, `material:settings`
2. **Desktop Theme Icons** - Use icon name directly:
```javascript
icon: "firefox" // Uses system icon theme
```
Examples: `firefox`, `chrome`, `folder`, `text-editor`
3. **No Icon** - Omit the `icon` field entirely:
```javascript
{
name: "😀 Grinning Face",
// No icon field
comment: "Copy emoji",
action: "copy:😀",
categories: ["MyPlugin"]
}
```
Perfect for emoji pickers or text-only items where the icon area should be hidden
**Action Format**: `type:data` where:
- `type` - Action handler (toast, copy, script, etc.)
- `data` - Action-specific data

View File

@@ -1221,11 +1221,51 @@ Item {
Each item returned by `getItems()` must include:
- `name` (string): Display name shown in launcher
- `icon` (string): Material Design icon name
- `icon` (string, optional): Icon specification (see Icon Types below)
- `comment` (string): Description/subtitle text
- `action` (string): Action identifier in `type:data` format
- `categories` (array): Array containing your plugin name
### Icon Types
The `icon` field supports three formats:
**1. Material Design Icons** - Use the `material:` prefix:
```javascript
{
name: "My Item",
icon: "material:lightbulb", // Material Symbols Rounded font
comment: "Uses Material Design icon",
action: "toast:Hello!",
categories: ["MyPlugin"]
}
```
Available icons: Any icon from Material Symbols font (e.g., `lightbulb`, `star`, `favorite`, `settings`, `terminal`, `translate`, `sentiment_satisfied`)
**2. Desktop Theme Icons** - Use icon name directly:
```javascript
{
name: "Firefox",
icon: "firefox", // Uses system icon theme
comment: "Launches Firefox browser",
action: "exec:firefox",
categories: ["MyPlugin"]
}
```
Uses the user's installed icon theme. Common examples: `firefox`, `chrome`, `folder`, `text-editor`
**3. No Icon** - Omit the `icon` field entirely:
```javascript
{
name: "😀 Grinning Face",
// No icon field - emoji/unicode in name displays without icon area
comment: "Copy emoji to clipboard",
action: "copy:😀",
categories: ["MyPlugin"]
}
```
When `icon` is omitted, the launcher hides the icon area and displays only the text, giving full width to the item name. Perfect for emoji pickers or text-only items.
### Trigger System
Triggers control when your plugin's items appear in the launcher: