draftomen 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. draftomen/__init__.py +17 -0
  2. draftomen/assets/draftomen.icns +0 -0
  3. draftomen/assets/draftomen.ico +0 -0
  4. draftomen/assets/draftomen_logo.png +0 -0
  5. draftomen/audit.py +606 -0
  6. draftomen/backtest.py +449 -0
  7. draftomen/benchmark.py +834 -0
  8. draftomen/carddb.py +1869 -0
  9. draftomen/cardimages.py +313 -0
  10. draftomen/cli.py +891 -0
  11. draftomen/config.py +133 -0
  12. draftomen/deckbuilder.py +2723 -0
  13. draftomen/events.py +772 -0
  14. draftomen/logfollow.py +451 -0
  15. draftomen/mock_session.py +745 -0
  16. draftomen/paths.py +120 -0
  17. draftomen/pickengine.py +1117 -0
  18. draftomen/pool.py +1259 -0
  19. draftomen/preferences.py +289 -0
  20. draftomen/qml/AboutDialog.qml +156 -0
  21. draftomen/qml/AppBar.qml +120 -0
  22. draftomen/qml/BacktestView.qml +316 -0
  23. draftomen/qml/BuildView.qml +1151 -0
  24. draftomen/qml/CardPreview.qml +434 -0
  25. draftomen/qml/DimensionalButton.qml +42 -0
  26. draftomen/qml/DimensionalComboBox.qml +182 -0
  27. draftomen/qml/DimensionalSurface.qml +118 -0
  28. draftomen/qml/DimensionalTabButton.qml +41 -0
  29. draftomen/qml/LiveDraftView.qml +468 -0
  30. draftomen/qml/Main.qml +175 -0
  31. draftomen/qml/NavigationRail.qml +226 -0
  32. draftomen/qml/PoolSummaryPanel.qml +322 -0
  33. draftomen/qml/PrivacyDialog.qml +96 -0
  34. draftomen/qml/RecentPickThumbnail.qml +83 -0
  35. draftomen/qml/RecentPicksGallery.qml +333 -0
  36. draftomen/qml/RecommendationRow.qml +404 -0
  37. draftomen/qml/SettingsSwitch.qml +141 -0
  38. draftomen/qml/SettingsView.qml +531 -0
  39. draftomen/qml/StateBanner.qml +189 -0
  40. draftomen/qml/StatusStrip.qml +84 -0
  41. draftomen/qml/Theme.qml +71 -0
  42. draftomen/qml/qmldir +23 -0
  43. draftomen/qt_adapter.py +884 -0
  44. draftomen/qt_gui.py +338 -0
  45. draftomen/qt_mock.py +63 -0
  46. draftomen/ranking.py +126 -0
  47. draftomen/replay.py +480 -0
  48. draftomen/session.py +3668 -0
  49. draftomen/setinfo.py +26 -0
  50. draftomen/seventeen.py +2766 -0
  51. draftomen/splash.py +617 -0
  52. draftomen/tui.py +4007 -0
  53. draftomen/watch.py +336 -0
  54. draftomen-0.3.0.dist-info/METADATA +156 -0
  55. draftomen-0.3.0.dist-info/RECORD +59 -0
  56. draftomen-0.3.0.dist-info/WHEEL +5 -0
  57. draftomen-0.3.0.dist-info/entry_points.txt +6 -0
  58. draftomen-0.3.0.dist-info/licenses/LICENSE +21 -0
  59. draftomen-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,434 @@
