Files
reasampler/src/action_buttons.cpp
T
daniel 36bdef742a M11: action-trigger buttons + keybinding labels in bank_panel
New pure action_buttons module (strip layout/hit-test + label format,
CTest-covered). Panel strip fires capture/insert/provenance actions via
NamedCommandLookup + Main_OnCommand; labels from kbd_getTextFromCmd.
2026-07-26 19:33:04 -04:00

108 lines
3.9 KiB
C++

// action_buttons — pure implementation. See action_buttons.h. NO REAPER / SWELL / vendor.
#include "action_buttons.h"
#include <cstddef>
namespace reasampler {
namespace {
// The left edge of button i when `count` buttons share a strip of the given x-origin and
// width. Boundary i is x + (i * width) / count, so button i spans [edge(i), edge(i+1)).
// Every boundary derives from the same formula, so consecutive buttons share an exact
// edge (no gap, no overlap) and edge(count) == x + width precisely. count assumed >= 1.
int buttonEdge(int x, int width, int i, int count) {
return x + (i * width) / count;
}
// True for an ASCII space or tab (the whitespace the SDK binding string might carry).
bool isBlankChar(char c) { return c == ' ' || c == '\t'; }
// The trimmed [first, last) view of `s` with leading/trailing blanks removed. Returns
// an empty range when `s` is all blanks.
std::string trimBlanks(const std::string& s) {
std::size_t b = 0;
std::size_t e = s.size();
while (b < e && isBlankChar(s[b])) ++b;
while (e > b && isBlankChar(s[e - 1])) --e;
return s.substr(b, e - b);
}
} // namespace
ButtonFit computeButtonFit(const ButtonStripRect& strip, int buttonCount,
int minButtonWidth) {
ButtonFit fit;
if (buttonCount <= 0 || strip.width <= 0 || minButtonWidth <= 0) return fit;
int fits = strip.width / minButtonWidth; // how many min-width buttons the strip holds
if (fits > buttonCount) fits = buttonCount;
if (fits < 0) fits = 0;
fit.visibleCount = fits;
fit.hiddenCount = buttonCount - fits;
return fit;
}
std::vector<ActionButtonRect> computeButtonRects(const ButtonStripRect& strip, int buttonCount,
int minButtonWidth) {
std::vector<ActionButtonRect> rects;
const ButtonFit fit = computeButtonFit(strip, buttonCount, minButtonWidth);
const int n = fit.visibleCount;
if (n <= 0) return rects;
rects.reserve(static_cast<std::size_t>(n));
for (int i = 0; i < n; ++i) {
const int left = buttonEdge(strip.x, strip.width, i, n);
const int right = buttonEdge(strip.x, strip.width, i + 1, n);
ActionButtonRect r;
r.index = i;
r.x = left;
r.y = strip.y;
r.width = right - left; // absorbs rounding; visible buttons abut and fill the strip
r.height = strip.height;
rects.push_back(r);
}
return rects;
}
int hitTestButton(int px, int py, const ButtonStripRect& strip, int buttonCount,
int minButtonWidth) {
if (strip.height <= 0) return -1;
// Reject anything outside the strip band first (half-open bounds match the rects).
if (px < strip.x || px >= strip.x + strip.width ||
py < strip.y || py >= strip.y + strip.height)
return -1;
const ButtonFit fit = computeButtonFit(strip, buttonCount, minButtonWidth);
const int n = fit.visibleCount;
if (n <= 0) return -1;
// Inside the band: find the visible button whose [edge(i), edge(i+1)) contains px.
// A point past the last visible button's right edge (narrow-panel overflow dead-zone)
// falls through to -1.
for (int i = 0; i < n; ++i) {
const int left = buttonEdge(strip.x, strip.width, i, n);
const int right = buttonEdge(strip.x, strip.width, i + 1, n);
if (px >= left && px < right) return i;
}
return -1;
}
std::string formatButtonLabel(const std::string& name, const std::string& rawBinding) {
const std::string binding = trimBlanks(rawBinding);
if (binding.empty()) {
return name + " (" + kUnboundMarker + ")";
}
std::string shown = binding;
if (static_cast<int>(shown.size()) > kMaxBindingChars) {
// Keep the leading portion and mark the truncation with a single-width "~".
shown = shown.substr(0, static_cast<std::size_t>(kMaxBindingChars - 1)) + "~";
}
return name + " " + shown;
}
} // namespace reasampler