acda259ab6
Button faces now show only the short label (keybinding moved to hover tooltip as "name — binding"). Cancel RT joins the overflow menu alongside Capture RT. Re-capture sits between the capture and placement clusters. Toolbar heights 40->28.
162 lines
9.3 KiB
C++
162 lines
9.3 KiB
C++
#pragma once
|
|
// action_bar — the REAPER-free, LICE-free layout + hit-test math behind the bank_panel's
|
|
// TASK-GROUPED toolbars (Phase L, L2 + L4 + L6). L2's dock-panel layout redesign (DS-3: a
|
|
// thorough layout, not a re-skin) groups the action-trigger button inventory BY TASK — a compact
|
|
// bar of clusters instead of one flat equal-tiled strip (the M11 action_buttons row this
|
|
// supersedes for the panel's action inventory). Each button carries a label sub-rect spanning
|
|
// its full height — a single-row short label (L6: the keybinding sub-row was on the button face
|
|
// through L5; L6 moves it to the hover tooltip instead). The bar degrades gracefully on a narrow
|
|
// panel by dropping WHOLE trailing buttons (never clipping) so the frequent leading cluster
|
|
// survives.
|
|
//
|
|
// L4 re-homes the inventory across TWO toolbars, BOTH driven by this one module: a TOP toolbar
|
|
// (Capture + Placement — the two acts the tool exists for) and a BOTTOM toolbar (the Design-View
|
|
// verbs, Tagging then Switching). The tiling is cluster-agnostic — it walks the caller's
|
|
// ClusterSpec list in order — so the same computeBarSlots / hitTestActionBar serve both bars;
|
|
// only the cluster membership and the band rect differ per toolbar.
|
|
//
|
|
// Why pure (CLAUDE.md §load-bearing split, DS-1 caution): the panel shell owns the SWELL
|
|
// window, the L1-kit draws, and the NamedCommandLookup/Main_OnCommand dispatch — all
|
|
// DAW-verified. What is NOT DAW-bound — how the clusters tile the bar, where each button and
|
|
// its label sub-rect sit, and which button a click hits — lives HERE, unit-tested outside the
|
|
// DAW. Mirror of mode_switch / action_buttons / prune_button.
|
|
//
|
|
// NAME NOTE (brief §name-collision): ButtonRect / ButtonStripRect / ActionButtonRect /
|
|
// SegmentRect / CellRect / FooterRect / KitButtonBox are already owned in this namespace, so
|
|
// this module's types are ActionBarRect / ActionBarSlot / ActionCluster — grep-checked free
|
|
// before minting. They are a distinct concept (a task-grouped multi-cluster bar with text
|
|
// sub-rects) from the flat action_buttons strip, so the separate names are correct, not merely
|
|
// non-colliding.
|
|
//
|
|
// SCOPE: the destructive PRUNE button is NOT in this bar — it stays set-apart in the footer,
|
|
// warn-marked, owned by prune_button (L2 keeps prune deliberately away from the frequent
|
|
// action cluster). This module lays out only the non-destructive capture/placement/maintenance
|
|
// actions.
|
|
//
|
|
// PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO vendor/ includes. Standard library only.
|
|
|
|
#include <vector>
|
|
|
|
namespace reasampler {
|
|
|
|
// The task cluster a button belongs to (the L2 "group by task" mandate). The order here is
|
|
// NOT itself the bar order — the caller passes ClusterSpecs in the order it wants; this enum
|
|
// only names the groups so a slot can carry (and a test/shell can assert) its membership.
|
|
//
|
|
// L4 split the panel's buttons across TWO toolbars, each an action_bar instance:
|
|
// * the TOP toolbar draws Capture + Placement (the two acts the tool exists for);
|
|
// * the BOTTOM toolbar draws the Design-View verbs, grouped Tagging then Switching.
|
|
// Both toolbars share this ONE pure layout module (the tiling is cluster-agnostic — it walks
|
|
// the caller's ClusterSpec list in order), so a cluster value belongs to whichever toolbar
|
|
// the shell places it in; nothing here couples a cluster to a specific bar.
|
|
enum class ActionCluster {
|
|
Capture, // capture item / track / realtime / batch — top toolbar, primary gesture
|
|
Placement, // insert at cursor / insert-conform — top toolbar, placing a sample
|
|
Maintenance, // re-capture from source / cancel realtime — rarer upkeep actions
|
|
Tagging, // tag / untag selected tracks for the active mode — bottom toolbar (L4)
|
|
Switching, // activate Arrange / Design, toggle mode, show-both — bottom toolbar (L4)
|
|
};
|
|
|
|
// The bar the clusters are drawn into, top-left origin (SWELL/LICE convention). (x, y) is the
|
|
// top-left corner; width/height are the bar extents. The panel reserves this as a fixed-height
|
|
// band (its own judgment where — above the tail footer, below the split body).
|
|
struct ActionBarRect {
|
|
int x = 0;
|
|
int y = 0;
|
|
int width = 0;
|
|
int height = 0;
|
|
|
|
bool operator==(const ActionBarRect& o) const {
|
|
return x == o.x && y == o.y && width == o.width && height == o.height;
|
|
}
|
|
};
|
|
|
|
// One visible button's placement within the bar, top-left origin. `index` is the button's
|
|
// position in the caller's flat action list (the caller supplies actions in cluster order, so
|
|
// index also selects the action to fire on a hit). `cluster` is the task group it was laid out
|
|
// under (surfaced so a test can assert the grouping is structural, and the shell can tint a
|
|
// cluster). `box` is the whole button rect; `labelBox` is the text area inset horizontally so
|
|
// text clears the button edge. Only VISIBLE buttons get a slot — a button that does not fit is
|
|
// omitted, never returned clipped, so every slot is fully drawable.
|
|
struct ActionBarSlot {
|
|
int index = 0;
|
|
ActionCluster cluster = ActionCluster::Capture;
|
|
int x = 0;
|
|
int y = 0;
|
|
int width = 0;
|
|
int height = 0;
|
|
// Label rect (absolute, top-left origin), inside `box`. The label spans the full button
|
|
// height — a single-row short label only (L6: keybinding sub-row removed from the face;
|
|
// binding is surfaced in the hover tooltip instead).
|
|
int labelX = 0, labelY = 0, labelW = 0, labelH = 0;
|
|
|
|
bool operator==(const ActionBarSlot& o) const {
|
|
return index == o.index && cluster == o.cluster &&
|
|
x == o.x && y == o.y && width == o.width && height == o.height &&
|
|
labelX == o.labelX && labelY == o.labelY &&
|
|
labelW == o.labelW && labelH == o.labelH;
|
|
}
|
|
};
|
|
|
|
// One cluster's button count, in the caller's flat action-list order. The caller passes these
|
|
// in the left-to-right order it wants them drawn (top toolbar: Capture then Placement; bottom
|
|
// toolbar: Tagging then Switching); a cluster with count 0 is skipped (no gap emitted for it).
|
|
// The flat action index a slot carries is the running sum across clusters (cluster 0's buttons
|
|
// are indices [0, counts[0]), etc.), so the shell's flat action table lines up with the slots
|
|
// by index.
|
|
struct ClusterSpec {
|
|
ActionCluster cluster = ActionCluster::Capture;
|
|
int count = 0;
|
|
};
|
|
|
|
// Layout inputs for the bar, in pixels. Defaults are the bank_panel action-bar metrics; the
|
|
// shell passes its own so draw and hit-test share ONE source of truth.
|
|
// * buttonWidth — each button's fixed width (buttons never render narrower; overflow drops
|
|
// whole trailing buttons instead of shrinking below this).
|
|
// * buttonGap — horizontal gap between buttons WITHIN a cluster.
|
|
// * clusterGap — horizontal gap between adjacent clusters (wider than buttonGap so the
|
|
// task grouping reads visually; the 8px-grid density decision).
|
|
// * sidePad — left/right inset from the bar edges to the first/last button.
|
|
// * verticalInset — top/bottom gap inside the bar (buttons read as raised, not full-bleed).
|
|
struct ActionBarSpec {
|
|
int buttonWidth = 108;
|
|
int buttonGap = 4;
|
|
int clusterGap = 16;
|
|
int sidePad = 8;
|
|
int verticalInset = 3;
|
|
};
|
|
|
|
// How many buttons (from the front, cluster by cluster) fit the bar at `spec.buttonWidth`.
|
|
// Split from slot tiling so the shell can size an overflow affordance / count without
|
|
// re-deriving it. Trailing buttons that do not fit are the overflow (dropped whole). A
|
|
// non-positive bar width, or a bar too narrow for even one button, yields 0. Clamps to
|
|
// [0, total-button-count].
|
|
struct BarFit {
|
|
int visibleCount = 0; // buttons that fit (laid out), counted from the front
|
|
int hiddenCount = 0; // total - visibleCount (the overflow, dropped whole)
|
|
};
|
|
|
|
BarFit computeBarFit(const ActionBarRect& bar, const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec);
|
|
|
|
// Lays out the VISIBLE buttons (per computeBarFit) left-to-right in cluster order: buttons
|
|
// pack at buttonWidth with buttonGap inside a cluster and clusterGap between clusters, starting
|
|
// at bar.x + sidePad. Each slot carries its flat action index, its cluster, its box, and the
|
|
// label sub-rect (full-height single row). Empty clusters emit no gap. Returns exactly
|
|
// visibleCount slots in ascending index order. A degenerate bar (width/height <= 0), an empty
|
|
// cluster list, or a non-positive buttonWidth yields empty.
|
|
std::vector<ActionBarSlot> computeBarSlots(const ActionBarRect& bar,
|
|
const std::vector<ClusterSpec>& clusters,
|
|
const ActionBarSpec& spec);
|
|
|
|
// The flat action index the point (px, py) (SWELL/LICE top-left client coords) lands on, or -1
|
|
// for a miss: outside the bar band, in an inter-button / inter-cluster gap, or past the last
|
|
// visible button (the narrow-panel overflow dead-zone — a harmless no-op the shell ignores).
|
|
// Half-open bounds [x, x+width) x [y, y+height) match computeBarSlots so no pixel is double-
|
|
// claimed and the hit maps to the button drawn there. Unlike an equal-tiled strip, the bar has
|
|
// real gaps, so a gap point is a clean miss (not the nearest button).
|
|
int hitTestActionBar(int px, int py, const ActionBarRect& bar,
|
|
const std::vector<ClusterSpec>& clusters, const ActionBarSpec& spec);
|
|
|
|
} // namespace reasampler
|