mirror of
https://codeberg.org/armin/sm-26.git
synced 2026-10-11 17:01:53 +02:00
Replace nav buttons with a virtual joystick pad
This commit is contained in:
parent
7fd884141b
commit
c7ebd3ed4d
6 changed files with 418 additions and 76 deletions
|
|
@ -54,6 +54,7 @@ target_sources(SM-26 PRIVATE
|
||||||
Source/Presets.cpp
|
Source/Presets.cpp
|
||||||
Source/LcdDisplay.cpp
|
Source/LcdDisplay.cpp
|
||||||
Source/MenuSystem.cpp
|
Source/MenuSystem.cpp
|
||||||
|
Source/JoystickPad.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(SM-26 PRIVATE Source)
|
target_include_directories(SM-26 PRIVATE Source)
|
||||||
|
|
|
||||||
287
Source/JoystickPad.cpp
Normal file
287
Source/JoystickPad.cpp
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
#include "JoystickPad.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr float kPi = juce::MathConstants<float>::pi;
|
||||||
|
|
||||||
|
float cardinalAngle (int direction)
|
||||||
|
{
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
case JoystickPad::Right: return 0.0f;
|
||||||
|
case JoystickPad::Down: return kPi * 0.5f;
|
||||||
|
case JoystickPad::Left: return kPi;
|
||||||
|
case JoystickPad::Up: return -kPi * 0.5f;
|
||||||
|
default: return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
juce::Path withCentre (juce::Path p, float cx, float cy)
|
||||||
|
{
|
||||||
|
auto bounds = p.getBounds();
|
||||||
|
p.applyTransform (juce::AffineTransform::translation (cx - bounds.getCentreX(),
|
||||||
|
cy - bounds.getCentreY()));
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Triangle pointing "up" (in screen space) centred on the origin.
|
||||||
|
juce::Path arrowUp (float s)
|
||||||
|
{
|
||||||
|
juce::Path p;
|
||||||
|
p.addTriangle (0.0f, -s * 0.90f,
|
||||||
|
-s * 0.55f, s * 0.55f,
|
||||||
|
s * 0.55f, s * 0.55f);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawArrow (juce::Graphics& g, float cx, float cy, int direction,
|
||||||
|
float r, juce::Colour col)
|
||||||
|
{
|
||||||
|
auto p = arrowUp (r);
|
||||||
|
float angle = cardinalAngle (direction);
|
||||||
|
p.applyTransform (juce::AffineTransform::rotation (angle + kPi * 0.5f, 0.0f, 0.0f));
|
||||||
|
p = withCentre (p, cx, cy);
|
||||||
|
g.setColour (col);
|
||||||
|
g.fillPath (p);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
JoystickPad::JoystickPad()
|
||||||
|
{
|
||||||
|
setOpaque (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::setRepeatSpeed (int initialDelayMs_, int repeatMs_)
|
||||||
|
{
|
||||||
|
initialDelayMs = initialDelayMs_;
|
||||||
|
repeatMs = repeatMs_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int JoystickPad::directionFrom (const juce::MouseEvent& e) const
|
||||||
|
{
|
||||||
|
const float w = (float) getWidth();
|
||||||
|
const float h = (float) getHeight();
|
||||||
|
const float cx = w * 0.5f;
|
||||||
|
const float cy = h * 0.5f;
|
||||||
|
const float wr = juce::jmin (w, h) * 0.5f * 0.78f;
|
||||||
|
|
||||||
|
const float dx = e.position.x - cx;
|
||||||
|
const float dy = e.position.y - cy;
|
||||||
|
const float rad = std::hypot (dy, dx);
|
||||||
|
if (rad < wr * 0.12f)
|
||||||
|
return None;
|
||||||
|
|
||||||
|
const float a = std::atan2 (dy, dx); // -pi..pi, y grows downwards
|
||||||
|
const float oct = kPi * 0.25f;
|
||||||
|
if (a >= -oct && a < oct) return Right;
|
||||||
|
if (a >= oct && a < 3.0f * oct) return Down;
|
||||||
|
if (a >= 3.0f * oct || a < -3.0f * oct) return Left;
|
||||||
|
return Up;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::beginHold (int direction)
|
||||||
|
{
|
||||||
|
active = (Direction) direction;
|
||||||
|
holdStartMs = juce::Time::getMillisecondCounter();
|
||||||
|
lastFireMs = holdStartMs;
|
||||||
|
repeatsStarted = false;
|
||||||
|
|
||||||
|
if (onAction)
|
||||||
|
onAction (direction);
|
||||||
|
|
||||||
|
startTimer (16);
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::endHold()
|
||||||
|
{
|
||||||
|
active = None;
|
||||||
|
stopTimer();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::mouseDown (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
const int d = directionFrom (e);
|
||||||
|
if (d != None)
|
||||||
|
beginHold (d);
|
||||||
|
else
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::mouseDrag (const juce::MouseEvent& e)
|
||||||
|
{
|
||||||
|
const int d = directionFrom (e);
|
||||||
|
if (d != None && d != (int) active)
|
||||||
|
beginHold (d);
|
||||||
|
else if (d == None && active != None)
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::mouseUp (const juce::MouseEvent&)
|
||||||
|
{
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::timerCallback()
|
||||||
|
{
|
||||||
|
if (active == None)
|
||||||
|
{
|
||||||
|
stopTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int64 now = juce::Time::getMillisecondCounter();
|
||||||
|
if (! repeatsStarted)
|
||||||
|
{
|
||||||
|
if (now - lastFireMs >= initialDelayMs)
|
||||||
|
{
|
||||||
|
repeatsStarted = true;
|
||||||
|
lastFireMs = now;
|
||||||
|
if (onAction)
|
||||||
|
onAction ((int) active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (now - lastFireMs >= repeatMs)
|
||||||
|
{
|
||||||
|
lastFireMs = now;
|
||||||
|
if (onAction)
|
||||||
|
onAction ((int) active);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JoystickPad::paint (juce::Graphics& g)
|
||||||
|
{
|
||||||
|
const float w = (float) getWidth();
|
||||||
|
const float h = (float) getHeight();
|
||||||
|
const float cx = w * 0.5f;
|
||||||
|
const float cy = h * 0.5f;
|
||||||
|
|
||||||
|
const float bezelR = juce::jmin (w, h) * 0.5f - 1.0f;
|
||||||
|
const float wellR = bezelR * 0.78f;
|
||||||
|
|
||||||
|
// Clear the full component first (opaque component: every pixel must be
|
||||||
|
// repainted or stale content tears/flickers during redraws).
|
||||||
|
g.fillAll (juce::Colour (0xFF000000));
|
||||||
|
|
||||||
|
// Ambient under-shadow (soft, offset downward).
|
||||||
|
{
|
||||||
|
float alpha = 0.5f;
|
||||||
|
for (int i = 3; i >= 1; --i)
|
||||||
|
{
|
||||||
|
g.setColour (juce::Colour::fromFloatRGBA (0.0f, 0.0f, 0.0f, alpha / (float) i));
|
||||||
|
g.fillEllipse (cx - bezelR - (float) i, cy - bezelR - (float) i + 3.0f,
|
||||||
|
2.0f * (bezelR + (float) i), 2.0f * (bezelR + (float) i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outer bezel: soft radial metal.
|
||||||
|
{
|
||||||
|
juce::ColourGradient bezel (juce::Colour (0xFF2E2E2E), cx - bezelR * 0.35f, cy - bezelR * 0.35f,
|
||||||
|
juce::Colour (0xFF0A0A0A), cx + bezelR * 0.45f, cy + bezelR * 0.45f, true);
|
||||||
|
g.setGradientFill (bezel);
|
||||||
|
g.fillEllipse (cx - bezelR, cy - bezelR, 2.0f * bezelR, 2.0f * bezelR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bezel rim shading for a 3D ring (dark only - no bright top-light arc).
|
||||||
|
{
|
||||||
|
auto ring = [&](float r0, float r1)
|
||||||
|
{
|
||||||
|
juce::Path p;
|
||||||
|
p.addEllipse (cx - r1, cy - r1, r1 * 2.0f, r1 * 2.0f);
|
||||||
|
p.addEllipse (cx - r0, cy - r0, r0 * 2.0f, r0 * 2.0f);
|
||||||
|
p.setUsingNonZeroWinding (false);
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
g.setColour (juce::Colour (0xFF050505));
|
||||||
|
g.fillPath (ring (bezelR * 0.93f, bezelR));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recessed well (flat).
|
||||||
|
{
|
||||||
|
g.setColour (juce::Colour (0xFF0A0A0A));
|
||||||
|
g.fillEllipse (cx - wellR, cy - wellR, 2.0f * wellR, 2.0f * wellR);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto wedgePath = [&](int direction) -> juce::Path
|
||||||
|
{
|
||||||
|
// JUCE measures pie/arc angles clockwise from 12 o'clock, so add
|
||||||
|
// 90 degrees to our visual heading (0 = right, +90 = down).
|
||||||
|
const float a = cardinalAngle (direction) + kPi * 0.5f;
|
||||||
|
const float start = a - kPi * 0.25f;
|
||||||
|
const float end = a + kPi * 0.25f;
|
||||||
|
juce::Path p;
|
||||||
|
p.addPieSegment (cx - wellR, cy - wellR, wellR * 2.0f, wellR * 2.0f,
|
||||||
|
start, end, 0.0f);
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Quarter-button wedges (flat).
|
||||||
|
for (int d = Up; d <= Right; ++d)
|
||||||
|
{
|
||||||
|
auto p = wedgePath (d);
|
||||||
|
g.setColour (juce::Colour (0xFF151515));
|
||||||
|
g.fillPath (p);
|
||||||
|
g.setColour (juce::Colour (0xFF242424));
|
||||||
|
g.strokePath (p, juce::PathStrokeType (1.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flat seams between the quarters (a single dark channel).
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const float a = juce::degreesToRadians (45.0f + 90.0f * (float) i);
|
||||||
|
const float dx = std::cos (a);
|
||||||
|
const float dy = std::sin (a);
|
||||||
|
g.setColour (juce::Colour (0xFF050505));
|
||||||
|
g.drawLine (cx + dx * 2.0f, cy + dy * 2.0f, cx + dx * (wellR - 2.0f), cy + dy * (wellR - 2.0f), 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resting glyphs on the four quarters.
|
||||||
|
for (int d = Up; d <= Right; ++d)
|
||||||
|
if (d != (int) active)
|
||||||
|
drawArrow (g, cx + std::cos (cardinalAngle (d)) * wellR * 0.55f,
|
||||||
|
cy + std::sin (cardinalAngle (d)) * wellR * 0.55f,
|
||||||
|
d, wellR * 0.16f, juce::Colour (0xFF454545));
|
||||||
|
|
||||||
|
// Lit quarter (active direction) - flat bright fill, black glyph.
|
||||||
|
if (active != None)
|
||||||
|
{
|
||||||
|
auto p = wedgePath ((int) active);
|
||||||
|
g.setColour (juce::Colour (0xFFECECEC));
|
||||||
|
g.fillPath (p);
|
||||||
|
drawArrow (g, cx + std::cos (cardinalAngle ((int) active)) * wellR * 0.55f,
|
||||||
|
cy + std::sin (cardinalAngle ((int) active)) * wellR * 0.55f,
|
||||||
|
(int) active, wellR * 0.16f, juce::Colour (0xFF0A0A0A));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Center cap that ties the four quarters together (static, no stick).
|
||||||
|
{
|
||||||
|
const float sr = wellR * 0.30f;
|
||||||
|
juce::ColourGradient cap (juce::Colour (0xFF181818), cx, cy - sr,
|
||||||
|
juce::Colour (0xFF080808), cx, cy + sr, false);
|
||||||
|
g.setGradientFill (cap);
|
||||||
|
g.fillEllipse (cx - sr, cy - sr, sr * 2.0f, sr * 2.0f);
|
||||||
|
g.setColour (juce::Colour (0xFF060606));
|
||||||
|
g.drawEllipse (cx - sr, cy - sr, sr * 2.0f, sr * 2.0f, 1.0f);
|
||||||
|
g.setColour (juce::Colour (0xFF2A2A2A));
|
||||||
|
g.drawEllipse (cx - sr, cy - sr, sr * 2.0f, sr * 2.0f, 0.5f);
|
||||||
|
g.setColour (juce::Colour (0xFF3A3A3A));
|
||||||
|
g.drawEllipse (cx + sr * 0.30f, cy + sr * 0.20f, sr * 0.28f, sr * 0.28f, 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mechanical screws around the bezel.
|
||||||
|
for (int i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const float a = juce::degreesToRadians (45.0f + 90.0f * (float) i);
|
||||||
|
const float srx = cx + std::cos (a) * bezelR * 0.92f;
|
||||||
|
const float sry = cy + std::sin (a) * bezelR * 0.92f;
|
||||||
|
const float sr0 = 2.0f;
|
||||||
|
g.setColour (juce::Colour (0xFF222222));
|
||||||
|
g.fillEllipse (srx - sr0, sry - sr0, sr0 * 2.0f, sr0 * 2.0f);
|
||||||
|
g.setColour (juce::Colour (0xFF080808));
|
||||||
|
g.fillEllipse (srx - sr0 * 0.4f, sry - sr0 * 0.4f, sr0 * 0.8f, sr0 * 0.8f);
|
||||||
|
}
|
||||||
|
}
|
||||||
70
Source/JoystickPad.h
Normal file
70
Source/JoystickPad.h
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <JuceHeader.h>
|
||||||
|
|
||||||
|
// Small flat circular button matching the joystick pad's dark style.
|
||||||
|
class RoundNavButton : public juce::Button
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RoundNavButton (const juce::String& label) : juce::Button (label) {}
|
||||||
|
|
||||||
|
void paintButton (juce::Graphics& g, bool isDown, bool isOver) override
|
||||||
|
{
|
||||||
|
const float w = (float) getWidth();
|
||||||
|
const float h = (float) getHeight();
|
||||||
|
const float cx = w * 0.5f;
|
||||||
|
const float cy = h * 0.5f;
|
||||||
|
const float r = juce::jmin (w, h) * 0.5f - 1.0f;
|
||||||
|
|
||||||
|
g.fillAll (juce::Colour (0xFF000000));
|
||||||
|
|
||||||
|
g.setColour (isDown ? juce::Colour (0xFFECECEC) : juce::Colour (0xFF151515));
|
||||||
|
g.fillEllipse (cx - r, cy - r, r * 2.0f, r * 2.0f);
|
||||||
|
|
||||||
|
g.setColour (isDown ? juce::Colour (0xFFECECEC) : juce::Colour (0xFF242424));
|
||||||
|
g.drawEllipse (cx - r, cy - r, r * 2.0f, r * 2.0f, 1.0f);
|
||||||
|
|
||||||
|
g.setFont (juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(),
|
||||||
|
12.0f, isDown ? juce::Font::bold : juce::Font::plain }));
|
||||||
|
g.setColour (isDown ? juce::Colour (0xFF0A0A0A) : juce::Colour (0xFF9A9A9A));
|
||||||
|
g.drawText (getButtonText(), juce::Rectangle<float> (0, 0, w, h), juce::Justification::centred);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A flat dark virtual-analog joystick pad. A circular well holds four
|
||||||
|
// quarter-circle buttons (up/down/left/right). Holding a direction glows
|
||||||
|
// the matching quarter and emits repeat actions like a gamepad D-pad.
|
||||||
|
class JoystickPad : public juce::Component, private juce::Timer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Direction : int { None = -1, Up = 0, Down = 1, Left = 2, Right = 3 };
|
||||||
|
|
||||||
|
JoystickPad();
|
||||||
|
~JoystickPad() override = default;
|
||||||
|
|
||||||
|
void paint (juce::Graphics&) override;
|
||||||
|
void mouseDown (const juce::MouseEvent&) override;
|
||||||
|
void mouseDrag (const juce::MouseEvent&) override;
|
||||||
|
void mouseUp (const juce::MouseEvent&) override;
|
||||||
|
|
||||||
|
// Called on press (immediately) and then on repeat while held.
|
||||||
|
std::function<void (int direction)> onAction;
|
||||||
|
|
||||||
|
void setRepeatSpeed (int initialDelayMs, int repeatMs);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void timerCallback() override;
|
||||||
|
void beginHold (int direction);
|
||||||
|
void endHold();
|
||||||
|
int directionFrom (const juce::MouseEvent&) const;
|
||||||
|
|
||||||
|
Direction active = None;
|
||||||
|
int initialDelayMs = 250;
|
||||||
|
int repeatMs = 50;
|
||||||
|
|
||||||
|
int64 holdStartMs = 0;
|
||||||
|
int64 lastFireMs = 0;
|
||||||
|
bool repeatsStarted = false;
|
||||||
|
|
||||||
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JoystickPad)
|
||||||
|
};
|
||||||
|
|
@ -240,8 +240,6 @@ void LcdDisplay::timerCallback() { repaint(); }
|
||||||
|
|
||||||
void LcdDisplay::mouseDown (const juce::MouseEvent& e)
|
void LcdDisplay::mouseDown (const juce::MouseEvent& e)
|
||||||
{
|
{
|
||||||
const int row = juce::jlimit (0, kRows - 1,
|
(void)e;
|
||||||
(int) (e.position.y / (float) (getHeight() / kRows)));
|
// Clicking disabled - navigate with joystick keys only.
|
||||||
if (onPressed)
|
|
||||||
onPressed (row);
|
|
||||||
}
|
}
|
||||||
|
|
@ -15,7 +15,7 @@ SM26AudioProcessorEditor::SM26AudioProcessorEditor (SM26AudioProcessor& p)
|
||||||
setResizeLimits (760, 620, 1500, 1300);
|
setResizeLimits (760, 620, 1500, 1300);
|
||||||
|
|
||||||
addAndMakeVisible (lcd);
|
addAndMakeVisible (lcd);
|
||||||
lcd.onPressed = [this](int) { if (! menu.isOpen()) openPresetMenu(); };
|
lcd.onPressed = [](int) {};
|
||||||
setupKnobs();
|
setupKnobs();
|
||||||
setupNavButtons();
|
setupNavButtons();
|
||||||
|
|
||||||
|
|
@ -69,38 +69,31 @@ void SM26AudioProcessorEditor::setupKnobs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
juce::Font SM26AudioProcessorEditor::NavButtonLookAndFeel::getTextButtonFont (juce::TextButton&, int)
|
|
||||||
{
|
|
||||||
return juce::Font (juce::FontOptions { juce::Font::getDefaultMonospacedFontName(),
|
|
||||||
10.5f, juce::Font::bold });
|
|
||||||
}
|
|
||||||
|
|
||||||
void SM26AudioProcessorEditor::setupNavButtons()
|
void SM26AudioProcessorEditor::setupNavButtons()
|
||||||
{
|
{
|
||||||
auto setup = [this](juce::TextButton& btn, juce::Colour bg)
|
addAndMakeVisible (navEnter);
|
||||||
|
addAndMakeVisible (navEsc);
|
||||||
|
|
||||||
|
addAndMakeVisible (navStick);
|
||||||
|
navStick.setRepeatSpeed (250, 50);
|
||||||
|
|
||||||
|
navStick.onAction = [this](int dir) {
|
||||||
|
switch (dir)
|
||||||
{
|
{
|
||||||
btn.setLookAndFeel (&navLookAndFeel);
|
case JoystickPad::Up:
|
||||||
btn.setColour (juce::TextButton::buttonColourId, bg);
|
if (menu.isOpen()) menu.up();
|
||||||
btn.setColour (juce::TextButton::textColourOffId, juce::Colour (0xFFE0E0E0));
|
else cyclePreset (-1);
|
||||||
btn.setColour (juce::ComboBox::outlineColourId, juce::Colour (0xFF444444));
|
break;
|
||||||
addAndMakeVisible (btn);
|
case JoystickPad::Down:
|
||||||
|
if (menu.isOpen()) menu.down();
|
||||||
|
else cyclePreset (+1);
|
||||||
|
break;
|
||||||
|
case JoystickPad::Left: menu.left(); break;
|
||||||
|
case JoystickPad::Right: menu.right(); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setup (navUp, juce::Colour (0xFF000000));
|
|
||||||
setup (navDown, juce::Colour (0xFF000000));
|
|
||||||
setup (navLeft, juce::Colour (0xFF000000));
|
|
||||||
setup (navRight, juce::Colour (0xFF000000));
|
|
||||||
setup (navEnter, juce::Colour (0xFF000000));
|
|
||||||
setup (navEsc, juce::Colour (0xFF000000));
|
|
||||||
|
|
||||||
navUp.setTriggeredOnMouseDown (true);
|
|
||||||
navUp.setRepeatSpeed (250, 50);
|
|
||||||
navDown.setTriggeredOnMouseDown (true);
|
|
||||||
navDown.setRepeatSpeed (250, 50);
|
|
||||||
navUp.onClick = [this] { if (menu.isOpen()) menu.up(); else cyclePreset (-1); };
|
|
||||||
navDown.onClick = [this] { if (menu.isOpen()) menu.down(); else cyclePreset (+1); };
|
|
||||||
navLeft.onClick = [this] { menu.left(); };
|
|
||||||
navRight.onClick = [this] { menu.right(); };
|
|
||||||
navEnter.onClick = [this] { if (menu.isOpen()) menu.enter(); else menu.open(); };
|
navEnter.onClick = [this] { if (menu.isOpen()) menu.enter(); else menu.open(); };
|
||||||
navEsc.onClick = [this] { if (menu.isOpen()) menu.escape(); else menu.close(); };
|
navEsc.onClick = [this] { if (menu.isOpen()) menu.escape(); else menu.close(); };
|
||||||
}
|
}
|
||||||
|
|
@ -332,45 +325,45 @@ void SM26AudioProcessorEditor::resized()
|
||||||
{
|
{
|
||||||
const float totalW = (float) getWidth();
|
const float totalW = (float) getWidth();
|
||||||
const float totalH = (float) getHeight();
|
const float totalH = (float) getHeight();
|
||||||
|
const float footerTop = totalH - 22.0f;
|
||||||
|
|
||||||
float knobSize = 78.0f;
|
// ---- LCD -----------------------------------------------------------
|
||||||
float knobGap = 20.0f;
|
lcd.setBounds (14, 16, (int) totalW - 28, 302);
|
||||||
float knobsW = 4.0f * knobSize + 3.0f * knobGap;
|
const float lcdBottom = 16.0f + 302.0f;
|
||||||
float knobsX = (totalW - knobsW) / 2.0f;
|
|
||||||
float knobsY = juce::jmax (360.0f, totalH - 300.0f);
|
// ---- navigation joystick: directly under the LCD --------------------
|
||||||
|
const float navSize = 30.0f;
|
||||||
|
const float navGap = 3.0f;
|
||||||
|
const float navCenterX = totalW / 2.0f;
|
||||||
|
const float navTop = lcdBottom + 12.0f;
|
||||||
|
const float stickS = juce::jlimit (64.0f, 116.0f, juce::jmin (116.0f, totalW * 0.35f));
|
||||||
|
const float stickLeft = navCenterX - stickS * 0.5f;
|
||||||
|
|
||||||
|
navStick.setBounds ((int) stickLeft, (int) navTop, (int) stickS, (int) stickS);
|
||||||
|
navEnter.setBounds ((int) (stickLeft - navSize - navGap),
|
||||||
|
(int) (navTop + stickS * 0.5f - navSize * 0.5f),
|
||||||
|
(int) navSize, (int) navSize);
|
||||||
|
navEsc.setBounds ((int) (stickLeft + stickS + navGap),
|
||||||
|
(int) (navTop + stickS * 0.5f - navSize * 0.5f),
|
||||||
|
(int) navSize, (int) navSize);
|
||||||
|
|
||||||
|
// ---- knobs: below the navigation -----------------------------------
|
||||||
|
const float knobSize = 62.0f;
|
||||||
|
const float knobGap = 14.0f;
|
||||||
|
const float knobsW = 4.0f * knobSize + 3.0f * knobGap;
|
||||||
|
const float knobsX = (totalW - knobsW) / 2.0f;
|
||||||
|
float knobsY = navTop + stickS + 26.0f;
|
||||||
|
knobsY = juce::jmin (knobsY, footerTop - (knobSize + 31.0f) - 10.0f);
|
||||||
|
|
||||||
for (int i = 0; i < 4; ++i)
|
for (int i = 0; i < 4; ++i)
|
||||||
{
|
{
|
||||||
float x = knobsX + (float) i * (knobSize + knobGap);
|
float x = knobsX + (float) i * (knobSize + knobGap);
|
||||||
knobSliders[i].setBounds ((int) x, (int) knobsY, (int) knobSize, (int) knobSize);
|
knobSliders[i].setBounds ((int) x, (int) knobsY, (int) knobSize, (int) knobSize);
|
||||||
knobLabels[i].setBounds ((int) x, (int) (knobsY + knobSize + 2), (int) knobSize, 18);
|
knobLabels[i].setBounds ((int) x, (int) (knobsY + knobSize + 2), (int) knobSize, 14);
|
||||||
knobValueLabels[i].setBounds ((int) x, (int) (knobsY + knobSize + 20), (int) knobSize, 16);
|
knobValueLabels[i].setBounds ((int) x, (int) (knobsY + knobSize + 16), (int) knobSize, 15);
|
||||||
}
|
}
|
||||||
|
|
||||||
float navSize = 30.0f;
|
footer.setBounds (8, (int) footerTop, (int) totalW - 16, 16);
|
||||||
float navGap = 3.0f;
|
|
||||||
float navCenterX = totalW / 2.0f;
|
|
||||||
float legendBottom = knobsY + knobSize + 20.0f + 16.0f;
|
|
||||||
float footerTop = totalH - 22.0f;
|
|
||||||
float navCenterY = (legendBottom + footerTop) * 0.5f;
|
|
||||||
|
|
||||||
navUp.setBounds ((int) (navCenterX - navSize / 2), (int) (navCenterY - navSize - navGap),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
navDown.setBounds ((int) (navCenterX - navSize / 2), (int) (navCenterY + navGap),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
navLeft.setBounds ((int) (navCenterX - navSize - navGap - navSize / 2), (int) (navCenterY - navSize / 2),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
navRight.setBounds ((int) (navCenterX + navGap + navSize / 2), (int) (navCenterY - navSize / 2),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
|
|
||||||
navEnter.setBounds ((int) (navCenterX - navSize - navGap), (int) (navCenterY + navSize + navGap),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
navEsc.setBounds ((int) (navCenterX + navGap), (int) (navCenterY + navSize + navGap),
|
|
||||||
(int) navSize, (int) navSize);
|
|
||||||
|
|
||||||
lcd.setBounds (14, 16, (int) totalW - 28, 302);
|
|
||||||
|
|
||||||
footer.setBounds (8, (int) totalH - 22, (int) totalW - 16, 16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SM26AudioProcessorEditor::timerCallback()
|
void SM26AudioProcessorEditor::timerCallback()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "PluginProcessor.h"
|
#include "PluginProcessor.h"
|
||||||
#include "LcdDisplay.h"
|
#include "LcdDisplay.h"
|
||||||
#include "MenuSystem.h"
|
#include "MenuSystem.h"
|
||||||
|
#include "JoystickPad.h"
|
||||||
#include "Presets.h"
|
#include "Presets.h"
|
||||||
#include "SynthParams.h"
|
#include "SynthParams.h"
|
||||||
|
|
||||||
|
|
@ -34,12 +35,6 @@ private:
|
||||||
SM26AudioProcessor& proc;
|
SM26AudioProcessor& proc;
|
||||||
MenuSystem menu;
|
MenuSystem menu;
|
||||||
|
|
||||||
class NavButtonLookAndFeel : public juce::LookAndFeel_V4
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
juce::Font getTextButtonFont (juce::TextButton&, int) override;
|
|
||||||
} navLookAndFeel;
|
|
||||||
|
|
||||||
LcdDisplay lcd;
|
LcdDisplay lcd;
|
||||||
|
|
||||||
bool knobDragging[4] = {};
|
bool knobDragging[4] = {};
|
||||||
|
|
@ -48,12 +43,10 @@ private:
|
||||||
juce::Label knobLabels[4];
|
juce::Label knobLabels[4];
|
||||||
juce::Label knobValueLabels[4];
|
juce::Label knobValueLabels[4];
|
||||||
|
|
||||||
juce::TextButton navUp { "UP" };
|
RoundNavButton navEnter { "OK" };
|
||||||
juce::TextButton navDown { "DN" };
|
RoundNavButton navEsc { "ESC" };
|
||||||
juce::TextButton navLeft { "LT" };
|
|
||||||
juce::TextButton navRight { "RT" };
|
JoystickPad navStick;
|
||||||
juce::TextButton navEnter { "OK" };
|
|
||||||
juce::TextButton navEsc { "ESC" };
|
|
||||||
|
|
||||||
juce::Label footer;
|
juce::Label footer;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue