L1: shared LICE drawing kit — theme/palette + component geometry + draw kit; retire GDI text in bank_panel
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
// component_geometry — the REAPER-free, LICE-free geometry + hit-test math for the shared
|
||||
// drawing kit's generic components (Phase L, L1): a button box, a slider's track/handle,
|
||||
// and a list row. These are the kit-level primitives that DON'T already have a pure owner:
|
||||
// bank_grid / mode_switch / tab_strip / action_buttons / prune_button stay the source of
|
||||
// truth for the surfaces THEY own; this module carries only the new, reusable component
|
||||
// shapes the kit's drawButton / drawSlider / drawListRow draw against.
|
||||
//
|
||||
// Why pure (CLAUDE.md §load-bearing split, DS-1 caution): even where the draw shell reuses
|
||||
// a WDL/vwnd drawing idiom, the hit-test geometry stays HERE, unit-tested outside the DAW —
|
||||
// vwnd's retained-mode controls own their hit-test internally, which this deliberately does
|
||||
// NOT import. The shell asks this module where a handle is and whether a point hit a row.
|
||||
//
|
||||
// NAME NOTE (brief §name-collision): the surrounding modules already own ButtonRect /
|
||||
// SegmentRect / CellRect / FooterRect etc. in this namespace, so this module's types are
|
||||
// named KitButtonBox / SliderGeometry / ListRowBox to avoid collision — checked with grep
|
||||
// before minting. They are distinct concepts (kit-generic component boxes vs. a specific
|
||||
// surface's hit rects), so the separate names are correct, not merely non-colliding.
|
||||
//
|
||||
// PURE MODULE: NO REAPER types, NO SWELL, NO LICE, NO vendor/ includes. Standard library
|
||||
// only. Builds and unit-tests without REAPER. Mirror of mode_switch / prune_button.
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
// A generic pixel box, top-left origin (SWELL/LICE convention). Shared shape for the kit
|
||||
// component rects below. A zero-area box (empty()) means "nothing to draw / hit" — the
|
||||
// same graceful-suppression convention prune_button uses.
|
||||
struct KitBox {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0; }
|
||||
|
||||
bool operator==(const KitBox& o) const {
|
||||
return x == o.x && y == o.y && width == o.width && height == o.height;
|
||||
}
|
||||
};
|
||||
|
||||
// True iff (px, py) falls inside `box`, half-open bounds [x, x+width) x [y, y+height) —
|
||||
// the same discipline as every sibling hit-test so draw and hit-test never double-claim a
|
||||
// pixel. An empty box claims no point (always false).
|
||||
bool hitTestBox(int px, int py, const KitBox& box);
|
||||
|
||||
// --- Button ------------------------------------------------------------------
|
||||
//
|
||||
// A button drawn inside a host cell, inset by a uniform padding so it reads as a raised
|
||||
// control rather than a full-bleed fill (the kit's drawButton draws the micro-gradient
|
||||
// surface inside this box). Distinct from prune_button/action_buttons, which own their
|
||||
// OWN placement within their strips — this is the generic "given a cell, where's the
|
||||
// button" helper for new kit consumers.
|
||||
struct KitButtonBox {
|
||||
KitBox box;
|
||||
|
||||
bool operator==(const KitButtonBox& o) const { return box == o.box; }
|
||||
};
|
||||
|
||||
// The button box inside `cell`, inset uniformly by `padding` on all four sides. Returns an
|
||||
// empty box (suppressed) when the cell is degenerate or the padding would collapse it to
|
||||
// zero-or-negative area — the caller then draws nothing (graceful, mirrors prune_button).
|
||||
// padding < 0 is treated as 0.
|
||||
KitButtonBox computeButtonBox(const KitBox& cell, int padding);
|
||||
|
||||
// --- Slider (horizontal) -----------------------------------------------------
|
||||
//
|
||||
// A horizontal slider: a track spanning the control width (inset at both ends by the
|
||||
// handle's half-width so the handle never clips past the track), and a square handle
|
||||
// centered on the track and positioned by the normalized value. drawSlider draws the
|
||||
// track, the filled portion up to the handle, and the handle. Hit-test is against the
|
||||
// handle (grab) and the track (jump); both are pure here.
|
||||
struct SliderGeometry {
|
||||
KitBox track; // the full track rect (the groove)
|
||||
KitBox filled; // the filled portion from the track's left up to the handle center
|
||||
KitBox handle; // the draggable handle rect
|
||||
|
||||
bool operator==(const SliderGeometry& o) const {
|
||||
return track == o.track && filled == o.filled && handle == o.handle;
|
||||
}
|
||||
};
|
||||
|
||||
// Lays out a horizontal slider inside `control` for a normalized `value` in [0, 1] with a
|
||||
// square handle of side `handleSize`. The track is vertically centered at a fixed
|
||||
// `trackThickness`, inset horizontally by handleSize/2 at each end so the handle's travel
|
||||
// stays within `control`. value is clamped to [0, 1]; a value of 0 puts the handle flush
|
||||
// left, 1 flush right. Returns all-empty boxes when the control is degenerate or too
|
||||
// small to host the handle (control width < handleSize or height < handleSize) — the
|
||||
// caller draws nothing. handleSize <= 0 or trackThickness <= 0 also yields empty.
|
||||
SliderGeometry computeSlider(const KitBox& control, double value,
|
||||
int handleSize, int trackThickness);
|
||||
|
||||
// The normalized value [0, 1] a click at px maps to, for a slider laid out in `control`
|
||||
// with `handleSize` (the inverse of computeSlider's handle placement — a track jump).
|
||||
// px left of / at the track start yields 0.0, at/right of the track end yields 1.0,
|
||||
// linear in between. Returns 0.0 for a degenerate/too-small control (no travel). py is
|
||||
// unused (a horizontal slider maps X only); the caller gates the whole slider region
|
||||
// with hitTestBox(control) before calling this.
|
||||
double sliderValueAt(int px, const KitBox& control, int handleSize);
|
||||
|
||||
// --- List row ----------------------------------------------------------------
|
||||
//
|
||||
// A single selectable row in a vertical list: full-width, fixed height, stacked from the
|
||||
// list's top by index (no scroll — the caller offsets the list origin for scroll). The
|
||||
// kit's drawListRow draws the row surface (rest/hover/selected/focus) and an optional
|
||||
// leading thumbnail; the panel's waveform cell is a specialization drawn the same way.
|
||||
struct ListRowBox {
|
||||
int index = 0; // the row's index in the caller's list (0-based, top-first)
|
||||
KitBox box;
|
||||
|
||||
bool operator==(const ListRowBox& o) const {
|
||||
return index == o.index && box == o.box;
|
||||
}
|
||||
};
|
||||
|
||||
// The row box for `index` in a list laid out inside `list` at `rowHeight` per row. Rows
|
||||
// stack from list.y; row i spans [list.y + i*rowHeight, +rowHeight). Returns an empty box
|
||||
// when the list is degenerate, rowHeight <= 0, index < 0, or the row would fall entirely
|
||||
// below the list's bottom (fully clipped) — a partially-visible last row IS returned (the
|
||||
// caller clips the draw). This is layout only; the caller decides how many rows exist.
|
||||
ListRowBox computeListRow(const KitBox& list, int index, int rowHeight);
|
||||
|
||||
// The index of the row a point (px, py) lands on, for a list laid out inside `list` at
|
||||
// `rowHeight`. Returns -1 for a miss: outside the list bounds, in the list band but below
|
||||
// the last row of `rowCount` rows (the empty tail), or a degenerate list/rowHeight/count.
|
||||
// rowCount bounds the hit so a click in blank space past the last row is a clean miss, not
|
||||
// a phantom row. Half-open bounds match computeListRow so the hit maps to the drawn row.
|
||||
int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCount);
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user