// tab_strip — pure implementation. See tab_strip.h. NO REAPER / SWELL / vendor. #include "tab_strip.h" #include namespace reasampler { TabStripLayout computeTabStripLayout(const TabStripRect& strip, int tabCount, const TabStripSpec& spec, int scrollOffset) { (void)scrollOffset; // layout depends on geometry only, not the current offset TabStripLayout out; if (tabCount <= 0 || strip.width <= 0) { out.trackX = strip.x; out.trackWidth = strip.width > 0 ? strip.width : 0; return out; // nothing to lay out: track == strip, no overflow, no chevrons } const int totalTabsWidth = tabCount * spec.tabWidth; if (totalTabsWidth <= strip.width) { // Everything fits: the whole strip is the track; no chevrons, no scroll. out.overflow = false; out.trackX = strip.x; out.trackWidth = strip.width; out.maxScroll = 0; return out; } // Overflow: reserve a chevron band at each end; the tabs live between them. out.overflow = true; out.leftChevron = true; out.rightChevron = true; out.trackX = strip.x + spec.chevronWidth; out.trackWidth = strip.width - 2 * spec.chevronWidth; if (out.trackWidth < 0) out.trackWidth = 0; // The tab run exceeds the track by this many pixels; the strip may scroll exactly // that far so the last tab's right edge reaches the track's right edge, no more. out.maxScroll = totalTabsWidth - out.trackWidth; if (out.maxScroll < 0) out.maxScroll = 0; return out; } int clampTabScroll(int desiredOffset, const TabStripLayout& layout) { if (desiredOffset < 0) return 0; if (desiredOffset > layout.maxScroll) return layout.maxScroll; return desiredOffset; } std::vector computeTabRects(const TabStripRect& strip, int tabCount, const TabStripSpec& spec, int scrollOffset) { std::vector rects; if (tabCount <= 0 || strip.width <= 0) return rects; const TabStripLayout layout = computeTabStripLayout(strip, tabCount, spec, scrollOffset); const int offset = layout.overflow ? clampTabScroll(scrollOffset, layout) : 0; const int trackLeft = layout.trackX; const int trackRight = layout.trackX + layout.trackWidth; rects.reserve(static_cast(tabCount)); for (int i = 0; i < tabCount; ++i) { const int rawLeft = trackLeft + i * spec.tabWidth - offset; const int rawRight = rawLeft + spec.tabWidth; // Clip to the track: a partially-scrolled tab must not draw under a chevron // or spill past the track. A tab whose clipped extent is empty is omitted. int left = rawLeft < trackLeft ? trackLeft : rawLeft; int right = rawRight > trackRight ? trackRight : rawRight; if (right <= left) continue; // fully scrolled out of view either side TabRect r; r.index = i; r.x = left; r.y = strip.y; r.width = right - left; r.height = strip.height; rects.push_back(r); } return rects; } TabHit hitTestTabStrip(int px, int py, const TabStripRect& strip, int tabCount, const TabStripSpec& spec, int scrollOffset) { TabHit miss; // {None, -1} if (tabCount <= 0 || strip.width <= 0 || strip.height <= 0) return miss; // Reject anything outside the strip band first (half-open bounds). if (px < strip.x || px >= strip.x + strip.width || py < strip.y || py >= strip.y + strip.height) return miss; const TabStripLayout layout = computeTabStripLayout(strip, tabCount, spec, scrollOffset); // Chevrons take precedence at the strip ends: a click in a reserved chevron band // is a scroll, never a tab (the tab track excludes those bands). if (layout.overflow) { if (px < strip.x + spec.chevronWidth) return TabHit{TabHitKind::ScrollLeft, -1}; if (px >= strip.x + strip.width - spec.chevronWidth) return TabHit{TabHitKind::ScrollRight, -1}; } // Inside the track: find the visible tab whose clipped rect contains px. Reuse // computeTabRects so the hit matches exactly what was drawn (clipping included). const std::vector rects = computeTabRects(strip, tabCount, spec, scrollOffset); for (const TabRect& r : rects) { if (px >= r.x && px < r.x + r.width) return TabHit{TabHitKind::Tab, r.index}; } return miss; // track dead space (no tab under the point) } } // namespace reasampler