// browser_scroll.h — scroll + type-to-filter geometry layered over capture_browser. Mirror // of capture_browser/editor_geometry; the shell draws the clipped card window, scrollbar, // and search field, and routes wheel/drag/keystrokes into these functions. // // capture_browser lays out every card top-down and the shell clips at the browser bottom — // a bank longer than the panel has no way to reach the rest. This module adds scroll (a // vertical pixel offset with max-offset clamp, visible-row window, scrollbar thumb, and // thumb-drag<->offset mapping) and search (a case-insensitive name-substring filter that // composes with capture_browser's bank filter — the shell applies the bank filter first, // then this search narrows within it). // // Holds no card data and draws nothing — knows only the browser layout, counts, and the // scroll offset the shell owns as transient UI state. #pragma once #include #include #include "core/instrument/ui/capture_browser.h" // BrowserLayout, cardCellRect, kBrowserCardHeight, Rect namespace reasampler::instrument::ui { // Width of the vertical scrollbar gutter at the grid's right edge. When content fits (no // scroll needed), scrollThumbRect returns empty and the shell may reclaim the gutter. inline constexpr int kScrollbarWidth = 10; // Height of the type-to-filter search box the shell draws above the tab strip. // capture_browser's tab strip + grid sit below this band. inline constexpr int kSearchBoxHeight = 22; // Total pixel height the card grid needs for `cardCount` cards at `layout`'s column // count: rows (ceil(cardCount / columns)) times the fixed cell height. int scrollContentHeight(const BrowserLayout& layout, int cardCount); // Maximum scroll offset: content height minus visible grid height, floored at 0. int scrollMaxOffset(const BrowserLayout& layout, int cardCount); // Clamps a proposed scroll offset into [0, scrollMaxOffset]. int clampScrollOffset(const BrowserLayout& layout, int cardCount, int proposedOffset); // Half-open range of card indices [first, last) at least partially visible at scroll // `offset` (assumed pre-clamped). The shell draws only these cards. struct VisibleRange { int first = 0; int last = 0; }; VisibleRange visibleCardRange(const BrowserLayout& layout, int cardCount, int offset); // Cell rect of card `index` shifted up by the scroll offset (the shell still adds the // browser sub-area origin). Rect scrolledCardCellRect(const BrowserLayout& layout, int index, int offset); // Vertical scrollbar thumb rect within the grid's right-edge gutter, sized proportional // to the visible fraction and positioned proportional to the scroll offset. Empty when // the content fits. A minimum thumb height keeps a tiny thumb grabbable on a long bank. Rect scrollThumbRect(const BrowserLayout& layout, int cardCount, int offset); // Maps a thumb-drag to a new (clamped) scroll offset: `startOffset` shifted by the pixel // delta scaled from thumb-track pixels to content pixels. A degenerate track or fitting // content pins to startOffset. int thumbDragToOffset(const BrowserLayout& layout, int cardCount, int startOffset, int dyPixels); // Search-box rect: full-width band of height kSearchBoxHeight at the top of the browser // area. `w` is the browser sub-area width; the shell adds its origin. Rect searchBoxRect(int w); // True iff `name` contains `query` as a case-insensitive ASCII substring. An empty query // matches everything. bool nameMatchesQuery(const std::string& name, const std::string& query); // Narrows a list of display `names` to the indices whose name matches `query`, preserving // order. An empty query returns every index. Kept name-only (indices, not card structs) // so this module stays free of the sample_map/bank_book chain — the shell applies the // bank filter first, then feeds the surviving display names here. std::vector filterNameIndices(const std::vector& names, const std::string& query); // --- Browse-modal top-level regions -------------------------------------------- // // A title band with a Back button, the search box, the browser sub-area (tabs + card // grid — layoutBrowser's origin), and a footer with Cancel / Load-confirm. The picker // covers the full window. Homed here (not editor_geometry) because the search-box // height feeds it. struct BrowseModal { Rect title; Rect back; Rect search; Rect content; // browser sub-area (tabs + grid) — layoutBrowser's origin Rect cancel; Rect confirm; }; BrowseModal computeBrowseModal(int w, int h); } // namespace reasampler::instrument::ui