#ifndef AXIOMDESIGNSYSTEM_H #define AXIOMDESIGNSYSTEM_H #pragma once #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(110, 255, 255); // #6EFFFF static inline const juce::Colour BACKGROUNDHOVER = juce::Colour::fromRGB(66, 66, 66); // #424242 static inline const juce::Colour FOREGROUNDHOVER = juce::Colour::fromRGB(255, 255, 255); // #FFFFFF static inline const juce::Colour SURFACEHOVER = juce::Colour::fromRGB(42, 42, 42); // #2A2A2A static inline const juce::Colour MUTEDTEXTHOVER = juce::Colour::fromRGB(230, 230, 230); // #E6E6E6 static inline const juce::Colour ACCENTWEAKHOVER = 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.6f, 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; 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 *= 0.5f; 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 { }; struct Components { struct ButtonStyles { }; struct SliderStyles { }; struct LabelStyles { }; struct TextInputStyles { }; struct ComboBoxStyles { }; }; struct Assets { }; struct Layout { }; private : }; } #endif //AXIOMDESIGNSYSTEM_H