rmx-19/Source/PluginEditor.cpp

257 lines
9.2 KiB
C++
Raw Normal View History

#include "PluginEditor.h"
#include "BuildInfo.h"
#include <juce_gui_basics/juce_gui_basics.h>
RackEditor::RackEditor (RMX19AudioProcessor& p)
: AudioProcessorEditor (p)
, proc (p)
, display (p)
{
face = juce::Colour (0xff16130f);
2026-09-09 12:53:57 +02:00
backgroundImage = juce::ImageFileFormat::loadFrom (BinaryData::b3_png,
BinaryData::b3_pngSize);
up = new NavButton (-1, "^", "UP");
down = new NavButton (1, "v", "DOWN");
left = new NavButton (-1, "<", "LEFT");
right = new NavButton (1, ">", "RIGHT");
enterB = new OkButton ("ENTER", true);
backB = new OkButton ("BACK", false);
for (auto* b : { (juce::Button*) up, (juce::Button*) down,
(juce::Button*) left, (juce::Button*) right,
(juce::Button*) enterB, (juce::Button*) backB })
addAndMakeVisible (b);
up->onClick = [this] { display.navUp(); };
down->onClick = [this] { display.navDown(); };
left->onClick = [this] { display.navLeft(); };
right->onClick = [this] { display.navRight(); };
enterB->onClick = [this] { display.navEnter(); };
backB->onClick = [this] { display.navExit(); };
addAndMakeVisible (display);
scaleBox.addItem ("50%", 1);
scaleBox.addItem ("75%", 2);
scaleBox.addItem ("100%", 3);
scaleBox.addItem ("125%", 4);
scaleBox.addItem ("150%", 5);
scaleBox.addItem ("200%", 6);
scaleBox.addItem ("250%", 7);
scaleBox.addItem ("300%", 8);
scaleBox.setSelectedId (5, juce::dontSendNotification);
scaleBox.setJustificationType (juce::Justification::centred);
scaleBox.setTextWhenNothingSelected ("UI scale");
scaleBox.setColour (juce::ComboBox::backgroundColourId, juce::Colour (0xff221d18));
scaleBox.setColour (juce::ComboBox::outlineColourId, juce::Colour (0xff6b5f4e));
scaleBox.setColour (juce::ComboBox::textColourId, juce::Colour (0xffd8cdb9));
scaleBox.setColour (juce::ComboBox::arrowColourId, juce::Colour (0xffd8cdb9));
scaleBox.setColour (juce::PopupMenu::backgroundColourId, juce::Colour (0xff1c1813));
scaleBox.setColour (juce::PopupMenu::textColourId, juce::Colour (0xffd8cdb9));
scaleBox.setColour (juce::PopupMenu::highlightedBackgroundColourId, juce::Colour (0xff3a332b));
scaleBox.onChange = [this]
{
switch (scaleBox.getSelectedId())
{
case 1: setScale (0.5f); break;
case 2: setScale (0.75f); break;
case 3: setScale (1.0f); break;
case 4: setScale (1.25f); break;
case 5: setScale (1.5f); break;
case 6: setScale (2.0f); break;
case 7: setScale (2.5f); break;
case 8: setScale (3.0f); break;
default: break;
}
};
addAndMakeVisible (scaleBox);
setWantsKeyboardFocus (true);
setScale (1.5f);
startTimerHz (18);
}
RackEditor::~RackEditor()
{
stopTimer();
}
void RackEditor::paint (juce::Graphics& g)
{
// full-area backdrop so scaled edges stay covered
g.fillAll (juce::Colour (0xff0d0b09));
const auto b = juce::Rectangle<int> (0, 0, baseW, baseH);
2026-09-10 02:26:12 +02:00
// --- metal rack face (b3.png background, cover-scaled) ---
if (backgroundImage.isValid())
{
const float imageAspect = (float) backgroundImage.getWidth() / (float) backgroundImage.getHeight();
const float boxAspect = (float) b.getWidth() / (float) b.getHeight();
int sx = 0, sy = 0, sw = backgroundImage.getWidth(), sh = backgroundImage.getHeight();
if (imageAspect > boxAspect)
{
sw = (int) (backgroundImage.getHeight() * boxAspect);
sx = (backgroundImage.getWidth() - sw) / 2;
}
else
{
sh = (int) (backgroundImage.getWidth() / boxAspect);
sy = (backgroundImage.getHeight() - sh) / 2;
}
g.drawImage (backgroundImage, b.getX(), b.getY(),
juce::roundToInt (b.getWidth() * uiScale),
juce::roundToInt (b.getHeight() * uiScale),
sx, sy, sw, sh);
}
else
{
g.setColour (face);
g.fillRect (b);
}
g.setColour (juce::Colour (0xff08));
g.addTransform (juce::AffineTransform::scale (uiScale));
// brushed texture
g.setColour (juce::Colour::fromRGBA (255, 255, 255, 7));
for (int x = 0; x < b.getWidth(); x += 3)
g.fillRect (x, 0, 1, b.getHeight());
// rack rails (top/bottom)
g.setColour (juce::Colour (0xff221e1a));
g.fillRect (0, 0, b.getWidth(), 4);
g.fillRect (0, b.getHeight() - 4, b.getWidth(), 4);
g.setColour (juce::Colour (0xff5a5347));
g.fillRect (0, 4, b.getWidth(), 1);
g.fillRect (0, b.getHeight() - 5, b.getWidth(), 1);
// brand plate
g.setColour (juce::Colour (0xff614f3a));
g.fillRect (12, 6, 150, 12);
g.setColour (juce::Colour (0xffc9a86f));
g.setFont (juce::FontOptions ("Arial", 10.0f, juce::Font::bold));
g.drawFittedText ("RMX-19", 12, 6, 150, 12, juce::Justification::centred, 1);
// rear engraving text (kept clear of the UI-scale dropdown at the bottom right)
g.setColour (juce::Colour (0xff6e6657));
g.setFont (juce::FontOptions ("Courier New", 8.0f, juce::Font::plain));
2026-09-09 13:58:05 +02:00
const juce::String ser = "RMX-19 S/N 0001-1943-04-19 24BIT/96K SUPER SOUND MODULE";
g.drawFittedText (ser, 12, b.getHeight() - 16, b.getWidth() - 128, 11, juce::Justification::centred, 1);
}
void RackEditor::resized()
{
2026-09-08 21:57:34 +02:00
// everything renders in base 656×248 space, scaled by an affine transform
const juce::AffineTransform tf = juce::AffineTransform::scale (uiScale);
2026-09-08 21:57:34 +02:00
// UI-scale dropdown now scales with the rest of the UI
scaleBox.setTransform (tf);
scaleBox.setBounds (baseW - 112, baseH - 30, 104, 20);
// LCD recessed module
const juce::Rectangle<int> lcdArea (14, 24, 356, 168);
display.setTransform (tf);
display.setBounds (lcdArea);
// nav diamond + enter/back to the right of the LCD
const juce::Rectangle<int> navArea (lcdArea.getRight() + 16, 26, baseW - lcdArea.getRight() - 30, 164);
const int bw = 58, bh = 20;
const int cx = navArea.getX() + (navArea.getWidth() - bw) / 2;
const int rowY = navArea.getY();
up->setTransform (tf); up->setBounds (cx + 6, rowY + 1, bw + 3, bh);
left->setTransform (tf); left->setBounds (navArea.getX() + 1, rowY + bh + 6, bw + 2, bh);
down->setTransform (tf); down->setBounds (cx + 6, rowY + bh + 6 + 1, bw + 3, bh);
right->setTransform (tf); right->setBounds (navArea.getRight() - bw + 3, rowY + bh + 6, bw - 3, bh);
enterB->setTransform (tf); enterB->setBounds (navArea.getX() + 2, rowY + 2 * (bh + 6) + 4, bw + 2, bh);
backB->setTransform (tf); backB->setBounds (navArea.getRight() - bw + 3, rowY + 2 * (bh + 6) + 4, bw - 3, bh);
}
void RackEditor::setScale (float s)
{
uiScale = s;
setSize (juce::roundToInt (baseW * s), juce::roundToInt (baseH * s));
resized(); // ensure child layouts refresh at the new size
}
void RackEditor::timerCallback()
{
powerPulse = 0.5f + 0.5f * std::sin (juce::Time::getMillisecondCounter() * 0.0022f);
// only the pulsing power LED needs redraws; everything else is static
const int ledX = getWidth() - 20, ledY = 18;
repaint (ledX - 26, ledY - 7, 30, 14);
}
bool RackEditor::keyPressed (const juce::KeyPress& k)
{
if (display.keyTrap (k))
{
repaint();
return true;
}
return false;
}
// ---- buttons -------------------------------------------------------------
void RackEditor::NavButton::paintButton (juce::Graphics& g, bool, bool down)
{
const auto b = getLocalBounds();
const juce::Colour base = juce::Colour (0xff221d18);
const juce::Colour edge = juce::Colour (0xff6b5f4e);
const juce::Colour text = juce::Colour (0xfffff3d8);
if (down)
{
g.setColour (edge.darker (0.4f));
g.fillRect (b);
g.setColour (base.brighter (0.1f));
g.fillRect (b.reduced (2, 2));
}
else
{
g.setColour (base);
g.fillRect (b);
g.setColour (edge);
g.drawRect (b, 1);
}
// glyph
g.setColour (text);
g.setFont (juce::FontOptions ("Arial", 12.0f, juce::Font::bold));
g.drawFittedText (mark, b.withTrimmedBottom (9), juce::Justification::centred, 1);
g.setFont (juce::FontOptions ("Arial", 7.0f, juce::Font::plain));
g.drawFittedText (getName(), b.withTrimmedTop (b.getHeight() - 8), juce::Justification::centred, 1);
}
void RackEditor::OkButton::paintButton (juce::Graphics& g, bool over, bool down)
{
const auto b = getLocalBounds();
const juce::Colour base = enter ? juce::Colour (0xff263a28) : juce::Colour (0xff332a20);
const juce::Colour edge = juce::Colour (0xff8a9b6a);
if (down)
{
g.setColour (edge.darker (0.35f));
g.fillRect (b);
g.setColour (base.brighter (0.12f));
g.fillRect (b.reduced (2, 2));
}
else
{
g.setColour (base);
g.fillRect (b);
g.setColour (edge);
g.drawRect (b, 1);
}
g.setColour (over ? juce::Colour (0xffffd977) : juce::Colour (0xffd8d2c6));
g.setFont (juce::FontOptions ("Arial", 9.0f, juce::Font::bold));
g.drawFittedText (getName(), b, juce::Justification::centred, 1);
}