#pragma once #include #include "../Resources.h" namespace serum { // =========================================================================== // Dark vector look-and-feel. Rotary and linear sliders are drawn with juce::Path // (arcs + indicator + thumb) so they stay crisp at every UI scale. // =========================================================================== class SerumLookAndFeel : public juce::LookAndFeel_V4 { public: SerumLookAndFeel() { setColour (juce::Slider::backgroundColourId, theme::panel); setColour (juce::Slider::thumbColourId, theme::accent); setColour (juce::Slider::trackColourId, theme::outline); setColour (juce::Slider::rotarySliderFillColourId, theme::accent); setColour (juce::Slider::rotarySliderOutlineColourId, theme::panelRaised); setColour (juce::Slider::textBoxTextColourId, theme::text); setColour (juce::Slider::textBoxBackgroundColourId, theme::panel); setColour (juce::Slider::textBoxHighlightColourId, theme::accent); setColour (juce::Slider::textBoxOutlineColourId, theme::outline); setColour (juce::ComboBox::backgroundColourId, theme::panelRaised); setColour (juce::ComboBox::textColourId, theme::text); setColour (juce::ComboBox::arrowColourId, theme::textDim); setColour (juce::ComboBox::outlineColourId, theme::outline); setColour (juce::ComboBox::buttonColourId, theme::panelRaised); setColour (juce::ComboBox::focusedOutlineColourId, theme::accent); setColour (juce::PopupMenu::backgroundColourId, theme::panelRaised); setColour (juce::PopupMenu::textColourId, theme::text); setColour (juce::PopupMenu::highlightedBackgroundColourId, theme::accent); setColour (juce::PopupMenu::highlightedTextColourId, juce::Colours::black); setColour (juce::TextButton::buttonColourId, theme::panelRaised); setColour (juce::TextButton::buttonOnColourId, theme::accent); setColour (juce::TextButton::textColourOffId, theme::textDim); setColour (juce::TextButton::textColourOnId, juce::Colours::black); setColour (juce::TextEditor::backgroundColourId, theme::panel); setColour (juce::TextEditor::textColourId, theme::text); setColour (juce::TextEditor::outlineColourId, theme::outline); setColour (juce::TextEditor::focusedOutlineColourId, theme::accent); } void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float, float, juce::Slider& slider) override { const int labelH = 18; const int knobH = height - labelH; const int knobSize = juce::jmin (width, knobH) - 6; const int cx = x + width / 2; const int cy = y + knobH / 2; const float radius = knobSize * 0.5f; const juce::Rectangle area ((float) cx - radius, (float) cy - radius, radius * 2.0f, radius * 2.0f); // Body. g.setColour (theme::panelRaised); g.fillEllipse (area); g.setColour (theme::outline); g.drawEllipse (area, 1.0f); const float startAngle = juce::MathConstants::pi * 1.25f; // 225 deg const float endAngle = juce::MathConstants::pi * -0.25f; const float valueAngle = startAngle + sliderPos * (endAngle - startAngle); // Track arc. juce::Path track; track.addCentredArc ((float) cx, (float) cy, radius - 4.0f, radius - 4.0f, 0.0f, startAngle, endAngle, true); g.setColour (theme::outline); g.strokePath (track, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved)); // Value arc. juce::Path valueArc; valueArc.addCentredArc ((float) cx, (float) cy, radius - 4.0f, radius - 4.0f, 0.0f, startAngle, valueAngle, true); g.setColour (theme::accent); g.strokePath (valueArc, juce::PathStrokeType (3.0f, juce::PathStrokeType::curved)); // Indicator. const float indX = (float) cx + (radius - 9.0f) * std::cos (valueAngle); const float indY = (float) cy + (radius - 9.0f) * std::sin (valueAngle); g.setColour (theme::text); g.drawLine ((float) cx, (float) cy, indX, indY, 2.0f); g.setColour (theme::text); g.fillEllipse (indX - 2.0f, indY - 2.0f, 4.0f, 4.0f); // Value readout. g.setColour (theme::text); g.setFont (juce::Font (10.0f, juce::Font::bold)); g.drawText (slider.getTextFromValue (slider.getValue()), juce::Rectangle (x, y, width, knobH - (int) radius + 8), juce::Justification::centred, false); // Name label. g.setColour (theme::textDim); g.setFont (juce::Font (11.0f)); g.drawText (slider.getName(), juce::Rectangle (x, y + knobH, width, labelH), juce::Justification::centred, false); } void drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float, float, juce::Slider::SliderStyle, juce::Slider& slider) override { const bool vertical = height > width; juce::Rectangle track; if (vertical) { track = juce::Rectangle (x + width / 2 - 3, y + 8, 6, height - 16); } else { track = juce::Rectangle (x + 8, y + height / 2 - 3, width - 16, 6); } g.setColour (theme::outline); g.fillRoundedRectangle (track.toFloat(), 3.0f); juce::Rectangle fill; if (vertical) { const float h = track.getHeight() * sliderPos; fill = juce::Rectangle ((float) track.getX(), (float) track.getBottom() - h, (float) track.getWidth(), h); } else { fill = juce::Rectangle ((float) track.getX(), (float) track.getY(), track.getWidth() * sliderPos, (float) track.getHeight()); } g.setColour (theme::accent); g.fillRoundedRectangle (fill, 3.0f); // Thumb. const float thumbC = vertical ? (track.getBottom() - track.getHeight() * sliderPos) : (track.getX() + track.getWidth() * sliderPos); juce::Point thumb (vertical ? (float) track.getCentreX() : thumbC, vertical ? thumbC : (float) track.getCentreY()); g.setColour (theme::text); g.fillEllipse (thumb.x - 6.0f, thumb.y - 6.0f, 12.0f, 12.0f); g.setColour (theme::textDim); g.setFont (juce::Font (10.0f)); g.drawText (slider.getName(), x, y, width, height, juce::Justification::bottomLeft, false); } }; } // namespace serum