Cut shell/panel comment bloat ~47% (comments only, zero code change)
This commit is contained in:
+90
-173
@@ -1,13 +1,9 @@
|
||||
// panel_input.cpp — the input + detection seam of the docked bank panel (Q-W2 split
|
||||
// of bank_panel.cpp). Owns left-click / wheel / keyboard routing (plain free-function
|
||||
// calls on the per-event path — T4-28), the accelerator registration, the tail-setting
|
||||
// read/mutate helpers, and the timer-driven new-content auto-tag detection (D2 Wave 2).
|
||||
// Mouse-MOVE (hover + the card-drag state machine) lives in panel_drag; the bank-change
|
||||
// panel_input.cpp — the input + detection seam of the docked bank panel: left-click /
|
||||
// wheel / keyboard routing, accelerator registration, tail-setting read/mutate helpers,
|
||||
// and timer-driven new-content auto-tag detection. Mouse-MOVE lives in panel_drag; the
|
||||
// fingerprint pass lives in panel_thumbnails (it owns the cache it invalidates).
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
|
||||
// extern (CLAUDE.md §contract). DAW-verified, not unit tested.
|
||||
// Compiled into the reaper_reasampler MODULE, without REAPERAPI_IMPLEMENT (main.cpp
|
||||
// owns the API pointers). DAW-verified, not unit-tested.
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
@@ -17,15 +13,15 @@
|
||||
#include "shell/panel/panel_state.h"
|
||||
#include "shell/panel/panel_input.h"
|
||||
|
||||
#include "shell/actions/bank_actions.h" // bankPruneCommandId — the footer Prune dispatch (R3)
|
||||
#include "shell/actions/bank_actions.h" // bankPruneCommandId — the footer Prune dispatch
|
||||
#include "shell/persist/session.h" // ReaSamplerSession — view/tail reads + mutation
|
||||
#include "core/view/view_mode_model.h" // autoTagNewContent / NewItem / AutoTag (D2 Wave 2)
|
||||
#include "shell/capture/item_read.h" // itemGuid / itemLaneName — shared item-read seam (D2 W3-B)
|
||||
#include "shell/capture/track_guid.h" // guidString — canonical track GUID key (D2 Wave 2)
|
||||
#include "shell/view/view.h" // applyMode / mintManagedLanes — mode activation (D2/D4)
|
||||
#include "core/view/view_mode_model.h" // autoTagNewContent / NewItem / AutoTag
|
||||
#include "shell/capture/item_read.h" // itemGuid / itemLaneName — shared item-read seam
|
||||
#include "shell/capture/track_guid.h" // guidString — canonical track GUID key
|
||||
#include "shell/view/view.h" // applyMode / mintManagedLanes — mode activation
|
||||
|
||||
// New-content detection (D2 Wave 2): enumerate live tracks + items and read fixed-lane
|
||||
// state to classify an item's lane as managed vs manual.
|
||||
// New-content detection: enumerate live tracks + items and read fixed-lane state to
|
||||
// classify an item's lane as managed vs manual.
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_CountTracks
|
||||
#define REAPERAPI_WANT_GetTrack
|
||||
@@ -50,23 +46,17 @@ TailSetting currentTail() {
|
||||
|
||||
namespace {
|
||||
|
||||
// Commits the current tail setting to ext state and marks the active project dirty
|
||||
// so the change travels inside the .rpp on Ctrl+S. saveToActiveProject() is the only
|
||||
// path that calls SetProjExtState for the tail key — calling it here closes the gap
|
||||
// where toggle/scroll would dirty the project but the new value was never written.
|
||||
// On an unsaved project saveToActiveProject() no-ops cleanly (documented in persist.h).
|
||||
// MarkProjectDirty runs unconditionally so REAPER knows a save is owed either way.
|
||||
// NON-DESTRUCTIVE: touches nothing in the bank/arrange.
|
||||
// Commits the current tail setting to ext state and marks the active project dirty so the
|
||||
// change travels inside the .rpp on Ctrl+S — closes the gap where toggle/scroll would dirty
|
||||
// the project but never write the new value. No-ops cleanly on an unsaved project.
|
||||
void markTailDirty() {
|
||||
if (g_panel.session) g_panel.session->saveToActiveProject();
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
if (proj) MarkProjectDirty(proj);
|
||||
}
|
||||
|
||||
// Routes a click in a toolbar to the hit button's action, fired through the command-id contract
|
||||
// (Main_OnCommand — REAPER runs the SAME action a keybinding would). Returns true iff the click
|
||||
// was inside the bar band (handled, or a harmless gap/overflow/unregistered no-op), so the
|
||||
// caller stops before grid handling. `rows` is the toolbar's inventory.
|
||||
// Routes a click in a toolbar to the hit button's action via Main_OnCommand. Returns true
|
||||
// iff the click was inside the bar band, so the caller stops before grid handling.
|
||||
bool handleToolbarClick(int x, int y, const ActionBarRect& bar,
|
||||
const std::vector<ActionBarRow>& rows) {
|
||||
if (bar.height <= 0) return false;
|
||||
@@ -78,46 +68,26 @@ bool handleToolbarClick(int x, int y, const ActionBarRect& bar,
|
||||
x >= bar.x && x < bar.x + bar.width;
|
||||
}
|
||||
const ActionBarRow& row = rows[static_cast<std::size_t>(hit)];
|
||||
// A disabled button (L5 opposite-mode gate) is claimed but no-ops — the click never fires the
|
||||
// action and never falls through to the grid (a dead button reads as inert, not absent).
|
||||
// A disabled button (opposite-mode gate) is claimed but no-ops — the click never fires
|
||||
// the action and never falls through to the grid (a dead button reads as inert, not absent).
|
||||
if (!row.enabled) return true;
|
||||
const int cmd = resolveBarCommandId(row);
|
||||
if (cmd != 0 && Main_OnCommand) Main_OnCommand(cmd, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- New-content detection (D2 Wave 2) ----------------------------------------
|
||||
//
|
||||
// REAPER exposes no "item/track added" callback, so we diff live project state on the
|
||||
// existing timer. Each tick: enumerate every track GUID and every item GUID, diff
|
||||
// against the previous tick (GuidBaseline, first-poll-guarded), and auto-tag the new
|
||||
// GUIDs into the active mode via the pure autoTagNewContent. An item on a MANUAL lane
|
||||
// is exempt (design point #1) — its lane's durable name lacks the managed prefix. All
|
||||
// enumeration is READ-ONLY on the project; the only mutation is to the in-memory
|
||||
// membership index (persisted by persist on the next save, same as an action-driven tag).
|
||||
|
||||
// True iff `tr` has I_FREEMODE==2 (fixed lanes enabled). The SDK value is verified
|
||||
// in view.cpp (kFreeModeFixedLanes=2); reproduced here as a local constant so
|
||||
// panel_input.cpp stays self-contained without pulling in view.cpp's private namespace.
|
||||
// True iff `tr` has I_FREEMODE==2 (fixed lanes enabled). Value verified in view.cpp;
|
||||
// reproduced locally so this file stays self-contained.
|
||||
constexpr int kFreeModeFixedLanes = 2;
|
||||
|
||||
bool isFixedLaneTrack(MediaTrack* tr) {
|
||||
return static_cast<int>(GetMediaTrackInfo_Value(tr, "I_FREEMODE")) == kFreeModeFixedLanes;
|
||||
}
|
||||
|
||||
// Item GUID + fixed-lane name reads come from the shared item_read seam (item_read.h):
|
||||
// itemGuid(it) and itemLaneName(tr, it). panel_input.cpp no longer carries its own copies.
|
||||
|
||||
// Enumerates the live project's track + item GUIDs. Fills `allGuids` (the full live set,
|
||||
// baseline input) and, for each item, records whether it sits on a manual lane so a
|
||||
// newly-detected item can be exempted from auto-tag without a second project walk.
|
||||
// `trackItemGuids` additionally maps each track GUID to the item GUIDs it carries, so a
|
||||
// newly-detected item's PRE-EXISTING siblings can be resolved (the adoption / strand
|
||||
// guard) without a second project walk.
|
||||
//
|
||||
// Manual-lane classification uses the single pure predicate isOnManualLane(isFixedLaneTrack,
|
||||
// laneName) from lane_keys — the same predicate the apply path consults — so the exemption
|
||||
// rule is defined in exactly one place and is unit-tested there.
|
||||
// Enumerates the live project's track + item GUIDs. Fills `allGuids` and, per item,
|
||||
// whether it sits on a manual lane (exempt from auto-tag). `trackItemGuids` maps each
|
||||
// track to its item GUIDs so a newly-detected item's PRE-EXISTING siblings resolve in
|
||||
// one lookup. Manual-lane classification uses the single pure predicate isOnManualLane.
|
||||
void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
||||
std::map<std::string, bool>& itemOnManualLane,
|
||||
std::map<std::string, std::vector<std::string>>& trackItemGuids) {
|
||||
@@ -140,9 +110,8 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
||||
std::string ig = itemGuid(it);
|
||||
if (ig.empty()) continue;
|
||||
allGuids.insert(ig);
|
||||
// Classify via the single shared predicate. For a fixed-lane track we read
|
||||
// the item's lane name; for a normal track we pass "" (isOnManualLane returns
|
||||
// false immediately for non-fixed-lane tracks regardless of name).
|
||||
// "" for a normal track — isOnManualLane returns false immediately for a
|
||||
// non-fixed-lane track regardless of name.
|
||||
const std::string ln = fixedLane ? itemLaneName(tr, it) : std::string{};
|
||||
itemOnManualLane[ig] = isOnManualLane(fixedLane, ln);
|
||||
itemsOnTrack.push_back(ig);
|
||||
@@ -150,35 +119,26 @@ void enumerateLiveGuids(ReaProject* proj, std::set<std::string>& allGuids,
|
||||
}
|
||||
}
|
||||
|
||||
// One detection tick: diff live GUIDs against the baseline and auto-tag the new ones
|
||||
// into the active mode. Runs every timer tick regardless of panel open/close (content
|
||||
// is created in the arrange). READ-ONLY on the project; mutates only the in-memory
|
||||
// membership index.
|
||||
// One detection tick: REAPER exposes no "item/track added" callback, so this diffs live
|
||||
// GUIDs against the baseline and auto-tags the new ones into the active mode. Runs every
|
||||
// timer tick regardless of panel open/close. READ-ONLY on the project; mutates only the
|
||||
// in-memory membership index — deliberately OUTSIDE any Undo block (auto-tag is a
|
||||
// background metadata update, not a destructive edit; an Undo block here would flood
|
||||
// REAPER's history with an entry per tick that sees new content).
|
||||
//
|
||||
// INTENTIONAL: membership mutation happens OUTSIDE any Undo block. Auto-tag is a
|
||||
// background metadata update (like setting a label), not a destructive project edit. The
|
||||
// persist shell (ext_state_io.cpp) writes it on the next project save alongside the bank
|
||||
// and view state, the same way an action-driven tag is persisted. Wrapping this in an Undo
|
||||
// block would flood the REAPER undo history with a new entry for every timer tick that sees
|
||||
// new content.
|
||||
// Returns true iff this tick tagged at least one new GUID into a mode — the signal the
|
||||
// caller uses to decide whether to run the lane-minting pass (a track can only newly
|
||||
// become multi-mode when auto-tag just placed content on it). No tag ⇒ nothing to mint.
|
||||
// Returns true iff this tick tagged at least one new GUID — the signal the caller uses to
|
||||
// decide whether to run the lane-minting pass.
|
||||
bool detectNewContent() {
|
||||
if (!g_panel.session) return false;
|
||||
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
|
||||
// A project (re)load re-arms the first-poll guard so we never diff across two
|
||||
// projects. The signal is persist's — main.cpp calls bankPanelNotifyProjectLoaded()
|
||||
// on the tick persist restores the project's membership + active mode, which sets
|
||||
// reloadPending. Draining it here re-baselines against the fully-loaded set (that
|
||||
// same tick's reapply-active-mode enumerated those tracks, so they are present),
|
||||
// and the observe() below returns nothing new — pre-existing untagged tracks stay
|
||||
// Arrange. GuidBaseline self-arms on its first observe() for the very first tick, so
|
||||
// no separate first-tick handling is needed here. Using persist's GUID-primary load
|
||||
// signal (not a local pointer compare) is what fixes the reload-mis-tag: the two
|
||||
// identity checks can no longer diverge on a recycled ReaProject* address.
|
||||
// projects. bankPanelNotifyProjectLoaded() sets reloadPending on the tick persist
|
||||
// restores membership + active mode; draining it here re-baselines against the
|
||||
// fully-loaded set, so pre-existing untagged tracks stay Arrange rather than getting
|
||||
// mass-tagged. Using persist's load signal (not a local ReaProject* compare) is what
|
||||
// fixes the reload-mis-tag bug: pointer identity can recycle across projects.
|
||||
if (g_panel.reloadPending) {
|
||||
g_panel.contentBaseline.reset();
|
||||
g_panel.reloadPending = false;
|
||||
@@ -210,12 +170,9 @@ bool detectNewContent() {
|
||||
for (const auto& [trackGuid, items] : trackItemGuids)
|
||||
for (const std::string& ig : items) trackOfItem[ig] = trackGuid;
|
||||
|
||||
// The distinct modes the PRE-EXISTING (not-new-this-tick) MANAGED-ELIGIBLE items on
|
||||
// `trackGuid` resolve to. Untagged siblings resolve to Arrange (leafBelongsToMode's
|
||||
// default); new siblings are excluded; manual-lane siblings are EXEMPT — exactly as
|
||||
// planLaneMinting ignores them when computing a track's own-item mode span, so the
|
||||
// adoption guard's view of the track matches the split decision's. Drives the adoption
|
||||
// / strand guard in autoTagNewContent.
|
||||
// The distinct modes the PRE-EXISTING, managed-eligible items on `trackGuid` resolve
|
||||
// to. Untagged siblings default to Arrange; new siblings excluded; manual-lane
|
||||
// siblings EXEMPT — matching planLaneMinting's own-item mode span computation.
|
||||
const auto preExistingTrackModes =
|
||||
[&](const std::string& trackGuid) -> std::set<std::string> {
|
||||
std::set<std::string> modes;
|
||||
@@ -233,8 +190,8 @@ bool detectNewContent() {
|
||||
};
|
||||
|
||||
// Split the new GUIDs into tracks vs items so the pure decision can apply the
|
||||
// manual-lane exemption to items only. A GUID present in the item-lane map is an
|
||||
// item; otherwise it is a track (track GUIDs never appear in that map).
|
||||
// manual-lane exemption to items only. A GUID in the item-lane map is an item;
|
||||
// otherwise it's a track.
|
||||
std::vector<std::string> newTracks;
|
||||
std::vector<NewItem> newItems;
|
||||
for (const std::string& g : added) {
|
||||
@@ -256,28 +213,21 @@ bool detectNewContent() {
|
||||
return !tags.empty();
|
||||
}
|
||||
|
||||
// The item count the SELECTION reasons over — the focused region's occupied-cell count.
|
||||
// L7: selection/navigation traverse OCCUPIED cells only (empty slots are gaps, not
|
||||
// selectable). Occupied count == index size by construction: every index member maps to
|
||||
// exactly one occupied slot (gaps are empty slots, which the index never backs), so the
|
||||
// raw index size IS the dense selection-space extent.
|
||||
// The item count the SELECTION reasons over. Occupied count == index size by
|
||||
// construction, so the raw index size IS the dense selection-space extent.
|
||||
int focusedItemCount() {
|
||||
const BankModel* idx = indexForRegion(g_panel.focusedRegion);
|
||||
return idx ? static_cast<int>(idx->size()) : 0;
|
||||
}
|
||||
|
||||
// --- Click routing ------------------------------------------------------------
|
||||
|
||||
// Handles a header/tab-strip/button click for the banks region. Returns true if the
|
||||
// click was consumed (a region-chrome hit), false to fall through to grid selection.
|
||||
bool handleBanksChromeClick(int x, int y, const RECT& region) {
|
||||
// Full-height toggle button.
|
||||
const RECT ftb = fullHtBtnRect(region);
|
||||
if (x >= ftb.left && x < ftb.right && y >= ftb.top && y < ftb.bottom) {
|
||||
bankPanelToggledBanksFullHeight();
|
||||
return true;
|
||||
}
|
||||
// "+" create button.
|
||||
const RECT cb = createBtnRect(region);
|
||||
if (x >= cb.left && x < cb.right && y >= cb.top && y < cb.bottom) {
|
||||
doCreateBank();
|
||||
@@ -326,16 +276,15 @@ bool handlePoolChromeClick(int x, int y, const RECT& region) {
|
||||
|
||||
// Applies a left-click at (x, y): route to top toolbar / footer (toggle / Tail / Prune) /
|
||||
// bottom toolbar / region chrome / grid selection, and arm a potential drag when the click
|
||||
// lands on a selected cell. L4 order mirrors the three-zone layout top-to-bottom.
|
||||
// lands on a selected cell. Order mirrors the three-zone layout top-to-bottom.
|
||||
void handleClick(int x, int y) {
|
||||
RECT cr{};
|
||||
GetClientRect(g_panel.hwnd, &cr);
|
||||
const int w = cr.right - cr.left, h = cr.bottom - cr.top;
|
||||
|
||||
// TOP toolbar: the far-right More button first (its rect sits in the band's reserved right
|
||||
// strip, outside the action rect), then the frequent capture/placement buttons. A button
|
||||
// fires its registered action via the command-id contract; the band is claimed whole (a
|
||||
// gap/overflow miss is a harmless no-op, never a fall-through). Capture never auto-inserts.
|
||||
// TOP toolbar: the far-right More button first, then the frequent capture/placement
|
||||
// buttons. A button fires its registered action via the command-id contract; the band
|
||||
// is claimed whole (a gap/overflow miss is a harmless no-op, never a fall-through).
|
||||
{
|
||||
const MenuButtonRect mb = topMenuButtonRect(w);
|
||||
if (hitTestMenuButton(x, y, mb)) { showMoreMenu(); return; }
|
||||
@@ -345,10 +294,8 @@ void handleClick(int x, int y) {
|
||||
// the More button) so a click there is inert chrome, never a fall-through to the grid.
|
||||
if (y >= 0 && y < kTopToolbarHeight && x >= 0 && x < w) return;
|
||||
|
||||
// Footer: mode toggle (left) -> Tail button -> Prune (right). The narrow [Arrange|Design]
|
||||
// toggle activates that mode; the Tail button cycles the tail setting (L4 §4 — was a
|
||||
// click-zone); Prune fires the guarded prune command. Checked before the bottom toolbar /
|
||||
// grid so a footer click never selects a cell.
|
||||
// Footer: mode toggle (left) -> Tail button -> Prune (right). Checked before the bottom
|
||||
// toolbar / grid so a footer click never selects a cell.
|
||||
{
|
||||
const int seg = footerToggleSegmentHit(x, y, w, h);
|
||||
if (seg >= 0) {
|
||||
@@ -363,9 +310,7 @@ void handleClick(int x, int y) {
|
||||
|
||||
const FooterBarLayout fb = footerBarLayoutFor(w, h);
|
||||
if (g_panel.session && hitTestFooterBar(x, y, fb) == FooterHit::Tail) {
|
||||
// Tail button click cycles the tail mode (None -> Auto -> Manual -> None). Mutates
|
||||
// the SESSION's tail setting (capture reads it; persist saves it with the project)
|
||||
// and marks the project dirty — touches NOTHING in the bank/arrange.
|
||||
// Cycles None -> Auto -> Manual -> None; touches NOTHING in the bank/arrange.
|
||||
TailSetting& tail = g_panel.session->tail();
|
||||
tail.mode = cycleTailMode(tail.mode);
|
||||
markTailDirty();
|
||||
@@ -373,10 +318,9 @@ void handleClick(int x, int y) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Prune button (R3): fires the "Prune bank folder" action THROUGH its registered
|
||||
// command id (fork R-E: dispatch the command, not the session directly) so the panel
|
||||
// affordance and the bindable action share the one guarded dry-run/confirm/delete path
|
||||
// in doBankPruneFolder. A 0 id (pre-registration) no-ops.
|
||||
// Fires through its registered command id (not the session directly) so the panel
|
||||
// affordance and the bindable action share the one guarded dry-run/confirm/delete
|
||||
// path in doBankPruneFolder.
|
||||
const ButtonRect pb = pruneButtonRectFor(w, h);
|
||||
if (hitTestPruneButton(x, y, pb)) {
|
||||
const int cmd = bankPruneCommandId();
|
||||
@@ -385,11 +329,8 @@ void handleClick(int x, int y) {
|
||||
}
|
||||
}
|
||||
|
||||
// BOTTOM toolbar (Design-View verbs): a button fires its registered action via the
|
||||
// command-id contract. Claimed whole like the top toolbar.
|
||||
if (handleToolbarClick(x, y, bottomToolbarRect(w, h), bottomBarRows())) return;
|
||||
|
||||
// Region chrome (headers, tab strip, buttons).
|
||||
if (poolShown()) {
|
||||
const RECT pr = poolRegionRect(w, h);
|
||||
if (y >= pr.top && y < regionGridRect(pr, false).top) {
|
||||
@@ -403,15 +344,13 @@ void handleClick(int x, int y) {
|
||||
}
|
||||
}
|
||||
|
||||
// Grid selection. Resolve which region's grid the point is in.
|
||||
Region reg = Region::Pool;
|
||||
if (!regionAt(x, y, reg)) return;
|
||||
const bool isBanks = reg == Region::Banks;
|
||||
const RECT region = isBanks ? banksRegionRect(w, h) : poolRegionRect(w, h);
|
||||
// L7: hit-test the SPARSE slot layout, then map the slot to a selection ordinal. An
|
||||
// empty (gap) slot hits selectionForSlot == -1, which reads as a grid miss below (a
|
||||
// click on a gap clears selection, exactly like a click in the margin) — empty slots
|
||||
// are decorative, not selectable.
|
||||
// Hit-test the SPARSE slot layout, then map the slot to a selection ordinal. An empty
|
||||
// (gap) slot hits selectionForSlot == -1, which reads as a grid miss below (a click on
|
||||
// a gap clears selection, exactly like a click in the margin).
|
||||
const RegionDisplay disp = regionDisplay(region, isBanks, reg);
|
||||
const int hitSlot = hitTestSlot(x, y, disp.slotRects);
|
||||
const int hit = hitSlot < 0 ? -1 : disp.selectionForSlot(hitSlot);
|
||||
@@ -434,19 +373,12 @@ void handleClick(int x, int y) {
|
||||
}
|
||||
|
||||
// Drag-arm disambiguation for plain (no ctrl, no shift) presses on a grid cell:
|
||||
//
|
||||
// • Already-selected cell: defer the selection change to LBUTTONUP so a plain
|
||||
// press on a multi-selection doesn't collapse it before we know whether a drag
|
||||
// will happen. Arm the drag with the current (multi-)selection as the payload
|
||||
// candidate; only the caret moves immediately.
|
||||
//
|
||||
// • Unselected cell: apply the plain-click selection immediately (collapses to
|
||||
// the single pressed cell) THEN arm a drag from it — so the user can press-and-
|
||||
// drag in one gesture without a prior selecting click. The selection is set
|
||||
// before arming so that focusedSelectionIds() resolves the right payload when
|
||||
// the threshold is crossed in onMouseMove.
|
||||
//
|
||||
// ctrl / shift presses are selection-only gestures — no drag arm in either case.
|
||||
// already-selected cell defers the selection change to LBUTTONUP (so a plain press on
|
||||
// a multi-selection doesn't collapse it before we know whether a drag will happen; only
|
||||
// the caret moves immediately); unselected cell applies the plain-click selection now
|
||||
// (collapses to the single pressed cell) so a press-and-drag works without a prior
|
||||
// selecting click and focusedSelectionIds() resolves the right payload once the
|
||||
// threshold is crossed. ctrl/shift presses are selection-only — no drag arm.
|
||||
const bool onSelected = g_panel.selection.contains(hit);
|
||||
if (!ctrlDown() && !shiftDown()) {
|
||||
if (!onSelected) {
|
||||
@@ -461,12 +393,10 @@ void handleClick(int x, int y) {
|
||||
g_panel.dragStartX = x;
|
||||
g_panel.dragStartY = y;
|
||||
g_panel.dragSourceRegion = reg;
|
||||
// Capture the mouse NOW so WM_MOUSEMOVE is delivered even when the pointer leaves the
|
||||
// panel client rect before the drag threshold is crossed. Without capture, outside moves
|
||||
// are not delivered, so a fast straight-out drag never transitions dragArmed → dragging
|
||||
// and the OS drag-out never fires on the first pass. The capture is released on button-up
|
||||
// (no drag: onLBtnUp dragArmed branch; drag: OsDrag path or onLBtnUp dragging branch)
|
||||
// and on WM_CAPTURECHANGED (stolen or external release — already calls resetDragState).
|
||||
// Capture the mouse NOW so WM_MOUSEMOVE is still delivered once the pointer leaves the
|
||||
// client rect before the drag threshold is crossed — without capture, a fast
|
||||
// straight-out drag never transitions dragArmed -> dragging. Released on button-up or
|
||||
// WM_CAPTURECHANGED (which already calls resetDragState).
|
||||
SetCapture(g_panel.hwnd);
|
||||
invalidatePanel();
|
||||
return;
|
||||
@@ -477,13 +407,10 @@ void handleClick(int x, int y) {
|
||||
invalidatePanel();
|
||||
}
|
||||
|
||||
// Handles a scroll-wheel notch over client (x, y) with signed wheel delta `delta`.
|
||||
// Fine-adjusts the Manual tail length in kManualStepMs steps ONLY when the cursor is
|
||||
// over the footer strip AND the mode is Manual — wheel up lengthens, down shortens,
|
||||
// clamped to [0, kMaxTailMs]. In Off/Auto (or off the footer) it does nothing (returns
|
||||
// false so the caller can let REAPER/the docker handle the wheel normally). On a real
|
||||
// change it mutates the SESSION's tail setting, marks the project dirty (so it saves),
|
||||
// and repaints the live length. Returns true iff the wheel was consumed.
|
||||
// Fine-adjusts the Manual tail length in kManualStepMs steps ONLY when the cursor is over
|
||||
// the footer strip AND the mode is Manual — wheel up lengthens, down shortens, clamped to
|
||||
// [0, kMaxTailMs]. Otherwise does nothing (returns false so the caller can let REAPER/the
|
||||
// docker handle the wheel normally). Returns true iff the wheel was consumed.
|
||||
bool handleWheel(int x, int y, int delta) {
|
||||
if (!g_panel.session) return false;
|
||||
if (!pointInFooter(x, y)) return false;
|
||||
@@ -542,8 +469,7 @@ bool handleKey(int vk) {
|
||||
stopAudition();
|
||||
return true;
|
||||
case VK_DELETE: {
|
||||
// Remove the focused-region selection (B5). Silent; a no-op when nothing
|
||||
// is selected.
|
||||
// Remove the focused-region selection. Silent; a no-op when nothing is selected.
|
||||
const std::vector<std::string> sel = focusedSelectionIds();
|
||||
if (sel.empty()) return false; // nothing selected — let the key fall through
|
||||
removeSamples(sel, bankIdForRegion(g_panel.focusedRegion));
|
||||
@@ -580,35 +506,27 @@ void unregisterAccel() {
|
||||
|
||||
} // namespace reasampler::panel
|
||||
|
||||
// --- Public API (the timer + tail read seam — panel_input.h) -------------------
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
void bankPanelNotifyProjectLoaded() {
|
||||
// Persist restored a project's membership + active mode this tick (main.cpp calls
|
||||
// this from the same consumeLoadSignal() branch that reapplies the active mode).
|
||||
// Arm the new-content detector to re-baseline on its next tick so the just-loaded
|
||||
// project's pre-existing content is treated as the baseline (nothing new) rather
|
||||
// than diffed against the previous project and mass-tagged into the active mode.
|
||||
// A flag (not an inline reset) because detectNewContent owns the baseline and runs
|
||||
// later in the SAME OnTimer tick — it drains this and re-baselines against the live
|
||||
// set in one place, keeping the reset and the observe() adjacent and ordered.
|
||||
// Arms the new-content detector to re-baseline on its next tick so the just-loaded
|
||||
// project's pre-existing content is the baseline (nothing new) rather than diffed
|
||||
// against the previous project and mass-tagged. A flag, not an inline reset, because
|
||||
// detectNewContent owns the baseline and runs later in the SAME OnTimer tick.
|
||||
panel::g_panel.reloadPending = true;
|
||||
}
|
||||
|
||||
void bankPanelRefresh() {
|
||||
// New-content auto-tag detection runs EVERY tick regardless of panel open/close:
|
||||
// tracks/items are created in the arrange view, not the panel, so detection must
|
||||
// not be gated on the dock being visible. READ-ONLY on the project; only mutates
|
||||
// the in-memory membership index (persist saves it like any action-driven tag).
|
||||
// tracks/items are created in the arrange view, not the panel. READ-ONLY on the
|
||||
// project; only mutates the in-memory membership index.
|
||||
const bool tagged = panel::detectNewContent();
|
||||
|
||||
// Lane minting (D2 Wave 3) runs ONLY when detection just tagged new content — a
|
||||
// track can only newly become multi-mode when auto-tag placed content on it. Unlike
|
||||
// the invisible membership tag above, minting is a visible structural mutation
|
||||
// Lane minting runs ONLY when detection just tagged new content — a track can only
|
||||
// newly become multi-mode when auto-tag placed content on it. Unlike the invisible
|
||||
// membership tag above, minting is a visible structural mutation
|
||||
// (I_FREEMODE/I_FIXEDLANE/P_LANENAME), so mintManagedLanes wraps it in its own Undo
|
||||
// block and only mints for tracks that hold >1 mode's content — a single-mode track
|
||||
// is left to D1 whole-track parking. Managed lanes only; manual lanes untouched.
|
||||
// block and only mints for tracks that hold >1 mode's content. Managed lanes only.
|
||||
if (tagged && panel::g_panel.session) {
|
||||
ReaProject* proj = EnumProjects(-1, nullptr, 0);
|
||||
mintManagedLanes(panel::g_panel.session->view(), proj);
|
||||
@@ -616,8 +534,8 @@ void bankPanelRefresh() {
|
||||
|
||||
if (!panel::g_panel.open || !panel::g_panel.hwnd) return;
|
||||
|
||||
// L5: the custom hover-delay tooltip is driven off this poll tick (no dedicated timer) — if a
|
||||
// toolbar button has rested under the pointer past the delay, latch + repaint the tooltip.
|
||||
// The custom hover-delay tooltip is driven off this poll tick (no dedicated timer) — if
|
||||
// a toolbar button has rested under the pointer past the delay, latch + repaint it.
|
||||
panel::maybeShowTooltip();
|
||||
|
||||
if (panel::refreshFingerprint())
|
||||
@@ -625,10 +543,9 @@ void bankPanelRefresh() {
|
||||
}
|
||||
|
||||
capture::TailSetting bankPanelTailSetting() {
|
||||
// The authoritative setting lives in the session (session->tail()) so it travels
|
||||
// inside the .rpp: it loads per project and saves with the project. This stays the
|
||||
// read seam for the capture actions. manualMs is clamped here so a caller always
|
||||
// receives a within-cap length regardless of what was stored/scrolled.
|
||||
// The authoritative setting lives in the session so it travels inside the .rpp; this
|
||||
// is the read seam for the capture actions. manualMs is clamped here so a caller
|
||||
// always receives a within-cap length regardless of what was stored/scrolled.
|
||||
capture::TailSetting s = panel::currentTail();
|
||||
s.manualMs = capture::clampManualMs(s.manualMs);
|
||||
return s;
|
||||
|
||||
Reference in New Issue
Block a user