Skip to content

Commit b8de6e1

Browse files
authored
Merge pull request #2411 from lonerOrz/cc-icon
add explicit iconPosition control for CustomButton widget
2 parents bec9726 + 9d4fad0 commit b8de6e1

10 files changed

Lines changed: 569 additions & 403 deletions

File tree

Assets/Translations/en.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@
105105
"collapse-condition-description": "If the output text matches this value, the button will collapse.",
106106
"collapse-condition-label": "Collapse condition",
107107
"color-selection-description": "Apply theme colors to icon and text.",
108+
"icon-color-selection-description": "Apply theme colors to icons.",
109+
"text-color-selection-description": "Apply theme colors to text.",
108110
"default-tooltip": "Custom button, configure in settings",
109111
"display-command-output-description": "Enter a command to run at a regular interval. The first line of its output will be displayed as text.",
110112
"display-command-output-label": "Display command output",
111113
"display-command-output-stream-description": "Enter a command to run continuously.",
112114
"dynamic-text": "Dynamic text",
113-
"enable-colorization-description": "Enable colorization for the custom button icon and text, applying theme colors.",
114-
"enable-colorization-label": "Enable colorization",
115115
"general-tooltip-text-description": "Custom text to display in the button's tooltip.",
116116
"general-tooltip-text-label": "Custom tooltip text",
117117
"hide-mode-always-expanded": "Always expanded",
@@ -120,6 +120,12 @@
120120
"hide-mode-label": "Hide mode",
121121
"hide-mode-max-transparent": "Max expanded but transparent",
122122
"icon-description": "Select an icon from the library.",
123+
"icon-position-description": "Select where the icon appears relative to the text.",
124+
"icon-position-label": "Icon position",
125+
"icon-position-left": "Left",
126+
"icon-position-right": "Right",
127+
"icon-position-top": "Top",
128+
"icon-position-bottom": "Bottom",
123129
"ipc-identifier-description": "Unique identifier for IPC commands. Use this identifier with 'qs -c noctalia-shell ipc call cb [action] [identifier]' to control this button via IPC.",
124130
"ipc-identifier-label": "IPC Identifier",
125131
"left-click-description": "Command to execute when the button is left-clicked.",
@@ -157,7 +163,10 @@
157163
"wheel-up": "Scroll up",
158164
"wheel-up-description": "Command to execute when the scroll wheel is scrolled up.",
159165
"wheel-up-label": "Wheel up command",
160-
"wheel-update-text": "Update displayed text on scroll"
166+
"wheel-update-text": "Update displayed text on scroll",
167+
"tab-actions": "Actions",
168+
"tab-icon": "Icon",
169+
"tab-text": "Text Command"
161170
},
162171
"keyboard-layout": {
163172
"show-icon-description": "Display the keyboard layout icon."
@@ -517,6 +526,7 @@
517526
"select-color": "Select color",
518527
"select-color-description": "Apply theme colors for emphasis.",
519528
"select-icon-color": "Select icon color",
529+
"select-text-color": "Select text color",
520530
"settings": "Settings",
521531
"shortcuts": "Shortcuts",
522532
"shutdown": "Shutdown",

Assets/settings-widgets-default.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"CustomButton": {
5454
"icon": "heart",
5555
"showIcon": true,
56+
"iconPosition": "left",
5657
"showExecTooltip": true,
5758
"showTextTooltip": true,
5859
"generalTooltipText": "",
@@ -79,8 +80,8 @@
7980
"horizontal": 10,
8081
"vertical": 10
8182
},
82-
"enableColorization": false,
8383
"colorizeSystemIcon": "none",
84+
"colorizeSystemText": "none",
8485
"ipcIdentifier": ""
8586
},
8687
"DarkMode": {

Modules/Bar/Extras/BarPill.qml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Item {
1010
required property ShellScreen screen
1111

1212
property string icon: ""
13+
property string iconPosition: ""
1314
property string text: ""
1415
property string suffix: ""
1516
property var tooltipText
@@ -55,6 +56,7 @@ Item {
5556
BarPillVertical {
5657
screen: root.screen
5758
icon: root.icon
59+
iconPosition: root.iconPosition
5860
text: root.text
5961
suffix: root.suffix
6062
tooltipText: root.tooltipText
@@ -84,6 +86,7 @@ Item {
8486
BarPillHorizontal {
8587
screen: root.screen
8688
icon: root.icon
89+
iconPosition: root.iconPosition
8790
text: root.text
8891
suffix: root.suffix
8992
tooltipText: root.tooltipText

Modules/Bar/Extras/BarPillHorizontal.qml

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Item {
1818
property bool forceOpen: false
1919
property bool forceClose: false
2020
property bool oppositeDirection: false
21+
property string iconPosition: ""
2122
property bool hovered: false
2223
property color customBackgroundColor: "transparent"
2324
property color customTextIconColor: "transparent"
@@ -111,16 +112,22 @@ Item {
111112
x: {
112113
if (!hasIcon)
113114
return 0;
115+
// iconPosition takes precedence, fallback to oppositeDirection
116+
if (iconPosition === "right")
117+
return (iconCircle.x + iconCircle.width / 2) - width;
118+
if (iconPosition === "left")
119+
return (iconCircle.x + iconCircle.width / 2);
114120
return oppositeDirection ? (iconCircle.x + iconCircle.width / 2) : (iconCircle.x + iconCircle.width / 2) - width;
115121
}
116122

117123
opacity: revealed ? Style.opacityFull : Style.opacityNone
118124
color: "transparent" // Make pill background transparent to avoid double opacity
119125

120-
topLeftRadius: oppositeDirection ? 0 : Style.radiusM
121-
bottomLeftRadius: oppositeDirection ? 0 : Style.radiusM
122-
topRightRadius: oppositeDirection ? Style.radiusM : 0
123-
bottomRightRadius: oppositeDirection ? Style.radiusM : 0
126+
// iconPosition takes precedence, fallback to oppositeDirection
127+
topLeftRadius: iconPosition ? (iconPosition === "right" ? Style.radiusM : 0) : (oppositeDirection ? 0 : Style.radiusM)
128+
bottomLeftRadius: iconPosition ? (iconPosition === "right" ? Style.radiusM : 0) : (oppositeDirection ? 0 : Style.radiusM)
129+
topRightRadius: iconPosition ? (iconPosition === "right" ? 0 : Style.radiusM) : (oppositeDirection ? Style.radiusM : 0)
130+
bottomRightRadius: iconPosition ? (iconPosition === "right" ? 0 : Style.radiusM) : (oppositeDirection ? Style.radiusM : 0)
124131
anchors.verticalCenter: parent.verticalCenter
125132

126133
NText {
@@ -132,10 +139,17 @@ Item {
132139

133140
// Better text horizontal centering
134141
var centerX = (parent.width - width) / 2;
135-
var offset = oppositeDirection ? Style.marginXS : -Style.marginXS;
142+
// iconPosition takes precedence, fallback to oppositeDirection
143+
var offset;
144+
if (iconPosition === "right")
145+
offset = -Style.marginXS;
146+
else if (iconPosition === "left")
147+
offset = Style.marginXS;
148+
else
149+
offset = oppositeDirection ? Style.marginXS : -Style.marginXS;
136150
if (forceOpen) {
137151
// If its force open, the icon disc background is the same color as the bg pill move text slightly
138-
offset += oppositeDirection ? -Style.marginXXS : Style.marginXXS;
152+
offset += iconPosition === "right" ? Style.marginXXS : (iconPosition === "left" ? -Style.marginXXS : oppositeDirection ? -Style.marginXXS : Style.marginXXS);
139153
}
140154
return centerX + offset;
141155
}
@@ -171,7 +185,8 @@ Item {
171185
color: "transparent" // Make icon background transparent to avoid double opacity
172186
anchors.verticalCenter: parent.verticalCenter
173187

174-
x: oppositeDirection ? 0 : (parent.width - width)
188+
// iconPosition takes precedence, fallback to oppositeDirection
189+
x: iconPosition ? (iconPosition === "right" ? (parent.width - width) : 0) : (oppositeDirection ? 0 : (parent.width - width))
175190

176191
NIcon {
177192
icon: root.icon

Modules/Bar/Extras/BarPillVertical.qml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Item {
1818
property bool forceOpen: false
1919
property bool forceClose: false
2020
property bool oppositeDirection: false
21+
property string iconPosition: ""
2122
property bool hovered: false
2223
property bool rotateText: false
2324
property color customBackgroundColor: "transparent"
@@ -48,9 +49,10 @@ Item {
4849
readonly property int maxPillWidth: rotateText ? Math.max(buttonSize, Math.round(textItem.implicitHeight + Style.margin2M)) : buttonSize
4950
readonly property int maxPillHeight: rotateText ? Math.max(1, Math.round(textItem.implicitWidth + Style.margin2M + Math.round(iconCircle.height / 4))) : Math.max(1, Math.round(textItem.implicitHeight + Style.margin2M))
5051

51-
// Determine pill direction based on section position
52-
readonly property bool openDownward: oppositeDirection
53-
readonly property bool openUpward: !oppositeDirection
52+
// Determine pill direction based on icon position (fallback to oppositeDirection if not set)
53+
// For vertical bar: iconPosition="left" (top) means icon at top, text expands downward
54+
readonly property bool openDownward: (iconPosition === "left" || iconPosition === "right") ? (iconPosition === "left") : oppositeDirection
55+
readonly property bool openUpward: (iconPosition === "left" || iconPosition === "right") ? (iconPosition === "right") : !oppositeDirection
5456

5557
// Effective shown state (true if animated open or forced, but not if force closed)
5658
readonly property bool revealed: !forceClose && (forceOpen || showPill)

Modules/Bar/Widgets/CustomButton.qml

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,27 @@ import qs.Widgets
1212
Item {
1313
id: root
1414

15+
NPopupContextMenu {
16+
id: contextMenu
17+
18+
model: [
19+
{
20+
"label": I18n.tr("actions.widget-settings"),
21+
"action": "widget-settings",
22+
"icon": "settings"
23+
},
24+
]
25+
26+
onTriggered: action => {
27+
contextMenu.close();
28+
PanelService.closeContextMenu(screen);
29+
30+
if (action === "widget-settings") {
31+
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
32+
}
33+
}
34+
}
35+
1536
property ShellScreen screen
1637

1738
// Widget properties passed from Bar.qml for per-instance settings
@@ -37,6 +58,7 @@ Item {
3758
readonly property bool isVerticalBar: barPosition === "left" || barPosition === "right"
3859

3960
readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
61+
readonly property string iconPosition: widgetSettings.iconPosition || widgetMetadata.iconPosition
4062
readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
4163
readonly property bool leftClickUpdateText: widgetSettings.leftClickUpdateText ?? widgetMetadata.leftClickUpdateText
4264
readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
@@ -139,14 +161,19 @@ Item {
139161
return " ".repeat(currentMaxTextLength);
140162
}
141163

142-
readonly property bool enableColorization: widgetSettings.enableColorization || false
143164
readonly property string colorizeSystemIcon: {
144165
if (widgetSettings.colorizeSystemIcon !== undefined)
145166
return widgetSettings.colorizeSystemIcon;
146167
return widgetMetadata.colorizeSystemIcon !== undefined ? widgetMetadata.colorizeSystemIcon : "none";
147168
}
169+
readonly property string colorizeSystemText: {
170+
if (widgetSettings.colorizeSystemText !== undefined)
171+
return widgetSettings.colorizeSystemText;
172+
return widgetMetadata.colorizeSystemText !== undefined ? widgetMetadata.colorizeSystemText : "none";
173+
}
148174

149-
readonly property bool isColorizing: enableColorization && colorizeSystemIcon !== "none"
175+
// Colorization is active if either icon or text has a color set
176+
readonly property bool isColorizing: colorizeSystemIcon !== "none" || colorizeSystemText !== "none"
150177

151178
// Get color value from color name (returns null for invalid names)
152179
function _getColorValue(colorName, forHover) {
@@ -187,8 +214,10 @@ Item {
187214
return isHover ? Color.mOnHover : Color.mOnSurface;
188215
}
189216

190-
readonly property color iconColor: _resolveIconColor(_dynamicColor, colorizeSystemIcon, false)
191-
readonly property color iconHoverColor: _resolveIconColor(_dynamicColor, colorizeSystemIcon, true)
217+
readonly property color iconColor: _resolveIconColor(_dynamicIconColor || _dynamicColor, colorizeSystemIcon, false)
218+
readonly property color iconHoverColor: _resolveIconColor(_dynamicIconColor || _dynamicColor, colorizeSystemIcon, true)
219+
readonly property color textColor: _resolveIconColor(_dynamicTextColor || _dynamicColor, colorizeSystemText, false)
220+
readonly property color textHoverColor: _resolveIconColor(_dynamicTextColor || _dynamicColor, colorizeSystemText, true)
192221

193222
implicitWidth: pill.width
194223
implicitHeight: pill.height
@@ -200,13 +229,15 @@ Item {
200229
opacity: _pillOpacity
201230
screen: root.screen
202231
oppositeDirection: BarService.getPillDirection(root)
232+
iconPosition: root.iconPosition
203233
icon: _pillIcon
204234
text: _pillText
205235
rotateText: isVerticalBar && currentMaxTextLength > 0
206236
autoHide: false
207237
forceOpen: _pillForceOpen
208238
forceClose: !_pillForceOpen
209-
customTextIconColor: iconColor
239+
customIconColor: iconColor
240+
customTextColor: textColor
210241

211242
// Helper function to build tooltip content
212243
function _buildTooltipContent() {
@@ -285,6 +316,8 @@ Item {
285316
property string _dynamicIcon: ""
286317
property string _dynamicTooltip: ""
287318
property string _dynamicColor: ""
319+
property string _dynamicIconColor: ""
320+
property string _dynamicTextColor: ""
288321

289322
// Maximum length for text display before scrolling (different values for horizontal and vertical)
290323
readonly property var maxTextLength: {
@@ -413,18 +446,34 @@ Item {
413446
const text = parsed.text || "";
414447
const icon = parsed.icon || "";
415448
let tooltip = parsed.tooltip || "";
416-
const color = parsed.color || "";
417449

418-
// Validate color value
450+
// Support both "color" (legacy) and "iconColor"/"textColor" (new)
451+
const legacyColor = parsed.color || "";
452+
const iconColorKey = parsed.iconColor || "";
453+
const textColorKey = parsed.textColor || "";
454+
419455
const validColors = ["primary", "secondary", "tertiary", "error", "none"];
420-
const validColor = (color && validColors.includes(color)) ? color : "";
456+
457+
// Helper to resolve color: legacy > specific > none
458+
function resolveColor(legacy, specific) {
459+
if (legacy && validColors.includes(legacy))
460+
return legacy;
461+
if (specific && validColors.includes(specific))
462+
return specific;
463+
return "";
464+
}
465+
466+
const resolvedIconColor = resolveColor(legacyColor, iconColorKey);
467+
const resolvedTextColor = resolveColor(legacyColor, textColorKey);
421468

422469
if (checkCollapse(text)) {
423470
_scrollState.originalText = "";
424471
_dynamicText = "";
425472
_dynamicIcon = "";
426473
_dynamicTooltip = "";
427474
_dynamicColor = "";
475+
_dynamicIconColor = "";
476+
_dynamicTextColor = "";
428477
_scrollState.needsScrolling = false;
429478
_scrollState.phase = 0;
430479
_scrollState.phaseCounter = 0;
@@ -444,7 +493,9 @@ Item {
444493
scrollTimer.stop();
445494
}
446495
_dynamicIcon = icon;
447-
_dynamicColor = validColor;
496+
_dynamicColor = legacyColor; // Keep legacy color for fallback
497+
_dynamicIconColor = resolvedIconColor;
498+
_dynamicTextColor = resolvedTextColor;
448499

449500
_dynamicTooltip = toHtml(tooltip);
450501
_scrollState.offset = 0;
@@ -460,6 +511,8 @@ Item {
460511
_dynamicIcon = "";
461512
_dynamicTooltip = "";
462513
_dynamicColor = "";
514+
_dynamicIconColor = "";
515+
_dynamicTextColor = "";
463516
_scrollState.needsScrolling = false;
464517
_scrollState.phase = 0;
465518
_scrollState.phaseCounter = 0;
@@ -480,6 +533,8 @@ Item {
480533
}
481534
_dynamicIcon = "";
482535
_dynamicColor = "";
536+
_dynamicIconColor = "";
537+
_dynamicTextColor = "";
483538
_dynamicTooltip = toHtml(contentStr);
484539
_scrollState.offset = 0;
485540
}
@@ -521,8 +576,8 @@ Item {
521576
if (rightClickExec) {
522577
Quickshell.execDetached(["sh", "-lc", rightClickExec]);
523578
Logger.i("CustomButton", `Executing command: ${rightClickExec}`);
524-
} else if (!rightClickUpdateText) {
525-
BarService.openWidgetSettings(screen, section, sectionWidgetIndex, widgetId, widgetSettings);
579+
} else {
580+
PanelService.showContextMenu(contextMenu, pill, screen);
526581
}
527582
if (!textStream && rightClickUpdateText) {
528583
runTextCommand();

Modules/Panels/Settings/Bar/BarWidgetSettingsDialog.qml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Popup {
1515
property string widgetId: ""
1616
property string sectionId: ""
1717
property var screen: null
18+
property bool barIsVertical: false
1819
property var settingsCache: ({})
1920

2021
readonly property real maxHeight: (screen ? screen.height : (parent ? parent.height : 800)) * 0.8

0 commit comments

Comments
 (0)