GUI polish: 3D metal knobs, Rajdhani font, background texture, scaling UI
- Custom Rajdhani font (Regular/Medium/Bold) embedded via BinaryData - Background carbon fiber noise texture overlay - 3D metal knobs with radial gradient, rim, highlight, center cap - Orange type (ADSR/Master/Pitch/Pan) + Dark/blue type (FX/Filter) - Intense glow on value arc (5 layers: outer -> hot center) - Intense glow on pointer (4 layers) - All thicknesses scale proportionally with knob pixel size - FX panel: bordered boxes for each section with gradient background - Pad glow: cyan multi-pass glow on selected pad - Pad numbers: dark background badge for contrast - Waveform display: grid lines + center reference line - VU meter: peak hold indicator + dB scale markers - Buttons: gradient fill + hover highlight + rounded border - All fonts and spacing scale proportionally with window size - Top bar: darker header with gradient - Double-click resets knobs to default values Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,12 @@ juce_add_plugin(InstaDrums
|
||||
|
||||
juce_generate_juce_header(InstaDrums)
|
||||
|
||||
juce_add_binary_data(InstaDrumsData SOURCES
|
||||
Resources/Rajdhani-Regular.ttf
|
||||
Resources/Rajdhani-Medium.ttf
|
||||
Resources/Rajdhani-Bold.ttf
|
||||
)
|
||||
|
||||
target_sources(InstaDrums
|
||||
PRIVATE
|
||||
Source/PluginProcessor.cpp
|
||||
@@ -42,6 +48,7 @@ target_compile_definitions(InstaDrums
|
||||
|
||||
target_link_libraries(InstaDrums
|
||||
PRIVATE
|
||||
InstaDrumsData
|
||||
juce::juce_audio_basics
|
||||
juce::juce_audio_devices
|
||||
juce::juce_audio_formats
|
||||
|
||||
BINáris
Resources/Rajdhani-Bold.ttf
Normal file
BINáris
Resources/Rajdhani-Bold.ttf
Normal file
Binary file not shown.
BINáris
Resources/Rajdhani-Medium.ttf
Normal file
BINáris
Resources/Rajdhani-Medium.ttf
Normal file
Binary file not shown.
BINáris
Resources/Rajdhani-Regular.ttf
Normal file
BINáris
Resources/Rajdhani-Regular.ttf
Normal file
Binary file not shown.
@@ -26,10 +26,11 @@ void FxPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& na
|
||||
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
|
||||
s.setRange (min, max, step);
|
||||
s.setValue (val, juce::dontSendNotification);
|
||||
s.setDoubleClickReturnValue (true, val);
|
||||
s.getProperties().set (InstaDrumsLookAndFeel::knobTypeProperty, "dark");
|
||||
addAndMakeVisible (s);
|
||||
|
||||
l.setText (name, juce::dontSendNotification);
|
||||
l.setFont (juce::FontOptions (9.0f));
|
||||
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
l.setJustificationType (juce::Justification::centred);
|
||||
addAndMakeVisible (l);
|
||||
@@ -38,7 +39,6 @@ void FxPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& na
|
||||
void FxPanel::setupTitle (juce::Label& l, const juce::String& text)
|
||||
{
|
||||
l.setText (text, juce::dontSendNotification);
|
||||
l.setFont (juce::FontOptions (10.0f, juce::Font::bold));
|
||||
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
|
||||
l.setJustificationType (juce::Justification::centredLeft);
|
||||
addAndMakeVisible (l);
|
||||
@@ -47,70 +47,116 @@ void FxPanel::setupTitle (juce::Label& l, const juce::String& text)
|
||||
void FxPanel::paint (juce::Graphics& g)
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat();
|
||||
g.setColour (InstaDrumsLookAndFeel::bgMedium);
|
||||
float scale = (float) getHeight() / 220.0f;
|
||||
|
||||
juce::ColourGradient bgGrad (InstaDrumsLookAndFeel::bgMedium, 0, bounds.getY(),
|
||||
InstaDrumsLookAndFeel::bgMedium.darker (0.2f), 0, bounds.getBottom(), false);
|
||||
g.setGradientFill (bgGrad);
|
||||
g.fillRoundedRectangle (bounds, 6.0f);
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.5f));
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.6f));
|
||||
g.drawRoundedRectangle (bounds, 6.0f, 1.0f);
|
||||
|
||||
// "FX" header
|
||||
// "FX" header bar
|
||||
int headerH = (int) (22 * scale);
|
||||
auto headerBar = bounds.reduced (1).removeFromTop ((float) headerH);
|
||||
g.setColour (InstaDrumsLookAndFeel::bgDark.withAlpha (0.5f));
|
||||
g.fillRoundedRectangle (headerBar.getX(), headerBar.getY(), headerBar.getWidth(), headerBar.getHeight(), 5.0f);
|
||||
g.setColour (InstaDrumsLookAndFeel::textSecondary);
|
||||
g.setFont (juce::FontOptions (14.0f, juce::Font::bold));
|
||||
g.drawText ("FX", bounds.reduced (6, 4).removeFromTop (18), juce::Justification::centredLeft);
|
||||
g.setFont (juce::FontOptions (std::max (13.0f, 16.0f * scale), juce::Font::bold));
|
||||
g.drawText ("FX", headerBar.reduced (8, 0), juce::Justification::centredLeft);
|
||||
|
||||
// Bordered boxes for each FX section
|
||||
auto area = bounds.reduced (6);
|
||||
area.removeFromTop ((float) headerH + 4);
|
||||
float halfW = area.getWidth() / 2;
|
||||
float rowH = area.getHeight() / 2;
|
||||
|
||||
auto drawFxBox = [&] (juce::Rectangle<float> r)
|
||||
{
|
||||
g.setColour (InstaDrumsLookAndFeel::bgDark.withAlpha (0.4f));
|
||||
g.fillRoundedRectangle (r, 4.0f);
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.35f));
|
||||
g.drawRoundedRectangle (r, 4.0f, 1.0f);
|
||||
};
|
||||
|
||||
float gap = 3.0f;
|
||||
drawFxBox ({ area.getX(), area.getY(), halfW - gap, rowH - gap });
|
||||
drawFxBox ({ area.getX() + halfW + gap, area.getY(), halfW - gap, rowH - gap });
|
||||
drawFxBox ({ area.getX(), area.getY() + rowH + gap, halfW - gap, rowH - gap });
|
||||
drawFxBox ({ area.getX() + halfW + gap, area.getY() + rowH + gap, halfW - gap, rowH - gap });
|
||||
}
|
||||
|
||||
void FxPanel::resized()
|
||||
{
|
||||
auto area = getLocalBounds().reduced (6);
|
||||
area.removeFromTop (20); // FX header
|
||||
auto area = getLocalBounds().reduced (8);
|
||||
float scale = (float) getHeight() / 220.0f;
|
||||
|
||||
float titleSize = std::max (10.0f, 13.0f * scale);
|
||||
float labelSize = std::max (9.0f, 12.0f * scale);
|
||||
int labelH = (int) (labelSize + 6);
|
||||
int headerH = (int) (22 * scale);
|
||||
int titleH = (int) (titleSize + 4);
|
||||
|
||||
area.removeFromTop (headerH + 4);
|
||||
|
||||
int halfW = area.getWidth() / 2;
|
||||
int rowH = area.getHeight() / 2;
|
||||
|
||||
// Top row: Compressor | EQ
|
||||
auto topRow = area.removeFromTop (rowH);
|
||||
// Update title fonts
|
||||
compTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
eqTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
distTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
reverbTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
|
||||
auto layoutSection = [&] (juce::Rectangle<int> secArea, juce::Label& title,
|
||||
juce::Slider* sliders[], juce::Label* labels[], int count)
|
||||
{
|
||||
auto compArea = topRow.removeFromLeft (halfW).reduced (2);
|
||||
compTitle.setBounds (compArea.removeFromTop (14));
|
||||
int kw = compArea.getWidth() / 2;
|
||||
auto c1 = compArea.removeFromLeft (kw);
|
||||
compThreshLabel.setBounds (c1.removeFromBottom (12));
|
||||
compThreshSlider.setBounds (c1);
|
||||
compRatioLabel.setBounds (compArea.removeFromBottom (12));
|
||||
compRatioSlider.setBounds (compArea);
|
||||
}
|
||||
title.setBounds (secArea.removeFromTop (titleH).reduced (4, 0));
|
||||
int kw = secArea.getWidth() / count;
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
labels[i]->setFont (juce::FontOptions (labelSize));
|
||||
auto col = secArea.removeFromLeft (kw);
|
||||
labels[i]->setBounds (col.removeFromBottom (labelH));
|
||||
sliders[i]->setBounds (col);
|
||||
}
|
||||
};
|
||||
|
||||
// Top-left: Compressor
|
||||
{
|
||||
auto eqArea = topRow.reduced (2);
|
||||
eqTitle.setBounds (eqArea.removeFromTop (14));
|
||||
int kw = eqArea.getWidth() / 3;
|
||||
auto c1 = eqArea.removeFromLeft (kw);
|
||||
eqLoLabel.setBounds (c1.removeFromBottom (12));
|
||||
eqLoSlider.setBounds (c1);
|
||||
auto c2 = eqArea.removeFromLeft (kw);
|
||||
eqMidLabel.setBounds (c2.removeFromBottom (12));
|
||||
eqMidSlider.setBounds (c2);
|
||||
eqHiLabel.setBounds (eqArea.removeFromBottom (12));
|
||||
eqHiSlider.setBounds (eqArea);
|
||||
auto sec = area.removeFromTop (rowH).removeFromLeft (halfW).reduced (4, 2);
|
||||
juce::Slider* s[] = { &compThreshSlider, &compRatioSlider };
|
||||
juce::Label* l[] = { &compThreshLabel, &compRatioLabel };
|
||||
layoutSection (sec, compTitle, s, l, 2);
|
||||
}
|
||||
|
||||
// Bottom row: Distortion | Reverb
|
||||
// Top-right: EQ (need to recalculate since we consumed area)
|
||||
// Reset area for right side
|
||||
auto rightTop = getLocalBounds().reduced (8);
|
||||
rightTop.removeFromTop ((int) (headerH + 4));
|
||||
rightTop.removeFromLeft (halfW);
|
||||
rightTop = rightTop.removeFromTop (rowH).reduced (4, 2);
|
||||
{
|
||||
auto distArea = area.removeFromLeft (halfW).reduced (2);
|
||||
distTitle.setBounds (distArea.removeFromTop (14));
|
||||
int kw = distArea.getWidth() / 2;
|
||||
auto c1 = distArea.removeFromLeft (kw);
|
||||
distDriveLabel.setBounds (c1.removeFromBottom (12));
|
||||
distDriveSlider.setBounds (c1);
|
||||
distMixLabel.setBounds (distArea.removeFromBottom (12));
|
||||
distMixSlider.setBounds (distArea);
|
||||
juce::Slider* s[] = { &eqLoSlider, &eqMidSlider, &eqHiSlider };
|
||||
juce::Label* l[] = { &eqLoLabel, &eqMidLabel, &eqHiLabel };
|
||||
layoutSection (rightTop, eqTitle, s, l, 3);
|
||||
}
|
||||
|
||||
// Bottom-left: Distortion
|
||||
auto bottomArea = getLocalBounds().reduced (8);
|
||||
bottomArea.removeFromTop ((int) (headerH + 4 + rowH));
|
||||
{
|
||||
auto revArea = area.reduced (2);
|
||||
reverbTitle.setBounds (revArea.removeFromTop (14));
|
||||
int kw = revArea.getWidth() / 2;
|
||||
auto c1 = revArea.removeFromLeft (kw);
|
||||
reverbSizeLabel.setBounds (c1.removeFromBottom (12));
|
||||
reverbSizeSlider.setBounds (c1);
|
||||
reverbDecayLabel.setBounds (revArea.removeFromBottom (12));
|
||||
reverbDecaySlider.setBounds (revArea);
|
||||
auto sec = bottomArea.removeFromLeft (halfW).reduced (4, 2);
|
||||
juce::Slider* s[] = { &distDriveSlider, &distMixSlider };
|
||||
juce::Label* l[] = { &distDriveLabel, &distMixLabel };
|
||||
layoutSection (sec, distTitle, s, l, 2);
|
||||
}
|
||||
|
||||
// Bottom-right: Reverb
|
||||
{
|
||||
auto sec = bottomArea.reduced (4, 2);
|
||||
juce::Slider* s[] = { &reverbSizeSlider, &reverbDecaySlider };
|
||||
juce::Label* l[] = { &reverbSizeLabel, &reverbDecayLabel };
|
||||
layoutSection (sec, reverbTitle, s, l, 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,271 @@
|
||||
#include "LookAndFeel.h"
|
||||
#include "BinaryData.h"
|
||||
|
||||
InstaDrumsLookAndFeel::InstaDrumsLookAndFeel()
|
||||
{
|
||||
// Load embedded fonts
|
||||
typefaceRegular = juce::Typeface::createSystemTypefaceFor (
|
||||
BinaryData::RajdhaniRegular_ttf, BinaryData::RajdhaniRegular_ttfSize);
|
||||
typefaceMedium = juce::Typeface::createSystemTypefaceFor (
|
||||
BinaryData::RajdhaniMedium_ttf, BinaryData::RajdhaniMedium_ttfSize);
|
||||
typefaceBold = juce::Typeface::createSystemTypefaceFor (
|
||||
BinaryData::RajdhaniBold_ttf, BinaryData::RajdhaniBold_ttfSize);
|
||||
|
||||
setColour (juce::ResizableWindow::backgroundColourId, bgDark);
|
||||
setColour (juce::Label::textColourId, textPrimary);
|
||||
setColour (juce::TextButton::buttonColourId, bgMedium);
|
||||
setColour (juce::TextButton::textColourOffId, textPrimary);
|
||||
|
||||
generateNoiseTexture();
|
||||
}
|
||||
|
||||
juce::Typeface::Ptr InstaDrumsLookAndFeel::getTypefaceForFont (const juce::Font& font)
|
||||
{
|
||||
if (font.isBold())
|
||||
return typefaceBold;
|
||||
return typefaceRegular;
|
||||
}
|
||||
|
||||
juce::Font InstaDrumsLookAndFeel::getRegularFont (float height) const
|
||||
{
|
||||
return juce::Font (juce::FontOptions (typefaceRegular).withHeight (height));
|
||||
}
|
||||
|
||||
juce::Font InstaDrumsLookAndFeel::getMediumFont (float height) const
|
||||
{
|
||||
return juce::Font (juce::FontOptions (typefaceMedium).withHeight (height));
|
||||
}
|
||||
|
||||
juce::Font InstaDrumsLookAndFeel::getBoldFont (float height) const
|
||||
{
|
||||
return juce::Font (juce::FontOptions (typefaceBold).withHeight (height));
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Background noise texture (subtle carbon fiber / grain effect)
|
||||
// ============================================================
|
||||
|
||||
void InstaDrumsLookAndFeel::generateNoiseTexture()
|
||||
{
|
||||
const int texW = 256, texH = 256;
|
||||
noiseTexture = juce::Image (juce::Image::ARGB, texW, texH, true);
|
||||
|
||||
juce::Random rng (42);
|
||||
|
||||
for (int y = 0; y < texH; ++y)
|
||||
{
|
||||
for (int x = 0; x < texW; ++x)
|
||||
{
|
||||
// Subtle noise grain
|
||||
float noise = rng.nextFloat() * 0.06f;
|
||||
|
||||
// Carbon fiber pattern: diagonal cross-hatch
|
||||
bool crossA = ((x + y) % 4 == 0);
|
||||
bool crossB = ((x - y + 256) % 4 == 0);
|
||||
float pattern = (crossA || crossB) ? 0.03f : 0.0f;
|
||||
|
||||
float alpha = noise + pattern;
|
||||
noiseTexture.setPixelAt (x, y, juce::Colour::fromFloatRGBA (1.0f, 1.0f, 1.0f, alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InstaDrumsLookAndFeel::drawBackgroundTexture (juce::Graphics& g, juce::Rectangle<int> area)
|
||||
{
|
||||
// Tile the noise texture
|
||||
for (int y = area.getY(); y < area.getBottom(); y += noiseTexture.getHeight())
|
||||
for (int x = area.getX(); x < area.getRight(); x += noiseTexture.getWidth())
|
||||
g.drawImageAt (noiseTexture, x, y);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Rotary slider (3D metal knob)
|
||||
// ============================================================
|
||||
|
||||
void InstaDrumsLookAndFeel::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
|
||||
float sliderPos, float rotaryStartAngle,
|
||||
float rotaryEndAngle, juce::Slider& slider)
|
||||
{
|
||||
auto bounds = juce::Rectangle<int> (x, y, width, height).toFloat().reduced (2.0f);
|
||||
// Scale factor based on knob pixel size (reference: 60px)
|
||||
float knobSize = std::min ((float) width, (float) height);
|
||||
float s = knobSize / 60.0f; // 1.0 at 60px, smaller below, larger above
|
||||
float margin = std::max (4.0f, 6.0f * s);
|
||||
|
||||
auto bounds = juce::Rectangle<int> (x, y, width, height).toFloat().reduced (margin);
|
||||
auto radius = std::min (bounds.getWidth(), bounds.getHeight()) / 2.0f;
|
||||
auto centreX = bounds.getCentreX();
|
||||
auto centreY = bounds.getCentreY();
|
||||
auto cx = bounds.getCentreX();
|
||||
auto cy = bounds.getCentreY();
|
||||
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
|
||||
|
||||
// Background arc
|
||||
juce::Path bgArc;
|
||||
bgArc.addCentredArc (centreX, centreY, radius - 2, radius - 2,
|
||||
0.0f, rotaryStartAngle, rotaryEndAngle, true);
|
||||
g.setColour (bgLight);
|
||||
g.strokePath (bgArc, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
auto knobType = slider.getProperties() [knobTypeProperty].toString();
|
||||
bool isDark = (knobType == "dark");
|
||||
|
||||
// Value arc
|
||||
juce::Path valueArc;
|
||||
valueArc.addCentredArc (centreX, centreY, radius - 2, radius - 2,
|
||||
0.0f, rotaryStartAngle, angle, true);
|
||||
g.setColour (accent);
|
||||
g.strokePath (valueArc, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
juce::Colour arcColour = isDark ? juce::Colour (0xff4488ff) : juce::Colour (0xffff8833);
|
||||
juce::Colour arcBgColour = isDark ? juce::Colour (0xff1a2a44) : juce::Colour (0xff2a1a0a);
|
||||
juce::Colour bodyTop = isDark ? juce::Colour (0xff3a3a4a) : juce::Colour (0xff5a4a3a);
|
||||
juce::Colour bodyBottom = isDark ? juce::Colour (0xff1a1a2a) : juce::Colour (0xff2a1a0a);
|
||||
juce::Colour rimColour = isDark ? juce::Colour (0xff555566) : juce::Colour (0xff886644);
|
||||
juce::Colour highlightCol = isDark ? juce::Colour (0x33aabbff) : juce::Colour (0x44ffcc88);
|
||||
juce::Colour pointerColour = isDark ? juce::Colour (0xff66aaff) : juce::Colour (0xffffaa44);
|
||||
|
||||
// Pointer
|
||||
juce::Path pointer;
|
||||
auto pointerLength = radius * 0.5f;
|
||||
pointer.addRectangle (-1.5f, -pointerLength, 3.0f, pointerLength);
|
||||
pointer.applyTransform (juce::AffineTransform::rotation (angle).translated (centreX, centreY));
|
||||
g.setColour (textPrimary);
|
||||
g.fillPath (pointer);
|
||||
// All thicknesses scale with knob size
|
||||
float arcW = std::max (1.5f, 2.5f * s); // core arc width
|
||||
float glowW1 = std::max (3.0f, 10.0f * s); // outermost glow
|
||||
float glowW2 = std::max (2.5f, 7.0f * s); // mid glow
|
||||
float glowW3 = std::max (2.0f, 4.5f * s); // inner glow
|
||||
float hotW = std::max (0.8f, 1.2f * s); // hot center
|
||||
float ptrW = std::max (1.2f, 2.0f * s); // pointer width
|
||||
float bodyRadius = radius * 0.72f;
|
||||
|
||||
// 1. Drop shadow
|
||||
g.setColour (juce::Colours::black.withAlpha (0.35f));
|
||||
g.fillEllipse (cx - bodyRadius + 1, cy - bodyRadius + 2, bodyRadius * 2, bodyRadius * 2);
|
||||
|
||||
// 2. Outer arc track
|
||||
{
|
||||
juce::Path arcBg;
|
||||
arcBg.addCentredArc (cx, cy, radius - 1, radius - 1, 0.0f,
|
||||
rotaryStartAngle, rotaryEndAngle, true);
|
||||
g.setColour (arcBgColour);
|
||||
g.strokePath (arcBg, juce::PathStrokeType (arcW, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
}
|
||||
|
||||
// 3. Outer arc value with scaled glow
|
||||
if (sliderPos > 0.01f)
|
||||
{
|
||||
juce::Path arcVal;
|
||||
arcVal.addCentredArc (cx, cy, radius - 1, radius - 1, 0.0f,
|
||||
rotaryStartAngle, angle, true);
|
||||
|
||||
// Glow layers (scale with knob size)
|
||||
g.setColour (arcColour.withAlpha (0.08f));
|
||||
g.strokePath (arcVal, juce::PathStrokeType (glowW1, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
g.setColour (arcColour.withAlpha (0.15f));
|
||||
g.strokePath (arcVal, juce::PathStrokeType (glowW2, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
g.setColour (arcColour.withAlpha (0.3f));
|
||||
g.strokePath (arcVal, juce::PathStrokeType (glowW3, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
|
||||
// Core arc
|
||||
g.setColour (arcColour);
|
||||
g.strokePath (arcVal, juce::PathStrokeType (arcW, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
|
||||
// Hot center
|
||||
g.setColour (arcColour.brighter (0.6f).withAlpha (0.5f));
|
||||
g.strokePath (arcVal, juce::PathStrokeType (hotW, juce::PathStrokeType::curved,
|
||||
juce::PathStrokeType::rounded));
|
||||
}
|
||||
|
||||
// 4. Knob body — radial gradient
|
||||
{
|
||||
juce::ColourGradient bodyGrad (bodyTop, cx, cy - bodyRadius * 0.5f,
|
||||
bodyBottom, cx, cy + bodyRadius, true);
|
||||
g.setGradientFill (bodyGrad);
|
||||
g.fillEllipse (cx - bodyRadius, cy - bodyRadius, bodyRadius * 2, bodyRadius * 2);
|
||||
}
|
||||
|
||||
// 5. Rim
|
||||
g.setColour (rimColour.withAlpha (0.6f));
|
||||
g.drawEllipse (cx - bodyRadius, cy - bodyRadius, bodyRadius * 2, bodyRadius * 2, std::max (0.8f, 1.2f * s));
|
||||
|
||||
// 6. Inner shadow
|
||||
{
|
||||
float innerR = bodyRadius * 0.85f;
|
||||
juce::ColourGradient innerGrad (juce::Colours::black.withAlpha (0.15f), cx, cy - innerR * 0.3f,
|
||||
juce::Colours::transparentBlack, cx, cy + innerR, true);
|
||||
g.setGradientFill (innerGrad);
|
||||
g.fillEllipse (cx - innerR, cy - innerR, innerR * 2, innerR * 2);
|
||||
}
|
||||
|
||||
// 7. Top highlight
|
||||
{
|
||||
float hlRadius = bodyRadius * 0.55f;
|
||||
float hlY = cy - bodyRadius * 0.35f;
|
||||
juce::ColourGradient hlGrad (highlightCol, cx, hlY - hlRadius * 0.5f,
|
||||
juce::Colours::transparentBlack, cx, hlY + hlRadius, true);
|
||||
g.setGradientFill (hlGrad);
|
||||
g.fillEllipse (cx - hlRadius, hlY - hlRadius * 0.6f, hlRadius * 2, hlRadius * 1.2f);
|
||||
}
|
||||
|
||||
// 8. Pointer with scaled glow
|
||||
{
|
||||
juce::Path pointer;
|
||||
float pointerLen = bodyRadius * 0.75f;
|
||||
|
||||
pointer.addRoundedRectangle (-ptrW * 0.5f, -pointerLen, ptrW, pointerLen * 0.55f, ptrW * 0.5f);
|
||||
pointer.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
|
||||
|
||||
// Wide outer glow
|
||||
g.setColour (pointerColour.withAlpha (0.1f));
|
||||
{
|
||||
juce::Path glow3;
|
||||
float gw = ptrW * 3.5f;
|
||||
glow3.addRoundedRectangle (-gw, -pointerLen, gw * 2, pointerLen * 0.55f, ptrW * 1.5f);
|
||||
glow3.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
|
||||
g.fillPath (glow3);
|
||||
}
|
||||
|
||||
// Medium glow
|
||||
g.setColour (pointerColour.withAlpha (0.25f));
|
||||
{
|
||||
juce::Path glow2;
|
||||
float gw = ptrW * 2.0f;
|
||||
glow2.addRoundedRectangle (-gw, -pointerLen, gw * 2, pointerLen * 0.55f, ptrW);
|
||||
glow2.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
|
||||
g.fillPath (glow2);
|
||||
}
|
||||
|
||||
// Core pointer
|
||||
g.setColour (pointerColour);
|
||||
g.fillPath (pointer);
|
||||
|
||||
// Hot center
|
||||
{
|
||||
juce::Path hotCenter;
|
||||
float hw = ptrW * 0.3f;
|
||||
hotCenter.addRoundedRectangle (-hw, -pointerLen, hw * 2, pointerLen * 0.5f, hw);
|
||||
hotCenter.applyTransform (juce::AffineTransform::rotation (angle).translated (cx, cy));
|
||||
g.setColour (pointerColour.brighter (0.7f).withAlpha (0.6f));
|
||||
g.fillPath (hotCenter);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. Center cap
|
||||
{
|
||||
float capR = bodyRadius * 0.18f;
|
||||
juce::ColourGradient capGrad (rimColour.brighter (0.3f), cx, cy - capR,
|
||||
bodyBottom, cx, cy + capR, false);
|
||||
g.setGradientFill (capGrad);
|
||||
g.fillEllipse (cx - capR, cy - capR, capR * 2, capR * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Button style
|
||||
// ============================================================
|
||||
|
||||
void InstaDrumsLookAndFeel::drawButtonBackground (juce::Graphics& g, juce::Button& button,
|
||||
const juce::Colour& backgroundColour,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown)
|
||||
{
|
||||
auto bounds = button.getLocalBounds().toFloat().reduced (0.5f);
|
||||
|
||||
auto baseColour = backgroundColour;
|
||||
if (shouldDrawButtonAsDown)
|
||||
baseColour = baseColour.brighter (0.2f);
|
||||
else if (shouldDrawButtonAsHighlighted)
|
||||
baseColour = baseColour.brighter (0.1f);
|
||||
|
||||
juce::ColourGradient grad (baseColour.brighter (0.05f), 0, bounds.getY(),
|
||||
baseColour.darker (0.1f), 0, bounds.getBottom(), false);
|
||||
g.setGradientFill (grad);
|
||||
g.fillRoundedRectangle (bounds, 4.0f);
|
||||
|
||||
g.setColour (bgLight.withAlpha (shouldDrawButtonAsHighlighted ? 0.8f : 0.5f));
|
||||
g.drawRoundedRectangle (bounds, 4.0f, 1.0f);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,35 @@ public:
|
||||
static inline const juce::Colour textSecondary { 0xff888899 };
|
||||
static inline const juce::Colour accent { 0xff00ff88 };
|
||||
|
||||
// Knob type property key
|
||||
static constexpr const char* knobTypeProperty = "knobType";
|
||||
|
||||
InstaDrumsLookAndFeel();
|
||||
|
||||
void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height,
|
||||
float sliderPosProportional, float rotaryStartAngle,
|
||||
float rotaryEndAngle, juce::Slider& slider) override;
|
||||
|
||||
void drawButtonBackground (juce::Graphics& g, juce::Button& button,
|
||||
const juce::Colour& backgroundColour,
|
||||
bool shouldDrawButtonAsHighlighted,
|
||||
bool shouldDrawButtonAsDown) override;
|
||||
|
||||
// Custom fonts
|
||||
juce::Font getRegularFont (float height) const;
|
||||
juce::Font getMediumFont (float height) const;
|
||||
juce::Font getBoldFont (float height) const;
|
||||
|
||||
// Background texture
|
||||
void drawBackgroundTexture (juce::Graphics& g, juce::Rectangle<int> area);
|
||||
|
||||
juce::Typeface::Ptr getTypefaceForFont (const juce::Font& font) override;
|
||||
|
||||
private:
|
||||
juce::Typeface::Ptr typefaceRegular;
|
||||
juce::Typeface::Ptr typefaceMedium;
|
||||
juce::Typeface::Ptr typefaceBold;
|
||||
|
||||
juce::Image noiseTexture;
|
||||
void generateNoiseTexture();
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
MasterPanel::MasterPanel()
|
||||
{
|
||||
masterTitle.setFont (juce::FontOptions (12.0f, juce::Font::bold));
|
||||
masterTitle.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
addAndMakeVisible (masterTitle);
|
||||
|
||||
@@ -21,10 +20,10 @@ void MasterPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String
|
||||
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
|
||||
s.setRange (min, max, step);
|
||||
s.setValue (val, juce::dontSendNotification);
|
||||
s.setDoubleClickReturnValue (true, val);
|
||||
addAndMakeVisible (s);
|
||||
|
||||
l.setText (name, juce::dontSendNotification);
|
||||
l.setFont (juce::FontOptions (9.0f));
|
||||
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
l.setJustificationType (juce::Justification::centred);
|
||||
addAndMakeVisible (l);
|
||||
@@ -41,22 +40,26 @@ void MasterPanel::paint (juce::Graphics& g)
|
||||
|
||||
void MasterPanel::resized()
|
||||
{
|
||||
auto area = getLocalBounds().reduced (4);
|
||||
auto area = getLocalBounds().reduced (6);
|
||||
float scale = (float) getHeight() / 52.0f;
|
||||
float titleSize = std::max (12.0f, 16.0f * scale);
|
||||
float labelSize = std::max (9.0f, 12.0f * scale);
|
||||
int labelH = (int) (labelSize + 4);
|
||||
|
||||
masterTitle.setBounds (area.removeFromLeft (55).reduced (0, 2));
|
||||
masterTitle.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
masterTitle.setBounds (area.removeFromLeft ((int) (65 * scale)).reduced (0, 2));
|
||||
|
||||
// VU meter on the right
|
||||
vuMeter.setBounds (area.removeFromRight (24).reduced (0, 2));
|
||||
vuMeter.setBounds (area.removeFromRight ((int) (28 * scale)).reduced (0, 2));
|
||||
area.removeFromRight (4);
|
||||
|
||||
// Knobs
|
||||
int knobW = area.getWidth() / 3;
|
||||
juce::Slider* sliders[] = { &volumeSlider, &tuneSlider, &panSlider };
|
||||
juce::Label* labels[] = { &volumeLabel, &tuneLabel, &panLabel };
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
labels[i]->setFont (juce::FontOptions (labelSize));
|
||||
auto col = area.removeFromLeft (knobW);
|
||||
labels[i]->setBounds (col.removeFromBottom (12));
|
||||
labels[i]->setBounds (col.removeFromBottom (labelH));
|
||||
sliders[i]->setBounds (col);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,39 +11,66 @@ void PadComponent::paint (juce::Graphics& g)
|
||||
auto bounds = getLocalBounds().toFloat().reduced (2.0f);
|
||||
float cornerSize = 6.0f;
|
||||
|
||||
// Background
|
||||
float alpha = isPressed ? 0.85f : (isDragOver ? 0.65f : (selected ? 0.55f : 0.3f));
|
||||
g.setColour (drumPad.colour.withAlpha (alpha));
|
||||
// Background — darker, less saturated base
|
||||
float alpha = isPressed ? 0.6f : (isDragOver ? 0.45f : (selected ? 0.35f : 0.18f));
|
||||
g.setColour (drumPad.colour.withSaturation (0.5f).withAlpha (alpha));
|
||||
g.fillRoundedRectangle (bounds, cornerSize);
|
||||
|
||||
// Border — selected = bright accent, normal = pad colour
|
||||
// Inner gradient for depth
|
||||
{
|
||||
juce::ColourGradient innerGrad (juce::Colours::white.withAlpha (0.04f), bounds.getCentreX(), bounds.getY(),
|
||||
juce::Colours::black.withAlpha (0.08f), bounds.getCentreX(), bounds.getBottom(), false);
|
||||
g.setGradientFill (innerGrad);
|
||||
g.fillRoundedRectangle (bounds, cornerSize);
|
||||
}
|
||||
|
||||
// Border — selected = cyan glow, normal = subtle
|
||||
if (selected)
|
||||
{
|
||||
g.setColour (juce::Colour (0xff00aaff)); // blue selection
|
||||
g.drawRoundedRectangle (bounds, cornerSize, 2.5f);
|
||||
// Outer glow effect (multiple passes)
|
||||
auto glowColour = juce::Colour (0xff00aaff);
|
||||
g.setColour (glowColour.withAlpha (0.1f));
|
||||
g.drawRoundedRectangle (bounds.expanded (2), cornerSize + 1, 4.0f);
|
||||
g.setColour (glowColour.withAlpha (0.25f));
|
||||
g.drawRoundedRectangle (bounds.expanded (0.5f), cornerSize, 2.0f);
|
||||
g.setColour (glowColour);
|
||||
g.drawRoundedRectangle (bounds, cornerSize, 1.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColour (drumPad.colour.withAlpha (0.6f));
|
||||
g.setColour (drumPad.colour.withAlpha (0.4f));
|
||||
g.drawRoundedRectangle (bounds, cornerSize, 1.0f);
|
||||
}
|
||||
|
||||
// Pad number (top-left, small)
|
||||
g.setColour (InstaDrumsLookAndFeel::textSecondary);
|
||||
g.setFont (juce::Font (juce::FontOptions (9.0f, juce::Font::bold)));
|
||||
g.drawText (juce::String (index + 1), bounds.reduced (4, 3), juce::Justification::topLeft);
|
||||
// Scale fonts to pad size
|
||||
float padScale = std::min (bounds.getWidth(), bounds.getHeight()) / 100.0f;
|
||||
float numFontSize = std::max (9.0f, 13.0f * padScale);
|
||||
float nameFontSize = std::max (9.0f, 14.0f * padScale);
|
||||
float numBoxH = numFontSize + 4;
|
||||
float numBoxW = numBoxH + 4;
|
||||
|
||||
// Pad number (top-left, with small dark background for contrast)
|
||||
{
|
||||
auto numBounds = bounds.reduced (5, 4).removeFromTop (numBoxH).removeFromLeft (numBoxW);
|
||||
g.setColour (juce::Colours::black.withAlpha (0.3f));
|
||||
g.fillRoundedRectangle (numBounds, 3.0f);
|
||||
g.setColour (drumPad.colour.brighter (0.4f));
|
||||
g.setFont (juce::Font (juce::FontOptions (numFontSize, juce::Font::bold)));
|
||||
g.drawText (juce::String (index + 1), numBounds, juce::Justification::centred);
|
||||
}
|
||||
|
||||
// Waveform thumbnail (center area)
|
||||
if (drumPad.hasSample())
|
||||
{
|
||||
auto waveArea = bounds.reduced (4, 16);
|
||||
auto waveArea = bounds.reduced (5, numBoxH + 6);
|
||||
waveArea.removeFromBottom (nameFontSize + 4);
|
||||
drawWaveformThumbnail (g, waveArea);
|
||||
}
|
||||
|
||||
// Pad name (bottom)
|
||||
g.setColour (InstaDrumsLookAndFeel::textPrimary);
|
||||
g.setFont (juce::Font (juce::FontOptions (10.0f, juce::Font::bold)));
|
||||
g.drawText (drumPad.name, bounds.reduced (4, 2), juce::Justification::centredBottom);
|
||||
g.setFont (juce::Font (juce::FontOptions (nameFontSize, juce::Font::bold)));
|
||||
g.drawText (drumPad.name, bounds.reduced (5, 3), juce::Justification::centredBottom);
|
||||
|
||||
// Playing flash
|
||||
if (drumPad.isPlaying())
|
||||
|
||||
@@ -4,19 +4,20 @@ InstaDrumsEditor::InstaDrumsEditor (InstaDrumsProcessor& p)
|
||||
: AudioProcessorEditor (&p), processor (p)
|
||||
{
|
||||
setLookAndFeel (&customLookAndFeel);
|
||||
juce::LookAndFeel::setDefaultLookAndFeel (&customLookAndFeel);
|
||||
|
||||
// Title
|
||||
titleLabel.setFont (juce::FontOptions (20.0f, juce::Font::bold));
|
||||
titleLabel.setFont (customLookAndFeel.getBoldFont (25.0f));
|
||||
titleLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
|
||||
titleLabel.setJustificationType (juce::Justification::centredLeft);
|
||||
addAndMakeVisible (titleLabel);
|
||||
|
||||
versionLabel.setFont (juce::FontOptions (10.0f));
|
||||
versionLabel.setFont (juce::FontOptions (12.5f));
|
||||
versionLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
versionLabel.setJustificationType (juce::Justification::centredRight);
|
||||
addAndMakeVisible (versionLabel);
|
||||
|
||||
padsLabel.setFont (juce::FontOptions (10.0f, juce::Font::bold));
|
||||
padsLabel.setFont (juce::FontOptions (12.5f, juce::Font::bold));
|
||||
padsLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
addAndMakeVisible (padsLabel);
|
||||
|
||||
@@ -108,6 +109,7 @@ InstaDrumsEditor::InstaDrumsEditor (InstaDrumsProcessor& p)
|
||||
|
||||
InstaDrumsEditor::~InstaDrumsEditor()
|
||||
{
|
||||
juce::LookAndFeel::setDefaultLookAndFeel (nullptr);
|
||||
setLookAndFeel (nullptr);
|
||||
}
|
||||
|
||||
@@ -145,14 +147,30 @@ void InstaDrumsEditor::selectPad (int index)
|
||||
|
||||
void InstaDrumsEditor::paint (juce::Graphics& g)
|
||||
{
|
||||
g.fillAll (InstaDrumsLookAndFeel::bgDark);
|
||||
// Background gradient
|
||||
juce::ColourGradient bgGrad (InstaDrumsLookAndFeel::bgDark, 0, 0,
|
||||
InstaDrumsLookAndFeel::bgDark.darker (0.3f), 0, (float) getHeight(), false);
|
||||
g.setGradientFill (bgGrad);
|
||||
g.fillAll();
|
||||
|
||||
// Subtle divider lines
|
||||
// Noise texture overlay
|
||||
customLookAndFeel.drawBackgroundTexture (g, getLocalBounds());
|
||||
|
||||
// Top header bar
|
||||
float sc = (float) getHeight() / 600.0f;
|
||||
int topH = std::max (28, (int) (36 * sc));
|
||||
g.setColour (InstaDrumsLookAndFeel::bgDark.darker (0.4f));
|
||||
g.fillRect (0, 0, getWidth(), topH);
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.3f));
|
||||
g.drawHorizontalLine (topH, 0, (float) getWidth());
|
||||
|
||||
// Divider lines
|
||||
auto bounds = getLocalBounds();
|
||||
int rightPanelX = (int) (bounds.getWidth() * 0.52f);
|
||||
int bottomPanelY = bounds.getHeight() - 56;
|
||||
int masterH = std::max (44, (int) (60 * sc));
|
||||
int bottomPanelY = bounds.getHeight() - masterH - 4;
|
||||
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.3f));
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.4f));
|
||||
g.drawVerticalLine (rightPanelX - 2, 30, (float) bottomPanelY);
|
||||
g.drawHorizontalLine (bottomPanelY - 1, 0, (float) bounds.getWidth());
|
||||
}
|
||||
@@ -160,25 +178,38 @@ void InstaDrumsEditor::paint (juce::Graphics& g)
|
||||
void InstaDrumsEditor::resized()
|
||||
{
|
||||
auto area = getLocalBounds();
|
||||
float scale = (float) getHeight() / 600.0f;
|
||||
|
||||
// Top bar (30px)
|
||||
auto topBar = area.removeFromTop (30).reduced (6, 4);
|
||||
titleLabel.setBounds (topBar.removeFromLeft (150));
|
||||
versionLabel.setBounds (topBar.removeFromRight (40));
|
||||
loadFolderButton.setBounds (topBar.removeFromRight (90).reduced (1));
|
||||
loadKitButton.setBounds (topBar.removeFromRight (70).reduced (1));
|
||||
saveKitButton.setBounds (topBar.removeFromRight (70).reduced (1));
|
||||
loadSampleButton.setBounds (topBar.removeFromRight (95).reduced (1));
|
||||
// Top bar
|
||||
int topBarH = std::max (28, (int) (36 * scale));
|
||||
auto topBar = area.removeFromTop (topBarH).reduced (8, 4);
|
||||
|
||||
// Bottom master bar (52px)
|
||||
masterPanel.setBounds (area.removeFromBottom (52).reduced (4, 2));
|
||||
float titleSize = std::max (18.0f, 26.0f * scale);
|
||||
titleLabel.setFont (customLookAndFeel.getBoldFont (titleSize));
|
||||
titleLabel.setBounds (topBar.removeFromLeft ((int) (180 * scale)));
|
||||
|
||||
float smallSize = std::max (10.0f, 13.0f * scale);
|
||||
versionLabel.setFont (juce::FontOptions (smallSize));
|
||||
versionLabel.setBounds (topBar.removeFromRight ((int) (50 * scale)));
|
||||
|
||||
int btnW = std::max (60, (int) (90 * scale));
|
||||
loadFolderButton.setBounds (topBar.removeFromRight (btnW).reduced (2));
|
||||
loadKitButton.setBounds (topBar.removeFromRight ((int) (btnW * 0.8f)).reduced (2));
|
||||
saveKitButton.setBounds (topBar.removeFromRight ((int) (btnW * 0.8f)).reduced (2));
|
||||
loadSampleButton.setBounds (topBar.removeFromRight (btnW).reduced (2));
|
||||
|
||||
// Bottom master bar
|
||||
int masterH = std::max (44, (int) (60 * scale));
|
||||
masterPanel.setBounds (area.removeFromBottom (masterH).reduced (4, 2));
|
||||
|
||||
// Left panel: pad grid (~52% width)
|
||||
int rightPanelX = (int) (area.getWidth() * 0.52f);
|
||||
auto leftArea = area.removeFromLeft (rightPanelX).reduced (4);
|
||||
auto leftArea = area.removeFromLeft (rightPanelX).reduced (6);
|
||||
|
||||
// Pads label
|
||||
auto padsHeader = leftArea.removeFromTop (16);
|
||||
int padsLabelH = (int) (20 * scale);
|
||||
padsLabel.setFont (juce::FontOptions (std::max (11.0f, 14.0f * scale), juce::Font::bold));
|
||||
auto padsHeader = leftArea.removeFromTop (padsLabelH);
|
||||
padsLabel.setBounds (padsHeader);
|
||||
|
||||
// Pad grid
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
|
||||
SampleEditorPanel::SampleEditorPanel()
|
||||
{
|
||||
titleLabel.setFont (juce::FontOptions (14.0f, juce::Font::bold));
|
||||
titleLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
addAndMakeVisible (titleLabel);
|
||||
|
||||
padNameLabel.setFont (juce::FontOptions (13.0f, juce::Font::bold));
|
||||
padNameLabel.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::accent);
|
||||
addAndMakeVisible (padNameLabel);
|
||||
|
||||
@@ -24,6 +22,9 @@ SampleEditorPanel::SampleEditorPanel()
|
||||
setupKnob (resoSlider, resoLabel, "Reso", 0.1, 10.0, 0.707, 0.01);
|
||||
|
||||
cutoffSlider.setSkewFactorFromMidPoint (1000.0);
|
||||
|
||||
cutoffSlider.getProperties().set (InstaDrumsLookAndFeel::knobTypeProperty, "dark");
|
||||
resoSlider.getProperties().set (InstaDrumsLookAndFeel::knobTypeProperty, "dark");
|
||||
}
|
||||
|
||||
void SampleEditorPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::String& name,
|
||||
@@ -33,11 +34,11 @@ void SampleEditorPanel::setupKnob (juce::Slider& s, juce::Label& l, const juce::
|
||||
s.setTextBoxStyle (juce::Slider::NoTextBox, false, 0, 0);
|
||||
s.setRange (min, max, step);
|
||||
s.setValue (val, juce::dontSendNotification);
|
||||
s.setDoubleClickReturnValue (true, val);
|
||||
s.addListener (this);
|
||||
addAndMakeVisible (s);
|
||||
|
||||
l.setText (name, juce::dontSendNotification);
|
||||
l.setFont (juce::FontOptions (9.0f));
|
||||
l.setColour (juce::Label::textColourId, InstaDrumsLookAndFeel::textSecondary);
|
||||
l.setJustificationType (juce::Justification::centred);
|
||||
addAndMakeVisible (l);
|
||||
@@ -85,7 +86,6 @@ void SampleEditorPanel::sliderValueChanged (juce::Slider* slider)
|
||||
else if (slider == &cutoffSlider) currentPad->filterCutoff = (float) slider->getValue();
|
||||
else if (slider == &resoSlider) currentPad->filterReso = (float) slider->getValue();
|
||||
|
||||
// Update ADSR overlay
|
||||
waveform.setADSR (currentPad->attack, currentPad->decay, currentPad->sustain, currentPad->release);
|
||||
}
|
||||
|
||||
@@ -100,37 +100,51 @@ void SampleEditorPanel::paint (juce::Graphics& g)
|
||||
|
||||
void SampleEditorPanel::resized()
|
||||
{
|
||||
auto area = getLocalBounds().reduced (6);
|
||||
auto area = getLocalBounds().reduced (8);
|
||||
|
||||
// Scale factor based on component height (reference: 280px)
|
||||
float scale = (float) getHeight() / 280.0f;
|
||||
float titleSize = std::max (13.0f, 18.0f * scale);
|
||||
float labelSize = std::max (10.0f, 13.0f * scale);
|
||||
int labelH = (int) (labelSize + 6);
|
||||
int headerH = (int) (titleSize + 8);
|
||||
int spacing = std::max (4, (int) (8 * scale));
|
||||
|
||||
// Header
|
||||
auto header = area.removeFromTop (20);
|
||||
titleLabel.setBounds (header.removeFromLeft (100));
|
||||
titleLabel.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
padNameLabel.setFont (juce::FontOptions (titleSize, juce::Font::bold));
|
||||
|
||||
auto header = area.removeFromTop (headerH);
|
||||
titleLabel.setBounds (header.removeFromLeft ((int) (120 * scale)));
|
||||
padNameLabel.setBounds (header);
|
||||
|
||||
area.removeFromTop (2);
|
||||
area.removeFromTop (spacing);
|
||||
|
||||
// Waveform (top portion ~40%)
|
||||
int waveHeight = std::max (60, (int) (area.getHeight() * 0.38f));
|
||||
// Waveform
|
||||
int waveHeight = std::max (50, (int) (area.getHeight() * 0.36f));
|
||||
waveform.setBounds (area.removeFromTop (waveHeight));
|
||||
|
||||
area.removeFromTop (4);
|
||||
area.removeFromTop (spacing);
|
||||
|
||||
// ADSR knobs row
|
||||
int knobH = std::max (40, (int) (area.getHeight() * 0.45f));
|
||||
auto adsrRow = area.removeFromTop (knobH);
|
||||
int knobH = (area.getHeight() - spacing - labelH * 2) / 2;
|
||||
knobH = std::max (30, knobH);
|
||||
|
||||
auto adsrRow = area.removeFromTop (knobH + labelH);
|
||||
int knobW = adsrRow.getWidth() / 4;
|
||||
{
|
||||
juce::Slider* s[] = { &attackSlider, &decaySlider, &sustainSlider, &releaseSlider };
|
||||
juce::Label* l[] = { &attackLabel, &decayLabel, &sustainLabel, &releaseLabel };
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
l[i]->setFont (juce::FontOptions (labelSize));
|
||||
auto col = adsrRow.removeFromLeft (knobW);
|
||||
l[i]->setBounds (col.removeFromBottom (14));
|
||||
l[i]->setBounds (col.removeFromBottom (labelH));
|
||||
s[i]->setBounds (col);
|
||||
}
|
||||
}
|
||||
|
||||
area.removeFromTop (2);
|
||||
area.removeFromTop (spacing);
|
||||
|
||||
// Bottom row: Pitch, Pan, Cutoff, Reso
|
||||
auto bottomRow = area;
|
||||
@@ -140,8 +154,9 @@ void SampleEditorPanel::resized()
|
||||
juce::Label* l[] = { &pitchLabel, &panLabel, &cutoffLabel, &resoLabel };
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
l[i]->setFont (juce::FontOptions (labelSize));
|
||||
auto col = bottomRow.removeFromLeft (knobW);
|
||||
l[i]->setBounds (col.removeFromBottom (14));
|
||||
l[i]->setBounds (col.removeFromBottom (labelH));
|
||||
s[i]->setBounds (col);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,12 @@ class VuMeter : public juce::Component
|
||||
public:
|
||||
void setLevel (float left, float right)
|
||||
{
|
||||
// Update peak hold
|
||||
if (left > peakL) peakL = left;
|
||||
else peakL *= 0.995f;
|
||||
if (right > peakR) peakR = right;
|
||||
else peakR *= 0.995f;
|
||||
|
||||
levelL = left;
|
||||
levelR = right;
|
||||
repaint();
|
||||
@@ -14,34 +20,60 @@ public:
|
||||
void paint (juce::Graphics& g) override
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat().reduced (1);
|
||||
float halfW = bounds.getWidth() / 2.0f - 1;
|
||||
float barGap = 2.0f;
|
||||
float halfW = (bounds.getWidth() - barGap) / 2.0f;
|
||||
auto leftBar = bounds.removeFromLeft (halfW);
|
||||
bounds.removeFromLeft (2);
|
||||
bounds.removeFromLeft (barGap);
|
||||
auto rightBar = bounds;
|
||||
|
||||
drawBar (g, leftBar, levelL);
|
||||
drawBar (g, rightBar, levelR);
|
||||
drawBar (g, leftBar, levelL, peakL);
|
||||
drawBar (g, rightBar, levelR, peakR);
|
||||
|
||||
// Scale markers
|
||||
g.setColour (juce::Colour (0x44ffffff));
|
||||
g.setFont (juce::FontOptions (9.0f));
|
||||
float totalH = getLocalBounds().toFloat().getHeight();
|
||||
// dB markers: 0dB = top, -6, -12, -24, -48
|
||||
float dbLevels[] = { 1.0f, 0.5f, 0.25f, 0.063f, 0.004f };
|
||||
const char* dbLabels[] = { "0", "-6", "-12", "-24", "-48" };
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
float yPos = (1.0f - dbLevels[i]) * totalH;
|
||||
g.drawHorizontalLine ((int) yPos, 0.0f, (float) getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float levelL = 0.0f, levelR = 0.0f;
|
||||
float peakL = 0.0f, peakR = 0.0f;
|
||||
|
||||
void drawBar (juce::Graphics& g, juce::Rectangle<float> bar, float level)
|
||||
void drawBar (juce::Graphics& g, juce::Rectangle<float> bar, float level, float peak)
|
||||
{
|
||||
g.setColour (juce::Colour (0xff222233));
|
||||
// Background
|
||||
g.setColour (juce::Colour (0xff111122));
|
||||
g.fillRoundedRectangle (bar, 2.0f);
|
||||
|
||||
float h = bar.getHeight() * juce::jlimit (0.0f, 1.0f, level);
|
||||
auto filled = bar.removeFromBottom (h);
|
||||
float clampedLevel = juce::jlimit (0.0f, 1.0f, level);
|
||||
float h = bar.getHeight() * clampedLevel;
|
||||
auto filled = bar.withTop (bar.getBottom() - h);
|
||||
|
||||
// Green -> Yellow -> Red gradient
|
||||
if (level < 0.6f)
|
||||
// Segmented colour
|
||||
if (clampedLevel < 0.6f)
|
||||
g.setColour (juce::Colour (0xff00cc44));
|
||||
else if (level < 0.85f)
|
||||
else if (clampedLevel < 0.85f)
|
||||
g.setColour (juce::Colour (0xffcccc00));
|
||||
else
|
||||
g.setColour (juce::Colour (0xffff3333));
|
||||
|
||||
g.fillRoundedRectangle (filled, 2.0f);
|
||||
|
||||
// Peak hold line
|
||||
float clampedPeak = juce::jlimit (0.0f, 1.0f, peak);
|
||||
if (clampedPeak > 0.01f)
|
||||
{
|
||||
float peakY = bar.getBottom() - bar.getHeight() * clampedPeak;
|
||||
g.setColour (juce::Colours::white.withAlpha (0.8f));
|
||||
g.fillRect (bar.getX(), peakY, bar.getWidth(), 1.5f);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -14,9 +14,30 @@ void WaveformDisplay::paint (juce::Graphics& g)
|
||||
{
|
||||
auto bounds = getLocalBounds().toFloat();
|
||||
|
||||
// Background
|
||||
g.setColour (InstaDrumsLookAndFeel::bgDark.darker (0.3f));
|
||||
g.fillRoundedRectangle (bounds, 4.0f);
|
||||
// Background with subtle gradient
|
||||
{
|
||||
juce::ColourGradient bgGrad (InstaDrumsLookAndFeel::bgDark.darker (0.4f), 0, bounds.getY(),
|
||||
InstaDrumsLookAndFeel::bgDark.darker (0.2f), 0, bounds.getBottom(), false);
|
||||
g.setGradientFill (bgGrad);
|
||||
g.fillRoundedRectangle (bounds, 4.0f);
|
||||
}
|
||||
|
||||
// Border
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.3f));
|
||||
g.drawRoundedRectangle (bounds, 4.0f, 1.0f);
|
||||
|
||||
// Grid lines (vertical)
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.12f));
|
||||
for (int i = 1; i < 8; ++i)
|
||||
{
|
||||
float xLine = bounds.getX() + bounds.getWidth() * (float) i / 8.0f;
|
||||
g.drawVerticalLine ((int) xLine, bounds.getY(), bounds.getBottom());
|
||||
}
|
||||
|
||||
// Center line (horizontal)
|
||||
g.setColour (InstaDrumsLookAndFeel::bgLight.withAlpha (0.2f));
|
||||
float midY = bounds.getCentreY();
|
||||
g.drawHorizontalLine ((int) midY, bounds.getX(), bounds.getRight());
|
||||
|
||||
if (audioBuffer == nullptr || audioBuffer->getNumSamples() == 0)
|
||||
return;
|
||||
@@ -27,7 +48,7 @@ void WaveformDisplay::paint (juce::Graphics& g)
|
||||
const int visibleSamples = std::max (1, endSample - startSample);
|
||||
const float width = bounds.getWidth();
|
||||
const float height = bounds.getHeight();
|
||||
const float midY = bounds.getCentreY();
|
||||
// midY already declared above
|
||||
|
||||
// Draw waveform
|
||||
juce::Path wavePath;
|
||||
|
||||
Reference in New Issue
Block a user