// editor_paint.cpp — the ReaSamplerEditor's paint dispatch: the WM_PAINT entry, the Sample // face's band composition (chrome / waveform / decks, each drawn by its own TU), the empty // state, and the drop-affordance banner. Windows-only; draws through the shared kit by // palette role. All layout math is pure (sample_bands) — this TU only sequences. #include "shell/instrument/reasampler_editor.h" #ifdef _WIN32 #include #include #include "shell/instrument/editor_internal.h" // kit adapters #include "shell/instrument/reasampler_processor.h" namespace reasampler::vst { using namespace reasampler::ui; // kit vocabulary (Role / InteractionState / KitBox / …) using namespace reasampler::instrument::ui; // pure geometry (bands / chrome) void ReaSamplerEditor::paint(HDC hdc) { RECT cr{}; GetClientRect(childHwnd_, &cr); const int w = cr.right - cr.left; const int h = cr.bottom - cr.top; if (w <= 0 || h <= 0) return; LICE_SysBitmap bmp(w, h); LICE_Clear(&bmp, toLice(roleColor(Role::BgBase))); // Sample is home; Browse is a full-window modal overlay drawn over it, so the Sample // face draws first and the modal reads as a sheet layered on top. paintSample(&bmp, w, h); if (view_ == View::kBrowse) paintBrowse(&bmp, w, h); // A transient banner flashed after a file was dropped on this window. It reiterates the // shipped ingest gesture rather than swallowing the drop silently. Drawn last so it // overlays whatever view is up; decays via onSyncTimer (dropHintTicks_). if (dropHintTicks_ > 0) { const int bannerTop = (std::min)(kTitleHeight, h); const int bannerH = (std::min)(kTitleHeight + 8, (std::max)(0, h - bannerTop)); Rect banner = Rect::ltrb(0, bannerTop, w, bannerTop + bannerH); // A transient notice, not the live layer — draw it on the accent-tertiary categorical // hue with a dark label so it reads as "attention, not action". fillSurface(&bmp, toKitBox(banner), Role::AccentTertiary, InteractionState::Rest); kitTextCentered(&bmp, banner, "Dropped here isn't loaded yet - drop files onto the ReaSampler bank panel to add them.", Font::Label, Role::BgBase); } BitBlt(hdc, 0, 0, w, h, bmp.getDC(), 0, 0, SRCCOPY); } void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) { const FaceLayout fl = faceLayout(w, h); const bool empty = selectedId_.empty(); paintChrome(bmp, fl, empty); // Nothing loaded: the lower bands carry the "pick a capture" prompt pointing at Browse // (which the chrome lit above), and there is nothing to deck. if (empty) { Rect body = Rect::ltrb(fl.bands.waveform.x, fl.bands.waveform.y, fl.bands.waveform.right(), fl.bands.decks.bottom()); paintEmptyState(bmp, body); return; } paintWaveform(bmp, fl.bands.waveform); paintDeck(bmp, fl); // The curve popup: a centered sheet over the whole face, drawn last. if (curvePopup_ != CurveTarget::kNone) paintCurvePopup(bmp, w, h); // The piano strip's note-name chip overhangs its band, so it goes on top of everything. paintChromeTooltip(bmp, fl, w, h); } void ReaSamplerEditor::paintEmptyState(LICE_IBitmap* bmp, const Rect& area) { // Shown when no card is drawn (nothing to pick): distinguish a genuinely empty bank from // a bank filter that hides everything. Either way it is the "pick a capture" empty state. const char* msg = samples_.empty() ? "No captures in this project yet - capture audio into the bank to play it here." : "No captures in this bank filter. Choose another bank tab above."; // Split the area so the primary line sits centered and the ingest affordance sits just // below it. The affordance is the shipped ingest gesture (drop onto the docked panel) — // kept discoverable here regardless of whether a drop ever lands on this window. Rect primary = Rect::ltrb(area.x, area.y, area.right(), area.y + area.height / 2); Rect hint = Rect::ltrb(area.x, primary.bottom(), area.right(), area.bottom()); kitTextCentered(bmp, primary, msg, Font::Label, Role::TextDim); kitTextCentered(bmp, hint, "To add a sample: drop a file onto the ReaSampler bank panel (the docked window).", Font::Micro, Role::TextDim); } } // namespace reasampler::vst #endif // _WIN32