main_crystalizereq/CrystalizerEQ/AXIOMDesignSystem.h
2025-11-26 14:31:10 +01:00

1597 lines
70 KiB
C++

#ifndef AXIOMDESIGNSYSTEM_H
#define AXIOMDESIGNSYSTEM_H
#pragma once
#include <corecrt_math_defines.h>
#include "JuceLibraryCode/BinaryData.h"
namespace AXIOM {
class DesignSystem : public juce::LookAndFeel_V4 {
public:
DesignSystem();
~DesignSystem() override = default;
struct Colours {
//=====================BASE-COLORS========================//
static inline const juce::Colour ACCENTCOLOUR = juce::Colour::fromRGB(86, 254, 255); // #56FEFF
static inline const juce::Colour BACKGROUNDCOLOUR = juce::Colour::fromRGB(51, 51, 51); // #333333
static inline const juce::Colour FOREGROUNDCOLOUR = juce::Colour::fromRGB(252, 250, 249); // #FCFAF9
static inline const juce::Colour SURFACECOLOUR = juce::Colour::fromRGB(31, 31, 31); // #1F1F1F
static inline const juce::Colour MUTEDTEXTCOLOUR = juce::Colour::fromRGB(207, 207, 207); // #CFCFCF
static inline const juce::Colour ACCENTWEAKCOLOUR = juce::Colour::fromRGB(61, 183, 183); // #3DB7B7
//===================== HOVER COLORS ====================//
static inline const juce::Colour ACCENTHOVER = juce::Colour::fromRGB(140, 255, 255); // #8CFFFF
static inline const juce::Colour BACKGROUNDHOVER = juce::Colour::fromRGB(70, 70, 70); // #464646
static inline const juce::Colour FOREGROUNDHOVER = juce::Colour::fromRGB(255, 255, 255); // #FFFFFF
static inline const juce::Colour SURFACEHOVER = juce::Colour::fromRGB(48, 48, 48); // #303030
static inline const juce::Colour MUTEDTEXTHOVER = juce::Colour::fromRGB(235, 235, 235); // #EBEBEB
static inline const juce::Colour ACCENTWEAKHOVER = juce::Colour::fromRGB(100, 225, 225); // #64E1E1
//=====================BYPASS-COLORS========================//
static inline const juce::Colour ACCENTBYPASS = juce::Colour::fromRGB(110, 255, 255); // #6EFFFF
static inline const juce::Colour BACKGROUNDBYPASS = juce::Colour::fromRGB(66, 66, 66); // #424242
static inline const juce::Colour FOREGROUNDBYPASS = juce::Colour::fromRGB(255, 255, 255); // #FFFFFF
static inline const juce::Colour SURFACEBYPASS = juce::Colour::fromRGB(42, 42, 42); // #2A2A2A
static inline const juce::Colour MUTEDTEXTBYPASS = juce::Colour::fromRGB(230, 230, 230); // #E6E6E6
static inline const juce::Colour ACCENTWEAKBYPASS = juce::Colour::fromRGB(82, 210, 210); // #52D2D2
};
struct Spacing {
enum class DensityMode {
Narrow,
Compact,
Regular,
Wide,
SuperWide
};
static constexpr float getDensityFactor(DensityMode mode) {
switch (mode) {
case DensityMode::Narrow: return {0.9f};
case DensityMode::Compact: return {0.95f};
case DensityMode::Regular: return {1.0f};
case DensityMode::Wide: return {1.08f};
case DensityMode::SuperWide: return {1.15f};
}
return {1.0f};
};
enum class SizeMode {
XS,
S,
M,
L,
XL
};
static constexpr int getSizeUnits(SizeMode size) {
switch (size) {
case SizeMode::XS: return 1;
case SizeMode::S: return 2;
case SizeMode::M: return 3;
case SizeMode::L: return 4;
case SizeMode::XL: return 6;
}
return 1;
};
enum class SnapMode {
Nearest, Floor, Ceiling
};
enum class uiScaleMode {
XS, S, M, L, XL
};
struct Scale {
float baseUnit = 8.0f;
float densityFactor = 1.0f;
float uiScale = 1.0f;
float uiScaleInfluence = 0.3f;
static constexpr float MINCLAMP = 4.0f;
static constexpr float MAXCLAMP = 56.0f;
SnapMode snapMode = SnapMode::Nearest;
float stepUnits = 1.0f;
float space(SizeMode size) noexcept {
float px;
auto units = static_cast<float>(getSizeUnits(size));
px = units * baseUnit;
px *= densityFactor;
px *= 1.0f + (uiScale - 1.0f) * uiScaleInfluence;
px = snapToGrid(px, stepUnits, snapMode);
return std::clamp(px, MINCLAMP, MAXCLAMP);
}
float snapToGrid(float px, float stepUnits, SnapMode mode) noexcept {
float grid = baseUnit * stepUnits;
if (grid <= 0.0f) return px;
float units = px / grid;
float snappedUnits;
switch (mode) {
case SnapMode::Nearest: snappedUnits = std::round(units);
break;
case SnapMode::Floor: snappedUnits = std::floor(units + 1e-6f);
break;
case SnapMode::Ceiling: snappedUnits = std::ceil(units - 1e-6f);
break;
}
return snappedUnits * grid;
};
};
inline static Scale scale;
static void setDensity(DensityMode mode) noexcept {
scale.densityFactor = getDensityFactor(mode);
}
static void setUiScale(uiScaleMode mode) noexcept {
switch (mode) {
case uiScaleMode::XS: scale.uiScale = 0.5f;
break;
case uiScaleMode::S: scale.uiScale = 0.75f;
break;
case uiScaleMode::M: scale.uiScale = 1.0f;
break;
case uiScaleMode::L: scale.uiScale = 1.5f;
break;
case uiScaleMode::XL: scale.uiScale = 1.75f;
break;
}
scale.uiScale = juce::jlimit(0.3f, 3.0f, scale.uiScale);
}
};
struct Typography {
inline static juce::Typeface::Ptr orbitronFont =
juce::Typeface::createSystemTypefaceFor(
BinaryData::OrbitronRegular_ttf,
BinaryData::OrbitronRegular_ttfSize);
inline static juce::Typeface::Ptr horizonFont =
juce::Typeface::createSystemTypefaceFor(
BinaryData::horizon_otf,
BinaryData::horizon_otfSize);
inline static juce::Typeface::Ptr jetBrainsMonoFont =
juce::Typeface::createSystemTypefaceFor(
BinaryData::JetBrainsMonoRegular_ttf,
BinaryData::JetBrainsMonoRegular_ttfSize);
inline static const juce::String primaryFamily{"Orbitron Bold"};
inline static const juce::String displayFamily{"Horizon"};
inline static const juce::String monoFamily{"JetBrains Mono"};
struct Scale {
float basePx = 14.0f;
float ratio = 1.125f;
float sizeFor(int step) const noexcept {
// step = 0 => basePx, step = 1 => base*ratio, step = -1 => base/ratio, ...
return basePx * std::pow(ratio, static_cast<float>(step));
}
};
inline static Scale scale{};
enum class Style {
Display,
H1, H2, H3,
Subtitle,
Body,
Small,
Caption,
Button,
Mono,
Overline
};
struct TextToken {
juce::String family;
int stepFromBase;
bool bold = false;
bool italic = false;
bool uppercase = false;
float letterSpacing = 0.0f;
float lineHeight = 1.25f;
bool useMonospace = false;
};
static TextToken getToken(Style style) noexcept {
switch (style) {
case Style::Display: return {displayFamily, 5, true, false, false, 0.0f, 1.15f, false};
case Style::H1: return {primaryFamily, 4, true, false, false, 0.0f, 1.15f, false};
case Style::H2: return {primaryFamily, 3, true, false, false, 0.0f, 1.20f, false};
case Style::H3: return {primaryFamily, 2, false, false, false, 0.0f, 1.20f, false};
case Style::Subtitle: return {primaryFamily, 1, false, false, false, 0.01f, 1.25f, false};
case Style::Body: return {primaryFamily, 0, false, false, false, 0.0f, 1.35f, false};
case Style::Small: return {primaryFamily, -1, false, false, false, 0.0f, 1.35f, false};
case Style::Caption: return {primaryFamily, -2, false, false, false, 0.02f, 1.40f, false};
case Style::Button: return {primaryFamily, 0, true, false, false, 0.02f, 1.10f, false};
case Style::Mono: return {monoFamily, -1, false, false, false, 0.0f, 1.30f, true};
case Style::Overline: return {primaryFamily, -3, true, false, true, 0.06f, 1.20f, false};
}
return {primaryFamily, 0, false, false, false, 0.0f, 1.3f, false};
}
static juce::Font getFont(Style style, float uiScale = Spacing::scale.uiScale) {
const auto t = getToken(style);
const auto fam = t.family;
auto height = scale.sizeFor(t.stepFromBase) * uiScale;
juce::Font f{fam, height, juce::Font::plain};
if (fam == displayFamily && horizonFont != nullptr) {
f = juce::Font(horizonFont).withHeight(height);
} else if (fam == primaryFamily && orbitronFont != nullptr) {
f = juce::Font(orbitronFont).withHeight(height);
} else if (fam == monoFamily && jetBrainsMonoFont != nullptr) {
f = juce::Font(jetBrainsMonoFont).withHeight(height);
} else
f.setTypefaceName(fam);
f.setBold(t.bold);
f.setItalic(t.italic);
f.setExtraKerningFactor(t.letterSpacing);
return f;
}
static void applyToLabel(juce::Label &label, Style style, float uiScale = Spacing::scale.uiScale) {
label.setFont(getFont(style, uiScale));
if (getToken(style).uppercase)
label.setText(label.getText().toUpperCase(), juce::NotificationType::dontSendNotification);
label.setMinimumHorizontalScale(1.0f);
}
static juce::TextLayout createTextLayout(juce::String text,
juce::Rectangle<float> bounds,
Style style,
juce::Colour colour,
float uiScale = Spacing::scale.uiScale,
juce::Justification just = juce::Justification::topLeft) {
const auto token = getToken(style);
const auto font = getFont(style, uiScale);
juce::AttributedString as;
as.setJustification(just);
if (token.uppercase)
text = text.toUpperCase();
as.append(text, font, colour);
juce::TextLayout tl;
tl.createLayout(as, bounds.getWidth());
return tl;
}
};
struct Shape {
enum class RadiusMode {
XS,
S,
M,
L,
XL
};
enum class StrokeMode {
Hairline,
Thin,
Regular,
Bold
};
enum class CornerStyle {
Rounded,
Cut,
Squircle
};
struct Scale {
float baseRadius = 8.0f;
float uiScale = Spacing::scale.uiScale;
float uiScaleInfluenceRadius = 0.5f;
float uiScaleInfluenceStroke = 0.3f;
static constexpr float MINRADIUS = 2.0f;
static constexpr float MAXRADIUS = 28.0f;
static constexpr float MINSTROKE = 1.0f;
static constexpr float MAXSTROKE = 3.0f;
};
inline static Scale scale{};
static constexpr int getRadiusUnits(RadiusMode mode) {
switch (mode) {
case RadiusMode::XS: return 1;
case RadiusMode::S: return 2;
case RadiusMode::M: return 3;
case RadiusMode::L: return 4;
case RadiusMode::XL: return 6;
}
return 1;
};
static float getRadius(RadiusMode mode, juce::Rectangle<float> bounds) {
int units = getRadiusUnits(mode);
float maxRadiusAllowed = std::min(bounds.getHeight(), bounds.getWidth()) * 0.5f;
float radius = units * scale.baseRadius;
radius *= (1.0f + (Spacing::scale.uiScale - 1.0f) * scale.uiScaleInfluenceRadius);
radius = std::round(radius * 2.0f) / 2.0f;
radius = juce::jlimit(scale.MINRADIUS, std::min(scale.MAXRADIUS, maxRadiusAllowed), radius);
return radius;
};
static constexpr float getStrokeUnits(StrokeMode mode) {
switch (mode) {
case StrokeMode::Hairline: return 0.5f;
case StrokeMode::Thin: return 1.0f;
case StrokeMode::Regular: return 1.5f;
case StrokeMode::Bold: return 2.0f;
}
return 1.0f;
};
static float getStrokeWidth(StrokeMode mode) {
float width = getStrokeUnits(mode);
width *= 1.0f + (Spacing::scale.uiScale - 1) * scale.uiScaleInfluenceStroke;
width = std::round(width * 2.0f) / 2.0f;
width = juce::jlimit(scale.MINSTROKE, scale.MAXSTROKE, width);
return width;
};
};
struct Shadows {
};
struct Opacity {
static constexpr float MINALPHA = 0.04f;
static constexpr float MAXALPHA = 0.95f;
static constexpr float MUTEDMUL = 0.5f;
struct AlphaToken {
enum class Role {
Text, Icon, Surface, Overlay, Stroke, FocusGlow, Disabled, InteractiveFill
};
enum class State {
Rest, Hover, Active, Checked, Dragged, Disabled
};
enum class Emphasis {
High, Medium, Low, Muted
};
Role role;
State state;
Emphasis emphasis;
};
static AlphaToken getAlphaToken(AlphaToken::Role role, AlphaToken::State state,
AlphaToken::Emphasis emphasis) {
return AlphaToken{role, state, emphasis};
};
static float getAlphaFactor(AlphaToken::Role role, AlphaToken::State state, AlphaToken::Emphasis emphasis) {
float alphaFactor;
if (role == AlphaToken::Role::Text || role == AlphaToken::Role::Icon) {
alphaFactor = fromEmphasis(emphasis);
} else {
alphaFactor = fromRole(role);
}
alphaFactor += fromState(state);
if (state == AlphaToken::State::Disabled) alphaFactor *= MUTEDMUL;
alphaFactor = std::clamp(alphaFactor, MINALPHA, MAXALPHA);
return alphaFactor;
}
static float fromEmphasis(AlphaToken::Emphasis emphasis) {
switch (emphasis) {
case AlphaToken::Emphasis::High: return 0.92f;
case AlphaToken::Emphasis::Medium: return 0.72f;
case AlphaToken::Emphasis::Low: return 0.56f;
case AlphaToken::Emphasis::Muted: return 0.42f;
}
return 0.92f;
}
static float fromRole(AlphaToken::Role role) {
switch (role) {
case AlphaToken::Role::Surface: return 0.96f;
case AlphaToken::Role::Overlay: return 0.28f;
case AlphaToken::Role::Stroke: return 0.14f;
case AlphaToken::Role::FocusGlow: return 0.18f;
case AlphaToken::Role::Disabled: return 0.45f;
case AlphaToken::Role::InteractiveFill: return 0.95f;
default: return 1.0f;
}
}
static float fromState(AlphaToken::State state) {
switch (state) {
case AlphaToken::State::Rest: return 0.0f;
case AlphaToken::State::Hover: return 0.06f;
case AlphaToken::State::Active: return 0.10f;
case AlphaToken::State::Checked: return 0.08f;
case AlphaToken::State::Dragged: return 0.10f;
case AlphaToken::State::Disabled: return 0.0f;
}
return 0.0f;
}
static juce::Colour applyAlpha(juce::Colour baseColour, AlphaToken token) {
float alphaFactor = getAlphaFactor(token.role, token.state, token.emphasis);
return baseColour.withMultipliedAlpha(alphaFactor);
}
};
/*struct Motion {
enum class Duration {
Slowest,
Slow,
Moderate,
Regular,
Fast
};
enum class Ease {
Standard,
Decel,
Accel,
Emphasized,
Overshoot
};
enum class Spring{
Snappy,
Natural,
Gentle,
SoftOvershoot
};
enum class MotionRole {
MenuEnter, MenuExit, ModalEnter, TooltipEnter, TooltipExit
};
enum class MotionMicro {
Hover, Pressed, Focus, Success, Error
};
enum class StaggerProfile {
Forward, CenterOut, Reverse
};
struct CubicBezier {
float x1, y1, x2, y2;
};
struct SpringParameters {
float tension, friction, damping, mass;
};
struct MicroParameters {
float scaleDelta, opacityDelta;
Duration duration;
Ease ease;
};
static float getDuration(Duration duration) {
switch (duration) {
case Duration::Slowest: return 0.36f;
case Duration::Slow: return 0.28f;
case Duration::Moderate: return 0.2f;
case Duration::Regular: return 0.14f;
case Duration::Fast: return 0.09f;
}
return 0.2f;
}
static CubicBezier getEasing(Ease easing) {
switch (easing) {
case Ease::Standard: return {0.2f, 0.0f, 0.0f, 1.0f};
case Ease::Decel: return {0.05f, 0.7f, 0.1f, 1.0f};
case Ease::Accel: return {0.3f, 0.0f, 0.8f, 0.15f};
case Ease::Emphasized: return {0.2f, 0.0f, 0.0f, 1.0f};
default: return {0.2f, 0.0f, 0.0f, 1.0f};;
// EIGENES STRUCT SPRING case Ease::Overshoot: return {0.2f, 0.0f, 0.0f, 1.0f};
}
}
static SpringParameters getSpringPreset(Spring mode) {
switch (mode) {
case Spring::Snappy: return {220.0f, 26.0f, 0.6f, 1.0f};
case Spring::Natural: return {170.0f, 22.0f, 0.68f, 1.0f};
case Spring::Gentle: return {120.0f, 20.0f, 0.75f, 1.0f};
case Spring::SoftOvershoot: return {180.0f, 17.0f, 0.55f, 1.0f};
}
return {170.0f, 22.0f, 0.68f, 1.0f};
}
static MicroParameters getMicroPreset(MotionMicro mode) {
switch (mode) {
case MotionMicro::Hover: return {0.02f, 0.06f, Duration::Fast, Ease::Emphasized};
case MotionMicro::Pressed: return {-0.02f, 0.0f, Duration::Fast, Ease::Standard};
case MotionMicro::Focus: return {0.0f, 0.02f, Duration::Regular, Ease::Decel};
case MotionMicro::Success: return {0.04f, 0.04f, Duration::Regular, Ease::Emphasized};
case MotionMicro::Error: return {0.00f, 0.00f, Duration::Fast, Ease::Standard};
}
return {0.0f, 0.0f, Duration::Regular, Ease::Standard};
}
};*/
struct Components {
class BypassButtonLookAndFeel : public juce::LookAndFeel_V4 {
public:
BypassButtonLookAndFeel() {
svgXml = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
}
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 0.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (svgXml != nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto icon = juce::Drawable::createFromSVG(*svgXml);
if (icon != nullptr) {
juce::Colour colour;
if (!isEnabled) {
colour = isToggled ? Colours::SURFACEBYPASS : Colours::ACCENTWEAKCOLOUR;
} else if (isHovered) {
colour = isToggled ? Colours::SURFACEHOVER : Colours::ACCENTHOVER;
} else {
colour = isToggled
? Colours::SURFACECOLOUR
: Colours::ACCENTCOLOUR;
}
// Icon zeichnen
icon->replaceColour(juce::Colours::black, colour);
icon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
}
}
}
private:
std::unique_ptr<juce::XmlElement> svgXml;
};
class PresetMenuButtonLookAndFeel : public juce::LookAndFeel_V4 {
public:
PresetMenuButtonLookAndFeel() {
presetMenuButton = juce::XmlDocument::parse(BinaryData::preset_menu_icon_svg);
}
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 1.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (presetMenuButton != nullptr) {
auto menuIcon = juce::Drawable::createFromSVG(*presetMenuButton);
juce::Colour colour;
if (isHovered) {
colour = isToggled ? Colours::ACCENTHOVER : Colours::BACKGROUNDHOVER;
} else {
colour = isToggled
? Colours::ACCENTCOLOUR
: Colours::BACKGROUNDCOLOUR;
}
// Icon zeichnen
menuIcon->replaceColour(juce::Colours::black, colour);
menuIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, 1.0f);
}
}
private:
std::unique_ptr<juce::XmlElement> presetMenuButton;
};
class SvgToggleButtonLookAndFeel : public juce::LookAndFeel_V4 {
public:
SvgToggleButtonLookAndFeel() {
passiveSvg = juce::XmlDocument::parse(BinaryData::crystalize_button_passive_icon_svg);
activeSvg = juce::XmlDocument::parse(BinaryData::crystalize_button_active_icon_svg);
};
float passiveIconOpacity = 1.0f;
float activeIconOpacity = 0.0f;
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
const bool isHovered = button.isMouseOverOrDragging();
const bool isToggled = button.getToggleState();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 2 * 0.95f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
auto passiveIcon = juce::Drawable::createFromSVG(*passiveSvg);
auto activeIcon = juce::Drawable::createFromSVG(*activeSvg);
if (passiveIcon != nullptr && activeIcon != nullptr) {
activeIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, activeIconOpacity);
passiveIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, passiveIconOpacity);
}
}
private:
std::unique_ptr<juce::XmlElement> passiveSvg;
std::unique_ptr<juce::XmlElement> activeSvg;
};
class lowBandButtonLookAndFeel : public juce::LookAndFeel_V4 {
public:
lowBandButtonLookAndFeel() {
lowBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
lowCutIcon = juce::XmlDocument::parse(BinaryData::low_cut_icon_svg);
lowBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg);
lowShelfIcon = juce::XmlDocument::parse(BinaryData::low_shelf_icon_svg);
}
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 0.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (lowBypassIcon != nullptr && lowCutIcon != nullptr && lowBellIcon != nullptr && lowShelfIcon !=
nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto bypassIcon = juce::Drawable::createFromSVG(*lowBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*lowCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*lowBellIcon);
auto shelfIcon = juce::Drawable::createFromSVG(*lowShelfIcon);
if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon !=
nullptr) {
juce::Colour colour;
if (!isEnabled) {
colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS;
} else if (isHovered) {
colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER;
} else {
colour = isToggled
? Colours::ACCENTCOLOUR
: Colours::SURFACECOLOUR;
}
// Icon zeichnen
if (button.getName() == "LowBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "LowCut") {
cutIcon->replaceColour(juce::Colours::black, colour);
cutIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "LowBell") {
bellIcon->replaceColour(juce::Colours::black, colour);
bellIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "LowShelf") {
shelfIcon->replaceColour(juce::Colours::black, colour);
shelfIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
}
}
}
}
private:
std::unique_ptr<juce::XmlElement> lowBypassIcon;
std::unique_ptr<juce::XmlElement> lowCutIcon;
std::unique_ptr<juce::XmlElement> lowBellIcon;
std::unique_ptr<juce::XmlElement> lowShelfIcon;
};
class highBandButtonLookAndFeel : public juce::LookAndFeel_V4 {
public:
highBandButtonLookAndFeel() {
highBypassIcon = juce::XmlDocument::parse(BinaryData::bypass_icon_svg);
highCutIcon = juce::XmlDocument::parse(BinaryData::high_cut_icon_svg);
highBellIcon = juce::XmlDocument::parse(BinaryData::bell_icon_svg);
highShelfIcon = juce::XmlDocument::parse(BinaryData::high_shelf_icon_svg);
}
void drawToggleButton(juce::Graphics &g, juce::ToggleButton &button,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
bool isHovered = button.isMouseOverOrDragging();
bool isToggled = button.getToggleState();
bool isEnabled = button.isEnabled();
auto bounds = button.getLocalBounds().toFloat();
auto iconSize = juce::jmin(bounds.getWidth(), bounds.getHeight()) * 0.5f;
auto hoverFactor = isHovered ? 1.05f : 1.0f;
auto iconBounds = juce::Rectangle<float>(iconSize * hoverFactor, iconSize * hoverFactor)
.withCentre(bounds.getCentre());
float bypassOpacity = 1.0f;
if (highBypassIcon != nullptr && highCutIcon != nullptr && highBellIcon != nullptr && highShelfIcon
!= nullptr) {
// WICHTIG: Erstelle für jeden Frame ein NEUES Drawable!
auto bypassIcon = juce::Drawable::createFromSVG(*highBypassIcon);
auto cutIcon = juce::Drawable::createFromSVG(*highCutIcon);
auto bellIcon = juce::Drawable::createFromSVG(*highBellIcon);
auto shelfIcon = juce::Drawable::createFromSVG(*highShelfIcon);
if (bypassIcon != nullptr && cutIcon != nullptr && bellIcon != nullptr && shelfIcon !=
nullptr) {
juce::Colour colour;
if (!isEnabled) {
colour = isToggled ? Colours::ACCENTWEAKCOLOUR : Colours::SURFACEBYPASS;
} else if (isHovered) {
colour = isToggled ? Colours::ACCENTHOVER : Colours::SURFACEHOVER;
} else {
colour = isToggled
? Colours::ACCENTCOLOUR
: Colours::SURFACECOLOUR;
}
// Icon zeichnen
if (button.getName() == "HighBypass") {
bypassIcon->replaceColour(juce::Colours::black, colour);
bypassIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "HighCut") {
cutIcon->replaceColour(juce::Colours::black, colour);
cutIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "HighBell") {
bellIcon->replaceColour(juce::Colours::black, colour);
bellIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
} else if (button.getName() == "HighShelf") {
shelfIcon->replaceColour(juce::Colours::black, colour);
shelfIcon->drawWithin(g, iconBounds,
juce::RectanglePlacement::centred, bypassOpacity);
}
}
}
}
private:
std::unique_ptr<juce::XmlElement> highBypassIcon;
std::unique_ptr<juce::XmlElement> highCutIcon;
std::unique_ptr<juce::XmlElement> highBellIcon;
std::unique_ptr<juce::XmlElement> highShelfIcon;
};
struct ButtonStyles {
};
class BaseSliderLookAndFeel : public juce::LookAndFeel_V4 {
public:
juce::Label *createSliderTextBox(juce::Slider &slider) override {
auto *label = juce::LookAndFeel_V4::createSliderTextBox(slider);
label->setColour(juce::Label::backgroundColourId, juce::Colours::transparentBlack);
label->setColour(juce::Label::outlineColourId, juce::Colours::transparentBlack);
label->setColour(juce::Label::textColourId, Colours::FOREGROUNDCOLOUR);
label->setJustificationType(juce::Justification::centred);
label->setInterceptsMouseClicks(false, false);
Typography::applyToLabel(*label, Typography::Style::Mono, 1.f);
return label;
}
juce::Slider::SliderLayout getSliderLayout(juce::Slider &slider) override {
juce::Slider::SliderLayout layout;
if (slider.getName() == "LowBand Slope" || slider.getName() == "HighBand Slope") {
layout.textBoxBounds = {};
return layout;
}
auto r = slider.getLocalBounds();
const int w = slider.getWidth();
const int h = slider.getHeight();
layout.textBoxBounds = juce::Rectangle<int>(
r.getCentreX() - w / 2,
r.getCentreY() - h / 2,
w, h);
layout.sliderBounds = r;
return layout;
}
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
// Indicator
juce::Path p;
auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f;
p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(Colours::ACCENTCOLOUR);
g.fillPath(p);
}
};
class GainSliderLookAndFeel : public BaseSliderLookAndFeel {
public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
bool isHovered = slider.isMouseOverOrDragging();
auto isEnabled = slider.isEnabled();
float hoverFactor = 1.0f;
auto surfaceCol = Colours::SURFACECOLOUR;
auto accentCol = Colours::ACCENTCOLOUR;
if (isEnabled) {
surfaceCol = isHovered ? Colours::SURFACEBYPASS : Colours::SURFACECOLOUR;
accentCol = isHovered ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
hoverFactor = isHovered ? 1.05f : 1.0f;
} else {
surfaceCol = Colours::SURFACEBYPASS;
accentCol = Colours::ACCENTWEAKCOLOUR;
}
auto radius = (juce::jmin(width / 2, height / 2) - 4.0f); // * hoverFactor;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
auto mid = (rotaryEndAngle - rotaryStartAngle) / 2 + rotaryStartAngle;
const float arcPathWidth = 3.0f;
// Background
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
g.setColour(surfaceCol);
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, arcPathWidth);
// Arc für Gain-Anzeige
juce::Path valueArc;
valueArc.addCentredArc(centreX, centreY, radius, radius,
0.0f, mid, angle, true);
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Pointer
juce::Path p;
p.addRectangle(-arcPathWidth / 2, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.5f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.fillPath(p);
}
};
class FreqQSliderLookAndFeel : public BaseSliderLookAndFeel {
public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
const float arcPathWidth = 2.0f;
bool isHovered = slider.isMouseOverOrDragging();
bool isEnabled = slider.isEnabled();
float hoverFactor = 1.0f;
auto surfaceCol = Colours::SURFACECOLOUR;
auto accentCol = Colours::ACCENTCOLOUR;
if (isEnabled) {
surfaceCol = isHovered ? Colours::SURFACEBYPASS : Colours::SURFACECOLOUR;
accentCol = isHovered ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
hoverFactor = isHovered ? 1.05f : 1.0f;
} else {
surfaceCol = Colours::SURFACEBYPASS;
accentCol = Colours::ACCENTWEAKCOLOUR;
}
// Einfacher Ring
g.setColour(surfaceCol);
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, 2.0f);
juce::Path valueArc;
valueArc.addCentredArc(centreX, centreY, radius, radius,
0.0f, rotaryStartAngle, angle, true);
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(accentCol);
g.fillPath(p);
}
};
class SlopeSliderLookAndFeel : public BaseSliderLookAndFeel {
public:
static constexpr float labelPadding = 20.0f;
juce::Slider::SliderLayout getSliderLayout(juce::Slider &slider) override {
juce::Slider::SliderLayout layout;
auto bounds = slider.getLocalBounds();
// TextBox in der Mitte (wie bei BaseSliderLookAndFeel)
layout.textBoxBounds = juce::Rectangle<int>(
bounds.getCentreX() - bounds.getWidth() / 2,
bounds.getCentreY() - bounds.getHeight() / 2,
bounds.getWidth(),
bounds.getHeight()
);
// HIER IST DER TRICK: Knob-Bereich kleiner als Slider-Bounds!
// So bleibt Platz für die Labels außerhalb
layout.sliderBounds = bounds.reduced(labelPadding);
return layout;
}
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
// Basis-Implementation - wird in Subklassen überschrieben
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
bool isEnabled = slider.isEnabled();
bool isHovered = slider.isMouseOverOrDragging();
// Outer ring
g.setColour(Colours::BACKGROUNDCOLOUR);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
juce::Colour accentCol = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
// Indicator
juce::Path p;
auto pointerLength = radius * 0.6f;
auto pointerThickness = 2.0f;
p.addRectangle(-pointerThickness * 0.5f, -radius, pointerThickness, pointerLength);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(accentCol);
g.fillPath(p);
// Dann zeichne die Labels
const auto bounds = juce::Rectangle<int>(x, y, width, height);
const auto centre = bounds.getCentre().toFloat();
const float textRadius = std::min(width, height) * 0.5f + 5.0f;
const juce::StringArray labels = {"12", "24", "36", "48"};
g.setFont(12.0f);
auto value = slider.getValue();
for (int i = 0; i < labels.size(); ++i) {
const double labelVal = labels[i].getDoubleValue(); // "12" -> 12.0 usw.
const bool isMatch = value == i; // Toleranz: 0.5
juce::Colour colour;
if (!isEnabled) {
colour = isMatch
? Colours::ACCENTWEAKCOLOUR
: Colours::FOREGROUNDCOLOUR.withMultipliedAlpha(0.4f);
} else {
colour = isMatch
? Colours::ACCENTCOLOUR
: Colours::FOREGROUNDCOLOUR;
}
g.setColour(colour);
const double position = (double) i / 3.0;
const float ang = rotaryStartAngle + position * (rotaryEndAngle - rotaryStartAngle);
juce::Point<float> pos(
centre.x + textRadius * std::cos(ang - juce::MathConstants<float>::halfPi),
centre.y + textRadius * std::sin(ang - juce::MathConstants<float>::halfPi)
);
juce::Rectangle<float> textRect(30.0f, 16.0f);
textRect.setCentre(pos);
g.drawText(labels[i], textRect, juce::Justification::centred);
}
}
};
class GlobalSliderLookAndFeel : public BaseSliderLookAndFeel {
public:
void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height,
float sliderPos, float rotaryStartAngle, float rotaryEndAngle,
juce::Slider &slider) override {
auto radius = juce::jmin(width / 2, height / 2) - 4.0f;
auto centreX = x + width * 0.5f;
auto centreY = y + height * 0.5f;
auto angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
const float arcPathWidth = 2.0f;
auto mid = (rotaryEndAngle - rotaryStartAngle) / 2 + rotaryStartAngle;
bool isHovered = slider.isMouseOverOrDragging();
bool isEnabled = slider.isEnabled();
float hoverFactor = 1.0f;
auto surfaceCol = Colours::SURFACECOLOUR;
auto accentCol = Colours::ACCENTCOLOUR;
auto ringCol = Colours::SURFACEHOVER;
if (isEnabled) {
surfaceCol = isHovered ? Colours::SURFACEHOVER : Colours::SURFACECOLOUR;
accentCol = isHovered ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
ringCol = isHovered ? Colours::SURFACECOLOUR : Colours::SURFACEHOVER;
hoverFactor = isHovered ? 1.05f : 1.0f;
} else {
surfaceCol = Colours::SURFACEBYPASS;
accentCol = Colours::ACCENTWEAKCOLOUR;
ringCol = Colours::SURFACEHOVER;
}
// Einfacher Ring
g.setColour(surfaceCol);
g.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f);
g.setColour(ringCol);
g.drawEllipse(centreX - radius, centreY - radius, radius * 2.0f, radius * 2.0f, arcPathWidth);
juce::Path valueArc;
valueArc.addCentredArc(centreX, centreY, radius, radius,
0.0f, mid, angle, true);
g.setColour(accentCol);
g.strokePath(valueArc, juce::PathStrokeType(arcPathWidth));
// Dünner Pointer
juce::Path p;
p.addRectangle(-1.0f, (-radius - arcPathWidth) * hoverFactor, arcPathWidth,
radius * 0.4f * hoverFactor);
p.applyTransform(juce::AffineTransform::rotation(angle).translated(centreX, centreY));
g.setColour(accentCol);
g.fillPath(p);
}
};
struct SliderStyles {
struct Size {
enum class SizeMode {
Gain,
Freq,
Q,
Slope,
Global,
Mix
};
static float getSliderSizeMod(SizeMode mode) {
switch (mode) {
case SizeMode::Gain: return 1.25f;
case SizeMode::Freq: return 0.8f;
case SizeMode::Q: return 0.8f;
case SizeMode::Slope: return 0.7f;
case SizeMode::Global: return 0.8f;
case SizeMode::Mix: return 0.8f;
default: return 1.0f;
}
}
};
};
struct LabelStyles {
};
struct TextInputStyles {
};
struct ComboBoxStyles {
};
};
struct Assets {
};
struct Layout {
///
/// @param mode Spacing mode -- e.g. Spacing::SizeMode::XS
/// @return Gap size in px as int
static int gap(Spacing::SizeMode mode) {
return juce::roundToInt(Spacing::scale.space(mode));
}
static juce::BorderSize<int> padding(Spacing::SizeMode mode) {
if (mode == Spacing::SizeMode::XS) return {0, 0, 0, 0};
const int px = gap(mode);
return juce::BorderSize<int>{px, px, px, px};
}
static juce::Rectangle<int> content(juce::Rectangle<int> rect, juce::BorderSize<int> pad) {
rect = pad.subtractedFrom(rect);
return rect;
}
static juce::FlexItem flexItem(juce::Component &component,
float flexGrow = 1.f,
float flexShrink = 1.f,
float basisPx = -1.f,
Spacing::SizeMode margin = Spacing::SizeMode::XS) {
juce::FlexItem item{component};
item.flexGrow = flexGrow;
item.flexShrink = flexShrink;
if (basisPx >= 0.f)
item.flexBasis = basisPx;
const float half = (float) gap(margin) * 0.5f;
item.margin = juce::FlexItem::Margin{half, half, half, half};
return item;
}
static void flexRow(juce::Rectangle<int> bounds,
juce::Array<juce::FlexItem> items,
Spacing::SizeMode gapSize,
juce::FlexBox::JustifyContent justifyContent,
juce::FlexBox::AlignItems alignItems
) {
const auto pad = padding(gapSize);
juce::FlexBox fb;
fb.flexDirection = juce::FlexBox::Direction::row;
fb.flexWrap = juce::FlexBox::Wrap::noWrap;
fb.justifyContent = justifyContent;
fb.alignItems = alignItems;
fb.items.addArray(items);
fb.performLayout(content(bounds, pad));
}
static void flexColumn(juce::Rectangle<int> bounds,
juce::Array<juce::FlexItem> items,
Spacing::SizeMode gapSize,
juce::FlexBox::JustifyContent justifyContent,
juce::FlexBox::AlignItems alignItems
) {
const auto pad = padding(gapSize);
juce::FlexBox fb;
fb.flexDirection = juce::FlexBox::Direction::column;
fb.flexWrap = juce::FlexBox::Wrap::noWrap;
fb.justifyContent = justifyContent;
fb.alignItems = alignItems;
fb.items.addArray(items);
fb.performLayout(content(bounds, pad));
}
static juce::Grid::TrackInfo fr(float value) {
jassert(value > 0.0f);
return juce::Grid::TrackInfo{juce::Grid::Fr{juce::roundToInt(value)}};
}
static juce::Grid::TrackInfo pxTrack(float value) {
jassert(value >= 0.0f);
return juce::Grid::TrackInfo{juce::Grid::Px{value}};
}
static juce::Grid::Px px(float value) {
jassert(value >= 0.0f);
return juce::Grid::Px{value};
}
struct GridSpec {
std::vector<juce::Grid::TrackInfo> cols;
std::vector<juce::Grid::TrackInfo> rows;
Spacing::SizeMode colGap{Spacing::SizeMode::M};
Spacing::SizeMode rowGap{Spacing::SizeMode::M};
juce::BorderSize<int> pad{0, 0, 0, 0};
};
static juce::GridItem area(juce::Component &c,
int rowStart, int colStart,
int rowEnd, int colEnd) {
return juce::GridItem(c)
.withArea(rowStart, colStart, rowEnd, colEnd);
}
static void grid(juce::Rectangle<int> bounds,
const GridSpec &spec,
std::initializer_list<juce::GridItem> children,
juce::Grid::AutoFlow autoFlow = juce::Grid::AutoFlow::row) {
juce::Grid g;
for (const auto &t: spec.cols) g.templateColumns.add(t);
for (const auto &t: spec.rows) g.templateRows.add(t);
g.autoFlow = autoFlow;
g.autoColumns = juce::Grid::TrackInfo(juce::Grid::Fr(1));
g.autoRows = juce::Grid::TrackInfo(juce::Grid::Fr(1));
g.columnGap = px((float) gap(spec.colGap));
g.rowGap = px((float) gap(spec.rowGap));
for (const auto &c: children)
g.items.add(c);
const auto inner = spec.pad.subtractedFrom(bounds);
g.performLayout(inner);
}
};
class PresetMenu : public juce::Component {
public:
PresetMenu(juce::TextEditor *inputField,
juce::TextButton *saveButton,
juce::TextButton *deleteButton) {
addAndMakeVisible(*inputField);
addAndMakeVisible(*saveButton);
addAndMakeVisible(*deleteButton);
}
};
class PresetMenuLookAndFeel : public juce::LookAndFeel_V4 {
public:
void drawCallOutBoxBackground(juce::CallOutBox &callOutBox, juce::Graphics &g, const juce::Path &path,
juce::Image &image) override {
g.setColour(Colours::SURFACECOLOUR);
g.fillPath(path);
g.setColour(Colours::FOREGROUNDCOLOUR.withAlpha(0.3f));
g.strokePath(path, juce::PathStrokeType(1.0f));
};
int getCallOutBoxBorderSize(const juce::CallOutBox &) override {
return 4;
}
};
class PresetButtonsLookAndFeel : public juce::LookAndFeel_V4 {
public:
void drawButtonText(juce::Graphics &g, juce::TextButton &button, bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
auto bounds = button.getLocalBounds();
// Text holen
auto text = button.getButtonText();
// Farbe wählen
auto isHovered = button.isMouseOver();
bool isEnabled = button.isEnabled();
juce::Colour colour = isHovered ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
if (!isEnabled) colour = colour.withMultipliedAlpha(0.6f);
g.setColour(colour);
// Font einstellen
auto font = Typography::getFont(Typography::Style::Button, 1.0f);
g.setFont(font);
// Text zeichnen
g.drawFittedText(text,
bounds.reduced(4),
juce::Justification::centred,
1);
}
void drawButtonBackground(juce::Graphics &g, juce::Button &button,
const juce::Colour &backgroundColour,
bool shouldDrawButtonAsHighlighted,
bool shouldDrawButtonAsDown) override {
auto bounds = button.getLocalBounds().toFloat();
bool isHovered = button.isMouseOver();
bool isEnabled = button.isEnabled();
auto colour = isHovered ? Colours::BACKGROUNDHOVER : Colours::BACKGROUNDCOLOUR;
if (!isEnabled) colour = colour.withMultipliedAlpha(0.6f);
g.setColour(colour);
g.fillRoundedRectangle(bounds.reduced(1.0f), 4.0f);
}
};
class ClickableTextEditor : public juce::TextEditor {
public:
std::function<void()> onMouseDown;
void mouseDown(const juce::MouseEvent &event) override {
juce::TextEditor::mouseDown(event); // Wichtig: Original-Verhalten beibehalten
if (onMouseDown)
onMouseDown();
}
};
class PresetInputLookAndFeel : public juce::LookAndFeel_V4 {
public:
void fillTextEditorBackground(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
// Hintergrundfarbe
if (textEditor.isEnabled()) {
g.setColour(Colours::SURFACECOLOUR);
} else {
g.setColour(Colours::SURFACEBYPASS);
}
g.fillRoundedRectangle(0, 0, width, height,
Shape::getRadius(Shape::RadiusMode::S,
juce::Rectangle<float>(0, 0, width, height)));
if (textEditor.getText().isEmpty() && !textEditor.hasKeyboardFocus(true)) {
g.setColour(placeholderColour);
g.setFont(Typography::getFont(Typography::Style::Body, 1.0f));
auto textBounds = juce::Rectangle<int>(0, 0, width, height).reduced(5);
g.drawText(placeholderText, textBounds, juce::Justification::centred, true);
}
}
void drawTextEditorOutline(juce::Graphics &g, int width, int height,
juce::TextEditor &textEditor) override {
// Rahmen
auto bounds = juce::Rectangle<float>(0, 0, width, height);
auto radius = Shape::getRadius(Shape::RadiusMode::S, bounds);
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
juce::Colour outlineColour;
if (!textEditor.isEnabled()) {
outlineColour = Colours::FOREGROUNDBYPASS.withAlpha(0.3f);
} else if (textEditor.hasKeyboardFocus(true)) {
// Fokussiert - Accent-Farbe
outlineColour = Colours::ACCENTCOLOUR;
} else if (textEditor.isMouseOver()) {
// Hover
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
// Normal
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
g.setColour(outlineColour);
g.drawRoundedRectangle(bounds.reduced(strokeWidth * 0.5f), radius, strokeWidth);
}
juce::CaretComponent *createCaretComponent(juce::Component *keyFocusOwner) override {
auto *caret = new juce::CaretComponent(keyFocusOwner);
caret->setColour(juce::CaretComponent::caretColourId, Colours::ACCENTCOLOUR);
return caret;
}
private:
juce::String placeholderText = "Preset Name";
juce::Colour placeholderColour = Colours::MUTEDTEXTCOLOUR;
};
class PresetComboBoxLookAndFeel : public juce::LookAndFeel_V4,
private juce::ComponentListener
{
public:
PresetComboBoxLookAndFeel() = default;
~PresetComboBoxLookAndFeel() override
{
}
void getIdealPopupMenuItemSize (const juce::String& text, bool /*isSeparator*/,
int /*standardMenuItemHeight*/, int& idealWidth,
int& idealHeight) override
{
idealHeight = 50;
auto font = Typography::getFont (Typography::Style::Body, 1.0f);
idealWidth = font.getStringWidth (text);
}
void drawPopupMenuUpDownArrow(juce::Graphics& g, int width, int height,
bool isScrollUpArrow) override
{
g.setColour(Colours::ACCENTCOLOUR);
auto arrowZone = juce::Rectangle<float>(0.0f, 0.0f, (float)width, (float)height);
auto arrowWidth = arrowZone.getWidth() * 0.1f;
auto arrowHeight = arrowZone.getHeight() * 0.1f;
juce::Path arrow;
arrow.startNewSubPath(arrowZone.getCentreX() - arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow ? arrowHeight * 0.5f : -arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX(),
arrowZone.getCentreY() + (isScrollUpArrow ? -arrowHeight * 0.5f : arrowHeight * 0.5f));
arrow.lineTo(arrowZone.getCentreX() + arrowWidth * 0.5f,
arrowZone.getCentreY() + (isScrollUpArrow ? arrowHeight * 0.5f : -arrowHeight * 0.5f));
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
void drawPopupMenuBackground(juce::Graphics& g, int width, int height) override
{
g.fillAll(Colours::SURFACECOLOUR);
}
void drawPopupMenuItem (juce::Graphics& g, const juce::Rectangle<int>& area,
bool isSeparator, bool isActive, bool isHighlighted,
bool isTicked, bool hasSubMenu, const juce::String& text,
const juce::String& shortcutKeyText, const juce::Drawable* icon,
const juce::Colour* textColourToUse) override {
juce::Colour backGroundColour = isHighlighted ? Colours::SURFACEHOVER : Colours::SURFACECOLOUR;
g.fillAll(backGroundColour);
g.setFont (Typography::getFont (Typography::Style::Mono, 1.0f));
if (isTicked) {
juce::Colour textColour = isHighlighted ? Colours::ACCENTHOVER : Colours::ACCENTCOLOUR;
g.setColour (textColour);
} else {
juce::Colour textColour = isHighlighted ? Colours::FOREGROUNDHOVER : Colours::FOREGROUNDCOLOUR;
g.setColour (textColour);
}
g.drawFittedText (text, area.reduced (10), juce::Justification::centredLeft, 1);
}
void drawComboBox(juce::Graphics& g, int width, int height, bool isButtonDown,
int buttonX, int buttonY, int buttonW, int buttonH,
juce::ComboBox& box) override
{
auto bounds = juce::Rectangle<float>(0, 0, (float)width, (float)height);
auto cornerSize = Shape::getRadius(Shape::RadiusMode::S, bounds);
bool isHovered = box.isMouseOver();
bool isEnabled = box.isEnabled();
juce::Colour bgColour;
if (!isEnabled) {
bgColour = Colours::SURFACEBYPASS;
} else if (isButtonDown) {
bgColour = Colours::SURFACEHOVER;
} else if (isHovered) {
bgColour = Colours::SURFACECOLOUR.brighter(0.1f);
} else {
bgColour = Colours::SURFACECOLOUR;
}
g.setColour(bgColour);
g.fillRoundedRectangle(bounds, cornerSize);
juce::Colour outlineColour;
if (!isEnabled) {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.2f);
} else if (isButtonDown) {
outlineColour = Colours::ACCENTCOLOUR;
} else if (isHovered) {
outlineColour = Colours::FOREGROUNDHOVER.withAlpha(0.5f);
} else {
outlineColour = Colours::FOREGROUNDCOLOUR.withAlpha(0.3f);
}
auto strokeWidth = Shape::getStrokeWidth(Shape::StrokeMode::Thin);
g.setColour(outlineColour);
g.drawRoundedRectangle(bounds.reduced(strokeWidth * 0.5f), cornerSize, strokeWidth);
auto arrowZone = juce::Rectangle<float>((float)buttonX, (float)buttonY,
(float)buttonW, (float)buttonH);
juce::Colour arrowColour = isEnabled ? Colours::ACCENTCOLOUR : Colours::ACCENTWEAKCOLOUR;
if (isHovered && isEnabled) {
arrowColour = Colours::ACCENTHOVER;
}
g.setColour(arrowColour);
juce::Path arrow;
auto arrowW = arrowZone.getWidth() * 0.3f;
auto arrowH = arrowZone.getHeight() * 0.15f;
auto centreX = arrowZone.getCentreX();
auto centreY = arrowZone.getCentreY();
arrow.startNewSubPath(centreX - arrowW * 0.5f, centreY - arrowH * 0.5f);
arrow.lineTo(centreX, centreY + arrowH * 0.5f);
arrow.lineTo(centreX + arrowW * 0.5f, centreY - arrowH * 0.5f);
g.strokePath(arrow, juce::PathStrokeType(1.5f));
}
void positionComboBoxText(juce::ComboBox& box, juce::Label& label) override
{
label.setBounds(8, 0, box.getWidth() - 30, box.getHeight());
label.setFont(Typography::getFont(Typography::Style::Mono, 1.0f));
label.setJustificationType(juce::Justification::centredLeft);
}
private:
};
private:
};
}
#endif //AXIOMDESIGNSYSTEM_H