1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

Add DankIconPicker

This commit is contained in:
bbedward
2025-08-12 19:55:06 -04:00
parent fc1beca5e7
commit a76f0de18e
4 changed files with 579 additions and 1 deletions

View File

@@ -40,6 +40,7 @@ Singleton {
property bool showControlCenterButton: true
property bool showWorkspaceIndex: false
property bool showWorkspacePadding: false
property var workspaceNameIcons: ({})
property bool clockCompactMode: false
property string clockDateFormat: "ddd d"
property string lockDateFormat: "dddd, MMMM d"
@@ -85,6 +86,7 @@ Singleton {
signal forceTopBarLayoutRefresh()
signal widgetDataChanged()
signal workspaceIconsUpdated()
function initializeListModels() {
updateListModel(leftWidgetsModel, topBarLeftWidgets);
@@ -132,6 +134,7 @@ Singleton {
showControlCenterButton = settings.showControlCenterButton !== undefined ? settings.showControlCenterButton : true;
showWorkspaceIndex = settings.showWorkspaceIndex !== undefined ? settings.showWorkspaceIndex : false;
showWorkspacePadding = settings.showWorkspacePadding !== undefined ? settings.showWorkspacePadding : false;
workspaceNameIcons = settings.workspaceNameIcons !== undefined ? settings.workspaceNameIcons : ({});
clockCompactMode = settings.clockCompactMode !== undefined ? settings.clockCompactMode : false;
clockDateFormat = settings.clockDateFormat !== undefined ? settings.clockDateFormat : "ddd d";
lockDateFormat = settings.lockDateFormat !== undefined ? settings.lockDateFormat : "dddd, MMMM d";
@@ -226,6 +229,7 @@ Singleton {
"showControlCenterButton": showControlCenterButton,
"showWorkspaceIndex": showWorkspaceIndex,
"showWorkspacePadding": showWorkspacePadding,
"workspaceNameIcons": workspaceNameIcons,
"clockCompactMode": clockCompactMode,
"clockDateFormat": clockDateFormat,
"lockDateFormat": lockDateFormat,
@@ -268,6 +272,52 @@ Singleton {
saveSettings();
}
function setWorkspaceNameIcon(workspaceName, iconData) {
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
iconMap[workspaceName] = iconData;
workspaceNameIcons = iconMap;
saveSettings();
workspaceIconsUpdated();
}
function removeWorkspaceNameIcon(workspaceName) {
var iconMap = JSON.parse(JSON.stringify(workspaceNameIcons));
delete iconMap[workspaceName];
workspaceNameIcons = iconMap;
saveSettings();
workspaceIconsUpdated();
}
function getWorkspaceNameIcon(workspaceName) {
return workspaceNameIcons[workspaceName] || null;
}
function hasNamedWorkspaces() {
if (typeof NiriService === "undefined" || !NiriService.niriAvailable)
return false;
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
var ws = NiriService.allWorkspaces[i];
if (ws.name && ws.name.trim() !== "")
return true;
}
return false;
}
function getNamedWorkspaces() {
var namedWorkspaces = [];
if (typeof NiriService === "undefined" || !NiriService.niriAvailable)
return namedWorkspaces;
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
var ws = NiriService.allWorkspaces[i];
if (ws.name && ws.name.trim() !== "") {
namedWorkspaces.push(ws.name);
}
}
return namedWorkspaces;
}
function setClockCompactMode(enabled) {
clockCompactMode = enabled;
saveSettings();

View File

@@ -456,6 +456,12 @@ Item {
width: parent.width
sourceComponent: workspaceComponent
}
Loader {
width: parent.width
sourceComponent: workspaceIconsComponent
visible: SettingsData.hasNamedWorkspaces()
}
}
}
@@ -779,6 +785,159 @@ Item {
}
}
// Workspace Icons Component
Component {
id: workspaceIconsComponent
StyledRect {
width: parent.width
height: workspaceIconsSection.implicitHeight + Theme.spacingL * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g,
Theme.surfaceVariant.b, 0.3)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.2)
border.width: 1
Column {
id: workspaceIconsSection
anchors.fill: parent
anchors.margins: Theme.spacingL
spacing: Theme.spacingM
Row {
width: parent.width
spacing: Theme.spacingM
DankIcon {
name: "label"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "Named Workspace Icons"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
width: parent.width
text: "Configure icons for named workspaces. Icons take priority over numbers when both are enabled."
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
wrapMode: Text.WordWrap
}
Repeater {
model: SettingsData.getNamedWorkspaces()
Rectangle {
width: parent.width
height: workspaceIconRow.implicitHeight + Theme.spacingM
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g,
Theme.surfaceContainer.b, 0.5)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g,
Theme.outline.b, 0.3)
border.width: 1
Row {
id: workspaceIconRow
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingM
anchors.rightMargin: Theme.spacingM
spacing: Theme.spacingM
StyledText {
text: "\"" + modelData + "\""
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
width: 150
elide: Text.ElideRight
}
DankIconPicker {
id: iconPicker
anchors.verticalCenter: parent.verticalCenter
Component.onCompleted: {
var iconData = SettingsData.getWorkspaceNameIcon(modelData)
if (iconData) {
setIcon(iconData.value, iconData.type)
}
}
onIconSelected: (iconName, iconType) => {
SettingsData.setWorkspaceNameIcon(modelData, {
type: iconType,
value: iconName
})
setIcon(iconName, iconType)
}
Connections {
target: SettingsData
function onWorkspaceIconsUpdated() {
var iconData = SettingsData.getWorkspaceNameIcon(modelData)
if (iconData) {
iconPicker.setIcon(iconData.value, iconData.type)
} else {
iconPicker.setIcon("", "icon")
}
}
}
}
Rectangle {
width: 28
height: 28
radius: Theme.cornerRadius
color: clearMouseArea.containsMouse ? Theme.errorHover : Theme.surfaceContainer
border.color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
border.width: 1
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "close"
size: 16
color: clearMouseArea.containsMouse ? Theme.error : Theme.outline
anchors.centerIn: parent
}
MouseArea {
id: clearMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
SettingsData.removeWorkspaceNameIcon(modelData)
}
}
}
Item {
width: parent.width - 150 - 240 - 28 - Theme.spacingM * 4
height: 1
}
}
}
}
}
}
}
DankWidgetSelectionPopup {
id: widgetSelectionPopup

View File

@@ -118,6 +118,16 @@ Rectangle {
property bool isPlaceholder: modelData === -1
property bool isHovered: mouseArea.containsMouse
property int sequentialNumber: index + 1
property var workspaceData: {
if (isPlaceholder || !NiriService.niriAvailable) return null
for (var i = 0; i < NiriService.allWorkspaces.length; i++) {
var ws = NiriService.allWorkspaces[i]
if (ws.idx + 1 === modelData) return ws
}
return null
}
property var iconData: workspaceData && workspaceData.name ? SettingsData.getWorkspaceNameIcon(workspaceData.name) : null
property bool hasIcon: iconData !== null
width: isActive ? Theme.spacingXL + Theme.spacingM : Theme.spacingL + Theme.spacingXS
height: Theme.spacingL
@@ -137,8 +147,37 @@ Rectangle {
}
}
// Icon display (priority over numbers)
DankIcon {
visible: hasIcon && iconData.type === "icon"
anchors.centerIn: parent
name: hasIcon && iconData.type === "icon" ? iconData.value : ""
size: Theme.fontSizeSmall
color: isActive ? Qt.rgba(
Theme.surfaceContainer.r,
Theme.surfaceContainer.g,
Theme.surfaceContainer.b,
0.95) : Theme.surfaceTextMedium
weight: isActive && !isPlaceholder ? 500 : 400
}
// Custom text display (priority over numbers)
StyledText {
visible: SettingsData.showWorkspaceIndex
visible: hasIcon && iconData.type === "text"
anchors.centerIn: parent
text: hasIcon && iconData.type === "text" ? iconData.value : ""
color: isActive ? Qt.rgba(
Theme.surfaceContainer.r,
Theme.surfaceContainer.g,
Theme.surfaceContainer.b,
0.95) : Theme.surfaceTextMedium
font.pixelSize: Theme.fontSizeSmall
font.weight: isActive && !isPlaceholder ? Font.DemiBold : Font.Normal
}
// Number display (secondary priority, only when no icon)
StyledText {
visible: SettingsData.showWorkspaceIndex && !hasIcon
anchors.centerIn: parent
text: isPlaceholder ? sequentialNumber : sequentialNumber
color: isActive ? Qt.rgba(

330
Widgets/DankIconPicker.qml Normal file
View File

@@ -0,0 +1,330 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Widgets
Rectangle {
id: root
property string currentIcon: ""
property string currentText: ""
property string iconType: "icon" // "icon" or "text"
signal iconSelected(string iconName, string iconType)
width: 240
height: 32
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: dropdownPopup.visible ? Theme.primary : (mouseArea.containsMouse ? Theme.outline : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.5))
border.width: 1
property var iconCategories: [
{
name: "Workspace",
icons: ["work", "laptop", "desktop_windows", "code", "terminal", "build", "settings", "folder", "view_module", "dashboard", "apps", "grid_view"]
},
{
name: "Development",
icons: ["code", "terminal", "bug_report", "build", "engineering", "integration_instructions", "data_object", "schema", "api", "webhook"]
},
{
name: "Communication",
icons: ["chat", "mail", "forum", "message", "video_call", "call", "contacts", "group", "notifications", "campaign"]
},
{
name: "Media",
icons: ["music_note", "headphones", "mic", "videocam", "photo", "movie", "library_music", "album", "radio", "volume_up"]
},
{
name: "System",
icons: ["memory", "storage", "developer_board", "monitor", "keyboard", "mouse", "battery_std", "wifi", "bluetooth", "security"]
},
{
name: "Navigation",
icons: ["home", "arrow_forward", "arrow_back", "expand_more", "expand_less", "menu", "close", "search", "filter_list", "sort"]
},
{
name: "Actions",
icons: ["add", "remove", "edit", "delete", "save", "download", "upload", "share", "content_copy", "content_paste", "content_cut", "undo", "redo"]
},
{
name: "Status",
icons: ["check", "close", "error", "warning", "info", "done", "pending", "schedule", "update", "sync", "offline_bolt"]
},
{
name: "Fun",
icons: ["celebration", "cake", "star", "favorite", "pets", "sports_esports", "local_fire_department", "bolt", "auto_awesome", "diamond"]
}
]
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: {
if (dropdownPopup.visible) {
dropdownPopup.visible = false
} else {
dropdownPopup.visible = true
}
}
}
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: root.iconType === "icon" && root.currentIcon ? root.currentIcon : (root.iconType === "text" ? "text_fields" : "add")
size: 16
color: root.currentIcon || root.currentText ? Theme.surfaceText : Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (root.iconType === "text" && root.currentText) return root.currentText
if (root.iconType === "icon" && root.currentIcon) return root.currentIcon
return "Choose icon or text"
}
font.pixelSize: Theme.fontSizeSmall
color: root.currentIcon || root.currentText ? Theme.surfaceText : Theme.outline
anchors.verticalCenter: parent.verticalCenter
width: 160
elide: Text.ElideRight
}
}
DankIcon {
name: dropdownPopup.visible ? "expand_less" : "expand_more"
size: 16
color: Theme.outline
anchors.right: parent.right
anchors.rightMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
}
PanelWindow {
id: dropdownPopup
visible: false
implicitWidth: 320
implicitHeight: Math.min(500, dropdownContent.implicitHeight + 32)
color: "transparent"
WlrLayershell.layer: WlrLayershell.Overlay
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
anchors {
top: true
left: true
right: true
bottom: true
}
// Click outside to close
MouseArea {
anchors.fill: parent
onClicked: dropdownPopup.visible = false
}
Rectangle {
width: 320
height: Math.min(500, dropdownContent.implicitHeight + 32)
x: {
// Get the root picker's position relative to the screen
var pickerPos = root.mapToItem(null, 0, 0)
return Math.max(16, Math.min(pickerPos.x, parent.width - width - 16))
}
y: {
// Position below the picker button
var pickerPos = root.mapToItem(null, 0, root.height + 4)
return Math.max(16, Math.min(pickerPos.y, parent.height - height - 16))
}
radius: Theme.cornerRadius
color: Theme.surface
border.color: Theme.outline
border.width: 1
// Prevent this from closing the dropdown when clicked
MouseArea {
anchors.fill: parent
// Don't propagate clicks to parent
}
layer.enabled: true
layer.effect: MultiEffect {
shadowEnabled: true
shadowColor: Theme.shadowDark
shadowBlur: 0.8
shadowHorizontalOffset: 0
shadowVerticalOffset: 4
}
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingS
contentHeight: dropdownContent.height
clip: true
Column {
id: dropdownContent
width: parent.width
spacing: Theme.spacingM
// Custom text section
Rectangle {
width: parent.width
height: customTextSection.implicitHeight + Theme.spacingS * 2
radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3)
Column {
id: customTextSection
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingS
StyledText {
text: "Custom Text"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
Rectangle {
width: parent.width
height: 28
radius: Theme.cornerRadius
color: Theme.surfaceContainer
border.color: customTextInput.activeFocus ? Theme.primary : Theme.outline
border.width: 1
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: Theme.spacingS
spacing: Theme.spacingS
DankIcon {
name: "text_fields"
size: 14
color: Theme.outline
anchors.verticalCenter: parent.verticalCenter
}
TextInput {
id: customTextInput
anchors.verticalCenter: parent.verticalCenter
width: 200
text: root.iconType === "text" ? root.currentText : ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
selectByMouse: true
onEditingFinished: {
var trimmedText = text.trim()
if (trimmedText) {
root.iconSelected(trimmedText, "text")
dropdownPopup.visible = false
}
}
}
}
StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS + 14 + Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: "1-2 characters"
font.pixelSize: Theme.fontSizeSmall
color: Theme.outline
opacity: 0.6
visible: customTextInput.text === ""
}
}
}
}
// Icon categories
Repeater {
model: root.iconCategories
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: modelData.name
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
}
Flow {
width: parent.width
spacing: 4
Repeater {
model: modelData.icons
Rectangle {
width: 36
height: 36
radius: Theme.cornerRadius
color: iconMouseArea.containsMouse ? Theme.primaryHover : "transparent"
border.color: root.currentIcon === modelData ? Theme.primary : "transparent"
border.width: 2
DankIcon {
name: modelData
size: 20
color: root.currentIcon === modelData ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
id: iconMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.iconSelected(modelData, "icon")
dropdownPopup.visible = false
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
}
}
}
}
}
}
}
}
}
function setIcon(iconName, type) {
if (type === "text") {
root.currentText = iconName
root.currentIcon = ""
root.iconType = "text"
} else {
root.currentIcon = iconName
root.currentText = ""
root.iconType = "icon"
}
}
}