1076 lines
44 KiB
C++
1076 lines
44 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())
|
|
.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<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()) * 0.5f;
|
|
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;
|
|
|
|
};
|
|
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<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);
|
|
|
|
//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<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);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
};
|
|
}
|
|
|
|
|
|
#endif //AXIOMDESIGNSYSTEM_H
|