mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-29 16:02:51 -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:
committed by
GitHub
parent
4e0c813db7
commit
92bcb83b16
@@ -7,6 +7,10 @@ import qs.Widgets
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: resultsContainer
|
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 appLauncher: null
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
|
|
||||||
@@ -90,19 +94,32 @@ Rectangle {
|
|||||||
width: resultsList.iconSize
|
width: resultsList.iconSize
|
||||||
height: resultsList.iconSize
|
height: resultsList.iconSize
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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 {
|
IconImage {
|
||||||
id: listIconImg
|
id: listIconImg
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: Quickshell.iconPath(model.icon, true)
|
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: !parent.isMaterial && status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !listIconImg.visible
|
visible: !parent.isMaterial && !listIconImg.visible
|
||||||
color: Theme.surfaceLight
|
color: Theme.surfaceLight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 1
|
border.width: 1
|
||||||
@@ -120,7 +137,7 @@ Rectangle {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -255,20 +272,33 @@ Rectangle {
|
|||||||
width: iconSize
|
width: iconSize
|
||||||
height: iconSize
|
height: iconSize
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
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 {
|
IconImage {
|
||||||
id: gridIconImg
|
id: gridIconImg
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: Quickshell.iconPath(model.icon, true)
|
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||||
smooth: true
|
smooth: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: !parent.isMaterial && status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: !gridIconImg.visible
|
visible: !parent.isMaterial && !gridIconImg.visible
|
||||||
color: Theme.surfaceLight
|
color: Theme.surfaceLight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|||||||
@@ -404,16 +404,29 @@ DankPopout {
|
|||||||
width: appList.iconSize
|
width: appList.iconSize
|
||||||
height: appList.iconSize
|
height: appList.iconSize
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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 {
|
IconImage {
|
||||||
id: listIconImg
|
id: listIconImg
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingXS
|
anchors.margins: Theme.spacingXS
|
||||||
source: Quickshell.iconPath(model.icon, true)
|
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||||
smooth: true
|
smooth: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: !parent.isMaterial && status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -421,7 +434,7 @@ DankPopout {
|
|||||||
anchors.leftMargin: Theme.spacingS
|
anchors.leftMargin: Theme.spacingS
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.bottomMargin: Theme.spacingM
|
anchors.bottomMargin: Theme.spacingM
|
||||||
visible: !listIconImg.visible
|
visible: !parent.isMaterial && listIconImg.status !== Image.Ready
|
||||||
color: Theme.surfaceLight
|
color: Theme.surfaceLight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 0
|
border.width: 0
|
||||||
@@ -435,11 +448,12 @@ DankPopout {
|
|||||||
font.weight: Font.Bold
|
font.weight: Font.Bold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
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 baseCellWidth: adaptiveColumns ? Math.max(minCellWidth, Math.min(maxCellWidth, width / columns)) : (width - Theme.spacingS * 2) / columns
|
||||||
property int baseCellHeight: baseCellWidth + 20
|
property int baseCellHeight: baseCellWidth + 20
|
||||||
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
property int actualColumns: adaptiveColumns ? Math.floor(width / cellWidth) : columns
|
||||||
|
|
||||||
property int remainingSpace: width - (actualColumns * cellWidth)
|
property int remainingSpace: width - (actualColumns * cellWidth)
|
||||||
|
|
||||||
signal keyboardNavigationReset
|
signal keyboardNavigationReset
|
||||||
@@ -578,6 +593,19 @@ DankPopout {
|
|||||||
width: iconSize
|
width: iconSize
|
||||||
height: iconSize
|
height: iconSize
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
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 {
|
IconImage {
|
||||||
id: gridIconImg
|
id: gridIconImg
|
||||||
@@ -586,10 +614,10 @@ DankPopout {
|
|||||||
anchors.leftMargin: Theme.spacingS
|
anchors.leftMargin: Theme.spacingS
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.bottomMargin: Theme.spacingS
|
anchors.bottomMargin: Theme.spacingS
|
||||||
source: Quickshell.iconPath(model.icon, true)
|
source: parent.isMaterial ? "" : Quickshell.iconPath(parent.iconValue, true)
|
||||||
smooth: true
|
smooth: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
visible: status === Image.Ready
|
visible: !parent.isMaterial && status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -597,7 +625,7 @@ DankPopout {
|
|||||||
anchors.leftMargin: Theme.spacingS
|
anchors.leftMargin: Theme.spacingS
|
||||||
anchors.rightMargin: Theme.spacingS
|
anchors.rightMargin: Theme.spacingS
|
||||||
anchors.bottomMargin: Theme.spacingS
|
anchors.bottomMargin: Theme.spacingS
|
||||||
visible: !gridIconImg.visible
|
visible: !parent.isMaterial && gridIconImg.status !== Image.Ready
|
||||||
color: Theme.surfaceLight
|
color: Theme.surfaceLight
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
border.width: 0
|
border.width: 0
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import qs.Widgets
|
|||||||
Item {
|
Item {
|
||||||
id: root
|
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 searchQuery: ""
|
||||||
property string selectedCategory: I18n.tr("All")
|
property string selectedCategory: I18n.tr("All")
|
||||||
property string viewMode: "list" // "list" or "grid"
|
property string viewMode: "list" // "list" or "grid"
|
||||||
@@ -163,7 +167,7 @@ Item {
|
|||||||
filteredModel.append({
|
filteredModel.append({
|
||||||
"name": app.name || "",
|
"name": app.name || "",
|
||||||
"exec": app.execString || app.exec || app.action || "",
|
"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 || "",
|
"comment": app.comment || "",
|
||||||
"categories": app.categories || [],
|
"categories": app.categories || [],
|
||||||
"isPlugin": isPluginItem,
|
"isPlugin": isPluginItem,
|
||||||
|
|||||||
@@ -26,35 +26,35 @@ Item {
|
|||||||
const baseItems = [
|
const baseItems = [
|
||||||
{
|
{
|
||||||
name: "Test Item 1",
|
name: "Test Item 1",
|
||||||
icon: "lightbulb",
|
icon: "material:lightbulb",
|
||||||
comment: "This is a test item that shows a toast notification",
|
comment: "This is a test item that shows a toast notification",
|
||||||
action: "toast:Test Item 1 executed!",
|
action: "toast:Test Item 1 executed!",
|
||||||
categories: ["LauncherExample"]
|
categories: ["LauncherExample"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test Item 2",
|
name: "Test Item 2",
|
||||||
icon: "star",
|
icon: "material:star",
|
||||||
comment: "Another test item with different action",
|
comment: "Another test item with different action",
|
||||||
action: "toast:Test Item 2 clicked!",
|
action: "toast:Test Item 2 clicked!",
|
||||||
categories: ["LauncherExample"]
|
categories: ["LauncherExample"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test Item 3",
|
name: "Test Item 3",
|
||||||
icon: "favorite",
|
icon: "material:favorite",
|
||||||
comment: "Third test item for demonstration",
|
comment: "Third test item for demonstration",
|
||||||
action: "toast:Test Item 3 activated!",
|
action: "toast:Test Item 3 activated!",
|
||||||
categories: ["LauncherExample"]
|
categories: ["LauncherExample"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Example Copy Action",
|
name: "Example Copy Action",
|
||||||
icon: "content_copy",
|
icon: "material:content_copy",
|
||||||
comment: "Demonstrates copying text to clipboard",
|
comment: "Demonstrates copying text to clipboard",
|
||||||
action: "copy:This text was copied by the launcher plugin!",
|
action: "copy:This text was copied by the launcher plugin!",
|
||||||
categories: ["LauncherExample"]
|
categories: ["LauncherExample"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Example Script Action",
|
name: "Example Script Action",
|
||||||
icon: "terminal",
|
icon: "material:terminal",
|
||||||
comment: "Demonstrates running a simple command",
|
comment: "Demonstrates running a simple command",
|
||||||
action: "script:echo 'Hello from launcher plugin!'",
|
action: "script:echo 'Hello from launcher plugin!'",
|
||||||
categories: ["LauncherExample"]
|
categories: ["LauncherExample"]
|
||||||
|
|||||||
@@ -88,13 +88,41 @@ function executeItem(item): void
|
|||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
name: "Item Name", // Display name
|
name: "Item Name", // Display name
|
||||||
icon: "icon_name", // Material icon
|
icon: "icon_name", // Icon (optional, see Icon Types below)
|
||||||
comment: "Description", // Subtitle text
|
comment: "Description", // Subtitle text
|
||||||
action: "type:data", // Action to execute
|
action: "type:data", // Action to execute
|
||||||
categories: ["PluginName"] // Category array
|
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:
|
**Action Format**: `type:data` where:
|
||||||
- `type` - Action handler (toast, copy, script, etc.)
|
- `type` - Action handler (toast, copy, script, etc.)
|
||||||
- `data` - Action-specific data
|
- `data` - Action-specific data
|
||||||
|
|||||||
@@ -1221,11 +1221,51 @@ Item {
|
|||||||
Each item returned by `getItems()` must include:
|
Each item returned by `getItems()` must include:
|
||||||
|
|
||||||
- `name` (string): Display name shown in launcher
|
- `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
|
- `comment` (string): Description/subtitle text
|
||||||
- `action` (string): Action identifier in `type:data` format
|
- `action` (string): Action identifier in `type:data` format
|
||||||
- `categories` (array): Array containing your plugin name
|
- `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
|
### Trigger System
|
||||||
|
|
||||||
Triggers control when your plugin's items appear in the launcher:
|
Triggers control when your plugin's items appear in the launcher:
|
||||||
|
|||||||
Reference in New Issue
Block a user