FahlGrahn Audio v1.0.0
Loading...
Searching...
No Matches
DeathMetalLookAndFeel.h
1#pragma once
2
3#include <Fonts.h>
4#include <JuceHeader.h>
5#include <cmath>
6#include <numbers>
7
8class DeathMetalLookAndFeel : public juce::LookAndFeel_V4
9{
10 public:
12 : deathMetalFont(juce::FontOptions(
13 juce::Typeface::createSystemTypefaceFor(BinaryData::ArtDystopia_ttf, BinaryData::ArtDystopia_ttfSize)))
14 {
15 }
16 ~DeathMetalLookAndFeel() override
17 {
18 }
19
20 juce::Font getLabelFont(juce::Label &label) override
21 {
22 std::ignore = label;
23 return deathMetalFont;
24 }
25
26 juce::Font getPopupMenuFont() override
27 {
28 return deathMetalFont;
29 }
30
31 void drawButtonText(juce::Graphics &g, juce::TextButton &button, bool isMouseOverButton, bool isButtonDown) override
32 {
33 std::ignore = isMouseOverButton;
34 std::ignore = isButtonDown;
35 auto font = deathMetalFont;
36 g.setFont(font);
37 g.setColour(button.findColour(juce::TextButton::textColourOnId));
38 g.drawText(button.getButtonText(), button.getLocalBounds(), juce::Justification::centred, true);
39
40 button.setColour(juce::TextButton::ColourIds::buttonColourId, juce::Colours::black);
41 button.setColour(juce::TextButton::ColourIds::buttonOnColourId, juce::Colours::black);
42 }
43
44 void drawLinearSlider(Graphics &g, int x, int y, int width, int height, float sliderPos, float minSliderPos,
45 float maxSliderPos, Slider::SliderStyle style, Slider &slider) override
46 {
47 if (slider.isBar())
48 {
49 g.setColour(slider.findColour(Slider::trackColourId));
50 g.fillRect(slider.isHorizontal() ? Rectangle<float>(static_cast<float>(x), (float)y + 0.5f,
51 sliderPos - (float)x, (float)height - 1.0f)
52 : Rectangle<float>((float)x + 0.5f, sliderPos, (float)width - 1.0f,
53 (float)y + ((float)height - sliderPos)));
54
55 drawLinearSliderOutline(g, x, y, width, height, style, slider);
56 }
57 else
58 {
59 auto isTwoVal =
60 (style == Slider::SliderStyle::TwoValueVertical || style == Slider::SliderStyle::TwoValueHorizontal);
61 auto isThreeVal = (style == Slider::SliderStyle::ThreeValueVertical ||
62 style == Slider::SliderStyle::ThreeValueHorizontal);
63
64 auto trackWidth = jmin(6.0f, slider.isHorizontal() ? (float)height * 0.25f : (float)width * 0.25f);
65
66 Point<float> startPoint(slider.isHorizontal() ? (float)x : (float)x + (float)width * 0.5f,
67 slider.isHorizontal() ? (float)y + (float)height * 0.5f : (float)(height + y));
68
69 Point<float> endPoint(slider.isHorizontal() ? (float)(width + x) : startPoint.x,
70 slider.isHorizontal() ? startPoint.y : (float)y);
71
72 Path backgroundTrack;
73 backgroundTrack.startNewSubPath(startPoint);
74 backgroundTrack.lineTo(endPoint);
75 g.setColour(slider.findColour(Slider::backgroundColourId));
76 g.strokePath(backgroundTrack, {trackWidth, PathStrokeType::curved, PathStrokeType::rounded});
77
78 Path valueTrack;
79 Point<float> minPoint, maxPoint, thumbPoint;
80
81 if (isTwoVal || isThreeVal)
82 {
83 minPoint = {slider.isHorizontal() ? minSliderPos : (float)width * 0.5f,
84 slider.isHorizontal() ? (float)height * 0.5f : minSliderPos};
85
86 if (isThreeVal)
87 thumbPoint = {slider.isHorizontal() ? sliderPos : (float)width * 0.5f,
88 slider.isHorizontal() ? (float)height * 0.5f : sliderPos};
89
90 maxPoint = {slider.isHorizontal() ? maxSliderPos : (float)width * 0.5f,
91 slider.isHorizontal() ? (float)height * 0.5f : maxSliderPos};
92 }
93 else
94 {
95 auto kx = slider.isHorizontal() ? sliderPos : ((float)x + (float)width * 0.5f);
96 auto ky = slider.isHorizontal() ? ((float)y + (float)height * 0.5f) : sliderPos;
97
98 minPoint = startPoint;
99 maxPoint = {kx, ky};
100 }
101
102 auto thumbWidth = getSliderThumbRadius(slider) + 4;
103
104 valueTrack.startNewSubPath(minPoint);
105 valueTrack.lineTo(isThreeVal ? thumbPoint : maxPoint);
106 g.setColour(slider.findColour(Slider::trackColourId));
107 g.strokePath(valueTrack, {trackWidth, PathStrokeType::curved, PathStrokeType::rounded});
108
109 if (!isTwoVal)
110 {
111 g.setColour(juce::Colours::black);
112 g.fillEllipse(Rectangle<float>(static_cast<float>(thumbWidth), static_cast<float>(thumbWidth))
113 .withCentre(isThreeVal ? thumbPoint : maxPoint));
114
115 g.setColour(juce::Colours::white);
116
117 juce::Point<float> point1(maxPoint.getX(), maxPoint.getY());
118 point1.setX(point1.getX() + (thumbWidth / 2) * ::sinf(0.f * std::numbers::pi / 180.f));
119 point1.setY(point1.getY() + (thumbWidth / 2) * ::cosf(0.f * std::numbers::pi / 180.f));
120
121 juce::Point<float> point2(maxPoint.getX(), maxPoint.getY());
122 point2.setX(point2.getX() +
123 (thumbWidth / 2) * ::sinf(144.f * static_cast<float>(std::numbers::pi) / 180.f));
124 point2.setY(point2.getY() +
125 (thumbWidth / 2) * ::cosf(144.f * static_cast<float>(std::numbers::pi) / 180.f));
126
127 juce::Point<float> point3(maxPoint.getX(), maxPoint.getY());
128 point3.setX(point3.getX() +
129 (thumbWidth / 2) * ::sinf(288.f * static_cast<float>(std::numbers::pi) / 180.f));
130 point3.setY(point3.getY() +
131 (thumbWidth / 2) * ::cosf(288.f * static_cast<float>(std::numbers::pi) / 180.f));
132
133 juce::Point<float> point4(maxPoint.getX(), maxPoint.getY());
134 point4.setX(point4.getX() +
135 (thumbWidth / 2) * ::sinf(72.f * static_cast<float>(std::numbers::pi) / 180.f));
136 point4.setY(point4.getY() +
137 (thumbWidth / 2) * ::cosf(72.f * static_cast<float>(std::numbers::pi) / 180.f));
138
139 juce::Point<float> point5(maxPoint.getX(), maxPoint.getY());
140 point5.setX(point5.getX() +
141 (thumbWidth / 2) * ::sinf(216.f * static_cast<float>(std::numbers::pi) / 180.f));
142 point5.setY(point5.getY() +
143 (thumbWidth / 2) * ::cosf(216.f * static_cast<float>(std::numbers::pi) / 180.f));
144
145 juce::Path pentagramPath;
146 pentagramPath.startNewSubPath(point1);
147 pentagramPath.lineTo(point2);
148 pentagramPath.lineTo(point3);
149 pentagramPath.lineTo(point4);
150 pentagramPath.lineTo(point5);
151 pentagramPath.closeSubPath();
152 g.strokePath(pentagramPath, juce::PathStrokeType(1));
153
154 g.drawEllipse(Rectangle<float>(static_cast<float>(thumbWidth), static_cast<float>(thumbWidth))
155 .withCentre(isThreeVal ? thumbPoint : maxPoint)
156 .reduced(2, 2),
157 1.);
158 }
159
160 if (isTwoVal || isThreeVal)
161 {
162 auto sr = jmin(trackWidth, (slider.isHorizontal() ? (float)height : (float)width) * 0.4f);
163 auto pointerColour = slider.findColour(Slider::thumbColourId);
164
165 if (slider.isHorizontal())
166 {
167 drawPointer(g, minSliderPos - sr, jmax(0.0f, (float)y + (float)height * 0.5f - trackWidth * 2.0f),
168 trackWidth * 2.0f, pointerColour, 2);
169
170 drawPointer(g, maxSliderPos - trackWidth,
171 jmin((float)(y + height) - trackWidth * 2.0f, (float)y + (float)height * 0.5f),
172 trackWidth * 2.0f, pointerColour, 4);
173 }
174 else
175 {
176 drawPointer(g, jmax(0.0f, (float)x + (float)width * 0.5f - trackWidth * 2.0f),
177 minSliderPos - trackWidth, trackWidth * 2.0f, pointerColour, 1);
178
179 drawPointer(g, jmin((float)(x + width) - trackWidth * 2.0f, (float)x + (float)width * 0.5f),
180 maxSliderPos - sr, trackWidth * 2.0f, pointerColour, 3);
181 }
182 }
183
184 if (slider.isBar())
185 drawLinearSliderOutline(g, x, y, width, height, style, slider);
186 }
187 }
188
189 void drawRotarySlider(juce::Graphics &g, int x, int y, int width, int height, float sliderPosProportional,
190 float rotaryStartAngle, float rotaryEndAngle, juce::Slider &slider) override
191 {
192 std::ignore = slider;
193 auto radius = (float)juce::jmin(width / 2, height / 2) - 4.0f;
194 auto centreX = (float)x + (float)width * 0.5f;
195 auto centreY = (float)y + (float)height * 0.5f;
196 auto rx = centreX - radius;
197 auto ry = centreY - radius;
198 auto rw = radius * 2.0f;
199 auto angle = rotaryStartAngle + sliderPosProportional * (rotaryEndAngle - rotaryStartAngle);
200 // auto lineW = jmin (5.0f, radius * 0.5f);
201 auto lineW = radius * 0.135f;
202
203 juce::Rectangle<float> bounds(rx, ry, rw, rw);
204
205 // Draw the black ellipse background
206 g.setColour(juce::Colours::black);
207 g.fillEllipse(bounds);
208
209 // auto arcThickness = 4.0;
210 float adjustedRadius = radius - (lineW / 2.0f);
211 // Draw the grey arc
212 Path backgroundArc;
213 backgroundArc.addCentredArc(centreX, centreY, adjustedRadius, adjustedRadius, 0.0f, rotaryStartAngle,
214 rotaryEndAngle, true);
215
216 g.setColour(juce::Colours::grey.darker(1.0));
217 g.strokePath(backgroundArc, PathStrokeType(lineW, PathStrokeType::curved, PathStrokeType::rounded));
218
219 // Draw the white arc indicating the value
220 juce::Path arcPath;
221 arcPath.addCentredArc(centreX, centreY, adjustedRadius, adjustedRadius, 0.0f, rotaryStartAngle, angle, true);
222
223 // Create an offscreen image to draw the arc
224 juce::Image offscreenImage(juce::Image::ARGB, width, height, true);
225 {
226 juce::Graphics offscreenGraphics(offscreenImage);
227
228 // Fill with transparency to avoid any artifacts
229 offscreenGraphics.fillAll(juce::Colours::transparentBlack);
230
231 // Draw the arc path to the offscreen image
232 offscreenGraphics.setColour(juce::Colours::white);
233
234 offscreenGraphics.strokePath(
235 arcPath, juce::PathStrokeType(lineW, juce::PathStrokeType::curved, juce::PathStrokeType::rounded));
236
237 // offscreenGraphics.strokePath(arcPath, juce::PathStrokeType(lineW, juce::PathStrokeType::curved,
238 // juce::PathStrokeType::rounded));
239 }
240
241 // Create a new image to unbind the offscreen image
242 juce::Image unboundImage = offscreenImage.createCopy();
243
244 // Apply the glow effect to the unbound image
245 juce::Image glowImage(juce::Image::ARGB, width, height, true);
246 {
247 juce::Graphics glowGraphics(glowImage);
248 juce::GlowEffect glowEffect;
249 glowEffect.setGlowProperties(5.0f, juce::Colours::yellow);
250 glowEffect.applyEffect(unboundImage, glowGraphics, 1.0f, 1.0f);
251 }
252
253 // Draw the image with the glow effect
254 g.drawImageAt(glowImage, x, y);
255
256 g.setColour(juce::Colours::white);
257
258 juce::Point<float> point1(centreX, centreY);
259 point1.setX(point1.getX() + (radius - lineW * 1.5f) * ::sinf(0.f * std::numbers::pi / 180.f));
260 point1.setY(point1.getY() + (radius - lineW * 1.5f) * ::cosf(0.f * std::numbers::pi / 180.f));
261
262 juce::Point<float> point2(centreX, centreY);
263 point2.setX(point2.getX() +
264 (radius - lineW * 1.5f) * ::sinf(144.f * static_cast<float>(std::numbers::pi) / 180.f));
265 point2.setY(point2.getY() +
266 (radius - lineW * 1.5f) * ::cosf(144.f * static_cast<float>(std::numbers::pi) / 180.f));
267
268 juce::Point<float> point3(centreX, centreY);
269 point3.setX(point3.getX() +
270 (radius - lineW * 1.5f) * ::sinf(288.f * static_cast<float>(std::numbers::pi) / 180.f));
271 point3.setY(point3.getY() +
272 (radius - lineW * 1.5f) * ::cosf(288.f * static_cast<float>(std::numbers::pi) / 180.f));
273
274 juce::Point<float> point4(centreX, centreY);
275 point4.setX(point4.getX() +
276 (radius - lineW * 1.5f) * ::sinf(72.f * static_cast<float>(std::numbers::pi) / 180.f));
277 point4.setY(point4.getY() +
278 (radius - lineW * 1.5f) * ::cosf(72.f * static_cast<float>(std::numbers::pi) / 180.f));
279
280 juce::Point<float> point5(centreX, centreY);
281 point5.setX(point5.getX() +
282 (radius - lineW * 1.5f) * ::sinf(216.f * static_cast<float>(std::numbers::pi) / 180.f));
283 point5.setY(point5.getY() +
284 (radius - lineW * 1.5f) * ::cosf(216.f * static_cast<float>(std::numbers::pi) / 180.f));
285
286 juce::Path pentagramPath;
287 pentagramPath.startNewSubPath(point1);
288 pentagramPath.lineTo(point2);
289 pentagramPath.lineTo(point3);
290 pentagramPath.lineTo(point4);
291 pentagramPath.lineTo(point5);
292 pentagramPath.closeSubPath();
293
294 // Apply rotation around the center point
295 juce::AffineTransform transform =
296 juce::AffineTransform::translation(-centreX, -centreY).rotated(angle).translated(centreX, centreY);
297 pentagramPath.applyTransform(transform);
298
299 g.strokePath(pentagramPath, juce::PathStrokeType(2));
300
301 // Draw inner circle
302 g.drawEllipse(bounds.reduced(lineW * 2.f), 2.f);
303
304 // Draw upside-down cross
305 juce::Path crossPath;
306
307 // Vertical part of the cross
308 crossPath.addRectangle(centreX - lineW / 2, centreY + radius / 6, lineW, -radius);
309 // crossPath.addRectangle(-crossWidth / 2.0f, -crossHeight / 2.0f, crossWidth, crossHeight);
310
311 // Horizontal part of the cross (near the bottom)
312 crossPath.addRectangle(centreX - (radius / 3 / 2), centreY - radius / 10, radius / 3, lineW);
313 // crossPath.addRectangle(-crossWidth / 2.0f, crossHeight / 4.0f, crossHeight / 2.0f, crossWidth);
314
315 // Apply the same transform
316 crossPath.applyTransform(transform);
317
318 g.setColour(juce::Colours::white);
319 g.fillPath(crossPath);
320 }
321
322 void drawGroupComponentOutline(juce::Graphics &g, int width, int height, const juce::String &text,
323 const juce::Justification &position, juce::GroupComponent &group) override
324 {
325 const float textH = 15.0f;
326 const float indent = 3.0f;
327 const float textEdgeGap = 4.0f;
328 auto cs = 5.0f;
329
330 Path p;
331 auto x = indent;
332 auto y = deathMetalFont.getAscent() - 3.0f;
333 auto w = jmax(0.0f, (float)width - x * 2.0f);
334 auto h = jmax(0.0f, (float)height - y - indent);
335 cs = jmin(cs, w * 0.5f, h * 0.5f);
336 auto cs2 = 2.0f * cs;
337
338 auto textW = text.isEmpty() ? 0 : jlimit(0.0f, jmax(0.0f, w - cs2 - textEdgeGap * 2), [&]() {
339 // Create a TextLayout and calculate the width
340 juce::TextLayout layout;
341 juce::AttributedString attributedText;
342 attributedText.append(text, deathMetalFont, juce::Colours::black); // Adjust the color as needed
343
344 layout.createLayout(attributedText, w - cs2 - textEdgeGap * 2);
345 return layout.getWidth() + textEdgeGap * 2.0f;
346 }());
347 auto textX = cs + textEdgeGap;
348
349 if (position.testFlags(Justification::horizontallyCentred))
350 textX = cs + (w - cs2 - textW) * 0.5f;
351 else if (position.testFlags(Justification::right))
352 textX = w - cs - textW - textEdgeGap;
353
354 p.startNewSubPath(x + textX + textW, y);
355 p.lineTo(x + w - cs, y);
356
357 p.addArc(x + w - cs2, y, cs2, cs2, 0, MathConstants<float>::halfPi);
358 p.lineTo(x + w, y + h - cs);
359
360 p.addArc(x + w - cs2, y + h - cs2, cs2, cs2, MathConstants<float>::halfPi, MathConstants<float>::pi);
361 p.lineTo(x + cs, y + h);
362
363 p.addArc(x, y + h - cs2, cs2, cs2, MathConstants<float>::pi, MathConstants<float>::pi * 1.5f);
364 p.lineTo(x, y + cs);
365
366 p.addArc(x, y, cs2, cs2, MathConstants<float>::pi * 1.5f, MathConstants<float>::twoPi);
367 p.lineTo(x + textX, y);
368
369 auto alpha = group.isEnabled() ? 1.0f : 0.5f;
370
371 g.setColour(group.findColour(GroupComponent::outlineColourId).withMultipliedAlpha(alpha));
372
373 g.strokePath(p, PathStrokeType(2.0f));
374
375 g.setColour(group.findColour(GroupComponent::textColourId).withMultipliedAlpha(alpha));
376 g.setFont(deathMetalFont);
377 g.drawText(text, roundToInt(x + textX), 0, roundToInt(textW), roundToInt(textH), Justification::centred, true);
378 }
379
380 void drawPopupMenuBackground(juce::Graphics &g, int width, int height) override
381 {
382 std::ignore = height;
383 std::ignore = width;
384 g.fillAll(juce::Colours::black); // Custom background color for the popup menu
385 }
386
387 private:
388 juce::Font deathMetalFont;
389 juce::ColourGradient mGradient;
390};
Definition DeathMetalLookAndFeel.h:9