Files
reasampler/src/shell/instrument/editor_paint_browse.cpp
T

160 lines
7.9 KiB
C++

// 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 <algorithm>
#include <cstdint>
#include <string>
#include <vector>
#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<int>(visible_.size()), scrollOffset_);
const int tabCount = static_cast<int>(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<std::size_t>(i - 1)].displayName;
const bool active = (i == 0) ? activeFilterBankId_.empty()
: (banks_[static_cast<std::size_t>(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<int>(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<std::size_t>(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