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,1151 @@
1
+ pragma ComponentBehavior: Bound
2
+
3
+ import QtQuick 2.15
4
+ import QtQuick.Controls 2.15
5
+ import QtQuick.Layouts 1.15
6
+
7
+ Item {
8
+ id: root
9
+ objectName: "buildView"
10
+
11
+ required property var sessionState
12
+ required property bool narrow
13
+ required property var displayPreferences
14
+ readonly property var rawBuild: root.sessionState ? root.sessionState.build : null
15
+ readonly property bool hasBuild: root.rawBuild !== null && root.rawBuild !== undefined
16
+ readonly property var build: root.hasBuild ? root.rawBuild : ({
17
+ selected_pair: "",
18
+ pair_options: [],
19
+ spells: [],
20
+ lands: [],
21
+ bench: [],
22
+ deck_size: 0,
23
+ average_mana_value: null,
24
+ pair_override: null,
25
+ spell_count: null,
26
+ warnings: [],
27
+ land_count: null,
28
+ creature_count: null,
29
+ instant_count: null
30
+ })
31
+ readonly property int wideMinimumWidth: 220 + 360 + 240 + Theme.gutter * 2
32
+ readonly property int wideBuildCardMinimumHeight: 300
33
+ readonly property real wideBuildContextMinimumHeight:
34
+ wideReasoning.implicitHeight + Theme.panelPadding * 2
35
+ readonly property real wideBenchHeight: (
36
+ root.benchExpanded
37
+ ? Math.min(
38
+ wideBenchRows.implicitHeight,
39
+ root.wideBenchViewportMaximumHeight
40
+ )
41
+ : 0
42
+ ) + Theme.targetHeight + wideBenchContent.spacing + 4 * 2
43
+ // Keep the expanded bench compact so the main row retains useful height.
44
+ readonly property real wideBuildMainRowMinimumHeight:
45
+ root.wideBuildCardMinimumHeight
46
+ + (root.displayPreferences.detailedBuildContext
47
+ ? wideBuildCardColumn.spacing + root.wideBuildContextMinimumHeight : 0)
48
+ readonly property real wideBenchViewportAvailableHeight:
49
+ wideBuildContent.height
50
+ - root.wideBuildMainRowMinimumHeight
51
+ - wideBuildContent.spacing
52
+ - Theme.targetHeight
53
+ - wideBenchContent.spacing
54
+ - 4 * 2
55
+ readonly property real wideBuildMinimumContentHeight:
56
+ Math.max(
57
+ wideBuildSummaryColumn.implicitHeight,
58
+ root.wideBuildMainRowMinimumHeight
59
+ )
60
+ + wideBuildContent.spacing
61
+ + Theme.targetHeight
62
+ + wideBenchContent.spacing
63
+ + 4 * 2
64
+ readonly property real wideBenchViewportMaximumHeight: Math.max(
65
+ root.wideBenchViewportAvailableHeight >= Theme.targetHeight + Theme.gutter
66
+ ? Theme.targetHeight + Theme.gutter : 0,
67
+ Math.min(
68
+ root.height * 0.2,
69
+ root.wideBenchViewportAvailableHeight
70
+ )
71
+ )
72
+ readonly property bool compactPresentation: root.narrow
73
+ || root.width < root.wideMinimumWidth
74
+ || wideBuildContent.height < root.wideBuildMinimumContentHeight
75
+ readonly property var automaticPair: {
76
+ for (let index = 0; index < root.build.pair_options.length; index++) {
77
+ if (root.build.pair_options[index].automatic)
78
+ return root.build.pair_options[index]
79
+ }
80
+ return null
81
+ }
82
+ readonly property string pairDescription: {
83
+ if (!root.build.pair_override)
84
+ return root.build.selected_pair + " was selected automatically from the drafted pool."
85
+ const automaticPairName = root.automaticPair ? root.automaticPair.pair : "another pair"
86
+ return root.build.selected_pair + " was explicitly requested. Automatic evaluation preferred " + automaticPairName + "."
87
+ }
88
+ readonly property string buildIdentity: {
89
+ if (!root.hasBuild)
90
+ return ""
91
+ return root.build.selected_pair + ":"
92
+ + root.build.spells.map(entry => entry.card.grp_id).join(",") + ":"
93
+ + root.build.bench.map(entry => entry.card.grp_id).join(",")
94
+ }
95
+ property var focusedCard: null
96
+ property string publishedBuildFocusKey: ""
97
+ property bool spellsExpanded: true
98
+ property bool landsExpanded: false
99
+ property bool benchExpanded: false
100
+ property bool contextExpanded: false
101
+ property bool cardDetailsExpanded: false
102
+
103
+ property bool buildFocusPublishedWhileVisible: false
104
+
105
+ onVisibleChanged: {
106
+ if (!root.visible) {
107
+ root.buildFocusPublishedWhileVisible = false
108
+ return
109
+ }
110
+ Qt.callLater(function() {
111
+ if (!root.visible || root.buildFocusPublishedWhileVisible
112
+ || !root.focusedCard) {
113
+ return
114
+ }
115
+ root.buildFocusPublishedWhileVisible = true
116
+ root.publishBuildFocus(root.focusedCard, true)
117
+ })
118
+ }
119
+
120
+ onBuildIdentityChanged: {
121
+ root.focusedCard = root.hasBuild && root.build.spells.length > 0 ? root.build.spells[0] : null
122
+ root.cardDetailsExpanded = false
123
+ if (!root.focusedCard) {
124
+ root.publishedBuildFocusKey = ""
125
+ return
126
+ }
127
+ if (!root.visible)
128
+ return
129
+ Qt.callLater(function() {
130
+ root.publishBuildFocus(root.focusedCard, false)
131
+ })
132
+ }
133
+
134
+ function publishBuildFocus(card, force) {
135
+ if (!card)
136
+ return
137
+ const focusKey = root.buildIdentity + ":" + card.card.grp_id
138
+ if (!force && focusKey === root.publishedBuildFocusKey)
139
+ return
140
+ root.publishedBuildFocusKey = focusKey
141
+ sessionProvider.focusBuildCard(card.card.grp_id)
142
+ }
143
+
144
+ function focusCardDetailsToggle() {
145
+ if (!cardDetailsToggle.visible)
146
+ return
147
+ narrowBuildScroll.ScrollBar.vertical.position = 0
148
+ cardDetailsToggle.forceActiveFocus()
149
+ }
150
+
151
+ function focusCard(card) {
152
+ root.focusedCard = card
153
+ root.publishBuildFocus(card, true)
154
+ if (root.compactPresentation && root.displayPreferences.cardPreview) {
155
+ root.cardDetailsExpanded = true
156
+ narrowBuildScroll.ScrollBar.vertical.position = 0
157
+ Qt.callLater(function() {
158
+ Qt.callLater(root.focusCardDetailsToggle)
159
+ })
160
+ }
161
+ }
162
+
163
+ function spellManaValue(spell) {
164
+ if (!spell || !spell.card || spell.card.mana_value === null || spell.card.mana_value === undefined)
165
+ return null
166
+ return spell.card.mana_value
167
+ }
168
+
169
+ function averageManaValueText() {
170
+ const average = root.build.average_mana_value
171
+ if (average === null || average === undefined)
172
+ return "Average mana value: —"
173
+ return "Average mana value: " + Number(average).toFixed(2)
174
+ }
175
+ function countText(value) {
176
+ return value === null || value === undefined ? "—" : value
177
+ }
178
+ function countLabel(value, singular) {
179
+ const text = root.countText(value)
180
+ return text + " " + singular + (text === 1 ? "" : "s")
181
+ }
182
+
183
+ function manaBucket(spell) {
184
+ const manaValue = root.spellManaValue(spell)
185
+ if (manaValue === null)
186
+ return 6
187
+ if (manaValue <= 1)
188
+ return 0
189
+ return manaValue >= 6 ? 6 : manaValue
190
+ }
191
+
192
+ function manaBucketLabel(bucket) {
193
+ if (bucket === 0)
194
+ return "MV 0/1"
195
+ return bucket === 6 ? "MV 6+" : "MV " + bucket
196
+ }
197
+
198
+ function manaCount(bucket) {
199
+ let count = 0
200
+ for (let index = 0; index < root.build.spells.length; index++) {
201
+ if (root.manaBucket(root.build.spells[index]) === bucket)
202
+ count += root.build.spells[index].quantity
203
+ }
204
+ return count
205
+ }
206
+
207
+ function benchQuantity() {
208
+ let count = 0
209
+ for (let index = 0; index < root.build.bench.length; index++)
210
+ count += root.build.bench[index].quantity
211
+ return count
212
+ }
213
+
214
+ function spellsInBucket(bucket) {
215
+ const spellEntries = []
216
+ for (let index = 0; index < root.build.spells.length; index++) {
217
+ const spell = root.build.spells[index]
218
+ if (root.manaBucket(spell) === bucket)
219
+ spellEntries.push({ cardEntry: spell, entryIndex: index })
220
+ }
221
+ return spellEntries
222
+ }
223
+
224
+ function landSummary(land) {
225
+ if (land.card)
226
+ return "drafted nonbasic"
227
+ return "basic source " + land.source_colors.join("/")
228
+ }
229
+
230
+ component ManaPips: Row {
231
+ id: manaPips
232
+ required property var colors
233
+ spacing: 3
234
+ Repeater {
235
+ model: manaPips.colors
236
+ delegate: Rectangle {
237
+ required property string modelData
238
+ width: 16
239
+ height: 16
240
+ radius: 8
241
+ color: Theme.colorForMana(modelData)
242
+ border.color: Theme.outline
243
+ border.width: 1
244
+ Label {
245
+ anchors.centerIn: parent
246
+ text: modelData
247
+ color: Theme.background
248
+ font.pixelSize: Theme.textPixelSize(9)
249
+ font.bold: true
250
+ }
251
+ }
252
+ }
253
+ Label {
254
+ visible: manaPips.colors.length === 0
255
+ text: "C"
256
+ color: Theme.textMuted
257
+ font.pixelSize: Theme.textPixelSize(10)
258
+ font.bold: true
259
+ }
260
+ }
261
+
262
+ component ManaCurve: Rectangle {
263
+ Layout.fillWidth: true
264
+ Layout.preferredHeight: Math.max(
265
+ 154,
266
+ manaCurveContent.implicitHeight + Theme.panelPadding * 2
267
+ )
268
+ color: Theme.surfaceLow
269
+ border.color: Theme.outline
270
+ border.width: 1
271
+ radius: Theme.radius
272
+ Accessible.role: Accessible.Pane
273
+ Accessible.name: "Mana curve"
274
+ Accessible.description: "Horizontally labelled mana curve for the suggested deck."
275
+
276
+ ColumnLayout {
277
+ id: manaCurveContent
278
+ anchors.left: parent.left
279
+ anchors.right: parent.right
280
+ anchors.top: parent.top
281
+ anchors.leftMargin: Theme.panelPadding
282
+ anchors.rightMargin: Theme.panelPadding
283
+ anchors.topMargin: Theme.panelPadding
284
+ spacing: 8
285
+ ColumnLayout {
286
+ Layout.fillWidth: true
287
+ spacing: 2
288
+ ColumnLayout {
289
+ Layout.fillWidth: true
290
+ spacing: 2
291
+ Label {
292
+ objectName: "manaCurveTitle"
293
+ Layout.fillWidth: true
294
+ text: "MANA CURVE"
295
+ color: Theme.textMuted
296
+ font.pixelSize: Theme.textPixelSize(10)
297
+ font.bold: true
298
+ font.letterSpacing: 1
299
+ }
300
+ Label {
301
+ objectName: "manaCurveAverage"
302
+ Layout.fillWidth: true
303
+ text: root.averageManaValueText()
304
+ color: Theme.textMuted
305
+ font.pixelSize: Theme.textPixelSize(11)
306
+ Accessible.name: text
307
+ }
308
+ }
309
+ Label {
310
+ Layout.fillWidth: true
311
+ text: "spells by mana value"
312
+ color: Theme.textMuted
313
+ font.pixelSize: Theme.textPixelSize(11)
314
+ }
315
+ }
316
+ RowLayout {
317
+ Layout.fillWidth: true
318
+ Layout.fillHeight: true
319
+ spacing: 8
320
+ Repeater {
321
+ model: [0, 2, 3, 4, 5, 6]
322
+ delegate: ColumnLayout {
323
+ required property int modelData
324
+ readonly property int cardCount: root.manaCount(modelData)
325
+ Layout.fillWidth: true
326
+ Layout.fillHeight: true
327
+ spacing: 4
328
+ Label {
329
+ Layout.alignment: Qt.AlignHCenter
330
+ text: cardCount
331
+ color: Theme.text
332
+ font.family: fixedFontFamily
333
+ font.bold: true
334
+ Accessible.name: cardCount + " cards at " + root.manaBucketLabel(modelData)
335
+ }
336
+ Item { Layout.fillHeight: true }
337
+ Rectangle {
338
+ Layout.fillWidth: true
339
+ Layout.preferredHeight: Math.max(4, Math.min(56, cardCount * 7))
340
+ color: Theme.primary
341
+ radius: 2
342
+ }
343
+ Label {
344
+ Layout.alignment: Qt.AlignHCenter
345
+ text: root.manaBucketLabel(modelData).replace("MV ", "")
346
+ color: Theme.textMuted
347
+ font.pixelSize: Theme.textPixelSize(10)
348
+ font.bold: true
349
+ }
350
+ }
351
+ }
352
+ }
353
+ }
354
+ }
355
+
356
+ component BuildCardRow: DimensionalButton {
357
+ id: cardRow
358
+ required property var cardEntry
359
+ required property int entryIndex
360
+ readonly property bool selected: root.focusedCard !== null
361
+ && root.focusedCard.card.grp_id === cardEntry.card.grp_id
362
+ readonly property bool accessibilitySelectable: Accessible.selectable
363
+ readonly property bool accessibilitySelected: Accessible.selected
364
+ readonly property string colorsText: cardEntry.card.colors.length > 0
365
+ ? cardEntry.card.colors.join(" · ") : "Colorless"
366
+ readonly property string scoreText: cardEntry.score !== null && cardEntry.score !== undefined
367
+ ? cardEntry.score : "—"
368
+ Layout.fillWidth: true
369
+ Layout.minimumHeight: 40
370
+ Layout.preferredHeight: root.displayPreferences.compactDensity ? 40 : 46
371
+ text: ""
372
+ dimensionalSelected: cardRow.selected
373
+ accented: cardRow.selected
374
+ Accessible.name: "Focus card " + cardEntry.card.name
375
+ Accessible.description: cardEntry.quantity + " copies, " + colorsText
376
+ + ", DO score " + scoreText + ", grade " + (cardEntry.letter_grade || "—")
377
+ + ", " + (cardEntry.color_fit || "—")
378
+ Accessible.selectable: true
379
+ Accessible.selected: cardRow.selected
380
+ onClicked: root.focusCard(cardEntry)
381
+ contentItem: RowLayout {
382
+ anchors.fill: parent
383
+ anchors.topMargin: cardRow.down ? Theme.controlPressOffset : 0
384
+ anchors.leftMargin: 10
385
+ anchors.rightMargin: 10
386
+ spacing: 8
387
+ Label {
388
+ Layout.preferredWidth: 24
389
+ text: "×" + cardEntry.quantity
390
+ color: cardRow.selected ? cardRow.contentColor : Theme.textMuted
391
+ font.family: fixedFontFamily
392
+ horizontalAlignment: Text.AlignRight
393
+ }
394
+ ManaPips { colors: cardEntry.card.colors }
395
+ Label {
396
+ Layout.fillWidth: true
397
+ text: cardEntry.card.name
398
+ color: cardRow.contentColor
399
+ font.bold: cardRow.selected
400
+ elide: Text.ElideRight
401
+ }
402
+ Label {
403
+ Layout.preferredWidth: 30
404
+ text: cardEntry.letter_grade || "—"
405
+ color: cardRow.selected ? cardRow.contentColor : Theme.warning
406
+ font.bold: true
407
+ horizontalAlignment: Text.AlignRight
408
+ }
409
+ Label {
410
+ Layout.preferredWidth: 42
411
+ text: "DO " + scoreText
412
+ color: cardRow.selected ? cardRow.contentColor : Theme.primary
413
+ font.family: fixedFontFamily
414
+ horizontalAlignment: Text.AlignRight
415
+ }
416
+ }
417
+ }
418
+
419
+ ColumnLayout {
420
+ anchors.fill: parent
421
+ spacing: Theme.gutter
422
+
423
+ RowLayout {
424
+ Layout.fillWidth: true
425
+ spacing: Theme.gutter
426
+ ColumnLayout {
427
+ Layout.fillWidth: true
428
+ spacing: 2
429
+ Label { text: "Suggested deck"; color: Theme.text; font.pixelSize: Theme.textPixelSize(22); font.bold: true }
430
+ Label {
431
+ Layout.fillWidth: true
432
+ text: "Recreate this build in Arena · Draft Omen remains read only"
433
+ color: Theme.textMuted
434
+ wrapMode: Text.WordWrap
435
+ }
436
+ }
437
+ Label {
438
+ visible: root.hasBuild
439
+ text: root.build.pair_override ? "OVERRIDE ACTIVE" : "AUTOMATIC PAIR"
440
+ color: root.build.pair_override ? Theme.warning : Theme.primary
441
+ font.pixelSize: Theme.textPixelSize(10)
442
+ font.bold: true
443
+ font.letterSpacing: 1
444
+ }
445
+ DimensionalComboBox {
446
+ id: pairSelector
447
+ objectName: "buildPairSelector"
448
+ visible: root.hasBuild
449
+ Layout.preferredWidth: root.compactPresentation ? 104 : 174
450
+ model: root.build.pair_options
451
+ textRole: "pair"
452
+ valueRole: "pair"
453
+ currentIndex: {
454
+ for (let index = 0; index < root.build.pair_options.length; index++) {
455
+ if (root.build.pair_options[index].pair === root.build.selected_pair)
456
+ return index
457
+ }
458
+ return 0
459
+ }
460
+ Accessible.name: "Deck color pair"
461
+ Accessible.description: "Choose a pair, then rebuild the suggested deck."
462
+ }
463
+ DimensionalButton {
464
+ objectName: "buildRebuildButton"
465
+ visible: root.hasBuild
466
+ text: "Rebuild"
467
+ Accessible.name: "Rebuild suggested deck"
468
+ Accessible.description: "Requests a new build with the selected color pair override."
469
+ onClicked: {
470
+ sessionProvider.requestBuild(pairSelector.currentValue || "")
471
+ }
472
+ }
473
+ }
474
+
475
+ StateBanner { Layout.fillWidth: true; sessionState: root.sessionState }
476
+
477
+ Rectangle {
478
+ visible: !root.hasBuild
479
+ Layout.fillWidth: true
480
+ Layout.fillHeight: true
481
+ color: Theme.surfaceLow
482
+ border.color: Theme.outline
483
+ border.width: 1
484
+ radius: Theme.radius
485
+ ColumnLayout {
486
+ anchors.centerIn: parent
487
+ width: Math.min(parent.width - 40, 440)
488
+ spacing: 12
489
+ Label { text: "No deck build available"; color: Theme.text; font.pixelSize: Theme.textPixelSize(20); font.bold: true }
490
+ Label { Layout.fillWidth: true; text: "Complete or recover a draft to request a suggested deck."; color: Theme.textMuted; wrapMode: Text.WordWrap }
491
+ DimensionalButton {
492
+ objectName: "buildRequestButton"
493
+ text: "Request build"
494
+ Accessible.name: "Request suggested deck build"
495
+ onClicked: sessionProvider.requestBuild("")
496
+ }
497
+ }
498
+ }
499
+
500
+ Item {
501
+ visible: root.hasBuild
502
+ Layout.fillWidth: true
503
+ Layout.fillHeight: true
504
+
505
+ ColumnLayout {
506
+ id: wideBuildContent
507
+ visible: !root.compactPresentation
508
+ anchors.fill: parent
509
+ spacing: Theme.gutter
510
+
511
+ RowLayout {
512
+ id: wideBuildMainRow
513
+ Layout.fillWidth: true
514
+ Layout.fillHeight: true
515
+ Layout.minimumHeight: root.wideBuildMainRowMinimumHeight
516
+ spacing: Theme.gutter
517
+
518
+ ColumnLayout {
519
+ id: wideBuildSummaryColumn
520
+ Layout.fillWidth: true
521
+ Layout.minimumWidth: 220
522
+ Layout.preferredWidth: 300
523
+ Layout.maximumWidth: 360
524
+ Layout.horizontalStretchFactor: 3
525
+ Layout.fillHeight: true
526
+ spacing: Theme.gutter
527
+
528
+ Rectangle {
529
+ Layout.fillWidth: true
530
+ Layout.preferredHeight: composition.implicitHeight + Theme.panelPadding * 2
531
+ color: Theme.surfaceLow
532
+ border.color: Theme.outline
533
+ border.width: 1
534
+ radius: Theme.radius
535
+ ColumnLayout {
536
+ id: composition
537
+ anchors.fill: parent
538
+ anchors.margins: Theme.panelPadding
539
+ spacing: 10
540
+ Label { text: "DECK COMPOSITION"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true; font.letterSpacing: 1 }
541
+ GridLayout {
542
+ Layout.fillWidth: true
543
+ columns: 2
544
+ columnSpacing: 20
545
+ rowSpacing: 8
546
+ Label { text: root.build.deck_size; color: Theme.text; font.pixelSize: Theme.textPixelSize(22); font.bold: true }
547
+ Label { text: root.countText(root.build.spell_count); color: Theme.primary; font.pixelSize: Theme.textPixelSize(22); font.bold: true }
548
+ Label { text: "TOTAL CARDS"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
549
+ Label { text: "SPELLS"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
550
+ Label { objectName: "wideBuildCreatureCount"; text: root.countText(root.build.creature_count); color: Theme.primary; font.pixelSize: Theme.textPixelSize(18); font.bold: true }
551
+ Label { objectName: "wideBuildInstantCount"; text: root.countText(root.build.instant_count); color: Theme.primary; font.pixelSize: Theme.textPixelSize(18); font.bold: true }
552
+ Label { text: "CREATURES"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
553
+ Label { text: "INSTANTS"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
554
+ Label { text: root.countText(root.build.land_count); color: Theme.warning; font.pixelSize: Theme.textPixelSize(18); font.bold: true }
555
+ Label { text: root.build.deck_size === 40 ? "DECK SIZE READY" : "DECK SIZE CHECK"; color: root.build.deck_size === 40 ? Theme.primary : Theme.warning; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
556
+ Label { text: "LANDS"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
557
+ Label { text: "SUGGESTED DECK"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
558
+ }
559
+ }
560
+ }
561
+
562
+ Rectangle {
563
+ objectName: "wideBuildManaBase"
564
+ Layout.fillWidth: true
565
+ Layout.preferredHeight: manaBase.implicitHeight + Theme.panelPadding * 2
566
+ color: Theme.surfaceLow
567
+ border.color: Theme.outline
568
+ border.width: 1
569
+ radius: Theme.radius
570
+ ColumnLayout {
571
+ id: manaBase
572
+ anchors.fill: parent
573
+ anchors.margins: Theme.panelPadding
574
+ spacing: 7
575
+ Label { text: "MANA BASE"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true; font.letterSpacing: 1 }
576
+ Repeater {
577
+ model: root.build.lands
578
+ delegate: RowLayout {
579
+ required property var modelData
580
+ Layout.fillWidth: true
581
+ ManaPips { colors: modelData.source_colors }
582
+ Label { Layout.fillWidth: true; text: modelData.name; color: Theme.text; elide: Text.ElideRight }
583
+ Label { text: modelData.quantity; color: Theme.text; font.family: fixedFontFamily }
584
+ }
585
+ }
586
+ }
587
+ }
588
+ ManaCurve { objectName: "wideBuildManaCurve" }
589
+
590
+ Rectangle {
591
+ objectName: "wideBuildWarnings"
592
+ visible: root.build.warnings.length > 0
593
+ Layout.fillWidth: true
594
+ Layout.preferredHeight: wideWarnings.implicitHeight + Theme.panelPadding * 2
595
+ color: Theme.warningDark
596
+ border.color: Theme.warning
597
+ border.width: 1
598
+ radius: Theme.radius
599
+ Label {
600
+ id: wideWarnings
601
+ anchors.fill: parent
602
+ anchors.margins: Theme.panelPadding
603
+ text: root.build.warnings.join("\n")
604
+ color: Theme.warning
605
+ wrapMode: Text.WordWrap
606
+ Accessible.name: "Build warnings: " + text
607
+ }
608
+ }
609
+ Item { Layout.fillHeight: true }
610
+
611
+ }
612
+
613
+ Rectangle {
614
+ Layout.fillWidth: true
615
+ Layout.minimumWidth: 360
616
+ Layout.preferredWidth: 600
617
+ Layout.horizontalStretchFactor: 6
618
+ Layout.fillHeight: true
619
+ color: Theme.surfaceLow
620
+ border.color: Theme.outline
621
+ border.width: 1
622
+ radius: Theme.radius
623
+ clip: true
624
+ ColumnLayout {
625
+ anchors.fill: parent
626
+ spacing: 0
627
+ RowLayout {
628
+ Layout.fillWidth: true
629
+ Layout.preferredHeight: 48
630
+ Layout.leftMargin: Theme.panelPadding
631
+ Layout.rightMargin: Theme.panelPadding
632
+ Label { text: "MAIN DECK SPELLS"; color: Theme.text; font.pixelSize: Theme.textPixelSize(12); font.bold: true; font.letterSpacing: 1 }
633
+ Item { Layout.fillWidth: true }
634
+ Label { text: root.build.spell_count !== null ? root.build.spell_count + " cards" : ""; color: Theme.textMuted; font.family: fixedFontFamily }
635
+ }
636
+ Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 1; color: Theme.outline }
637
+ ScrollView {
638
+ id: wideSpellScroll
639
+ objectName: "buildSpellGroups"
640
+ Layout.fillWidth: true
641
+ Layout.fillHeight: true
642
+ clip: true
643
+ contentWidth: availableWidth
644
+ ColumnLayout {
645
+ width: wideSpellScroll.availableWidth
646
+ spacing: 12
647
+ Repeater {
648
+ model: [0, 2, 3, 4, 5, 6]
649
+ delegate: ColumnLayout {
650
+ required property int modelData
651
+ readonly property int bucket: modelData
652
+ Layout.fillWidth: true
653
+ Layout.leftMargin: 8
654
+ Layout.rightMargin: 8
655
+ spacing: 3
656
+ Rectangle {
657
+ Layout.fillWidth: true
658
+ Layout.preferredHeight: 28
659
+ color: Theme.surface
660
+ radius: Theme.radius
661
+ Label {
662
+ anchors.verticalCenter: parent.verticalCenter
663
+ anchors.left: parent.left
664
+ anchors.leftMargin: 10
665
+ text: root.manaBucketLabel(modelData)
666
+ color: Theme.textMuted
667
+ font.pixelSize: Theme.textPixelSize(11)
668
+ font.bold: true
669
+ Accessible.name: text
670
+ }
671
+ Label {
672
+ anchors.verticalCenter: parent.verticalCenter
673
+ anchors.right: parent.right
674
+ anchors.rightMargin: 10
675
+ text: root.manaCount(modelData)
676
+ color: Theme.textMuted
677
+ font.family: fixedFontFamily
678
+ }
679
+ }
680
+ Repeater {
681
+ model: root.spellsInBucket(bucket)
682
+ delegate: BuildCardRow {
683
+ required property var modelData
684
+ cardEntry: modelData.cardEntry
685
+ entryIndex: modelData.entryIndex
686
+ objectName: root.compactPresentation ? "" : "buildSpellButton" + entryIndex
687
+ }
688
+ }
689
+ Label {
690
+ visible: root.manaCount(modelData) === 0
691
+ text: "No spells at this mana value"
692
+ color: Theme.textMuted
693
+ font.pixelSize: Theme.textPixelSize(11)
694
+ leftPadding: 10
695
+ }
696
+ }
697
+ }
698
+ }
699
+ }
700
+ }
701
+ }
702
+
703
+ ColumnLayout {
704
+ id: wideBuildCardColumn
705
+ Layout.fillWidth: true
706
+ Layout.minimumWidth: 240
707
+ Layout.preferredWidth: 300
708
+ Layout.maximumWidth: 360
709
+ Layout.horizontalStretchFactor: 3
710
+ Layout.fillHeight: true
711
+ spacing: Theme.gutter
712
+ CardPreview {
713
+ id: wideBuildCardPreview
714
+ objectName: "wideBuildCardPreview"
715
+ constrainImageFrameToHeight: true
716
+ visible: root.displayPreferences.cardPreview
717
+ Layout.fillWidth: true
718
+ Layout.fillHeight: true
719
+ Layout.minimumHeight: root.wideBuildCardMinimumHeight
720
+ recommendation: root.focusedCard
721
+ imageState: root.sessionState.card_image
722
+ }
723
+ Rectangle {
724
+ objectName: "wideBuildContext"
725
+ visible: root.displayPreferences.detailedBuildContext
726
+ Layout.fillWidth: true
727
+ Layout.preferredHeight: root.wideBuildContextMinimumHeight
728
+ color: Theme.surfaceLow
729
+ border.color: Theme.outline
730
+ border.width: 1
731
+ radius: Theme.radius
732
+ ColumnLayout {
733
+ id: wideReasoning
734
+ anchors.left: parent.left
735
+ anchors.right: parent.right
736
+ anchors.top: parent.top
737
+ anchors.leftMargin: Theme.panelPadding
738
+ anchors.rightMargin: Theme.panelPadding
739
+ anchors.topMargin: Theme.panelPadding
740
+ spacing: 4
741
+ DimensionalButton {
742
+ objectName: "wideBuildContextToggle"
743
+ Layout.fillWidth: true
744
+ text: (root.contextExpanded ? "Hide" : "Show") + " why this pair"
745
+ expanded: root.contextExpanded
746
+ accented: false
747
+ Accessible.name: text
748
+ Accessible.description: root.contextExpanded
749
+ ? "The pair rationale is currently expanded. Activating this button collapses it."
750
+ : "The pair rationale is currently collapsed. Activating this button expands it."
751
+ onClicked: root.contextExpanded = !root.contextExpanded
752
+ }
753
+ ColumnLayout {
754
+ objectName: "wideBuildContextDetails"
755
+ visible: root.contextExpanded
756
+ Layout.fillWidth: true
757
+ spacing: 4
758
+ Label { text: "WHY THIS PAIR"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true; font.letterSpacing: 1 }
759
+ Label {
760
+ Layout.fillWidth: true
761
+ text: root.pairDescription
762
+ color: Theme.text
763
+ wrapMode: Text.WordWrap
764
+ }
765
+ Repeater {
766
+ model: root.build.pair_options
767
+ delegate: Label {
768
+ required property var modelData
769
+ objectName: "widePairOption" + modelData.pair
770
+ Layout.fillWidth: true
771
+ text: modelData.pair + " · score " + modelData.score.toFixed(1)
772
+ + " · " + (modelData.playable_count !== null
773
+ ? modelData.playable_count + " playables"
774
+ : "playables unavailable")
775
+ + (modelData.automatic ? " · automatic" : "")
776
+ color: modelData.selected ? Theme.primary : Theme.textMuted
777
+ wrapMode: Text.WordWrap
778
+ Accessible.name: text
779
+ }
780
+ }
781
+ }
782
+ }
783
+ }
784
+ }
785
+ }
786
+
787
+ Rectangle {
788
+ objectName: "wideBuildBench"
789
+ Layout.fillWidth: true
790
+ Layout.minimumHeight: root.wideBenchHeight
791
+ Layout.preferredHeight: root.wideBenchHeight
792
+ Layout.maximumHeight: Math.max(
793
+ root.wideBenchHeight,
794
+ root.height * 0.45
795
+ )
796
+ color: Theme.surfaceLow
797
+ border.color: Theme.outline
798
+ border.width: 1
799
+ radius: Theme.radius
800
+ clip: true
801
+ ColumnLayout {
802
+ id: wideBenchContent
803
+ anchors.fill: parent
804
+ anchors.margins: 4
805
+ spacing: 4
806
+ DimensionalButton {
807
+ objectName: root.compactPresentation ? "" : "buildBenchToggle"
808
+ Layout.fillWidth: true
809
+ text: (root.benchExpanded ? "Hide" : "Show") + " bench · " + root.benchQuantity()
810
+ expanded: root.benchExpanded
811
+ accented: false
812
+ Accessible.name: "Toggle bench"
813
+ onClicked: root.benchExpanded = !root.benchExpanded
814
+ }
815
+ ScrollView {
816
+ id: wideBenchScroll
817
+ objectName: "wideBuildBenchScroll"
818
+ visible: root.benchExpanded
819
+ Layout.fillWidth: true
820
+ Layout.preferredHeight: Math.min(
821
+ wideBenchRows.implicitHeight,
822
+ root.wideBenchViewportMaximumHeight
823
+ )
824
+ Layout.maximumHeight: root.wideBenchViewportMaximumHeight
825
+ clip: true
826
+ contentWidth: availableWidth
827
+ ScrollBar.vertical.policy: ScrollBar.AsNeeded
828
+ ColumnLayout {
829
+ id: wideBenchRows
830
+ width: wideBenchScroll.availableWidth
831
+ spacing: 5
832
+ Repeater {
833
+ model: root.build.bench
834
+ delegate: BuildCardRow {
835
+ required property var modelData
836
+ required property int index
837
+ cardEntry: modelData
838
+ entryIndex: index
839
+ objectName: root.compactPresentation ? "" : "buildBenchButton" + index
840
+ onActiveFocusChanged: {
841
+ if (!activeFocus)
842
+ return
843
+ const top = mapToItem(wideBenchRows, 0, 0).y
844
+ const bottom = top + height
845
+ const flickable = wideBenchScroll.contentItem
846
+ if (top < flickable.contentY)
847
+ flickable.contentY = top
848
+ else if (bottom > flickable.contentY + wideBenchScroll.height)
849
+ flickable.contentY = bottom - wideBenchScroll.height
850
+ }
851
+ }
852
+ }
853
+ Label {
854
+ visible: root.build.bench.length === 0
855
+ text: "No bench cards"
856
+ color: Theme.textMuted
857
+ }
858
+ }
859
+ }
860
+ }
861
+ }
862
+ }
863
+
864
+ ScrollView {
865
+ id: narrowBuildScroll
866
+ objectName: "narrowBuildScroll"
867
+ visible: root.compactPresentation
868
+ anchors.fill: parent
869
+ clip: true
870
+ contentWidth: availableWidth
871
+ ColumnLayout {
872
+ width: narrowBuildScroll.availableWidth
873
+ spacing: Theme.gutter
874
+
875
+ Rectangle {
876
+ Layout.fillWidth: true
877
+ Layout.preferredHeight: narrowSummary.implicitHeight + Theme.panelPadding * 2
878
+ color: Theme.surfaceLow
879
+ border.color: Theme.outline
880
+ border.width: 1
881
+ radius: Theme.radius
882
+ ColumnLayout {
883
+ id: narrowSummary
884
+ anchors.fill: parent
885
+ anchors.margins: Theme.panelPadding
886
+ spacing: 6
887
+ RowLayout {
888
+ Layout.fillWidth: true
889
+ Label { text: root.build.deck_size; color: Theme.primary; font.pixelSize: Theme.textPixelSize(22); font.bold: true }
890
+ Label { text: "/ 40 CARDS"; color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10); font.bold: true }
891
+ Item { Layout.fillWidth: true }
892
+ Label {
893
+ text: root.countText(root.build.spell_count) + " spells · "
894
+ + root.countText(root.build.land_count) + " lands"
895
+ color: Theme.textMuted
896
+ font.pixelSize: Theme.textPixelSize(11)
897
+ }
898
+ }
899
+ Label {
900
+ objectName: "narrowBuildTypeSummary"
901
+ Layout.fillWidth: true
902
+ text: root.countLabel(root.build.creature_count, "creature") + " · "
903
+ + root.countLabel(root.build.instant_count, "instant")
904
+ color: Theme.textMuted
905
+ font.pixelSize: Theme.textPixelSize(11)
906
+ }
907
+ Label {
908
+ text: root.build.selected_pair + " · " + (root.build.pair_override ? "override" : "automatic")
909
+ color: root.build.pair_override ? Theme.warning : Theme.primary
910
+ font.bold: true
911
+ Accessible.name: "Selected pair " + text
912
+ }
913
+ Label {
914
+ text: root.build.deck_size === 40 ? "DECK SIZE READY" : "DECK SIZE CHECK"
915
+ color: root.build.deck_size === 40 ? Theme.primary : Theme.warning
916
+ font.pixelSize: Theme.textPixelSize(10)
917
+ font.bold: true
918
+ font.letterSpacing: 1
919
+ }
920
+ }
921
+ }
922
+
923
+ Rectangle {
924
+ visible: root.build.warnings.length > 0
925
+ Layout.fillWidth: true
926
+ Layout.preferredHeight: narrowWarnings.implicitHeight + Theme.panelPadding * 2
927
+ color: Theme.warningDark
928
+ border.color: Theme.warning
929
+ border.width: 1
930
+ radius: Theme.radius
931
+ Label {
932
+ id: narrowWarnings
933
+ anchors.fill: parent
934
+ anchors.margins: Theme.panelPadding
935
+ text: root.build.warnings.join("\n")
936
+ color: Theme.warning
937
+ wrapMode: Text.WordWrap
938
+ Accessible.name: "Build warnings: " + text
939
+ }
940
+ }
941
+
942
+ ManaCurve { objectName: "narrowBuildManaCurve" }
943
+
944
+ DimensionalButton {
945
+ id: cardDetailsToggle
946
+ objectName: "buildCardDetailsToggle"
947
+ visible: root.displayPreferences.cardPreview && root.focusedCard
948
+ Layout.fillWidth: true
949
+ text: (root.cardDetailsExpanded ? "Hide" : "Show") + " selected card details"
950
+ expanded: root.cardDetailsExpanded
951
+ accented: false
952
+ Accessible.name: "Toggle selected card details"
953
+ onClicked: {
954
+ root.cardDetailsExpanded = !root.cardDetailsExpanded
955
+ if (!root.cardDetailsExpanded)
956
+ Qt.callLater(root.focusCardDetailsToggle)
957
+ }
958
+ }
959
+ CardPreview {
960
+ objectName: "narrowBuildCardPreview"
961
+ visible: root.displayPreferences.cardPreview && root.cardDetailsExpanded
962
+ Layout.fillWidth: true
963
+ Layout.preferredHeight: 250
964
+ recommendation: root.focusedCard
965
+ imageState: root.sessionState.card_image
966
+ }
967
+
968
+ Rectangle {
969
+ Layout.fillWidth: true
970
+ Layout.preferredHeight: spellsContent.implicitHeight + Theme.panelPadding * 2
971
+ color: Theme.surfaceLow
972
+ border.color: Theme.outline
973
+ border.width: 1
974
+ radius: Theme.radius
975
+ ColumnLayout {
976
+ id: spellsContent
977
+ anchors.fill: parent
978
+ anchors.margins: Theme.panelPadding
979
+ spacing: 5
980
+ DimensionalButton {
981
+ objectName: "buildSpellsToggle"
982
+ Layout.fillWidth: true
983
+ text: (root.spellsExpanded ? "Hide" : "Show") + " spells · " + (root.build.spell_count !== null ? root.build.spell_count : root.build.spells.length)
984
+ expanded: root.spellsExpanded
985
+ accented: false
986
+ Accessible.name: "Toggle deck spells"
987
+ onClicked: root.spellsExpanded = !root.spellsExpanded
988
+ }
989
+ Repeater {
990
+ visible: root.spellsExpanded
991
+ model: [0, 2, 3, 4, 5, 6]
992
+ delegate: ColumnLayout {
993
+ required property int modelData
994
+ readonly property int bucket: modelData
995
+ Layout.fillWidth: true
996
+ spacing: 3
997
+ Label {
998
+ text: root.manaBucketLabel(modelData) + " · " + root.manaCount(modelData)
999
+ color: Theme.textMuted
1000
+ font.pixelSize: Theme.textPixelSize(10)
1001
+ font.bold: true
1002
+ font.letterSpacing: 1
1003
+ Accessible.name: text
1004
+ }
1005
+ Repeater {
1006
+ model: root.spellsInBucket(bucket)
1007
+ delegate: BuildCardRow {
1008
+ required property var modelData
1009
+ cardEntry: modelData.cardEntry
1010
+ entryIndex: modelData.entryIndex
1011
+ objectName: root.compactPresentation ? "buildSpellButton" + entryIndex : ""
1012
+ }
1013
+ }
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+
1019
+ Rectangle {
1020
+ Layout.fillWidth: true
1021
+ Layout.preferredHeight: landsContent.implicitHeight + Theme.panelPadding * 2
1022
+ color: Theme.surfaceLow
1023
+ border.color: Theme.outline
1024
+ border.width: 1
1025
+ radius: Theme.radius
1026
+ ColumnLayout {
1027
+ id: landsContent
1028
+ anchors.fill: parent
1029
+ anchors.margins: Theme.panelPadding
1030
+ spacing: 5
1031
+ DimensionalButton {
1032
+ objectName: "buildLandsToggle"
1033
+ Layout.fillWidth: true
1034
+ text: (root.landsExpanded ? "Hide" : "Show") + " lands · " + root.build.land_count
1035
+ expanded: root.landsExpanded
1036
+ accented: false
1037
+ Accessible.name: "Toggle lands"
1038
+ onClicked: root.landsExpanded = !root.landsExpanded
1039
+ }
1040
+ Repeater {
1041
+ visible: root.landsExpanded
1042
+ model: root.build.lands
1043
+ delegate: RowLayout {
1044
+ required property var modelData
1045
+ Layout.fillWidth: true
1046
+ ManaPips { colors: modelData.source_colors }
1047
+ Label { Layout.fillWidth: true; text: "×" + modelData.quantity + " " + modelData.name; color: Theme.text; wrapMode: Text.WordWrap }
1048
+ Label { text: root.landSummary(modelData); color: Theme.textMuted; font.pixelSize: Theme.textPixelSize(10) }
1049
+ }
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ Rectangle {
1055
+ objectName: "narrowBuildBench"
1056
+ Layout.fillWidth: true
1057
+ Layout.preferredHeight: benchContent.implicitHeight + Theme.panelPadding * 2
1058
+ color: Theme.surfaceLow
1059
+ border.color: Theme.outline
1060
+ border.width: 1
1061
+ radius: Theme.radius
1062
+ ColumnLayout {
1063
+ id: benchContent
1064
+ anchors.fill: parent
1065
+ anchors.margins: Theme.panelPadding
1066
+ spacing: 5
1067
+ DimensionalButton {
1068
+ objectName: root.compactPresentation ? "buildBenchToggle" : ""
1069
+ Layout.fillWidth: true
1070
+ text: (root.benchExpanded ? "Hide" : "Show") + " bench · " + root.benchQuantity()
1071
+ expanded: root.benchExpanded
1072
+ accented: false
1073
+ Accessible.name: "Toggle bench"
1074
+ onClicked: root.benchExpanded = !root.benchExpanded
1075
+ }
1076
+ Repeater {
1077
+ visible: root.benchExpanded
1078
+ model: root.build.bench
1079
+ delegate: BuildCardRow {
1080
+ visible: root.benchExpanded
1081
+ required property var modelData
1082
+ required property int index
1083
+ cardEntry: modelData
1084
+ entryIndex: index
1085
+ objectName: root.compactPresentation ? "buildBenchButton" + index : ""
1086
+ }
1087
+ }
1088
+ }
1089
+ }
1090
+
1091
+ Rectangle {
1092
+ objectName: "narrowBuildContext"
1093
+ visible: root.displayPreferences.detailedBuildContext
1094
+ Layout.fillWidth: true
1095
+ Layout.preferredHeight: contextContent.implicitHeight + Theme.panelPadding * 2
1096
+ color: Theme.surfaceLow
1097
+ border.color: Theme.outline
1098
+ border.width: 1
1099
+ radius: Theme.radius
1100
+ ColumnLayout {
1101
+ id: contextContent
1102
+ anchors.fill: parent
1103
+ anchors.margins: Theme.panelPadding
1104
+ spacing: 6
1105
+ DimensionalButton {
1106
+ objectName: "narrowBuildContextToggle"
1107
+ Layout.fillWidth: true
1108
+ text: (root.contextExpanded ? "Hide" : "Show") + " why this pair"
1109
+ expanded: root.contextExpanded
1110
+ accented: false
1111
+ Accessible.name: text
1112
+ Accessible.description: root.contextExpanded
1113
+ ? "The pair rationale is currently expanded. Activating this button collapses it."
1114
+ : "The pair rationale is currently collapsed. Activating this button expands it."
1115
+ onClicked: root.contextExpanded = !root.contextExpanded
1116
+ }
1117
+ ColumnLayout {
1118
+ objectName: "narrowBuildContextDetails"
1119
+ visible: root.contextExpanded
1120
+ Layout.fillWidth: true
1121
+ spacing: 6
1122
+ Label {
1123
+ Layout.fillWidth: true
1124
+ text: root.pairDescription
1125
+ color: Theme.text
1126
+ wrapMode: Text.WordWrap
1127
+ }
1128
+ Repeater {
1129
+ model: root.build.pair_options
1130
+ delegate: Label {
1131
+ required property var modelData
1132
+ objectName: "narrowPairOption" + modelData.pair
1133
+ Layout.fillWidth: true
1134
+ text: modelData.pair + " · score " + modelData.score.toFixed(1)
1135
+ + " · " + (modelData.playable_count !== null
1136
+ ? modelData.playable_count + " playables"
1137
+ : "playables unavailable")
1138
+ + (modelData.automatic ? " · automatic" : "")
1139
+ color: modelData.selected ? Theme.primary : Theme.textMuted
1140
+ wrapMode: Text.WordWrap
1141
+ Accessible.name: text
1142
+ }
1143
+ }
1144
+ }
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ }