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:
hariel1985
2026-03-22 17:09:04 +01:00
szülő ec9a8b4e23
commit 20b9fe2674
13 fájl változott, egészen pontosan 579 új sor hozzáadva és 144 régi sor törölve

Fájl megtekintése

@@ -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

Binary file not shown.

BINáris
Resources/Rajdhani-Medium.ttf Normal file

Binary file not shown.

BINáris
Resources/Rajdhani-Regular.ttf Normal file

Binary file not shown.

Fájl megtekintése

@@ -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);
}
}

Fájl megtekintése

@@ -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);
}

Fájl megtekintése

@@ -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();
};

Fájl megtekintése

@@ -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);
}
}

Fájl megtekintése

@@ -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())

Fájl megtekintése

@@ -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

Fájl megtekintése

@@ -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);
}
}

Fájl megtekintése

@@ -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);
}
}
};

Fájl megtekintése

@@ -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;