// action_buttons — pure implementation. See action_buttons.h. NO REAPER / SWELL / vendor. #include "action_buttons.h" #include 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 computeButtonRects(const ButtonStripRect& strip, int buttonCount, int minButtonWidth) { std::vector rects; const ButtonFit fit = computeButtonFit(strip, buttonCount, minButtonWidth); const int n = fit.visibleCount; if (n <= 0) return rects; rects.reserve(static_cast(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(shown.size()) > kMaxBindingChars) { // Keep the leading portion and mark the truncation with a single-width "~". shown = shown.substr(0, static_cast(kMaxBindingChars - 1)) + "~"; } return name + " " + shown; } } // namespace reasampler