// editor_paint_browse.cpp — the Browse modal's painter: the full-window // select-then-confirm picker (wash, search box, filter tabs, card grid, scrollbar, footer). // Windows-only. Shares the Sample face's title-band + empty-state painters via the class + // editor_internal.h. #include "shell/instrument/reasampler_editor.h" #ifdef _WIN32 #include #include #include #include #include "core/instrument/ui/browser_scroll.h" // BrowseModal + scroll/search geometry #include "shell/instrument/editor_internal.h" // kit adapters + labels #include "shell/instrument/reasampler_processor.h" namespace reasampler::vst { using namespace reasampler::ui; // kit vocabulary using namespace reasampler::instrument::ui; // browser geometry using namespace reasampler::instrument::map; // SampleChoice / BankChoice void ReaSamplerEditor::paintBrowse(LICE_IBitmap* bmp, int w, int h) { // A full-window modal sheet over the Sample face. Dim the underlying Sample face with a // bg/base wash, then draw the picker opaque on top. LICE_FillRect(bmp, 0, 0, w, h, toLice(roleColor(Role::BgBase)), 0.82f, 0); const BrowseModal bm = computeBrowseModal(w, h); // Title band + Back button (returns to Sample, discarding any pending pick). drawTitleBand(bmp, bm.title, "Browse - pick a capture"); { const KitButtonBox box{toKitBox(bm.back)}; const InteractionState st = isHovered(HoverKind::kBack, -1) ? InteractionState::Hover : InteractionState::Rest; drawButton(bmp, box, "Back", st, /*warn=*/false); } // Search box (type-to-filter). A focused box lifts to Focus + a ring; else Rest/Hover. const Rect searchAbs = bm.search; const InteractionState searchState = searchFocused_ ? InteractionState::Focus : (isHovered(HoverKind::kSearchBox, -1) ? InteractionState::Hover : InteractionState::Rest); fillSurface(bmp, toKitBox(searchAbs), Role::BgCell, searchState); if (searchFocused_) { LICE_DrawRect(bmp, searchAbs.x, searchAbs.y, searchAbs.width - 1, searchAbs.height - 1, toLice(roleColor(Role::TextPrimary)), 1.0f, 0); } { std::string sb = searchQuery_.empty() ? std::string("Search captures...") : ("Search: " + searchQuery_ + (searchFocused_ ? "_" : "")); Rect sbText = Rect::ltrb(searchAbs.x + 6, searchAbs.y, searchAbs.right() - 6, searchAbs.bottom()); kitText(bmp, sbText, sb.c_str(), Font::Label, searchQuery_.empty() ? Role::TextDim : Role::TextPrimary); } // Tabs + card grid, laid out over the content sub-area by the pure module (origin-offset). const Rect browserArea = bm.content; const BrowserLayout bl = layoutBrowser(browserArea.width, browserArea.height); const int ox = browserArea.x; const int oy = browserArea.y; scrollOffset_ = clampScrollOffset(bl, static_cast(visible_.size()), scrollOffset_); const int tabCount = static_cast(banks_.size()) + 1; for (int i = 0; i < tabCount; ++i) { Rect t = filterTabRect(bl, tabCount, i); t = Rect::ltrb(t.x + ox, t.y + oy, t.right() + ox, t.bottom() + oy); const std::string label = (i == 0) ? "All" : banks_[static_cast(i - 1)].displayName; const bool active = (i == 0) ? activeFilterBankId_.empty() : (banks_[static_cast(i - 1)].id == activeFilterBankId_); const InteractionState state = active ? InteractionState::Active : (isHovered(HoverKind::kFilterTab, i) ? InteractionState::Hover : InteractionState::Rest); fillSurface(bmp, toKitBox(t), Role::BgCell, state); kitTextCentered(bmp, t, label.c_str(), Font::Label, active ? Role::BgBase : Role::TextPrimary); } // Cards (the visible window at the current scroll offset). The pending pick // (browsePendingId_) is marked with the accent-primary border; the currently-loaded id // gets a faint tertiary border. const int bins = thumbBins(bl); const int cardCount = static_cast(visible_.size()); const VisibleRange vr = visibleCardRange(bl, cardCount, scrollOffset_); for (int i = vr.first; i < vr.last; ++i) { Rect content = cardContentRect(bl, i); Rect thumb = cardThumbnailRect(bl, i); Rect labelR = cardLabelRect(bl, i); content = Rect::ltrb(content.x + ox, content.y + oy - scrollOffset_, content.right() + ox, content.bottom() + oy - scrollOffset_); thumb = Rect::ltrb(thumb.x + ox, thumb.y + oy - scrollOffset_, thumb.right() + ox, thumb.bottom() + oy - scrollOffset_); labelR = Rect::ltrb(labelR.x + ox, labelR.y + oy - scrollOffset_, labelR.right() + ox, labelR.bottom() + oy - scrollOffset_); const SampleChoice& s = visible_[static_cast(i)]; const bool pending = (s.id == browsePendingId_); const bool loaded = (s.id == selectedId_); const InteractionState cardState = isHovered(HoverKind::kCard, i) ? InteractionState::Hover : InteractionState::Rest; fillSurface(bmp, toKitBox(content), Role::BgCell, cardState); const KitColor cardBorder = pending ? roleColor(Role::AccentPrimary) : (loaded ? roleColor(Role::AccentTertiary) : roleColor(Role::LineHairline)); LICE_DrawRect(bmp, content.x, content.y, content.width - 1, content.height - 1, toLice(cardBorder), 1.0f, 0); drawEnvelope(bmp, thumb, thumbnailFor(s.id, bins)); std::string caption = s.displayName.empty() ? s.id : s.displayName; Rect nameR = Rect::ltrb(labelR.x + 3, labelR.y, labelR.right() - 3, labelR.y + labelR.height / 2); Rect badgeR = Rect::ltrb(labelR.x + 3, nameR.bottom(), labelR.right() - 3, labelR.bottom()); kitText(bmp, nameR, caption.c_str(), Font::Label, Role::TextPrimary); std::string badge; if (s.rootNote) badge = "root " + noteLabel(*s.rootNote); else if (s.key) badge = *s.key; else badge = "root -"; kitText(bmp, badgeR, badge.c_str(), Font::Micro, Role::TextDim); } // Scrollbar thumb. { const Rect thumb = scrollThumbRect(bl, cardCount, scrollOffset_); if (thumb.height > 0) { const bool dragging = (drag_ == DragKind::kScrollThumb); const KitColor tc = roleColor(dragging ? Role::AccentHot : Role::AccentPrimary); LICE_FillRect(bmp, thumb.x + ox, thumb.y + oy, thumb.width, thumb.height, toLice(tc), 0.8f, 0); } } if (visible_.empty()) paintEmptyState(bmp, browserArea); // Footer: Cancel (discard, return to Sample) + Load (commit the pending pick). Load is inert // (no accent) until a card is picked. Draw a footer strip so the buttons read as a modal bar. Rect footer = Rect::ltrb(0, bm.content.bottom(), w, h); fillSurface(bmp, toKitBox(footer), Role::BgPanel, InteractionState::Rest); { const KitButtonBox box{toKitBox(bm.cancel)}; const InteractionState st = isHovered(HoverKind::kBrowseCancel, -1) ? InteractionState::Hover : InteractionState::Rest; drawButton(bmp, box, "Cancel", st, /*warn=*/false); } { const KitButtonBox box{toKitBox(bm.confirm)}; const bool armed = !browsePendingId_.empty(); const InteractionState st = armed ? (isHovered(HoverKind::kBrowseConfirm, -1) ? InteractionState::Hover : InteractionState::Active) : InteractionState::Rest; drawButton(bmp, box, "Load", st, /*warn=*/false); } } } // namespace reasampler::vst #endif // _WIN32