// 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 #include "editor_geometry.h" #include "reaper_bridge.h" #ifdef _WIN32 #include // 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(ReaperBridge* bridge) : CPluginView(nullptr), bridge_(bridge) { // Default view size; the host may resize (canResize() == true). ViewRect r(0, 0, 420, 260); setRect(r); } 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(systemWindow); if (!parent) return; HINSTANCE hInst = reinterpret_cast( 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; } 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(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. LICE_FillRect(&bmp, layout.titleBar.left, layout.titleBar.top, layout.titleBar.width(), layout.titleBar.height(), kColTitleBg, 1.0f, 0); // Read a known live-state value over the bridge to prove the read spike. Show the // raw ext-state presence (never the full blob) so the title reflects live project // state without dumping JSON into the UI. std::string title = "ReaSampler Instrument"; if (bridge_ && bridge_->isConnected()) { auto banks = bridge_->readReasamplerExtState("banks"); title += banks ? " [bank: linked]" : " [bank: none]"; } 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); // The clickable button — fill reflects the last hit-test (the routing proof). LICE_FillRect(&bmp, layout.button.left, layout.button.top, layout.button.width(), layout.button.height(), buttonHit_ ? kColBtnHitBg : kColBtnBg, 1.0f, 0); LICE_DrawRect(&bmp, layout.button.left, layout.button.top, layout.button.width(), layout.button.height(), kColBtnBorder, 1.0f, 0); Rect btnText{layout.button.left + 8, layout.button.top, layout.button.right, layout.button.bottom}; drawText(&bmp, btnText, buttonHit_ ? "clicked" : "click me", kRgbText); BitBlt(hdc, 0, 0, w, h, bmp.getDC(), 0, 0, SRCCOPY); } void ReaSamplerEditor::onClick(int x, int y) { RECT cr{}; GetClientRect(childHwnd_, &cr); const EditorLayout layout = layoutEditor(cr.right - cr.left, cr.bottom - cr.top); if (hitTest(layout, x, y) == HitTarget::kButton) { buttonHit_ = !buttonHit_; InvalidateRect(childHwnd_, nullptr, FALSE); } } LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { auto* self = reinterpret_cast( 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