Ψ-W1-T2: disjoint per-mode solo surfaces and a playback-gated mode switch
Solo is cached, cleared and replayed per mode on a real switch only; the switch is refused visibly while the transport runs. The footer segment now routes through the activate actions, so a panel switch finally persists.
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
#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
|
||||
#include "shell/view/view.h" // mintManagedLanes / transportBlocksModeSwitch
|
||||
|
||||
// New-content detection: enumerate live tracks + items and read fixed-lane state to
|
||||
// classify an item's lane as managed vs manual.
|
||||
@@ -76,6 +76,18 @@ bool handleToolbarClick(int x, int y, const ActionBarRect& bar,
|
||||
return true;
|
||||
}
|
||||
|
||||
// The registered activate action behind a footer mode segment. Routing the segment
|
||||
// through the SAME action the Actions list fires is what gives a panel-initiated switch
|
||||
// the persist + repaint it used to skip; a mode with no such action (the model is
|
||||
// N-mode, the UI ships two) is unreachable from the footer rather than routed around it.
|
||||
int modeActivateCommandId(const std::string& modeId) {
|
||||
ActionBarRow row{};
|
||||
if (modeId == kArrangeModeId) row.suffix = "VIEW_ACTIVATE_ARRANGE";
|
||||
else if (modeId == kDesignModeId) row.suffix = "VIEW_ACTIVATE_DESIGN";
|
||||
else return 0;
|
||||
return resolveBarCommandId(row);
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -299,11 +311,18 @@ void handleClick(int x, int y) {
|
||||
{
|
||||
const int seg = footerToggleSegmentHit(x, y, w, h);
|
||||
if (seg >= 0) {
|
||||
const std::vector<Mode>& modes = g_panel.session->view().modes().all();
|
||||
const ViewModeModel& view = g_panel.session->view();
|
||||
const std::vector<Mode>& modes = view.modes().all();
|
||||
if (seg < static_cast<int>(modes.size())) {
|
||||
applyMode(g_panel.session->view(),
|
||||
modes[static_cast<std::size_t>(seg)].id, nullptr);
|
||||
invalidatePanel();
|
||||
const std::string& id = modes[static_cast<std::size_t>(seg)].id;
|
||||
const bool isActive = id == view.activeModeId();
|
||||
// Disabled while the transport runs: claimed but inert, the same shape a
|
||||
// disabled toolbar row takes — a dead segment reads inert, never absent,
|
||||
// and never falls through to the grid.
|
||||
if (modeSegmentEnabled(isActive, g_panel.modeSwitchBlocked)) {
|
||||
const int cmd = modeActivateCommandId(id);
|
||||
if (cmd != 0 && Main_OnCommand) Main_OnCommand(cmd, 0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -534,6 +553,14 @@ void bankPanelRefresh() {
|
||||
|
||||
if (!panel::g_panel.open || !panel::g_panel.hwnd) return;
|
||||
|
||||
// Transport transitions are not ours to cause and REAPER offers no change callback,
|
||||
// so the mode segments' disabled state is polled here and repainted only on an edge.
|
||||
const bool blocked = transportBlocksModeSwitch(nullptr);
|
||||
if (blocked != panel::g_panel.modeSwitchBlocked) {
|
||||
panel::g_panel.modeSwitchBlocked = blocked;
|
||||
InvalidateRect(panel::g_panel.hwnd, nullptr, FALSE);
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
@@ -112,13 +112,17 @@ void drawFooter(LICE_IBitmap* bmp, int w, int h) {
|
||||
const SegmentRect& s = segs[static_cast<std::size_t>(i)];
|
||||
const Mode& mode = modes[static_cast<std::size_t>(i)];
|
||||
const bool active = mode.id == activeId;
|
||||
// A switch is refused while the transport runs, so an unreachable segment
|
||||
// draws dead rather than inviting a click that would silently do nothing.
|
||||
const bool live = modeSegmentEnabled(active, g_panel.modeSwitchBlocked);
|
||||
const InteractionState state =
|
||||
active ? InteractionState::Active
|
||||
: hoverState(g_panel.hovered, HoverKind::ModeSegment, i);
|
||||
: (live ? hoverState(g_panel.hovered, HoverKind::ModeSegment, i)
|
||||
: InteractionState::Disabled);
|
||||
fillSurface(bmp, KitBox{s.x, s.y, s.width, s.height}, Role::BgCell, state);
|
||||
LICE_DrawRect(bmp, s.x, s.y, s.width, s.height,
|
||||
toLice(roleColor(Role::LineHairline)), 1.0f, 0);
|
||||
const Role tr = active ? Role::BgBase : Role::TextPrimary;
|
||||
const Role tr = active ? Role::BgBase : (live ? Role::TextPrimary : Role::TextDim);
|
||||
kitText(bmp, KitBox{s.x, s.y, s.width, s.height}, mode.displayName.c_str(),
|
||||
Font::Label, tr, Align::Center);
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ using ui::hitTestPruneButton;
|
||||
using ui::hitTestSlot;
|
||||
using ui::hitTestTabStrip;
|
||||
using ui::menuButtonReserve;
|
||||
using ui::modeSegmentEnabled;
|
||||
using ui::navigate;
|
||||
using ui::roleColor;
|
||||
using ui::stripActionPrefix;
|
||||
@@ -291,6 +292,12 @@ struct PanelState {
|
||||
unsigned int hoverSinceTick = 0;
|
||||
bool tooltipShown = false;
|
||||
|
||||
// Polled on the OnTimer tick (REAPER exposes no transport-change callback): a mode
|
||||
// switch is refused while the transport runs, so the footer's [Arrange|Design]
|
||||
// segments paint disabled. Cached rather than read per paint AND per click so the
|
||||
// pixel the user saw and the click they made cannot disagree within a tick.
|
||||
bool modeSwitchBlocked = false;
|
||||
|
||||
BankPanelFullHeight fullHeight = BankPanelFullHeight::Split;
|
||||
|
||||
// The named bank the banks region shows — distinct from the active/capture-target
|
||||
|
||||
Reference in New Issue
Block a user