Files
InstaDrums/Source/PadComponent.cpp
hariel1985 20b9fe2674 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>
2026-03-22 17:09:04 +01:00

194 sor
6.1 KiB
C++

#include "PadComponent.h"
#include "LookAndFeel.h"
PadComponent::PadComponent (DrumPad& pad, std::function<void(int, const juce::File&)> loadCallback, int padIndex)
: drumPad (pad), onLoadSample (std::move (loadCallback)), index (padIndex)
{
}
void PadComponent::paint (juce::Graphics& g)
{
auto bounds = getLocalBounds().toFloat().reduced (2.0f);
float cornerSize = 6.0f;
// 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);
// 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)
{
// 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.4f));
g.drawRoundedRectangle (bounds, cornerSize, 1.0f);
}
// 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 (5, numBoxH + 6);
waveArea.removeFromBottom (nameFontSize + 4);
drawWaveformThumbnail (g, waveArea);
}
// Pad name (bottom)
g.setColour (InstaDrumsLookAndFeel::textPrimary);
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())
{
g.setColour (juce::Colours::white.withAlpha (0.12f));
g.fillRoundedRectangle (bounds, cornerSize);
}
}
void PadComponent::drawWaveformThumbnail (juce::Graphics& g, juce::Rectangle<float> area)
{
auto& buf = drumPad.getSampleBuffer();
if (buf.getNumSamples() == 0) return;
const float* data = buf.getReadPointer (0);
const int numSamples = buf.getNumSamples();
const float w = area.getWidth();
const float h = area.getHeight();
const float midY = area.getCentreY();
juce::Path path;
int blockSize = std::max (1, numSamples / (int) w);
for (int x = 0; x < (int) w; ++x)
{
int si = (int) ((float) x / w * numSamples);
si = juce::jlimit (0, numSamples - 1, si);
float maxVal = 0.0f;
for (int j = 0; j < blockSize && (si + j) < numSamples; ++j)
maxVal = std::max (maxVal, std::abs (data[si + j]));
float topY = midY - maxVal * (h * 0.45f);
float botY = midY + maxVal * (h * 0.45f);
if (x == 0)
path.startNewSubPath (area.getX() + (float) x, topY);
path.lineTo (area.getX() + (float) x, topY);
}
// Mirror bottom
for (int x = (int) w - 1; x >= 0; --x)
{
int si = (int) ((float) x / w * numSamples);
si = juce::jlimit (0, numSamples - 1, si);
float maxVal = 0.0f;
for (int j = 0; j < blockSize && (si + j) < numSamples; ++j)
maxVal = std::max (maxVal, std::abs (data[si + j]));
float botY = midY + maxVal * (h * 0.45f);
path.lineTo (area.getX() + (float) x, botY);
}
path.closeSubPath();
// Greenish waveform tint
g.setColour (juce::Colour (0xff44cc88).withAlpha (0.4f));
g.fillPath (path);
g.setColour (juce::Colour (0xff44cc88).withAlpha (0.7f));
g.strokePath (path, juce::PathStrokeType (0.8f));
}
void PadComponent::resized() {}
void PadComponent::mouseDown (const juce::MouseEvent& event)
{
if (event.mods.isRightButtonDown())
return;
// Select this pad
if (onSelected)
onSelected (index);
isPressed = true;
drumPad.trigger (1.0f);
repaint();
}
void PadComponent::mouseUp (const juce::MouseEvent& event)
{
if (event.mods.isRightButtonDown())
return;
isPressed = false;
if (! drumPad.oneShot)
drumPad.stop();
repaint();
}
bool PadComponent::isInterestedInFileDrag (const juce::StringArray& files)
{
for (auto& f : files)
{
juce::File file (f);
if (file.isDirectory()) return true;
auto ext = file.getFileExtension().toLowerCase();
if (ext == ".wav" || ext == ".aiff" || ext == ".aif" || ext == ".flac"
|| ext == ".ogg" || ext == ".mp3")
return true;
}
return false;
}
void PadComponent::filesDropped (const juce::StringArray& files, int, int)
{
isDragOver = false;
if (! files.isEmpty() && onLoadSample)
onLoadSample (index, juce::File (files[0]));
repaint();
}
void PadComponent::fileDragEnter (const juce::StringArray&, int, int)
{
isDragOver = true;
repaint();
}
void PadComponent::fileDragExit (const juce::StringArray&)
{
isDragOver = false;
repaint();
}