36bdef742a
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.
108 lines
3.9 KiB
C++
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
|