/*
FxSound
Copyright (C) 2023 FxSound LLC
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include
#include "FxTheme.h"
//==============================================================================
FxTheme::FxTheme() : LookAndFeel_V4(getFxColourScheme())
{
setColour(ComboBox::ColourIds::arrowColourId, Colour(0xe63462).withAlpha(1.0f));
setColour(ComboBox::ColourIds::backgroundColourId, Colour(0x000000).withAlpha(1.0f));
setColour(ComboBox::ColourIds::outlineColourId, Colour(0x000000).withAlpha(1.0f));
setColour(ComboBox::ColourIds::focusedOutlineColourId, Colour(0x000000).withAlpha(1.0f));
setColour(ComboBox::ColourIds::textColourId, Colour(0xb1b1b1).withAlpha(1.0f));
setColour(TextEditor::ColourIds::backgroundColourId, Colour(0x000000).withAlpha(1.0f));
setColour(TextEditor::ColourIds::outlineColourId, Colour(0x000000).withAlpha(1.0f));
setColour(TextEditor::ColourIds::focusedOutlineColourId, Colour(0x000000).withAlpha(1.0f));
setColour(TextEditor::ColourIds::textColourId, Colour(0xb1b1b1).withAlpha(1.0f));
setColour(TextEditor::ColourIds::highlightedTextColourId, Colour(0xffffff).withAlpha(1.0f));
setColour(TextButton::ColourIds::buttonColourId, Colour(0xd51535).withAlpha(1.0f));
setColour(TextButton::ColourIds::buttonOnColourId, Colour(0xd51535).withAlpha(1.0f));
setColour(TextButton::ColourIds::textColourOffId, Colour(0xffffff).withAlpha(1.0f));
setColour(TextButton::ColourIds::textColourOnId, Colour(0xffffff).withAlpha(1.0f));
setColour(HyperlinkButton::ColourIds::textColourId, Colour(0xffffff).withAlpha(1.0f));
setColour(CaretComponent::ColourIds::caretColourId, Colour(0xb1b1b1).withAlpha(1.0f));
setColour(PopupMenu::ColourIds::backgroundColourId, Colour(0x000000).withAlpha(1.0f));
setColour(PopupMenu::ColourIds::highlightedBackgroundColourId, Colour(0xe63462).withAlpha(1.0f));
setColour(Slider::ColourIds::rotarySliderOutlineColourId, Colour(0xe33250).withAlpha(0.2f));
setColour(Slider::ColourIds::rotarySliderFillColourId, Colour(0xe33250).withAlpha(1.0f));
drop_down_arrow_ = Drawable::createFromImageData(BinaryData::dropdown_arrow_hover_svg, BinaryData::dropdown_arrow_hover_svgSize);
slider_thumb_ = Drawable::createFromImageData(BinaryData::Slider_Thumb_svg, BinaryData::Slider_Thumb_svgSize);
drop_down_arrow_grey_ = Drawable::createFromImageData(BinaryData::dropdown_arrow_bw_svg, BinaryData::dropdown_arrow_bw_svgSize);
slider_thumb_grey_ = Drawable::createFromImageData(BinaryData::Slider_Thumb_bw_svg, BinaryData::Slider_Thumb_bw_svgSize);
}
LookAndFeel_V4::ColourScheme FxTheme::getFxColourScheme()
{
return { Colour(0x181818).withAlpha(1.0f), Colour(0x181818).withAlpha(1.0f), Colour(0x383838).withAlpha(1.0f),
Colour(0x2B2B2B).withAlpha(1.0f), Colour(0xB1B1B1).withAlpha(1.0f), Colour(0x0).withAlpha(0.2f),
0xffffffff, Colour(0x0c0c0c).withAlpha(1.0f), 0xffffffff };
}
Label* FxTheme::createComboBoxTextBox(ComboBox& box)
{
auto label = LookAndFeel_V4::createComboBoxTextBox(box);
label->setMouseCursor(MouseCursor::PointingHandCursor);
return label;
}
Font FxTheme::getComboBoxFont(ComboBox&)
{
return Font(font_600_).withHeight(17.0f);
}
void FxTheme::positionComboBoxText(ComboBox& box, Label& label)
{
label.setMinimumHorizontalScale(1.0);
LookAndFeel_V4::positionComboBoxText(box, label);
label.setBounds(label.getBounds().withX(20));
}
void FxTheme::drawComboBox(Graphics& g, int width, int height, bool,
int, int, int, int, ComboBox& box)
{
auto cornerSize = (float) height / 5;
Rectangle boxBounds(0, 0, width, height);
g.setColour(box.findColour(ComboBox::backgroundColourId));
g.fillRoundedRectangle(boxBounds.toFloat(), cornerSize);
if (box.hasKeyboardFocus(true))
{
g.setColour(Colour(0xf7546f).withAlpha(0.2f));
g.drawRoundedRectangle(boxBounds.toFloat().reduced(0.5f, 0.5f), cornerSize, 1.0f);
}
else
{
g.setColour(box.findColour(ComboBox::outlineColourId));
g.drawRoundedRectangle(boxBounds.toFloat().reduced(0.5f, 0.5f), cornerSize, 1.0f);
}
g.setColour(box.findColour(ComboBox::arrowColourId).withAlpha((box.isEnabled() ? 1.0f : 0.2f)));
if (box.isEnabled())
drop_down_arrow_->drawWithin(g, Rectangle(width - 32, 0, 12, height), { RectanglePlacement::centred }, 1.0f);
else
drop_down_arrow_grey_->drawWithin(g, Rectangle(width - 32, 0, 12, height), { RectanglePlacement::centred }, 1.0f);
}
void FxTheme::drawLinearSlider(Graphics& g, int x, int y, int width, int height,
float sliderPos, float minSliderPos, float maxSliderPos,
const Slider::SliderStyle style, Slider& slider)
{
if (style == Slider::LinearVertical)
{
float dash_lengths[] = { 5, 2 };
auto radius = getSliderThumbRadius(slider);
Colour colour1;
colour1 = Colour(0xe33250).withAlpha(0.4f);
Colour colour2;
colour2 = Colour(0xf3f3f3).withAlpha(0.4f);
if (!slider.isEnabled())
{
colour1 = colour1.withSaturation(0.0);
colour2 = colour2.withSaturation(0.0);
}
g.setGradientFill(ColourGradient(colour1, { 0, 0 }, colour2, { 0, (float)height }, false));
g.drawDashedLine(Line(x+width/2, y, x+width/2, y+height), dash_lengths, _countof(dash_lengths), 1.0f);
if (slider.isEnabled())
slider_thumb_->drawWithin(g, Rectangle(x+width/2-radius, sliderPos-radius, radius*2, radius*2), { RectanglePlacement::centred }, 1.0f);
else
slider_thumb_grey_->drawWithin(g, Rectangle(x + width / 2 - radius, sliderPos - radius, radius * 2, radius * 2), { RectanglePlacement::centred }, 1.0f);
if (slider.getThumbBeingDragged() >= 0 || slider.hasKeyboardFocus(true))
{
Colour colour = Colour(0xf7546f).withAlpha(0.1f);
g.setFillType(colour);
g.fillRoundedRectangle(juce::Rectangle(x + (width - SLIDER_THUMB_RADIUS*4) / 2, y, SLIDER_THUMB_RADIUS * 4, height).expanded(0, SLIDER_THUMB_RADIUS), 20);
}
}
else if (style == Slider::LinearHorizontal)
{
auto radius = getSliderThumbRadius(slider);
Colour colour = Colour(0xe33250).withAlpha(0.2f);
if (!slider.isEnabled())
{
colour = colour.withSaturation(0.0);
}
g.setFillType(colour);
g.fillRoundedRectangle(x, y+(height-3)/2, width, 3, 5.6f);
colour = Colour(0xe33250).withAlpha(1.0f);
if (!slider.isEnabled())
{
colour = colour.withSaturation(0.0);
}
g.setFillType(colour);
g.fillRoundedRectangle(x, y+(height-3)/2, sliderPos, 3, 5.6f);
if (slider.isEnabled())
slider_thumb_->drawWithin(g, Rectangle(sliderPos-radius, y+height/2-radius, radius*2, radius*2), { RectanglePlacement::centred }, 1.0f);
else
slider_thumb_grey_->drawWithin(g, Rectangle(sliderPos - radius, y + height / 2 - radius, radius * 2, radius * 2), { RectanglePlacement::centred }, 1.0f);
if (slider.hasKeyboardFocus(true))
{
Colour colour = Colour(0xf7546f).withAlpha(0.1f);
g.setFillType(colour);
g.fillRoundedRectangle(juce::Rectangle(x, y, width, height).expanded(SLIDER_THUMB_RADIUS/2, SLIDER_THUMB_RADIUS/2), height+SLIDER_THUMB_RADIUS);
}
}
else
{
LookAndFeel_V4::drawLinearSlider(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
}
}
void FxTheme::drawRotarySlider(Graphics& g, int x, int y, int width, int height, float sliderPos,
const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider)
{
auto outline = slider.findColour(Slider::rotarySliderOutlineColourId);
auto fill = slider.findColour(Slider::rotarySliderFillColourId);
if (!slider.isEnabled())
{
outline = outline.withSaturation(0.0);
fill = fill.withSaturation(0.0);
}
auto bounds = Rectangle(x, y, width, height).toFloat().reduced(2);
auto radius = jmin(bounds.getWidth(), bounds.getHeight()) / 2.0f;
auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
auto lineW = 5.0f;
auto arcRadius = radius - lineW * 0.5f;
Path backgroundArc;
backgroundArc.addCentredArc(bounds.getCentreX(),
bounds.getCentreY(),
arcRadius,
arcRadius,
0.0f,
rotaryStartAngle,
rotaryEndAngle,
true);
g.setColour(outline);
g.strokePath(backgroundArc, PathStrokeType(lineW, PathStrokeType::curved, PathStrokeType::rounded));
Path valueArc;
valueArc.addCentredArc(bounds.getCentreX(),
bounds.getCentreY(),
arcRadius,
arcRadius,
0.0f,
rotaryStartAngle,
toAngle,
true);
g.setColour(fill);
g.strokePath(valueArc, PathStrokeType(lineW, PathStrokeType::curved, PathStrokeType::rounded));
if (slider.hasKeyboardFocus(true))
{
DropShadow shadow;
shadow.colour = Colour(0xf7546f).withAlpha(0.1f);
shadow.drawForPath(g, backgroundArc);
}
auto thumbRadius = getSliderThumbRadius(slider);
Point thumbPoint(bounds.getCentreX() + arcRadius * std::cos(toAngle - MathConstants::halfPi),
bounds.getCentreY() + arcRadius * std::sin(toAngle - MathConstants::halfPi));
auto thumbX = thumbPoint.getX() - thumbRadius;
auto thumbY = thumbPoint.getY() - thumbRadius;
if (slider.isEnabled())
slider_thumb_->drawWithin(g, Rectangle(thumbX, thumbY, thumbRadius*2, thumbRadius*2), { RectanglePlacement::centred }, 1.0f);
else
slider_thumb_grey_->drawWithin(g, Rectangle(thumbX, thumbY, thumbRadius * 2, thumbRadius * 2), { RectanglePlacement::centred }, 1.0f);
}
int FxTheme::getSliderThumbRadius(Slider& slider)
{
if (slider.getSliderStyle() == Slider::Rotary)
{
return ROTARY_SLIDER_THUMB_RADIUS;
}
else
{
return SLIDER_THUMB_RADIUS;
}
}
Slider::SliderLayout FxTheme::getSliderLayout(Slider& slider)
{
auto layout = LookAndFeel_V4::getSliderLayout(slider);
auto style = slider.getSliderStyle();
if (style == Slider::LinearVertical)
{
auto y = layout.sliderBounds.getY() + (SLIDER_THUMB_RADIUS*2);
auto height = layout.sliderBounds.getHeight() - (SLIDER_THUMB_RADIUS*2);
layout.sliderBounds.setY(y);
layout.sliderBounds.setHeight(height);
}
else if (style == Slider::LinearHorizontal)
{
auto width = layout.sliderBounds.getWidth() - (SLIDER_THUMB_RADIUS*3);
layout.sliderBounds.setWidth(width);
}
return layout;
}
void FxTheme::drawPopupMenuItem(Graphics& g, const juce::Rectangle& area, bool is_separator, bool is_active,
bool is_highlighted, bool is_ticked, bool has_submenu, const String& text,
const String& shortcut_key_text, const Drawable* icon, const Colour* text_colour)
{
LookAndFeel_V4::drawPopupMenuItem(g, area, is_separator, is_active, is_highlighted|is_ticked, false,
has_submenu, text, shortcut_key_text, icon, text_colour);
if (is_ticked)
{
g.setColour(findColour(PopupMenu::textColourId).withAlpha(1.0f));
g.drawRect(area.toFloat());
}
}
Font FxTheme::getPopupMenuFont()
{
return Font(font_600_).withHeight(17.0f);
}
void FxTheme::preparePopupMenuWindow(Component& new_window)
{
new_window.setMouseCursor(MouseCursor::PointingHandCursor);
auto children = new_window.getChildren();
for (auto child : children)
{
child->setMouseCursor(MouseCursor::PointingHandCursor);
}
}
void FxTheme::loadFont(String language)
{
if (language.startsWithIgnoreCase("en"))
{
font_400_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyRegular_ttf, BinaryData::GilroyRegular_ttfSize);
font_600_ = Typeface::createSystemTypefaceFor(BinaryData::GilroySemibold_ttf, BinaryData::GilroySemibold_ttfSize);
font_700_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyBold_ttf, BinaryData::GilroyBold_ttfSize);
}
else if (language.startsWithIgnoreCase("ko"))
{
font_400_ = loadTypeface("NotoSansKR-Regular.otf");
font_600_ = loadTypeface("NotoSansKR-Medium.otf");
font_700_ = loadTypeface("NotoSansKR-Medium.otf");
}
else if (language.startsWithIgnoreCase("zh"))
{
font_400_ = loadTypeface("NotoSansSC-Regular.otf");
font_600_ = loadTypeface("NotoSansSC-Medium.otf");
font_700_ = loadTypeface("NotoSansSC-Medium.otf");
}
else if (language.startsWithIgnoreCase("th"))
{
font_400_ = loadTypeface("NotoSansThai-Regular.ttf");
font_600_ = loadTypeface("NotoSansThai-Medium.ttf");
font_700_ = loadTypeface("NotoSansThai-Medium.ttf");
}
else if (language.startsWithIgnoreCase("vi"))
{
font_400_ = loadTypeface("MontserratAlternates-Regular.ttf");
font_600_ = loadTypeface("MontserratAlternates-Medium.ttf");
font_700_ = loadTypeface("MontserratAlternates-Bold.ttf");
}
else if (language.startsWithIgnoreCase("ja"))
{
font_400_ = loadTypeface("NotoSansJP-Regular.ttf");
font_600_ = loadTypeface("NotoSansJP-Medium.ttf");
font_700_ = loadTypeface("NotoSansJP-Bold.ttf");
}
else if (language.startsWithIgnoreCase("ar"))
{
font_400_ = loadTypeface("IBMPlexSansArabic-Regular.ttf");
font_600_ = loadTypeface("IBMPlexSansArabic-Medium.ttf");
font_700_ = loadTypeface("IBMPlexSansArabic-Bold.ttf");
}
else
{
font_400_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyRegular_ttf, BinaryData::GilroyRegular_ttfSize);
font_600_ = Typeface::createSystemTypefaceFor(BinaryData::GilroySemibold_ttf, BinaryData::GilroySemibold_ttfSize);
font_700_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyBold_ttf, BinaryData::GilroyBold_ttfSize);
}
if (font_400_ == nullptr)
{
font_400_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyRegular_ttf, BinaryData::GilroyRegular_ttfSize);
}
if (font_600_ == nullptr)
{
font_600_ = Typeface::createSystemTypefaceFor(BinaryData::GilroySemibold_ttf, BinaryData::GilroySemibold_ttfSize);
}
if (font_700_ == nullptr)
{
font_700_ = Typeface::createSystemTypefaceFor(BinaryData::GilroyBold_ttf, BinaryData::GilroyBold_ttfSize);
}
setDefaultSansSerifTypeface(font_600_);
}
Font FxTheme::getTextButtonFont(TextButton&, int button_height)
{
return Font(font_600_.get()).withHeight(jmin(17.0f, (float)button_height));
}
Font FxTheme::getNormalFont()
{
return Font(font_600_).withHeight(17.0f);
}
Font FxTheme::getSmallFont()
{
return Font(font_400_).withHeight(14.0f);
}
Font FxTheme::getTitleFont()
{
return Font(font_700_).withHeight(17.0f);
}
Typeface::Ptr FxTheme::getDefaultTypeface()
{
return font_400_;
}
Rectangle FxTheme::getTooltipBounds(const String& tipText, Point screenPos, Rectangle parentArea)
{
const TextLayout tl(layoutTooltipText(tipText, Colours::black));
auto w = (int)(tl.getWidth() + 20.0f);
auto h = (int)(tl.getHeight() + 12.0f);
return Rectangle(screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 18) : screenPos.x + 36,
screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 12) : screenPos.y + 12,
w, h)
.constrainedWithin(parentArea);
}
void FxTheme::drawTooltip(Graphics& g, const String& text, int width, int height)
{
Rectangle bounds(width, height);
auto cornerSize = 5.0f;
g.setColour(findColour(TooltipWindow::backgroundColourId));
g.fillRoundedRectangle(bounds.toFloat(), cornerSize);
g.setColour(findColour(TooltipWindow::outlineColourId));
g.drawRoundedRectangle(bounds.toFloat().reduced(0.5f, 0.5f), cornerSize, 1.0f);
layoutTooltipText(text, findColour(TooltipWindow::textColourId))
.draw(g, bounds.toFloat().reduced(10, 0));
}
void FxTheme::drawDocumentWindowTitleBar(DocumentWindow& window, Graphics& g,
int w, int h, int titleSpaceX, int titleSpaceW,
const Image* icon, bool drawTitleTextOnLeft)
{
if (w * h == 0)
return;
auto isActive = window.isActiveWindow();
g.setColour(getCurrentColourScheme().getUIColour(ColourScheme::widgetBackground));
g.fillAll();
Font font(12.0f, Font::plain);
g.setFont(font);
auto textW = font.getStringWidth(window.getName());
auto iconW = 0;
auto iconH = 0;
if (icon != nullptr)
{
iconH = static_cast (font.getHeight());
iconW = icon->getWidth() * iconH / icon->getHeight() + 4;
}
textW = jmin(titleSpaceW, textW + iconW);
auto textX = drawTitleTextOnLeft ? titleSpaceX
: jmax(titleSpaceX, (w - textW) / 2);
if (textX + textW > titleSpaceX + titleSpaceW)
textX = titleSpaceX + titleSpaceW - textW;
if (icon != nullptr)
{
g.setOpacity(isActive ? 1.0f : 0.6f);
g.drawImageWithin(*icon, textX, (h - iconH) / 2, iconW, iconH,
RectanglePlacement::centred, false);
textX += iconW;
textW -= iconW;
}
if (window.isColourSpecified(DocumentWindow::textColourId) || isColourSpecified(DocumentWindow::textColourId))
g.setColour(window.findColour(DocumentWindow::textColourId));
else
g.setColour(getCurrentColourScheme().getUIColour(ColourScheme::defaultText));
g.drawText(window.getName(), textX, 0, textW, h, Justification::centredLeft, true);
}
class FxTheme_DocumentWindowButton : public Button
{
public:
FxTheme_DocumentWindowButton(const String& name, Colour c, const Path& normal, const Path& toggled)
: Button(name), colour(c), normalShape(normal), toggledShape(toggled)
{
}
void paintButton(Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override
{
auto background = Colours::grey;
if (auto* rw = findParentComponentOfClass())
if (auto lf = dynamic_cast (&rw->getLookAndFeel()))
background = lf->getCurrentColourScheme().getUIColour(LookAndFeel_V4::ColourScheme::widgetBackground);
g.fillAll(background);
g.setColour((!isEnabled() || shouldDrawButtonAsDown) ? colour.withAlpha(0.6f)
: colour);
if (shouldDrawButtonAsHighlighted)
{
g.fillAll();
g.setColour(background);
}
auto& p = getToggleState() ? toggledShape : normalShape;
auto reducedRect = Justification(Justification::centred)
.appliedToRectangle(Rectangle(12, 12), getLocalBounds())
.toFloat();
g.fillPath(p, p.getTransformToScaleToFit(reducedRect, true));
}
private:
Colour colour;
Path normalShape, toggledShape;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(FxTheme_DocumentWindowButton)
};
Button* FxTheme::createDocumentWindowButton(int buttonType)
{
Path shape;
auto crossThickness = 0.15f;
if (buttonType == DocumentWindow::closeButton)
{
shape.addLineSegment({ 0.0f, 0.0f, 1.0f, 1.0f }, crossThickness);
shape.addLineSegment({ 1.0f, 0.0f, 0.0f, 1.0f }, crossThickness);
return new FxTheme_DocumentWindowButton("close", Colour(0xffffffff), shape, shape);
}
if (buttonType == DocumentWindow::minimiseButton)
{
shape.addLineSegment({ 0.0f, 0.5f, 1.0f, 0.5f }, crossThickness);
return new FxTheme_DocumentWindowButton("minimise", Colour(0xffffffff), shape, shape);
}
if (buttonType == DocumentWindow::maximiseButton)
{
shape.addLineSegment({ 0.5f, 0.0f, 0.5f, 1.0f }, crossThickness);
shape.addLineSegment({ 0.0f, 0.5f, 1.0f, 0.5f }, crossThickness);
Path fullscreenShape;
fullscreenShape.startNewSubPath(45.0f, 100.0f);
fullscreenShape.lineTo(0.0f, 100.0f);
fullscreenShape.lineTo(0.0f, 0.0f);
fullscreenShape.lineTo(100.0f, 0.0f);
fullscreenShape.lineTo(100.0f, 45.0f);
fullscreenShape.addRectangle(45.0f, 45.0f, 100.0f, 100.0f);
PathStrokeType(30.0f).createStrokedPath(fullscreenShape, fullscreenShape);
return new FxTheme_DocumentWindowButton("maximise", Colour(0xffffffff), shape, fullscreenShape);
}
jassertfalse;
return nullptr;
}
TextLayout FxTheme::layoutTooltipText(const String& text, Colour colour) noexcept
{
const float tooltipFontSize = 14.0f;
const int maxToolTipWidth = 400;
AttributedString s;
s.setWordWrap(AttributedString::WordWrap::byWord);
s.setJustification(Justification::centredLeft);
s.append(text, getNormalFont().withHeight(tooltipFontSize), colour);
TextLayout tl;
tl.createLayout(s, (float)maxToolTipWidth);
return tl;
}
Typeface::Ptr FxTheme::loadTypeface(String fileName)
{
MemoryBlock fontBuffer;
String filePath = File::addTrailingSeparator(File::getCurrentWorkingDirectory().getFullPathName());
File fontFile = File(filePath+fileName);
if (fontFile.exists())
{
if (fontFile.loadFileAsData(fontBuffer))
{
return Typeface::createSystemTypefaceFor(fontBuffer.getData(), fontBuffer.getSize());
}
}
return nullptr;
}