160 lines
7.9 KiB
C++
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
|