#ifndef AXIOMDESIGNSYSTEM_H #define AXIOMDESIGNSYSTEM_H #pragma once #include #include "JuceLibraryCode/BinaryData.h" #include "PluginEditor.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(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(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 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 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(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 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(iconSize * hoverFactor, iconSize * hoverFactor) .withCentre(bounds.getCentre()) .withX(bounds.getX()); // ganz links im Button starten ; 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 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()) * 0.5f; auto hoverFactor = isHovered ? 1.05f : 1.0f; auto iconBounds = juce::Rectangle(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 passiveSvg; std::unique_ptr 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(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 lowBypassIcon; std::unique_ptr lowCutIcon; std::unique_ptr lowBellIcon; std::unique_ptr 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(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 highBypassIcon; std::unique_ptr highCutIcon; std::unique_ptr highBellIcon; std::unique_ptr 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, juce::Colours::white); 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; auto r = slider.getLocalBounds(); const int w = slider.getWidth(); const int h = slider.getHeight(); layout.textBoxBounds = juce::Rectangle ( 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); //TODO: ON BYPASS SET TO RING TO SURFACEHOVER, ACCENT TO WEAKACCENTHOVER } }; 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 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; 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.fillEllipse(centreX - radius, centreY - radius, radius * 2.0f , radius * 2.0f); //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, 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 1.0f; 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 padding(Spacing::SizeMode mode) { if (mode == Spacing::SizeMode::XS) return {0, 0, 0, 0}; const int px = gap(mode); return juce::BorderSize{px, px, px, px}; } static juce::Rectangle content(juce::Rectangle rect, juce::BorderSize 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 bounds, juce::Array 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 bounds, juce::Array 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 cols; std::vector rows; Spacing::SizeMode colGap { Spacing::SizeMode::M }; Spacing::SizeMode rowGap { Spacing::SizeMode::M }; juce::BorderSize 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 bounds, const GridSpec& spec, std::initializer_list 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); } }; private: }; } #endif //AXIOMDESIGNSYSTEM_H