|
26 | 26 | #include <QVector3D> |
27 | 27 | #include <QColor> |
28 | 28 |
|
| 29 | +#include <climits> |
| 30 | + |
29 | 31 | #include "vcpage.h" |
30 | 32 |
|
31 | 33 | class Doc; |
@@ -459,26 +461,112 @@ private slots: |
459 | 461 | QString name; ///< Profile channel name (for logging/preview) |
460 | 462 | }; |
461 | 463 |
|
462 | | - /** Pools of still-unassigned controller channels, refilled by |
463 | | - * buildControllerPools() at the start of a mapping run. */ |
464 | | - struct CtrlPools |
| 464 | + /** The role a VC widget plays, which decides WHERE on the controller it |
| 465 | + * lands. On a grid surface each role owns a horizontal band of rows, so a |
| 466 | + * given kind of control always sits in the same place no matter which page |
| 467 | + * is showing — that spatial stability is the whole point of mapping to a |
| 468 | + * grid rather than handing out channels in creation order. */ |
| 469 | + enum CtrlRole |
465 | 470 | { |
466 | | - QList<CtrlChannel> buttons; ///< Button-type channels |
467 | | - QList<CtrlChannel> faders; ///< Slider/Knob/Encoder-type channels |
468 | | - int buttonTotal = 0; ///< Pool sizes before any hand-out |
469 | | - int faderTotal = 0; |
470 | | - int nextButton = 0; ///< Hand-out cursors |
471 | | - int nextFader = 0; |
| 471 | + CtrlRolePageTab, ///< Frame page selector (shared across pages) |
| 472 | + CtrlRoleColor, ///< Colour / colour-wheel swatch |
| 473 | + CtrlRoleEffect, ///< Movement, shutter, strobe, blinder … |
| 474 | + CtrlRoleShowCue, ///< Blackout / Open / Big Moment … (shared) |
| 475 | + CtrlRoleCount |
| 476 | + }; |
| 477 | + |
| 478 | + /** How the wizard understood the patched controller. */ |
| 479 | + enum CtrlSurfaceKind |
| 480 | + { |
| 481 | + CtrlSurfaceNone, ///< Nothing patched / nothing usable |
| 482 | + CtrlSurfaceGrid, ///< A rows x cols pad matrix was recognised |
| 483 | + CtrlSurfaceStrip, ///< Mixer-style: faders only, buttons left alone |
| 484 | + CtrlSurfaceLinear ///< Unknown layout: hand out buttons in order |
472 | 485 | }; |
473 | 486 |
|
474 | | - /** Fill $pools from the input profile patched on m_ctrlUniverse. With no |
475 | | - * profile assigned, falls back to a synthetic layout (channels 0..7 faders, |
476 | | - * 8.. buttons) so a bare MIDI/OSC patch still gets a usable mapping. */ |
477 | | - void buildControllerPools(CtrlPools &pools) const; |
| 487 | + /** A recognised pad grid. $cells is row-major and normalised so that row 0 |
| 488 | + * is always the PHYSICAL TOP row: profiles disagree on whether "row 1" is |
| 489 | + * the top or the bottom (APC mini numbers from the bottom, Launchpad Mini |
| 490 | + * MK3 from the top), so buildControllerSurface() flips when needed. A cell |
| 491 | + * with channel == invalidChannel is a hole in the matrix. */ |
| 492 | + struct CtrlGrid |
| 493 | + { |
| 494 | + int rows = 0; |
| 495 | + int cols = 0; |
| 496 | + QList<CtrlChannel> cells; ///< rows * cols, row-major, top row first |
| 497 | + |
| 498 | + static const quint32 invalidChannel = UINT_MAX; |
| 499 | + |
| 500 | + bool isValid() const { return rows > 0 && cols > 0; } |
| 501 | + const CtrlChannel *at(int r, int c) const |
| 502 | + { |
| 503 | + if (r < 0 || c < 0 || r >= rows || c >= cols) |
| 504 | + return nullptr; |
| 505 | + const CtrlChannel &cc = cells.at(r * cols + c); |
| 506 | + return cc.channel == invalidChannel ? nullptr : &cc; |
| 507 | + } |
| 508 | + }; |
| 509 | + |
| 510 | + /** A contiguous band of grid rows reserved for one role, plus the cursor |
| 511 | + * tracking how far into it we have allocated. */ |
| 512 | + struct CtrlBand |
| 513 | + { |
| 514 | + int firstRow = 0; |
| 515 | + int rowCount = 0; |
| 516 | + int nextCell = 0; ///< Running index within the band, row-major |
| 517 | + }; |
| 518 | + |
| 519 | + /** Everything the mapper knows about the patched controller: the grid (when |
| 520 | + * one was recognised), the per-role bands, and the leftover pools. */ |
| 521 | + struct CtrlSurface |
| 522 | + { |
| 523 | + CtrlSurfaceKind kind = CtrlSurfaceNone; |
| 524 | + |
| 525 | + CtrlGrid grid; |
| 526 | + CtrlBand bands[CtrlRoleCount]; |
| 527 | + |
| 528 | + /// Buttons that are NOT grid cells (Shift, Scene launch, arrows, …). |
| 529 | + /// Used as overflow once a role's band is full. |
| 530 | + QList<CtrlChannel> aux; |
| 531 | + int nextAux = 0; |
| 532 | + |
| 533 | + /// Buttons in plain creation order — CtrlSurfaceLinear only. |
| 534 | + QList<CtrlChannel> buttons; |
| 535 | + int nextButton = 0; |
| 536 | + |
| 537 | + QList<CtrlChannel> faders; |
| 538 | + int nextFader = 0; |
| 539 | + |
| 540 | + int buttonTotal = 0; ///< Mappable counts, for the step 5 preview |
| 541 | + int faderTotal = 0; |
| 542 | + }; |
478 | 543 |
|
479 | | - /** Take the next free button/fader channel. Returns false when exhausted. */ |
480 | | - bool nextButtonChannel(CtrlPools &pools, quint32 &channel) const; |
481 | | - bool nextFaderChannel(CtrlPools &pools, quint32 &channel) const; |
| 544 | + /** Inspect the profile patched on m_ctrlUniverse and decide how to lay the |
| 545 | + * VC out on it: recognise a pad grid from the channel names, split the |
| 546 | + * non-grid buttons into the aux pool, and reserve a row band per role. |
| 547 | + * With no profile, falls back to a synthetic linear layout so a bare |
| 548 | + * MIDI/OSC patch still gets a usable mapping. */ |
| 549 | + void buildControllerSurface(CtrlSurface &surface) const; |
| 550 | + |
| 551 | + /** Parse $profile's button names into a normalised grid. Returns false when |
| 552 | + * no grid convention is recognised; $aux collects the buttons left over. */ |
| 553 | + static bool parseControllerGrid(const QLCInputProfile *profile, |
| 554 | + CtrlGrid &grid, QList<CtrlChannel> &aux); |
| 555 | + |
| 556 | + /** Reserve a band of rows per role, sized for the layout the VC generator |
| 557 | + * is about to build ($pageCount pages, $colorRows rows of swatches). */ |
| 558 | + static void assignControllerBands(CtrlSurface &surface, int pageCount, |
| 559 | + int colorRows); |
| 560 | + |
| 561 | + /** Take the next channel for $role. On a grid this walks the role's band |
| 562 | + * left-to-right / top-to-bottom and spills into the aux pool when full; |
| 563 | + * otherwise it just pops the linear button pool. Returns false when |
| 564 | + * everything is exhausted. */ |
| 565 | + bool nextRoleChannel(CtrlSurface &surface, CtrlRole role, |
| 566 | + quint32 &channel) const; |
| 567 | + |
| 568 | + /** Take the next free fader channel. Returns false when exhausted. */ |
| 569 | + bool nextFaderChannel(CtrlSurface &surface, quint32 &channel) const; |
482 | 570 |
|
483 | 571 | /** Attach a controller input source to $widget's control $controlID, and |
484 | 572 | * apply feedback / colour feedback when enabled. $color is the widget's |
|
0 commit comments