- 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>
194 sor
6.1 KiB
C++
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();
|
|
}
|