2026-09-07 21:03:31 +02:00
|
|
|
|
#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);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
// circular joystick ring: angles run clockwise from 12 o'clock
|
|
|
|
|
|
up = new DirWedge ("UP", 0.0);
|
|
|
|
|
|
right = new DirWedge ("RIGHT", juce::MathConstants<double>::halfPi);
|
|
|
|
|
|
down = new DirWedge ("DOWN", juce::MathConstants<double>::pi);
|
|
|
|
|
|
left = new DirWedge ("LEFT", -juce::MathConstants<double>::halfPi);
|
|
|
|
|
|
|
|
|
|
|
|
okB = new CentreOkButton ("OK");
|
|
|
|
|
|
escB = new EscButton ("ESC");
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
|
|
|
|
|
for (auto* b : { (juce::Button*) up, (juce::Button*) down,
|
|
|
|
|
|
(juce::Button*) left, (juce::Button*) right,
|
2026-10-02 18:33:31 +02:00
|
|
|
|
(juce::Button*) okB, (juce::Button*) escB })
|
2026-09-07 21:03:31 +02:00
|
|
|
|
addAndMakeVisible (b);
|
|
|
|
|
|
|
|
|
|
|
|
up->onClick = [this] { display.navUp(); };
|
|
|
|
|
|
down->onClick = [this] { display.navDown(); };
|
|
|
|
|
|
left->onClick = [this] { display.navLeft(); };
|
|
|
|
|
|
right->onClick = [this] { display.navRight(); };
|
2026-10-02 18:33:31 +02:00
|
|
|
|
okB->onClick = [this] { display.navEnter(); };
|
|
|
|
|
|
escB->onClick = [this] { display.navExit(); };
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
|
|
|
|
|
addAndMakeVisible (display);
|
|
|
|
|
|
|
2026-09-08 15:10:12 +02:00
|
|
|
|
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);
|
|
|
|
|
|
|
2026-09-07 21:03:31 +02:00
|
|
|
|
setWantsKeyboardFocus (true);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
setScale (1.5f);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
startTimerHz (18);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
RackEditor::~RackEditor()
|
|
|
|
|
|
{
|
|
|
|
|
|
stopTimer();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RackEditor::paint (juce::Graphics& g)
|
|
|
|
|
|
{
|
2026-09-08 15:10:12 +02:00
|
|
|
|
// full-area backdrop so scaled edges stay covered
|
|
|
|
|
|
g.fillAll (juce::Colour (0xff0d0b09));
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-09-08 15:10:12 +02:00
|
|
|
|
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) ---
|
2026-09-08 15:10:12 +02:00
|
|
|
|
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));
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
|
|
|
|
|
// 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));
|
2026-09-08 15:10:12 +02:00
|
|
|
|
g.drawFittedText ("RMX-19", 12, 6, 150, 12, juce::Justification::centred, 1);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-09-08 15:10:12 +02:00
|
|
|
|
// rear engraving text (kept clear of the UI-scale dropdown at the bottom right)
|
2026-09-07 21:03:31 +02:00
|
|
|
|
g.setColour (juce::Colour (0xff6e6657));
|
|
|
|
|
|
g.setFont (juce::FontOptions ("Courier New", 8.0f, juce::Font::plain));
|
2026-09-16 15:21:08 +02:00
|
|
|
|
const juce::String ser = "RMX-19 S/N 0001-1943-04-19 24BIT/96K SUPER SOUND MODULE BUILT "
|
|
|
|
|
|
+ juce::String (BUILD_DATE) + " " + juce::String (BUILD_TIME);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
g.drawFittedText (ser, 12, b.getHeight() - 16, b.getWidth() - 128, 11, juce::Justification::centred, 1);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RackEditor::resized()
|
|
|
|
|
|
{
|
2026-09-08 21:57:34 +02:00
|
|
|
|
// everything renders in base 656×248 space, scaled by an affine transform
|
2026-09-08 15:10:12 +02:00
|
|
|
|
const juce::AffineTransform tf = juce::AffineTransform::scale (uiScale);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
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);
|
|
|
|
|
|
|
2026-09-07 21:03:31 +02:00
|
|
|
|
// LCD recessed module
|
|
|
|
|
|
const juce::Rectangle<int> lcdArea (14, 24, 356, 168);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
display.setTransform (tf);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
display.setBounds (lcdArea);
|
|
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
// circular joystick ring + separate ESC key, to the right of the LCD
|
2026-09-08 15:10:12 +02:00
|
|
|
|
const juce::Rectangle<int> navArea (lcdArea.getRight() + 16, 26, baseW - lcdArea.getRight() - 30, 164);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
constexpr float ro = DirWedge::outerR;
|
|
|
|
|
|
const float ringCx = (float) navArea.getCentreX();
|
|
|
|
|
|
const float ringCy = (float) lcdArea.getCentreY(); // ring sits centred on the display
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
// all four wedges share the ring's bounding box; each paints only its own sector
|
|
|
|
|
|
const juce::Rectangle<int> ringBox ((int) (ringCx - ro), (int) (ringCy - ro),
|
|
|
|
|
|
(int) (ro * 2.0f), (int) (ro * 2.0f));
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
up->setTransform (tf); up->setBounds (ringBox);
|
|
|
|
|
|
right->setTransform (tf); right->setBounds (ringBox);
|
|
|
|
|
|
down->setTransform (tf); down->setBounds (ringBox);
|
|
|
|
|
|
left->setTransform (tf); left->setBounds (ringBox);
|
|
|
|
|
|
|
|
|
|
|
|
// OK, dead centre of the ring
|
|
|
|
|
|
okB->setTransform (tf);
|
|
|
|
|
|
okB->setBounds ((int) (ringCx - CentreOkButton::radius), (int) (ringCy - CentreOkButton::radius),
|
|
|
|
|
|
(int) (CentreOkButton::radius * 2.0f), (int) (CentreOkButton::radius * 2.0f));
|
|
|
|
|
|
|
|
|
|
|
|
// ESC, a round key centred on the point the old rectangular key occupied
|
|
|
|
|
|
constexpr float escD = EscButton::diameter;
|
|
|
|
|
|
const float escCx = (float) navArea.getX() + 54.0f;
|
|
|
|
|
|
const float escCy = (float) ringBox.getBottom() - 10.0f;
|
|
|
|
|
|
escB->setTransform (tf);
|
|
|
|
|
|
escB->setBounds ((int) (escCx - escD * 0.5f), (int) (escCy - escD * 0.5f),
|
|
|
|
|
|
(int) escD, (int) escD);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|
2026-09-07 21:03:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RackEditor::timerCallback()
|
|
|
|
|
|
{
|
|
|
|
|
|
powerPulse = 0.5f + 0.5f * std::sin (juce::Time::getMillisecondCounter() * 0.0022f);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
// 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);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool RackEditor::keyPressed (const juce::KeyPress& k)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (display.keyTrap (k))
|
|
|
|
|
|
{
|
|
|
|
|
|
repaint();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ---- buttons -------------------------------------------------------------
|
|
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
// shared palette for the black joystick keys
|
|
|
|
|
|
static const juce::Colour keyFace (0xff080808); // black key body
|
|
|
|
|
|
static const juce::Colour keyDown (0xff1c1c1c); // pressed
|
|
|
|
|
|
static const juce::Colour keyRim (0xff4a4741); // keeps black keys legible on dark metal
|
|
|
|
|
|
static const juce::Colour keyRimDn (0xff8a8a86);
|
|
|
|
|
|
static const juce::Colour legendIdle (0xfff2f2f2); // white arrow legends
|
|
|
|
|
|
static const juce::Colour legendHot (0xffffffff);
|
|
|
|
|
|
|
|
|
|
|
|
// solid triangular arrow centred on `centre`, pointing along `angleRad`
|
|
|
|
|
|
// (radians clockwise from 12 o'clock, screen y pointing down)
|
|
|
|
|
|
static void drawArrow (juce::Graphics& g, const juce::Point<float>& centre,
|
|
|
|
|
|
double angleRad, float size)
|
2026-09-07 21:03:31 +02:00
|
|
|
|
{
|
2026-10-02 18:33:31 +02:00
|
|
|
|
const float dx = (float) std::sin (angleRad);
|
|
|
|
|
|
const float dy = (float) -std::cos (angleRad);
|
|
|
|
|
|
const float px = -dy, py = dx; // perpendicular, for the two rear corners
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
const float back = size * 0.80f;
|
|
|
|
|
|
const float wide = size * 0.85f;
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
juce::Path t;
|
|
|
|
|
|
t.startNewSubPath (centre.x + dx * size, centre.y + dy * size);
|
|
|
|
|
|
t.lineTo (centre.x - dx * back + px * wide, centre.y - dy * back + py * wide);
|
|
|
|
|
|
t.lineTo (centre.x - dx * back - px * wide, centre.y - dy * back - py * wide);
|
|
|
|
|
|
t.closeSubPath();
|
|
|
|
|
|
g.fillPath (t);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
void RackEditor::DirWedge::paintButton (juce::Graphics& g, bool over, bool down)
|
2026-09-07 21:03:31 +02:00
|
|
|
|
{
|
2026-10-02 18:33:31 +02:00
|
|
|
|
const juce::Point<float> c (outerR, outerR);
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
// a pressed key sinks slightly into the ring
|
|
|
|
|
|
const float ro = down ? outerR - 1.5f : outerR;
|
|
|
|
|
|
const float ri = down ? innerR + 1.5f : innerR;
|
2026-09-07 21:03:31 +02:00
|
|
|
|
|
2026-10-02 18:33:31 +02:00
|
|
|
|
const double a0 = angle - halfSpan;
|
|
|
|
|
|
const double a1 = angle + halfSpan;
|
|
|
|
|
|
|
|
|
|
|
|
juce::Path wedge;
|
|
|
|
|
|
wedge.startNewSubPath (c.getPointOnCircumference (ro, a0));
|
|
|
|
|
|
wedge.addCentredArc (c.x, c.y, ro, ro, 0.0f, a0, a1, false);
|
|
|
|
|
|
wedge.lineTo (c.getPointOnCircumference (ri, a1));
|
|
|
|
|
|
wedge.addCentredArc (c.x, c.y, ri, ri, 0.0f, a1, a0, false);
|
|
|
|
|
|
wedge.closeSubPath();
|
|
|
|
|
|
|
|
|
|
|
|
g.setColour (down ? keyDown : keyFace);
|
|
|
|
|
|
g.fillPath (wedge);
|
|
|
|
|
|
g.setColour (down ? keyRimDn : keyRim);
|
|
|
|
|
|
g.strokePath (wedge, juce::PathStrokeType (1.0f));
|
|
|
|
|
|
|
|
|
|
|
|
// white arrow legend, centred across the wedge band and pointing outwards
|
|
|
|
|
|
g.setColour (over || down ? legendHot : legendIdle);
|
|
|
|
|
|
drawArrow (g, c.getPointOnCircumference ((innerR + outerR) * 0.5f, angle), angle, 11.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool RackEditor::DirWedge::hitTest (int x, int y)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (! juce::Component::hitTest (x, y))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
const float dx = (float) x - outerR;
|
|
|
|
|
|
const float dy = (float) y - outerR;
|
|
|
|
|
|
const float r = std::sqrt (dx * dx + dy * dy);
|
|
|
|
|
|
|
|
|
|
|
|
if (r < innerR || r > outerR)
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
// same convention as the painted sector: radians clockwise from 12 o'clock
|
|
|
|
|
|
double diff = std::atan2 (dx, -dy) - angle;
|
|
|
|
|
|
|
|
|
|
|
|
for (; diff > juce::MathConstants<double>::pi; diff -= juce::MathConstants<double>::twoPi) {}
|
|
|
|
|
|
for (; diff < -juce::MathConstants<double>::pi; diff += juce::MathConstants<double>::twoPi) {}
|
|
|
|
|
|
|
|
|
|
|
|
return std::abs (diff) <= halfSpan;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool RackEditor::CentreOkButton::hitTest (int x, int y)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (! juce::Component::hitTest (x, y))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
const float dx = (float) x - radius;
|
|
|
|
|
|
const float dy = (float) y - radius;
|
|
|
|
|
|
|
|
|
|
|
|
return dx * dx + dy * dy <= radius * radius;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RackEditor::CentreOkButton::paintButton (juce::Graphics& g, bool over, bool down)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto c = getLocalBounds().getCentre().toFloat();
|
|
|
|
|
|
const float r = radius - (down ? 1.0f : 0.0f);
|
|
|
|
|
|
|
|
|
|
|
|
juce::Path disc;
|
|
|
|
|
|
disc.addEllipse (c.x - r, c.y - r, r * 2.0f, r * 2.0f);
|
|
|
|
|
|
|
|
|
|
|
|
g.setColour (down ? keyDown : keyFace);
|
|
|
|
|
|
g.fillPath (disc);
|
|
|
|
|
|
g.setColour (down ? keyRimDn : keyRim);
|
|
|
|
|
|
g.strokePath (disc, juce::PathStrokeType (1.0f));
|
|
|
|
|
|
|
|
|
|
|
|
g.setColour (over || down ? legendHot : legendIdle);
|
|
|
|
|
|
g.setFont (juce::FontOptions ("Arial", 9.5f, juce::Font::bold));
|
|
|
|
|
|
g.drawText (getName(), getLocalBounds(), juce::Justification::centred, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool RackEditor::EscButton::hitTest (int x, int y)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (! juce::Component::hitTest (x, y))
|
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
const float r = diameter * 0.5f;
|
|
|
|
|
|
const float dx = (float) x - r;
|
|
|
|
|
|
const float dy = (float) y - r;
|
|
|
|
|
|
|
|
|
|
|
|
return dx * dx + dy * dy <= r * r;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void RackEditor::EscButton::paintButton (juce::Graphics& g, bool over, bool down)
|
|
|
|
|
|
{
|
|
|
|
|
|
const auto c = getLocalBounds().getCentre().toFloat();
|
|
|
|
|
|
const float r = diameter * 0.5f - (down ? 1.0f : 0.0f);
|
|
|
|
|
|
|
|
|
|
|
|
juce::Path disc;
|
|
|
|
|
|
disc.addEllipse (c.x - r, c.y - r, r * 2.0f, r * 2.0f);
|
|
|
|
|
|
|
|
|
|
|
|
g.setColour (down ? keyDown : keyFace);
|
|
|
|
|
|
g.fillPath (disc);
|
|
|
|
|
|
g.setColour (down ? keyRimDn : keyRim);
|
|
|
|
|
|
g.strokePath (disc, juce::PathStrokeType (1.0f));
|
|
|
|
|
|
|
|
|
|
|
|
g.setColour (over || down ? legendHot : legendIdle);
|
|
|
|
|
|
g.setFont (juce::FontOptions ("Arial", 7.0f, juce::Font::bold));
|
|
|
|
|
|
g.drawText (getName(), getLocalBounds(), juce::Justification::centred, true);
|
2026-09-08 15:10:12 +02:00
|
|
|
|
}
|