250 lines
8.9 KiB
C++
250 lines
8.9 KiB
C++
// reasampler_editor.cpp — see reasampler_editor.h. The IPlugView<->LICE bridge.
|
|
// Windows-only (D5); the whole file is guarded so a non-Windows build (not a target,
|
|
// but keeps the TU honest) degrades to the CPluginView defaults.
|
|
|
|
#include "reasampler_editor.h"
|
|
|
|
#include <string>
|
|
|
|
#include "editor_geometry.h"
|
|
#include "ext_keys.h"
|
|
#include "reaper_bridge.h"
|
|
#include "reasampler_processor.h"
|
|
#include "sample_map.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM
|
|
|
|
// LICE — the same drawing stack bank_panel uses. On Windows LICE routes through native
|
|
// GDI; no SWELL needed for the child window itself.
|
|
#include "wdltypes.h"
|
|
#include "lice/lice.h"
|
|
#endif
|
|
|
|
using namespace Steinberg;
|
|
|
|
namespace reasampler::vst {
|
|
|
|
namespace {
|
|
#ifdef _WIN32
|
|
constexpr const wchar_t* kChildClassName = L"ReaSamplerVstEditor";
|
|
|
|
// Palette — house style, mirrored from bank_panel's dark theme so the instrument reads
|
|
// as the same tool.
|
|
const LICE_pixel kColBackground = LICE_RGBA(28, 28, 30, 255);
|
|
const LICE_pixel kColTitleBg = LICE_RGBA(20, 20, 22, 255);
|
|
const LICE_pixel kColBtnBg = LICE_RGBA(44, 44, 48, 255);
|
|
const LICE_pixel kColBtnHitBg = LICE_RGBA(58, 96, 84, 255);
|
|
const LICE_pixel kColBtnBorder = LICE_RGBA(120, 200, 160, 255);
|
|
const COLORREF kRgbText = RGB(210, 230, 220);
|
|
|
|
void drawText(LICE_IBitmap* bmp, const Rect& r, const char* s, COLORREF col) {
|
|
// LICE has no built-in font handle here; use GDI text into the bitmap DC, matching
|
|
// bank_panel's drawCenteredText approach (SetTextColor + DrawText on getDC()).
|
|
HDC dc = bmp->getDC();
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, col);
|
|
RECT gr{r.left, r.top, r.right, r.bottom};
|
|
DrawTextA(dc, s, -1, &gr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
|
|
}
|
|
#endif
|
|
} // namespace
|
|
|
|
ReaSamplerEditor::ReaSamplerEditor(ReaSamplerProcessor* processor)
|
|
: CPluginView(nullptr), processor_(processor) {
|
|
// Default view size; the host may resize (canResize() == true).
|
|
ViewRect r(0, 0, 420, 260);
|
|
setRect(r);
|
|
}
|
|
|
|
void ReaSamplerEditor::refreshSampleList() {
|
|
// Main/UI thread only — reads the live bank over the bridge (allocates, calls REAPER).
|
|
if (!processor_) {
|
|
samples_.clear();
|
|
selectedId_.clear();
|
|
return;
|
|
}
|
|
auto banks = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
|
samples_ = banks ? listSamples(*banks) : std::vector<SampleChoice>{};
|
|
selectedId_ = processor_->selectedSampleId();
|
|
}
|
|
|
|
ReaSamplerEditor::~ReaSamplerEditor() {
|
|
#ifdef _WIN32
|
|
// Defensive teardown: the host normally calls removed() (which destroys the child)
|
|
// before releasing us, but if we're destroyed while still attached, don't leak the
|
|
// window — mirror the create in attachedToParent().
|
|
if (childHwnd_) {
|
|
DestroyWindow(childHwnd_);
|
|
childHwnd_ = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
tresult PLUGIN_API ReaSamplerEditor::isPlatformTypeSupported(FIDString type) {
|
|
#ifdef _WIN32
|
|
if (type && std::string(type) == kPlatformTypeHWND) return kResultTrue;
|
|
#endif
|
|
return kResultFalse;
|
|
}
|
|
|
|
tresult PLUGIN_API ReaSamplerEditor::canResize() {
|
|
return kResultTrue;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
void ReaSamplerEditor::attachedToParent() {
|
|
// systemWindow is the parent HWND the host created (CPluginView::attached set it
|
|
// from the void* parent when the type is HWND).
|
|
HWND parent = static_cast<HWND>(systemWindow);
|
|
if (!parent) return;
|
|
|
|
HINSTANCE hInst = reinterpret_cast<HINSTANCE>(
|
|
GetWindowLongPtr(parent, GWLP_HINSTANCE));
|
|
if (!hInst) hInst = GetModuleHandle(nullptr);
|
|
|
|
// Register the child window class once per module.
|
|
static bool classRegistered = false;
|
|
if (!classRegistered) {
|
|
WNDCLASSW wc{};
|
|
wc.lpfnWndProc = &ReaSamplerEditor::wndProc;
|
|
wc.hInstance = hInst;
|
|
wc.lpszClassName = kChildClassName;
|
|
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
RegisterClassW(&wc);
|
|
classRegistered = true;
|
|
}
|
|
|
|
// Snapshot the live bank so the first paint shows the sample list.
|
|
refreshSampleList();
|
|
|
|
const ViewRect& r = getRect();
|
|
childHwnd_ = CreateWindowExW(
|
|
0, kChildClassName, L"", WS_CHILD | WS_VISIBLE, 0, 0, r.getWidth(),
|
|
r.getHeight(), parent, nullptr, hInst, nullptr);
|
|
if (childHwnd_) {
|
|
// Stash `this` so wndProc can route messages back to the instance.
|
|
SetWindowLongPtr(childHwnd_, GWLP_USERDATA,
|
|
reinterpret_cast<LONG_PTR>(this));
|
|
}
|
|
}
|
|
|
|
void ReaSamplerEditor::removedFromParent() {
|
|
if (childHwnd_) {
|
|
DestroyWindow(childHwnd_);
|
|
childHwnd_ = nullptr;
|
|
}
|
|
}
|
|
|
|
tresult PLUGIN_API ReaSamplerEditor::onSize(ViewRect* newSize) {
|
|
// Let CPluginView latch the new rect, then resize the child window to match so the
|
|
// LICE surface fills the host-provided seat.
|
|
tresult res = CPluginView::onSize(newSize);
|
|
if (childHwnd_ && newSize) {
|
|
MoveWindow(childHwnd_, 0, 0, newSize->getWidth(), newSize->getHeight(), TRUE);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
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;
|
|
|
|
// Same double-buffered LICE pattern as bank_panel::paintPanel: draw into a
|
|
// LICE_SysBitmap, then BitBlt to the window DC.
|
|
LICE_SysBitmap bmp(w, h);
|
|
LICE_Clear(&bmp, kColBackground);
|
|
|
|
const EditorLayout layout = layoutEditor(w, h);
|
|
|
|
// Title band: the plugin name + whether a live bank is linked.
|
|
LICE_FillRect(&bmp, layout.titleBar.left, layout.titleBar.top,
|
|
layout.titleBar.width(), layout.titleBar.height(), kColTitleBg, 1.0f,
|
|
0);
|
|
std::string title = "ReaSampler Instrument";
|
|
if (processor_ && processor_->bridge().isConnected()) {
|
|
title += samples_.empty() ? " [bank: empty]" : " [pick a sample]";
|
|
} else {
|
|
title += " [host: no bridge]";
|
|
}
|
|
Rect titleText{layout.titleBar.left + 8, layout.titleBar.top,
|
|
layout.titleBar.right - 8, layout.titleBar.bottom};
|
|
drawText(&bmp, titleText, title.c_str(), kRgbText);
|
|
|
|
// Sample list: one row per bank sample, the selected one highlighted. Clip drawing
|
|
// to rows that fall within the canvas (sampleRowRect computes all; we skip off-screen
|
|
// ones so a huge bank doesn't waste paint).
|
|
for (int i = 0; i < static_cast<int>(samples_.size()); ++i) {
|
|
const Rect row = sampleRowRect(layout, i);
|
|
if (row.top >= layout.canvas.bottom) break; // past the visible area
|
|
const bool sel = !selectedId_.empty() && samples_[i].id == selectedId_;
|
|
LICE_FillRect(&bmp, row.left, row.top, row.width(), row.height(),
|
|
sel ? kColBtnHitBg : kColBtnBg, 1.0f, 0);
|
|
if (sel) {
|
|
LICE_DrawRect(&bmp, row.left, row.top, row.width() - 1, row.height() - 1,
|
|
kColBtnBorder, 1.0f, 0);
|
|
}
|
|
Rect textR{row.left + 8, row.top, row.right - 8, row.bottom};
|
|
const std::string& name = samples_[i].displayName;
|
|
drawText(&bmp, textR, name.empty() ? samples_[i].id.c_str() : name.c_str(),
|
|
kRgbText);
|
|
}
|
|
|
|
BitBlt(hdc, 0, 0, w, h, bmp.getDC(), 0, 0, SRCCOPY);
|
|
}
|
|
|
|
void ReaSamplerEditor::onClick(int x, int y) {
|
|
if (!processor_) return;
|
|
RECT cr{};
|
|
GetClientRect(childHwnd_, &cr);
|
|
const EditorLayout layout = layoutEditor(cr.right - cr.left, cr.bottom - cr.top);
|
|
const int row = sampleRowHitTest(layout, static_cast<int>(samples_.size()), x, y);
|
|
if (row < 0) return;
|
|
|
|
// Select this sample and rebuild the instrument OFF the audio thread (this WM_
|
|
// handler runs on the UI thread). reloadFromBank reads the id we just set.
|
|
processor_->setSelectedSampleId(samples_[row].id);
|
|
processor_->reloadFromBank();
|
|
selectedId_ = samples_[row].id;
|
|
InvalidateRect(childHwnd_, nullptr, FALSE);
|
|
}
|
|
|
|
LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
|
LPARAM lParam) {
|
|
auto* self = reinterpret_cast<ReaSamplerEditor*>(
|
|
GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
|
switch (msg) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps{};
|
|
HDC hdc = BeginPaint(hwnd, &ps);
|
|
if (self) self->paint(hdc);
|
|
EndPaint(hwnd, &ps);
|
|
return 0;
|
|
}
|
|
case WM_LBUTTONDOWN:
|
|
if (self) self->onClick(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
|
|
return 0;
|
|
case WM_ERASEBKGND:
|
|
return 1; // we fully repaint in WM_PAINT; skip the flicker-inducing erase
|
|
default:
|
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
#else // non-Windows: not a build target (D5), but keep the TU compilable.
|
|
|
|
void ReaSamplerEditor::attachedToParent() {}
|
|
void ReaSamplerEditor::removedFromParent() {}
|
|
tresult PLUGIN_API ReaSamplerEditor::onSize(ViewRect* newSize) {
|
|
return CPluginView::onSize(newSize);
|
|
}
|
|
|
|
#endif // _WIN32
|
|
|
|
} // namespace reasampler::vst
|