1
+ import QtQuick 2.15
2
+ import QtQuick.Controls 2.15
3
+ import QtQuick.Layouts 1.15
4
+
5
+ Rectangle {
6
+ id: root
7
+
8
+ property var recommendation: null
9
+ property bool loading: false
10
+ property var imageState: null
11
+ property bool detailedIntel: false
12
+ property bool constrainImageFrameToHeight: false
13
+ // Keep the existing detailed-preview sizing by default; Live Draft's
14
+ // wide preview can opt into a larger bounded image.
15
+ property real detailedImageMaximumWidth: 200
16
+ property real detailedImageWidthRatio: 0.52
17
+
18
+ readonly property bool imageCurrent: Boolean(
19
+ recommendation
20
+ && imageState
21
+ && imageState.grp_id === recommendation.card.grp_id
22
+ )
23
+ readonly property bool imageLoading: Boolean(
24
+ loading || imageCurrent && imageState.phase === "loading"
25
+ )
26
+ readonly property bool imageAvailable: Boolean(
27
+ imageCurrent
28
+ && imageState.image_path
29
+ && imageState.phase === "ready"
30
+ )
31
+ readonly property string colorsText: {
32
+ if (!recommendation)
33
+ return "—"
34
+ if (recommendation.card.colors.length === 0)
35
+ return "Colorless"
36
+ return recommendation.card.colors.join(" · ")
37
+ }
38
+ readonly property string manaValueText: recommendation
39
+ && recommendation.card.mana_value !== null
40
+ && recommendation.card.mana_value !== undefined
41
+ ? Number(recommendation.card.mana_value).toFixed(0) : "—"
42
+ readonly property string alsaText: recommendation
43
+ && recommendation.average_last_seen_at !== null
44
+ && recommendation.average_last_seen_at !== undefined
45
+ ? Number(recommendation.average_last_seen_at).toFixed(2) : "—"
46
+ readonly property string winRateText: recommendation
47
+ && recommendation.win_rate !== null
48
+ && recommendation.win_rate !== undefined
49
+ ? (Number(recommendation.win_rate) * 100).toFixed(1) + "%" : "—"
50
+
51
+ color: Theme.surfaceLow
52
+ border.color: Theme.outline
53
+ border.width: 1
54
+ radius: Theme.radius
55
+ Accessible.role: Accessible.Pane
56
+ Accessible.name: {
57
+ if (recommendation) {
58
+ const prefix = root.detailedIntel
59
+ ? "Focused card intel, "
60
+ : "Selected card, "
61
+ return prefix + recommendation.card.name
62
+ }
63
+ return root.detailedIntel ? "Focused card intel" : "Card details"
64
+ }
65
+
66
+ readonly property real imageFrameAvailableHeight: Math.max(
67
+ 0,
68
+ root.height - Theme.panelPadding * 2
69
+ - previewHeading.implicitHeight
70
+ - (previewDetails.visible
71
+ ? previewDetailsColumn.implicitHeight : 0)
72
+ - previewLayout.rowSpacing * (previewDetails.visible ? 2 : 1)
73
+ )
74
+ readonly property real detailedImageFrameAvailableHeight: Math.max(
75
+ 0,
76
+ root.height - Theme.panelPadding * 2
77
+ - previewHeading.implicitHeight
78
+ - previewLayout.rowSpacing * 2
79
+ )
80
+ readonly property real detailedContentWidth: root.width
81
+ - Theme.panelPadding * 2 - previewLayout.columnSpacing
82
+ readonly property real detailedImageFrameWidth: {
83
+ const heightBound = Math.max(
84
+ 0,
85
+ root.detailedImageFrameAvailableHeight / 1.4
86
+ )
87
+ const maximumWidth = Math.max(
88
+ 0, root.detailedImageMaximumWidth
89
+ )
90
+ const contentBound = Math.max(
91
+ 0,
92
+ root.detailedContentWidth
93
+ * Math.max(0, root.detailedImageWidthRatio)
94
+ )
95
+ return Math.min(
96
+ maximumWidth,
97
+ heightBound,
98
+ Math.max(120, contentBound)
99
+ )
100
+ }
101
+ readonly property real imageFrameWidth: {
102
+ if (root.detailedIntel)
103
+ return root.detailedImageFrameWidth
104
+ if (root.constrainImageFrameToHeight) {
105
+ return Math.max(
106
+ 0,
107
+ Math.min(
108
+ 260,
109
+ root.width - Theme.panelPadding * 2,
110
+ root.imageFrameAvailableHeight / 1.4
111
+ )
112
+ )
113
+ }
114
+ return Math.max(0, Math.min(
115
+ 240,
116
+ root.width - Theme.panelPadding * 2
117
+ ))
118
+ }
119
+ readonly property real imageFrameHeight: root.imageFrameWidth * 1.4
120
+ readonly property real previewDetailsSideMargin: {
121
+ if (root.detailedIntel || !root.constrainImageFrameToHeight)
122
+ return 0
123
+ return Math.max(
124
+ 0,
125
+ (root.width - Theme.panelPadding * 2 - root.imageFrameWidth) / 2
126
+ )
127
+ }
128
+
129
+ GridLayout {
130
+ id: previewLayout
131
+ anchors.fill: parent
132
+ anchors.margins: Theme.panelPadding
133
+ columns: root.detailedIntel ? 2 : 1
134
+ rowSpacing: root.detailedIntel ? 10
135
+ : root.constrainImageFrameToHeight ? 18 : 12
136
+ columnSpacing: 12
137
+
138
+ Label {
139
+ id: previewHeading
140
+ objectName: "cardPreviewHeading"
141
+ Layout.columnSpan: root.detailedIntel ? 2 : 1
142
+ text: root.detailedIntel ? "FOCUSED INTEL" : "SELECTED CARD"
143
+ color: root.detailedIntel ? Theme.primary : Theme.textMuted
144
+ font.pixelSize: Theme.textPixelSize(11)
145
+ font.bold: true
146
+ font.letterSpacing: 1.2
147
+ }
148
+
149
+ Rectangle {
150
+ id: imageFrame
151
+ objectName: "cardPreviewImageFrame"
152
+ Layout.alignment: root.detailedIntel ? Qt.AlignTop : Qt.AlignHCenter
153
+ Layout.preferredWidth: root.imageFrameWidth
154
+ Layout.preferredHeight: root.imageFrameHeight
155
+ color: Theme.background
156
+ border.color: root.imageLoading ? Theme.warning : Theme.outline
157
+ border.width: 2
158
+ radius: 10
159
+ clip: true
160
+
161
+ Image {
162
+ id: cardImage
163
+ objectName: "cardPreviewImage"
164
+ anchors.fill: parent
165
+ source: root.imageAvailable ? root.imageState.image_path : ""
166
+ fillMode: Image.PreserveAspectFit
167
+ asynchronous: true
168
+ visible: root.imageAvailable && status === Image.Ready
169
+ }
170
+
171
+ ColumnLayout {
172
+ id: previewFallback
173
+ objectName: "cardPreviewFallback"
174
+ anchors.centerIn: parent
175
+ width: parent.width - 32
176
+ spacing: 10
177
+ visible: !cardImage.visible
178
+
179
+ Rectangle {
180
+ Layout.alignment: Qt.AlignHCenter
181
+ Layout.preferredWidth: 48
182
+ Layout.preferredHeight: 48
183
+ radius: 24
184
+ color: root.recommendation
185
+ && root.recommendation.card.colors.length > 0
186
+ ? Theme.colorForMana(root.recommendation.card.colors[0])
187
+ : Theme.surfaceHigh
188
+ opacity: 0.85
189
+ }
190
+
191
+ Label {
192
+ Layout.fillWidth: true
193
+ text: {
194
+ if (root.imageLoading)
195
+ return "Loading card image"
196
+ if (root.recommendation)
197
+ return root.recommendation.card.name
198
+ return "No card selected"
199
+ }
200
+ color: Theme.text
201
+ font.pixelSize: Theme.textPixelSize(16)
202
+ font.bold: true
203
+ horizontalAlignment: Text.AlignHCenter
204
+ wrapMode: Text.WordWrap
205
+ }
206
+
207
+ Label {
208
+ Layout.fillWidth: true
209
+ text: {
210
+ if (cardImage.status === Image.Error)
211
+ return "Card image could not be displayed."
212
+ if (root.imageCurrent)
213
+ return root.imageState.message
214
+ return "Card image unavailable"
215
+ }
216
+ color: Theme.textMuted
217
+ font.pixelSize: Theme.textPixelSize(11)
218
+ horizontalAlignment: Text.AlignHCenter
219
+ wrapMode: Text.WordWrap
220
+ }
221
+ }
222
+ }
223
+
224
+ Flickable {
225
+ id: previewDetails
226
+ objectName: "cardPreviewDetails"
227
+ visible: root.recommendation !== null
228
+ Layout.alignment: root.detailedIntel ? Qt.AlignTop : Qt.AlignLeft
229
+ Layout.fillWidth: true
230
+ Layout.leftMargin: root.previewDetailsSideMargin
231
+ Layout.rightMargin: root.previewDetailsSideMargin
232
+ Layout.maximumWidth: root.detailedIntel
233
+ ? Math.max(0, root.detailedContentWidth - root.imageFrameWidth)
234
+ : root.width
235
+ Layout.preferredHeight: root.detailedIntel
236
+ ? root.imageFrameHeight
237
+ : previewDetailsColumn.implicitHeight
238
+ contentWidth: width
239
+ contentHeight: previewDetailsColumn.implicitHeight
240
+ clip: true
241
+ boundsBehavior: Flickable.StopAtBounds
242
+ activeFocusOnTab: root.detailedIntel
243
+ Accessible.role: Accessible.Pane
244
+ Accessible.name: "Scrollable card details"
245
+ Accessible.description: "Use Up and Down, Page Up, Page Down, Home, or End to scroll the card details."
246
+
247
+ function scrollTo(position) {
248
+ contentY = Math.max(0, Math.min(
249
+ position, Math.max(0, contentHeight - height)
250
+ ))
251
+ }
252
+
253
+ Keys.onPressed: function(event) {
254
+ const pageStep = Math.max(1, height - 24)
255
+ const lineStep = 40
256
+ switch (event.key) {
257
+ case Qt.Key_Up:
258
+ previewDetails.scrollTo(contentY - lineStep)
259
+ break
260
+ case Qt.Key_Down:
261
+ previewDetails.scrollTo(contentY + lineStep)
262
+ break
263
+ case Qt.Key_PageUp:
264
+ previewDetails.scrollTo(contentY - pageStep)
265
+ break
266
+ case Qt.Key_PageDown:
267
+ previewDetails.scrollTo(contentY + pageStep)
268
+ break
269
+ case Qt.Key_Home:
270
+ previewDetails.scrollTo(0)
271
+ break
272
+ case Qt.Key_End:
273
+ previewDetails.scrollTo(contentHeight - height)
274
+ break
275
+ default:
276
+ return
277
+ }
278
+ event.accepted = true
279
+ }
280
+
281
+ ScrollBar.vertical: ScrollBar {
282
+ objectName: "cardPreviewScrollBar"
283
+ policy: ScrollBar.AsNeeded
284
+ width: 8
285
+ Accessible.name: "Card details vertical scrollbar"
286
+ }
287
+
288
+ ColumnLayout {
289
+ id: previewDetailsColumn
290
+ width: previewDetails.width
291
+ spacing: 6
292
+
293
+ Label {
294
+ objectName: "cardPreviewName"
295
+ Layout.fillWidth: true
296
+ text: root.recommendation ? root.recommendation.card.name : ""
297
+ color: Theme.text
298
+ font.pixelSize: Theme.textPixelSize(18)
299
+ font.bold: true
300
+ wrapMode: Text.WordWrap
301
+ }
302
+
303
+ Label {
304
+ objectName: "cardPreviewFacts"
305
+ Layout.fillWidth: true
306
+ text: {
307
+ if (!root.recommendation)
308
+ return ""
309
+ if (root.detailedIntel)
310
+ return root.colorsText + " · "
311
+ + root.recommendation.card.types.join(" · ")
312
+ + " · MV " + root.manaValueText
313
+ return root.recommendation.card.types.join(" · ")
314
+ + " · MV " + root.recommendation.card.mana_value
315
+ }
316
+ color: Theme.textMuted
317
+ font.pixelSize: Theme.textPixelSize(12)
318
+ wrapMode: Text.WordWrap
319
+ }
320
+
321
+ GridLayout {
322
+ visible: root.detailedIntel
323
+ objectName: "cardPreviewScores"
324
+ Layout.fillWidth: true
325
+ columns: 2
326
+ columnSpacing: 12
327
+ rowSpacing: 4
328
+
329
+ Label { text: "DO Score"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
330
+ Label {
331
+ text: root.recommendation ? root.recommendation.score : "—"
332
+ color: Theme.primary
333
+ font.family: fixedFontFamily
334
+ font.pixelSize: Theme.textPixelSize(16)
335
+ font.bold: true
336
+ }
337
+
338
+ Label { text: "17L WR"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
339
+ Label {
340
+ text: root.winRateText
341
+ color: Theme.text
342
+ font.family: fixedFontFamily
343
+ font.pixelSize: Theme.textPixelSize(15)
344
+ }
345
+
346
+ Label { text: "Grade"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
347
+ Label {
348
+ text: root.recommendation
349
+ ? root.recommendation.letter_grade || "—" : "—"
350
+ color: Theme.warning
351
+ font.pixelSize: Theme.textPixelSize(15)
352
+ font.bold: true
353
+ }
354
+
355
+ Label { text: "ALSA"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
356
+ Label {
357
+ text: root.alsaText
358
+ color: Theme.text
359
+ font.family: fixedFontFamily
360
+ font.pixelSize: Theme.textPixelSize(15)
361
+ }
362
+
363
+ Label { text: "Fit"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
364
+ Label {
365
+ Layout.fillWidth: true
366
+ text: root.recommendation
367
+ ? root.recommendation.color_fit || "Open" : "—"
368
+ color: Theme.text
369
+ elide: Text.ElideRight
370
+ }
371
+
372
+ Label { text: "Source"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(11) }
373
+ Label {
374
+ Layout.fillWidth: true
375
+ text: root.recommendation
376
+ ? root.recommendation.source_label || "Unavailable" : "—"
377
+ color: Theme.text
378
+ elide: Text.ElideRight
379
+ }
380
+ }
381
+
382
+ RowLayout {
383
+ visible: !root.detailedIntel
384
+ Layout.fillWidth: true
385
+
386
+ Label {
387
+ text: root.recommendation ? "DO " + root.recommendation.score : ""
388
+ color: Theme.primary
389
+ font.family: fixedFontFamily
390
+ font.bold: true
391
+ }
392
+
393
+ Label {
394
+ text: root.recommendation && root.recommendation.win_rate !== null
395
+ ? "17L " + (root.recommendation.win_rate * 100).toFixed(1) + "%"
396
+ : "17L —"
397
+ color: Theme.textMuted
398
+ font.family: fixedFontFamily
399
+ }
400
+
401
+ Label {
402
+ text: root.recommendation
403
+ ? root.recommendation.letter_grade || "—" : ""
404
+ color: Theme.warning
405
+ font.bold: true
406
+ }
407
+ }
408
+
409
+ Label {
410
+ objectName: "cardPreviewExplanation"
411
+ Layout.fillWidth: true
412
+ text: {
413
+ if (!root.recommendation)
414
+ return ""
415
+ if (root.detailedIntel) {
416
+ return root.recommendation.explanation
417
+ || "Explanation unavailable."
418
+ }
419
+ return root.recommendation.explanation || ""
420
+ }
421
+ color: Theme.textMuted
422
+ font.pixelSize: Theme.textPixelSize(12)
423
+ wrapMode: Text.WordWrap
424
+ }
425
+ }
426
+ }
427
+
428
+ Item {
429
+ Layout.columnSpan: root.detailedIntel ? 2 : 1
430
+ Layout.fillHeight: true
431
+ }
432
+ }
433
+ }
434
+
@@ -0,0 +1,42 @@
1
+ import QtQuick 2.15
2
+ import QtQuick.Controls 2.15
3
+
4
+ Button {
5
+ id: control
6
+
7
+ property bool dimensionalSelected: checked
8
+ property bool expanded: false
9
+ property bool accented: true
10
+ property color accentColor: Theme.primary
11
+ property color accentContentColor: Theme.primaryContent
12
+ readonly property color contentColor: {
13
+ if (!control.enabled)
14
+ return Theme.controlDisabledContent
15
+ return control.accented ? control.accentContentColor : Theme.neutralContent
16
+ }
17
+
18
+ implicitHeight: Theme.targetHeight
19
+
20
+ background: DimensionalSurface {
21
+ stateEnabled: control.enabled
22
+ stateHovered: control.hovered
23
+ statePressed: control.down
24
+ stateFocused: control.activeFocus
25
+ stateSelected: control.dimensionalSelected
26
+ stateExpanded: control.expanded
27
+ accented: control.accented
28
+ accentColor: control.accentColor
29
+ }
30
+
31
+ contentItem: Text {
32
+ transform: Translate {
33
+ y: control.down ? Theme.controlPressOffset : 0
34
+ }
35
+ text: control.text
36
+ color: control.contentColor
37
+ font: control.font
38
+ horizontalAlignment: Text.AlignHCenter
39
+ verticalAlignment: Text.AlignVCenter
40
+ elide: Text.ElideRight
41
+ }
42
+ }
@@ -0,0 +1,182 @@
1
+ pragma ComponentBehavior: Bound
2
+
3
+ import QtQuick 2.15
4
+ import QtQml 2.15
5
+ import QtQuick.Controls 2.15
6
+ import QtQuick.Layouts 1.15
7
+
8
+ ComboBox {
9
+ id: control
10
+
11
+ property bool accented: true
12
+ property color accentColor: Theme.primary
13
+ property color accentContentColor: Theme.primaryContent
14
+ readonly property color contentColor: {
15
+ if (!control.enabled)
16
+ return Theme.controlDisabledContent
17
+ return control.accented ? control.accentContentColor : Theme.neutralContent
18
+ }
19
+
20
+ implicitWidth: 150
21
+ implicitHeight: Theme.targetHeight
22
+
23
+ function labelFor(model, index) {
24
+ if (!control.textRole)
25
+ return control.textAt(index)
26
+
27
+ const roleValue = model[control.textRole]
28
+ if (control.textRole === "screen_name" && !roleValue)
29
+ return model.account_id ? String(model.account_id) : ""
30
+
31
+ if (roleValue === undefined || roleValue === null)
32
+ return ""
33
+
34
+ const label = String(roleValue)
35
+ if (control.textRole === "pair" && model.automatic)
36
+ return label + " · automatic"
37
+ return label
38
+ }
39
+
40
+ background: DimensionalSurface {
41
+ stateEnabled: control.enabled
42
+ stateHovered: control.hovered
43
+ statePressed: control.pressed
44
+ stateFocused: control.activeFocus
45
+ stateSelected: control.currentIndex >= 0
46
+ stateExpanded: control.popup.visible
47
+ accented: control.accented
48
+ accentColor: control.accentColor
49
+ }
50
+
51
+ contentItem: Text {
52
+ transform: Translate {
53
+ y: control.pressed ? Theme.controlPressOffset : 0
54
+ }
55
+ leftPadding: 12
56
+ rightPadding: control.indicator.width + 10
57
+ text: control.displayText
58
+ color: control.contentColor
59
+ font: control.font
60
+ verticalAlignment: Text.AlignVCenter
61
+ elide: Text.ElideRight
62
+ }
63
+
64
+ indicator: Canvas {
65
+ id: chevron
66
+ objectName: "dimensionalComboIndicator"
67
+ x: control.width - width - 10
68
+ y: (control.height - height) / 2
69
+ + (control.pressed ? Theme.controlPressOffset : 0)
70
+ width: 14
71
+ height: 10
72
+ opacity: control.enabled ? 1 : 0.45
73
+ rotation: control.popup.visible ? 180 : 0
74
+ onPaint: {
75
+ const context = getContext("2d")
76
+ context.clearRect(0, 0, width, height)
77
+ context.strokeStyle = control.contentColor
78
+ context.lineWidth = 1.5
79
+ context.lineCap = "square"
80
+ context.beginPath()
81
+ context.moveTo(2, 3)
82
+ context.lineTo(7, 8)
83
+ context.lineTo(12, 3)
84
+ context.stroke()
85
+ }
86
+ onRotationChanged: requestPaint()
87
+ onOpacityChanged: requestPaint()
88
+ }
89
+ Connections {
90
+ target: control
91
+ function onContentColorChanged() {
92
+ chevron.requestPaint()
93
+ }
94
+ }
95
+
96
+ delegate: ItemDelegate {
97
+ id: comboDelegate
98
+ objectName: "dimensionalComboDelegate"
99
+ required property var model
100
+ required property int index
101
+
102
+ width: comboPopup.availableWidth
103
+ height: Theme.targetHeight
104
+ text: control.labelFor(comboDelegate.model, comboDelegate.index)
105
+ highlighted: control.highlightedIndex === comboDelegate.index
106
+ Accessible.name: text
107
+
108
+ background: DimensionalSurface {
109
+ stateEnabled: comboDelegate.enabled
110
+ stateHovered: comboDelegate.hovered || comboDelegate.highlighted
111
+ statePressed: comboDelegate.down
112
+ stateFocused: comboDelegate.activeFocus
113
+ stateSelected: control.currentIndex === comboDelegate.index
114
+ stateExpanded: comboDelegate.highlighted
115
+ accented: control.currentIndex === comboDelegate.index
116
+ accentColor: control.accentColor
117
+ }
118
+
119
+ contentItem: RowLayout {
120
+ anchors.fill: parent
121
+ anchors.topMargin: comboDelegate.down ? Theme.controlPressOffset : 0
122
+ anchors.leftMargin: 10
123
+ anchors.rightMargin: 10
124
+ spacing: 8
125
+
126
+ Text {
127
+ objectName: "dimensionalComboSelectedIndicator"
128
+ visible: control.currentIndex === comboDelegate.index
129
+ text: "✓"
130
+ color: comboDelegate.enabled
131
+ ? control.accentContentColor : Theme.controlDisabledContent
132
+ font.bold: true
133
+ Layout.preferredWidth: 14
134
+ horizontalAlignment: Text.AlignHCenter
135
+ verticalAlignment: Text.AlignVCenter
136
+ Accessible.ignored: true
137
+ }
138
+
139
+ Text {
140
+ Layout.fillWidth: true
141
+ text: comboDelegate.text
142
+ color: {
143
+ if (!comboDelegate.enabled)
144
+ return Theme.controlDisabledContent
145
+ if (control.currentIndex === comboDelegate.index)
146
+ return control.accentContentColor
147
+ return Theme.neutralContent
148
+ }
149
+ font: comboDelegate.font
150
+ verticalAlignment: Text.AlignVCenter
151
+ elide: Text.ElideRight
152
+ }
153
+ }
154
+ }
155
+
156
+ popup: Popup {
157
+ id: comboPopup
158
+ objectName: "dimensionalComboPopup"
159
+ y: control.height + 4
160
+ width: control.width
161
+ padding: 4
162
+ implicitHeight: Math.min(320, comboList.contentHeight + padding * 2)
163
+ modal: false
164
+
165
+ background: DimensionalSurface {
166
+ stateEnabled: control.enabled
167
+ stateExpanded: true
168
+ accented: false
169
+ }
170
+
171
+ contentItem: ListView {
172
+ id: comboList
173
+ objectName: "dimensionalComboList"
174
+ clip: true
175
+ implicitHeight: contentHeight
176
+ model: comboPopup.visible ? control.delegateModel : null
177
+ currentIndex: control.highlightedIndex
178
+ boundsBehavior: Flickable.StopAtBounds
179
+ ScrollIndicator.vertical: ScrollIndicator { }
180
+ }
181
+ }
182
+ }