-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Expand file tree
/
Copy pathBarWidget.qml
More file actions
369 lines (322 loc) · 13 KB
/
Copy pathBarWidget.qml
File metadata and controls
369 lines (322 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
import QtQuick
import Quickshell
import qs.Ui
import qs.Commons
BarWidget {
id: root
moduleName: "omarchy.media"
readonly property var mediaService: bar?.shell?.firstPartyServiceFor("omarchy.media")
readonly property var activePlayer: mediaService ? mediaService.activePlayer : null
readonly property var sourcePlayers: mediaService ? mediaService.sourcePlayers : []
readonly property bool hasMedia: activePlayer !== null && (activePlayer.trackTitle || activePlayer.trackArtist)
readonly property string playIcon: activePlayer && activePlayer.isPlaying ? "" : ""
readonly property string title: activePlayer ? (activePlayer.trackTitle || "") : ""
readonly property string artist: activePlayer ? (activePlayer.trackArtist || "") : ""
property bool popupOpen: false
function close() { popupOpen = false }
// Tunables from this widget's inline shell.json entry. Defaults reproduce the
// previous hardcoded behaviour, so an entry without them is unchanged.
readonly property bool scrollLabel: setting("scroll", true)
readonly property string separator: setting("separator", " · ")
readonly property real iconGap: setting("iconGap", 6)
property real maxLabelWidth: setting("maxLabelWidth", 180)
visible: hasMedia
implicitWidth: hasMedia ? row.implicitWidth + Style.space(14) : 0
implicitHeight: barSize
Row {
id: row
anchors.centerIn: parent
spacing: Style.space(root.iconGap)
Text {
id: glyph
anchors.verticalCenter: parent.verticalCenter
text: root.playIcon
color: activePlayer && activePlayer.isPlaying ? root.bar.barForeground : Qt.darker(root.bar.barForeground, 1.5)
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
Behavior on color {
enabled: !root.bar || root.bar.foregroundAnimationEnabled
ColorAnimation { duration: 160 }
}
}
Item {
id: scrollClip
width: root.scrollLabel
? Math.min(root.maxLabelWidth, labelText.implicitWidth)
: staticLabel.implicitWidth
height: glyph.height
clip: true
anchors.verticalCenter: parent.verticalCenter
visible: !root.bar.vertical && root.title !== ""
// Scrolling mode: title and artist as one string, slid horizontally.
Text {
id: labelText
visible: root.scrollLabel
text: root.title + (root.artist ? root.separator + root.artist : "")
color: root.bar.barForeground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
anchors.verticalCenter: parent.verticalCenter
property bool needsScroll: implicitWidth > scrollClip.width
NumberAnimation on x {
id: scrollAnim
running: root.scrollLabel && labelText.needsScroll && !root.popupOpen && !root.bar.vertical
loops: Animation.Infinite
duration: Math.max(6000, labelText.implicitWidth * 25)
from: scrollClip.width
to: -labelText.implicitWidth
easing.type: Easing.Linear
}
}
// Static mode: title and artist elide against their own share of the
// budget, so a long title truncates instead of pushing the artist out of
// the label entirely. The shares are constants rather than being derived
// from the text metrics, which keeps each label's width binding clear of
// its own implicitWidth.
Row {
id: staticLabel
visible: !root.scrollLabel
spacing: 0
anchors.verticalCenter: parent.verticalCenter
Item {
width: Math.min(root.maxLabelWidth * 0.65, titleText.implicitWidth)
height: titleText.implicitHeight
clip: true
Text {
id: titleText
text: root.title
width: parent.width
elide: Text.ElideRight
color: root.bar.barForeground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
}
}
Text {
id: sepText
visible: root.artist !== ""
text: root.separator
color: root.bar.barForeground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
}
Item {
visible: root.artist !== ""
width: Math.min(root.maxLabelWidth * 0.35, artistText.implicitWidth)
height: artistText.implicitHeight
clip: true
Text {
id: artistText
text: root.artist
width: parent.width
elide: Text.ElideRight
color: root.bar.barForeground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
}
}
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: root.activePlayer ? Qt.PointingHandCursor : Qt.ArrowCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: function(mouse) {
if (!root.activePlayer) return
if (mouse.button === Qt.MiddleButton) {
if (root.mediaService) root.mediaService.runAction("next", false)
} else if (mouse.button === Qt.RightButton) {
root.popupOpen = !root.popupOpen
} else {
if (root.mediaService) root.mediaService.runAction("playPause", false)
}
}
onWheel: function(wheel) {
if (!root.activePlayer) return
if (wheel.angleDelta.y > 0 && root.mediaService) root.mediaService.runAction("previous", false)
else if (wheel.angleDelta.y < 0 && root.mediaService) root.mediaService.runAction("next", false)
}
onEntered: if (root.bar) root.bar.showTooltip(root, root.hasMedia ? (root.title + (root.artist ? " — " + root.artist : "")) : "")
onExited: if (root.bar) root.bar.hideTooltip(root)
}
PopupCard {
id: popup
anchorItem: root
bar: root.bar
owner: root
open: root.popupOpen
contentWidth: popup.fittedContentWidth(Style.space(320))
contentHeight: popup.fittedContentHeight(column.implicitHeight)
Column {
id: column
anchors.fill: parent
spacing: Style.space(10)
Row {
spacing: Style.space(10)
width: parent.width
BorderSurface {
width: Style.space(64)
height: Style.space(64)
radius: Style.spacing.labelGap
color: Style.normalFillFor(root.bar.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.bar.foreground, Color.accent)
Image {
anchors.fill: parent
anchors.margins: Style.space(2)
fillMode: Image.PreserveAspectCrop
asynchronous: true
source: root.activePlayer && root.activePlayer.trackArtUrl ? root.activePlayer.trackArtUrl : ""
visible: source !== ""
}
Text {
anchors.centerIn: parent
visible: !root.activePlayer || !root.activePlayer.trackArtUrl
text: ""
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.displayLarge
}
}
Column {
spacing: Style.space(4)
width: parent.width - Style.space(74)
Text {
text: root.title || "Nothing playing"
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.subtitle
font.bold: true
elide: Text.ElideRight
width: parent.width
}
Text {
text: root.artist
color: Qt.darker(root.bar.foreground, 1.3)
font.family: root.bar.fontFamily
font.pixelSize: Style.font.bodySmall
elide: Text.ElideRight
width: parent.width
visible: text !== ""
}
Text {
text: root.activePlayer && root.activePlayer.trackAlbum ? root.activePlayer.trackAlbum : ""
color: Qt.darker(root.bar.foreground, 1.6)
font.family: root.bar.fontFamily
font.pixelSize: Style.font.caption
elide: Text.ElideRight
width: parent.width
visible: text !== ""
}
}
}
Row {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.space(6)
Button {
iconText: ""
foreground: root.bar.foreground
horizontalPadding: Style.spacing.controlPaddingX
verticalPadding: Style.spacing.controlPaddingY
enabled: root.activePlayer && root.activePlayer.canGoPrevious
opacity: enabled ? 1.0 : 0.4
onClicked: if (root.mediaService) root.mediaService.runAction("previous", false, root.mediaService.playerKey(root.activePlayer))
}
Button {
iconText: root.activePlayer && root.activePlayer.isPlaying ? "" : ""
foreground: root.bar.foreground
horizontalPadding: Style.spacing.panelGap
verticalPadding: Style.spacing.controlPaddingY
iconSize: Style.font.iconLarge
enabled: root.activePlayer && (root.activePlayer.canTogglePlaying || root.activePlayer.canPlay || root.activePlayer.canPause)
opacity: enabled ? 1.0 : 0.4
onClicked: if (root.mediaService) root.mediaService.runAction("playPause", false, root.mediaService.playerKey(root.activePlayer))
}
Button {
iconText: ""
foreground: root.bar.foreground
horizontalPadding: Style.spacing.controlPaddingX
verticalPadding: Style.spacing.controlPaddingY
enabled: root.activePlayer && root.activePlayer.canGoNext
opacity: enabled ? 1.0 : 0.4
onClicked: if (root.mediaService) root.mediaService.runAction("next", false, root.mediaService.playerKey(root.activePlayer))
}
}
PanelSeparator {
visible: root.sourcePlayers.length > 1
foreground: root.bar.foreground
}
Column {
id: sourceList
visible: root.sourcePlayers.length > 1
width: parent.width
spacing: Style.space(4)
Repeater {
model: root.sourcePlayers
BorderSurface {
id: sourceRow
required property var modelData
readonly property var player: modelData
readonly property bool selected: root.activePlayer && player
&& root.mediaService.playerKey(root.activePlayer) === root.mediaService.playerKey(player)
readonly property string sourceTitle: player ? (player.trackTitle || player.identity || player.desktopEntry || "Media source") : "Media source"
readonly property string sourceDetail: player && player.trackArtist ? player.trackArtist : (player && player.identity ? player.identity : "")
width: sourceList.width
height: sourceInner.implicitHeight + Style.space(10)
radius: Style.spacing.labelGap
color: selected ? Style.selectedFillFor(root.bar.foreground, Color.accent) : "transparent"
borderSpec: selected ? Border.controlSpec("normal", root.bar.foreground, Color.accent) : Border.none()
Row {
id: sourceInner
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.leftMargin: sourceRow.borderLeft + Style.space(8)
anchors.rightMargin: sourceRow.borderRight + Style.space(8)
spacing: Style.space(8)
Text {
text: sourceRow.player && sourceRow.player.isPlaying ? "" : ""
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.body
width: Style.space(18)
horizontalAlignment: Text.AlignHCenter
anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width - Style.space(26)
spacing: Style.space(1)
anchors.verticalCenter: parent.verticalCenter
Text {
text: sourceRow.sourceTitle
color: root.bar.foreground
font.family: root.bar.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: sourceRow.selected
elide: Text.ElideRight
width: parent.width
}
Text {
text: sourceRow.sourceDetail
color: Qt.darker(root.bar.foreground, 1.5)
font.family: root.bar.fontFamily
font.pixelSize: Style.font.caption
elide: Text.ElideRight
width: parent.width
visible: text !== ""
}
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: if (root.mediaService) root.mediaService.selectPlayer(root.mediaService.playerKey(sourceRow.player))
}
}
}
}
}
}
}