rmx-19/Source/PluginEditor.cpp
2026-09-09 14:05:20 +02:00

257 lines
9.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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);
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);
// --- metal rack face (b2.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));
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()
{
// everything renders in base 656×248 space, scaled by an affine transform
const juce::AffineTransform tf = juce::AffineTransform::scale (uiScale);
// 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);
}