feat(bar): add workspace numbers to TaskbarGrouped widget

Add workspace number badges to TaskbarGrouped widget that display in the
top-left corner of task groups, similar to existing Workspace widget
styling.

Features:
- Circular workspace number badges with negative margins for border overlap
- Color states: focused (primary), urgent (error), occupied (secondary), empty (outline)
- Burst animation effects when workspace becomes focused
- User-configurable settings: showWorkspaceNumbers, showNumbersOnlyWhenOccupied
- Comprehensive internationalization support for all supported locales

Implementation:
- Enhanced TaskbarGrouped.qml with workspace number display and animation system
- Created TaskbarGroupedSettings.qml for user configuration
- Updated BarWidgetRegistry.qml with default settings metadata
- Added settings dialog mapping in BarWidgetSettingsDialog.qml
- Added translations for all supported locales (en, de, es, fr, pt, zh-CN, uk-UA)

The workspace numbers provide quick visual identification of workspace
assignment while maintaining the clean aesthetic of the taskbar.
This commit is contained in:
Ahmet Çetinkaya
2025-11-03 15:54:24 +03:00
parent 5ca5aa602f
commit 9c1bce0fe4
11 changed files with 281 additions and 1 deletions
+10
View File
@@ -1258,6 +1258,16 @@
"description": "Theme-Farben auf Taskbar-Symbole anwenden."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Arbeitsbereichsnummern anzeigen",
"description": "Arbeitsbereichsnummern in der oberen linken Ecke der Task-Gruppen anzeigen."
},
"show-numbers-only-when-occupied": {
"label": "Nur bei belegten Arbeitsbereichen anzeigen",
"description": "Arbeitsbereichsnummern nur für Arbeitsbereiche mit geöffneten Fenstern anzeigen."
}
},
"tray": {
"colorize-icons": {
"label": "Symbole einfärben",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "Apply theme colors to taskbar icons."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Show workspace numbers",
"description": "Display workspace numbers in the top-left corner of task groups."
},
"show-numbers-only-when-occupied": {
"label": "Show numbers only when occupied",
"description": "Only show workspace numbers for workspaces that have open windows."
}
},
"tray": {
"colorize-icons": {
"label": "Colorize Icons",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "Aplicar colores del tema a los iconos de la barra de tareas."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Mostrar números de espacio de trabajo",
"description": "Mostrar números de espacio de trabajo en la esquina superior izquierda de los grupos de tareas."
},
"show-numbers-only-when-occupied": {
"label": "Mostrar números solo cuando estén ocupados",
"description": "Mostrar números de espacio de trabajo solo para espacios de trabajo con ventanas abiertas."
}
},
"tray": {
"colorize-icons": {
"label": "Colorear iconos",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "Appliquer les couleurs du thème aux icônes de la barre des tâches."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Afficher les numéros d'espace de travail",
"description": "Afficher les numéros d'espace de travail dans le coin supérieur gauche des groupes de tâches."
},
"show-numbers-only-when-occupied": {
"label": "Afficher les numéros seulement lorsqu'occupés",
"description": "Afficher les numéros d'espace de travail uniquement pour les espaces de travail avec des fenêtres ouvertes."
}
},
"tray": {
"colorize-icons": {
"label": "Coloriser les icônes",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "Controla como o widget se comporta quando não há janelas correspondentes."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Mostrar números do espaço de trabalho",
"description": "Exibir números do espaço de trabalho no canto superior esquerdo dos grupos de tarefas."
},
"show-numbers-only-when-occupied": {
"label": "Mostrar números apenas quando ocupados",
"description": "Mostrar números do espaço de trabalho apenas para espaços de trabalho com janelas abertas."
}
},
"tray": {
"colorize-icons": {
"label": "Colorir ícones",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "Застосувати кольори теми до значків панелі завдань."
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "Показувати номери робочих просторів",
"description": "Показувати номери робочих просторів у лівому верхньому куті групи завдань"
},
"show-numbers-only-when-occupied": {
"label": "Показувати номера лише коли зайнято",
"description": "Показувати номери лише для робочих просторів з відкритими вікнами"
}
},
"tray": {
"colorize-icons": {
"label": "Розфарбувати значки",
+10
View File
@@ -1241,6 +1241,16 @@
"description": "将主题颜色应用到任务栏图标。"
}
},
"taskbar-grouped": {
"show-workspace-numbers": {
"label": "显示工作区编号",
"description": "在任务组左上角显示工作区编号"
},
"show-numbers-only-when-occupied": {
"label": "仅在有窗口时显示编号",
"description": "只为有打开窗口的工作区显示编号"
}
},
"tray": {
"colorize-icons": {
"label": "着色图标",
+165
View File
@@ -33,7 +33,12 @@ Item {
return {}
}
readonly property bool hideUnoccupied: (widgetSettings.hideUnoccupied !== undefined) ? widgetSettings.hideUnoccupied : false
readonly property bool showWorkspaceNumbers: (widgetSettings.showWorkspaceNumbers !== undefined) ? widgetSettings.showWorkspaceNumbers : true
readonly property bool showNumbersOnlyWhenOccupied: (widgetSettings.showNumbersOnlyWhenOccupied !== undefined) ? widgetSettings.showNumbersOnlyWhenOccupied : true
property ListModel localWorkspaces: ListModel {}
property real masterProgress: 0.0
property bool effectsActive: false
property color effectColor: Color.mPrimary
function refreshWorkspaces() {
localWorkspaces.clear()
@@ -56,6 +61,22 @@ Item {
localWorkspaces.append(workspaceData)
}
updateWorkspaceFocus()
}
function triggerUnifiedWave() {
effectColor = Color.mPrimary
masterAnimation.restart()
}
function updateWorkspaceFocus() {
for (var i = 0; i < localWorkspaces.count; i++) {
const ws = localWorkspaces.get(i)
if (ws.isFocused === true) {
root.triggerUnifiedWave()
break
}
}
}
Component.onCompleted: {
@@ -79,6 +100,33 @@ Item {
}
}
SequentialAnimation {
id: masterAnimation
PropertyAction {
target: root
property: "effectsActive"
value: true
}
NumberAnimation {
target: root
property: "masterProgress"
from: 0.0
to: 1.0
duration: Style.animationSlow * 2
easing.type: Easing.OutQuint
}
PropertyAction {
target: root
property: "effectsActive"
value: false
}
PropertyAction {
target: root
property: "masterProgress"
value: 0.0
}
}
Component {
id: workspaceRepeaterDelegate
@@ -106,6 +154,123 @@ Item {
}
}
Item {
id: workspaceNumberContainer
visible: root.showWorkspaceNumbers && (!root.showNumbersOnlyWhenOccupied || hasWindows)
anchors {
left: parent.left
top: parent.top
leftMargin: -Style.fontSizeXS * 0.5
topMargin: -Style.fontSizeXS * 0.5
}
width: Math.max(workspaceNumber.implicitWidth + Style.marginXS, Style.fontSizeXXS * 2)
height: Math.max(workspaceNumber.implicitHeight + Style.marginXS, Style.fontSizeXXS * 2)
Rectangle {
id: workspaceNumberBackground
anchors.fill: parent
radius: width * 0.5
color: {
if (workspaceModel.isFocused)
return Color.mPrimary
if (workspaceModel.isUrgent)
return Color.mError
if (hasWindows)
return Color.mSecondary
return Qt.alpha(Color.mOutline, 0.3)
}
scale: workspaceModel.isActive ? 1.0 : 0.9
Behavior on scale {
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutBack
}
}
Behavior on color {
ColorAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
}
// Burst effect overlay for focused workspace number (smaller outline)
Rectangle {
id: workspaceNumberBurst
anchors.centerIn: workspaceNumberContainer
width: workspaceNumberContainer.width + 12 * root.masterProgress
height: workspaceNumberContainer.height + 12 * root.masterProgress
radius: width / 2
color: Color.transparent
border.color: root.effectColor
border.width: Math.max(1, Math.round((2 + 4 * (1.0 - root.masterProgress))))
opacity: root.effectsActive && workspaceModel.isFocused ? (1.0 - root.masterProgress) * 0.7 : 0
visible: root.effectsActive && workspaceModel.isFocused
z: 1
}
NText {
id: workspaceNumber
anchors.centerIn: parent
text: workspaceModel.idx.toString()
family: Settings.data.ui.fontFixed
font {
pointSize: Style.fontSizeXXS
weight: Style.fontWeightBold
capitalization: Font.AllUppercase
}
applyUiScale: false
color: {
if (workspaceModel.isFocused)
return Color.mOnPrimary
if (workspaceModel.isUrgent)
return Color.mOnError
if (hasWindows)
return Color.mOnSecondary
return Color.mOnSurface
}
opacity: {
if (workspaceModel.isFocused)
return 1.0
if (workspaceModel.isUrgent)
return 0.9
if (hasWindows)
return 0.8
return 0.6
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
}
Behavior on opacity {
NumberAnimation {
duration: Style.animationFast
easing.type: Easing.InOutCubic
}
}
}
Flow {
id: iconsFlow
@@ -134,6 +134,7 @@ Popup {
"NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml",
"Spacer": "WidgetSettings/SpacerSettings.qml",
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
"TaskbarGrouped": "WidgetSettings/TaskbarGroupedSettings.qml",
"Volume": "WidgetSettings/VolumeSettings.qml",
"WiFi": "WidgetSettings/WiFiSettings.qml",
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
@@ -0,0 +1,42 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
property bool valueShowWorkspaceNumbers: widgetData.showWorkspaceNumbers !== undefined ? widgetData.showWorkspaceNumbers : widgetMetadata.showWorkspaceNumbers
property bool valueShowNumbersOnlyWhenOccupied: widgetData.showNumbersOnlyWhenOccupied !== undefined ? widgetData.showNumbersOnlyWhenOccupied : widgetMetadata.showNumbersOnlyWhenOccupied
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showWorkspaceNumbers = valueShowWorkspaceNumbers
settings.showNumbersOnlyWhenOccupied = valueShowNumbersOnlyWhenOccupied
return settings
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.taskbar-grouped.show-workspace-numbers.label")
description: I18n.tr("bar.widget-settings.taskbar-grouped.show-workspace-numbers.description")
checked: root.valueShowWorkspaceNumbers
onToggled: checked => root.valueShowWorkspaceNumbers = checked
}
NToggle {
Layout.fillWidth: true
label: I18n.tr("bar.widget-settings.taskbar-grouped.show-numbers-only-when-occupied.label")
description: I18n.tr("bar.widget-settings.taskbar-grouped.show-numbers-only-when-occupied.description")
checked: root.valueShowNumbersOnlyWhenOccupied
onToggled: checked => root.valueShowNumbersOnlyWhenOccupied = checked
visible: root.valueShowWorkspaceNumbers
}
}
+3 -1
View File
@@ -146,7 +146,9 @@ Singleton {
"colorizeIcons": false
},
"TaskbarGrouped": {
"allowUserSettings": true
"allowUserSettings": true,
"showWorkspaceNumbers": true,
"showNumbersOnlyWhenOccupied": true
},
"Tray": {
"allowUserSettings": true,