// SPDX-License-Identifier: GPLv3-or-later WITH Appstore-exception // Copyright (C) 2020 Jesse Chappell #include "CustomLookAndFeel.h" //============================================================================== CustomLookAndFeel::CustomLookAndFeel() { // setColour (mainBackgroundColourId, Colour::greyLevel (0.8f)); //DBG("Sonolook and feel"); setUsingNativeAlertWindows(true); fontScale = 1.0; setColourScheme(getDarkColourScheme()); getCurrentColourScheme().setUIColour(ColourScheme::UIColour::windowBackground, Colour::fromFloatRGBA(0.0, 0.0, 0.0, 1.0)); getCurrentColourScheme().setUIColour(ColourScheme::UIColour::widgetBackground, Colour::fromFloatRGBA(0.1, 0.1, 0.1, 1.0)); getCurrentColourScheme().setUIColour(ColourScheme::UIColour::outline, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5)); setColour (Label::textColourId, Colour (0xffcccccc)); setColour (Label::textWhenEditingColourId, Colour (0xffe9e9e9)); setColour(ResizableWindow::backgroundColourId, Colour(0xff111111)); //setColour (TextButton::buttonColourId, Colour (0xff363636)); setColour (TextButton::buttonColourId, Colour::fromFloatRGBA(0.15, 0.15, 0.15, 0.7)); // old one //setColour (TextButton::buttonColourId, Colour::fromFloatRGBA(0.15, 0.15, 0.15, 0.0)); //setColour (TextButton::buttonOnColourId, Colour (0xff3d70c8)); setColour (TextButton::buttonOnColourId, Colour::fromFloatRGBA(0.5, 0.4, 0.6, 0.8)); setColour (TextButton::textColourOnId, Colour (0xddcccccc)); setColour (TextButton::textColourOffId, Colour (0xdde9e9e9)); setColour (ToggleButton::textColourId, Colour (0xddcccccc)); setColour (ScrollBar::ColourIds::thumbColourId, Colour::fromFloatRGBA(0.7, 0.7, 0.7, 0.7)); //setColour (ComboBox::backgroundColourId, Colour (0xff161616)); setColour (ComboBox::backgroundColourId, Colour::fromFloatRGBA(0.15, 0.15, 0.15, 0.7)); setColour (ComboBox::textColourId, Colour (0xffe9e9e9)); setColour (ComboBox::outlineColourId, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5)); setColour (TextEditor::backgroundColourId, Colour (0xff050505)); setColour (TextEditor::textColourId, Colour (0xffe9e9e9)); setColour (TextEditor::highlightColourId, Colour (0xff5959f9)); setColour (TextEditor::outlineColourId, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5)); setColour (TextEditor::focusedOutlineColourId, Colour::fromFloatRGBA(0.5, 0.5, 0.5, 0.7)); setColour (Slider::backgroundColourId, Colour::fromFloatRGBA(0.2, 0.2, 0.2, 1.0)); setColour (Slider::rotarySliderOutlineColourId, Colour::fromFloatRGBA(0.2, 0.2, 0.2, 1.0)); setColour (Slider::textBoxTextColourId, Colour(0xddcccccc)); setColour (Slider::textBoxBackgroundColourId, Colour::fromFloatRGBA(0.05, 0.05, 0.05, 1.0)); setColour (Slider::textBoxHighlightColourId, Colour (0xaa555555)); setColour (Slider::textBoxOutlineColourId, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5)); setColour (Slider::trackColourId, Colour::fromFloatRGBA(0.1, 0.4, 0.6, 0.8)); setColour (Slider::thumbColourId, Colour::fromFloatRGBA(0.5, 0.4, 0.6, 0.9)); //setColour (Slider::thumbColourId, Colour::fromFloatRGBA(0.2, 0.5, 0.7, 1.0)); setColour (Slider::rotarySliderFillColourId, Colour::fromFloatRGBA(0.5, 0.4, 0.6, 0.9)); setColour (TabbedButtonBar::tabOutlineColourId, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.6)); setColour (ListBox::backgroundColourId, Colour::fromFloatRGBA(0.15, 0.15, 0.15, 0.7)); setColour (ListBox::outlineColourId, Colour::fromFloatRGBA(0.3, 0.3, 0.3, 0.5)); setColour (BubbleComponent::backgroundColourId, Colour::fromFloatRGBA(0.25, 0.25, 0.25, 1.0)); setColour (BubbleComponent::outlineColourId, Colour::fromFloatRGBA(0.4, 0.4, 0.4, 0.5)); //setColour (TooltipWindow::textColourId, Colour(0xeecccccc)); setColour (TooltipWindow::textColourId, Colour(0xee222222)); setColour (TooltipWindow::backgroundColourId, Colour(0xeeffff99)); setColour (PopupMenu::backgroundColourId, Colour::fromFloatRGBA(0.2, 0.2, 0.2, 1.0)); setColour (PopupMenu::highlightedBackgroundColourId, Colour::fromFloatRGBA(0.35, 0.35, 0.4, 1.0)); setColour (SidePanel::backgroundColour, Colour::fromFloatRGBA(0.17, 0.17, 0.17, 1.0)); //setColour (SonoDrawableButton::overOverlayColourId, Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.08)); //setColour (SonoDrawableButton::downOverlayColourId, Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.3)); setColour (DrawableButton::textColourId, Colour (0xffb9b9b9)); setColour (DrawableButton::textColourOnId, Colour (0xffe9e9e9)); //setColour (DrawableButton::backgroundColourId, Colour (0xffb9b9b9)); setColour (DrawableButton::backgroundOnColourId, Colour::fromFloatRGBA(0.5, 0.4, 0.6, 0.8)); //setColour (ConfigurationRowView::backgroundColourId, Colour::fromFloatRGBA(0.05, 0.05, 0.05, 1.0)); //setColour (ConfigurationRowView::selectedBackgroundColourId, Colour::fromFloatRGBA(0.15, 0.15, 0.15, 1.0)); setColour(ToggleButton::tickColourId, Colour::fromFloatRGBA(0.4, 0.8, 1.0, 1.0)); setColour (DirectoryContentsDisplayComponent::highlightColourId, Colour::fromFloatRGBA(0.1, 0.4, 0.6, 0.9)); setColour (DirectoryContentsDisplayComponent::textColourId, Colour (0xffe9e9e9)); // setColour (Label::textColourId, Colour (0xffe9e9e9)); //myFont = Typeface::createSystemTypefaceFor (BinaryData::DejaVuSans_ttf, BinaryData::DejaVuSans_ttfSize); //setDefaultSansSerifTypefaceName("Gill Sans"); //setDefaultSansSerifTypefaceName("Arial Unicode MS"); //setDefaultSansSerifTypefaceName(myFont.getTypefaceName()); //myFont = Typeface::createSystemTypefaceFor (BinaryData::GillSans_ttc, BinaryData::GillSans_ttcSize); myFont = Font(16 * fontScale); if (auto * deflnf = dynamic_cast(&LookAndFeel::getDefaultLookAndFeel())) { setLanguageCode(deflnf->languageCode); } //DBG("Myfont name " << myFont.getTypefaceName()); } void CustomLookAndFeel::setLanguageCode(const String & lang) { languageCode = lang; if (lang.startsWith("zh")) { fontScale = 1.0f; } else if (lang.startsWith("ko")) { fontScale = 1.15f; } } Font CustomLookAndFeel::getMenuBarFont (MenuBarComponent& menuBar, int /*itemIndex*/, const String& /*itemText*/) { return Font (menuBar.getHeight() * 0.7f); } //============================================================================== void CustomLookAndFeel::drawCallOutBoxBackground (CallOutBox& box, Graphics& g, const Path& path, Image& cachedImage) { if (cachedImage.isNull()) { cachedImage = Image (Image::ARGB, box.getWidth(), box.getHeight(), true); Graphics g2 (cachedImage); DropShadow (Colours::black.withAlpha (0.7f), 8, Point (0, 2)).drawForPath (g2, path); } g.setColour (Colours::black); g.drawImageAt (cachedImage, 0, 0); //g.setColour (getCurrentColourScheme().getUIColour (ColourScheme::UIColour::widgetBackground).withAlpha (0.8f)); g.setColour (getCurrentColourScheme().getUIColour (ColourScheme::UIColour::widgetBackground)); g.fillPath (path); g.setColour (getCurrentColourScheme().getUIColour (ColourScheme::UIColour::outline).withAlpha (0.8f)); g.strokePath (path, PathStrokeType (1.0f)); } int CustomLookAndFeel::getTabButtonBestWidth (TabBarButton& button, int depth) { return 250; // 120; } int CustomLookAndFeel::getTabButtonSpaceAroundImage() { return 0; } static Colour getTabBackgroundColour (TabBarButton& button) { const Colour bkg (button.findColour (TabbedComponent::backgroundColourId).contrasting (0.15f)); if (button.isFrontTab()) return bkg.overlaidWith (Colours::yellow.withAlpha (0.8f)); return bkg; } Rectangle CustomLookAndFeel::getTabButtonExtraComponentBounds (const TabBarButton& button, Rectangle& textArea, Component& comp) { Rectangle extraComp; auto orientation = button.getTabbedButtonBar().getOrientation(); if (button.getExtraComponentPlacement() == TabBarButton::beforeText) { switch (orientation) { case TabbedButtonBar::TabsAtBottom: case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromLeft (comp.getWidth()); break; case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromBottom (comp.getHeight()); break; case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromTop (comp.getHeight()); break; default: jassertfalse; break; } } else if (button.getExtraComponentPlacement() == TabBarButton::afterText) { switch (orientation) { case TabbedButtonBar::TabsAtBottom: case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromRight (comp.getWidth()); break; case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromTop (comp.getHeight()); break; case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromBottom (comp.getHeight()); break; default: jassertfalse; break; } } else if (button.getExtraComponentPlacement() == TabBarButton::aboveText) { switch (orientation) { case TabbedButtonBar::TabsAtBottom: case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromTop (comp.getHeight()); break; case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromTop (comp.getHeight()); break; case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromTop (comp.getHeight()); break; default: jassertfalse; break; } // DBG("Extra comp bounds: " << extraComp.toString()) extraComp.translate(0, 3); //DBG("After Extra comp bounds: " << extraComp.toString()) } else if (button.getExtraComponentPlacement() == TabBarButton::belowText) { switch (orientation) { case TabbedButtonBar::TabsAtBottom: case TabbedButtonBar::TabsAtTop: extraComp = textArea.removeFromBottom (comp.getHeight()); break; case TabbedButtonBar::TabsAtLeft: extraComp = textArea.removeFromBottom (comp.getHeight()); break; case TabbedButtonBar::TabsAtRight: extraComp = textArea.removeFromBottom (comp.getHeight()); break; default: jassertfalse; break; } } return extraComp; } void CustomLookAndFeel::createTabTextLayout (const TabBarButton& button, float length, float depth, Colour colour, TextLayout& textLayout) { float hscale = 0.6f; #if JUCE_IOS hscale = 0.5f; #endif float fontsize = button.getExtraComponent() != nullptr ? jmin(depth, 32.0f) * hscale : jmin(depth, 32.0f) * hscale; Font font = myFont.withHeight(fontsize * fontScale); font.setUnderline (button.hasKeyboardFocus (false)); AttributedString s; s.setWordWrap(AttributedString::byWord); s.setJustification (Justification::centred); s.append (button.getButtonText().trim(), font, colour); textLayout.createLayout (s, length); } void CustomLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) { const Rectangle activeArea (button.getActiveArea()); const TabbedButtonBar::Orientation o = button.getTabbedButtonBar().getOrientation(); const Colour bkg (button.getTabBackgroundColour()); const Colour selcol = Colour::fromFloatRGBA(0.0f, 0.2f, 0.4f, 1.0f); // DBG("Sono draw tab button"); if (button.getToggleState() && bkg != Colours::black) { //g.setColour (bkg); g.setColour (selcol); } else { Point p1, p2; switch (o) { case TabbedButtonBar::TabsAtBottom: p1 = activeArea.getBottomLeft(); p2 = activeArea.getTopLeft(); break; case TabbedButtonBar::TabsAtTop: p1 = activeArea.getTopLeft(); p2 = activeArea.getBottomLeft(); break; case TabbedButtonBar::TabsAtRight: p1 = activeArea.getTopRight(); p2 = activeArea.getTopLeft(); break; case TabbedButtonBar::TabsAtLeft: p1 = activeArea.getTopLeft(); p2 = activeArea.getTopRight(); break; default: jassertfalse; break; } g.setColour(isMouseDown ? bkg.brighter(0.1) : bkg); //g.setGradientFill (ColourGradient (bkg.darker (0.1f), (float) p1.x, (float) p1.y, // bkg.darker (0.5f), (float) p2.x, (float) p2.y, false)); } Rectangle p (activeArea.reduced(1)); g.fillRect (p); //g.fillRect (activeArea); #if 0 g.setColour (button.findColour (TabbedButtonBar::tabOutlineColourId)); Rectangle r (activeArea); if (o != TabbedButtonBar::TabsAtBottom) g.fillRect (r.removeFromTop (1)); if (o != TabbedButtonBar::TabsAtTop) g.fillRect (r.removeFromBottom (1)); if (o != TabbedButtonBar::TabsAtRight) g.fillRect (r.removeFromLeft (1)); if (o != TabbedButtonBar::TabsAtLeft) g.fillRect (r.removeFromRight (1)); #endif const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f; Colour col (bkg.contrasting().withMultipliedAlpha (alpha)); if (TabbedButtonBar* bar = button.findParentComponentOfClass()) { TabbedButtonBar::ColourIds colID = button.isFrontTab() ? TabbedButtonBar::frontTextColourId : TabbedButtonBar::tabTextColourId; if (bar->isColourSpecified (colID)) col = bar->findColour (colID); else if (isColourSpecified (colID)) col = findColour (colID); } const Rectangle area (button.getTextArea().toFloat()); float length = area.getWidth(); float depth = area.getHeight(); if (button.getTabbedButtonBar().isVertical()) std::swap (length, depth); TextLayout textLayout; createTabTextLayout (button, length, depth, col, textLayout); AffineTransform t; switch (o) { case TabbedButtonBar::TabsAtLeft: t = t.rotated (float_Pi * -0.5f).translated (area.getX(), area.getBottom()); break; case TabbedButtonBar::TabsAtRight: t = t.rotated (float_Pi * 0.5f).translated (area.getRight(), area.getY()); break; case TabbedButtonBar::TabsAtTop: case TabbedButtonBar::TabsAtBottom: t = t.translated (area.getX(), area.getY()); break; default: jassertfalse; break; } g.addTransform (t); textLayout.draw (g, Rectangle (length, depth)); } /* void CustomLookAndFeel::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) { const Rectangle activeArea (button.getActiveArea()); const Colour bkg (getTabBackgroundColour (button)); g.setGradientFill (ColourGradient (bkg.brighter (0.1f), 0, (float) activeArea.getY(), bkg.darker (0.1f), 0, (float) activeArea.getBottom(), false)); g.fillRect (activeArea); g.setColour (button.findColour (TabbedComponent::backgroundColourId).darker (0.3f)); g.drawRect (activeArea); const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f; const Colour col (bkg.contrasting().withMultipliedAlpha (alpha)); TextLayout textLayout; LookAndFeel_V3::createTabTextLayout (button, (float) activeArea.getWidth(), (float) activeArea.getHeight(), col, textLayout); textLayout.draw (g, button.getTextArea().toFloat()); } */ void CustomLookAndFeel::drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) {} void CustomLookAndFeel::drawTabAreaBehindFrontButton (TabbedButtonBar& bar, Graphics& g, const int w, const int h) { const float shadowSize = 0.15f; Rectangle shadowRect, line; ColourGradient gradient (Colours::black.withAlpha (bar.isEnabled() ? 0.08f : 0.04f), 0, 0, Colours::transparentBlack, 0, 0, false); switch (bar.getOrientation()) { case TabbedButtonBar::TabsAtLeft: gradient.point1.x = (float) w; gradient.point2.x = w * (1.0f - shadowSize); shadowRect.setBounds ((int) gradient.point2.x, 0, w - (int) gradient.point2.x, h); line.setBounds (w - 1, 0, 1, h); break; case TabbedButtonBar::TabsAtRight: gradient.point2.x = w * shadowSize; shadowRect.setBounds (0, 0, (int) gradient.point2.x, h); line.setBounds (0, 0, 1, h); break; case TabbedButtonBar::TabsAtTop: gradient.point1.y = (float) h; gradient.point2.y = h * (1.0f - shadowSize); shadowRect.setBounds (0, (int) gradient.point2.y, w, h - (int) gradient.point2.y); line.setBounds (0, h - 1, w, 1); break; case TabbedButtonBar::TabsAtBottom: gradient.point2.y = h * shadowSize; shadowRect.setBounds (0, 0, w, (int) gradient.point2.y); line.setBounds (0, 0, w, 1); break; default: break; } g.setGradientFill (gradient); g.fillRect (shadowRect.expanded (2, 2)); g.setColour (bar.findColour (TabbedButtonBar::tabOutlineColourId)); g.fillRect (line); } void CustomLookAndFeel::drawTabButtonText (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) { const Rectangle area (button.getTextArea().toFloat()); //DBG("Sono look and feel drawtabbutton text: " << button.getButtonText()); float length = area.getWidth(); float depth = area.getHeight(); if (button.getTabbedButtonBar().isVertical()) std::swap (length, depth); Font font = myFont.withHeight(jmin(depth,30.0f) * 0.6f * fontScale); font.setUnderline (button.hasKeyboardFocus (false)); AffineTransform t; switch (button.getTabbedButtonBar().getOrientation()) { case TabbedButtonBar::TabsAtLeft: t = t.rotated (float_Pi * -0.5f).translated (area.getX(), area.getBottom()); break; case TabbedButtonBar::TabsAtRight: t = t.rotated (float_Pi * 0.5f).translated (area.getRight(), area.getY()); break; case TabbedButtonBar::TabsAtTop: case TabbedButtonBar::TabsAtBottom: t = t.translated (area.getX(), area.getY()); break; default: jassertfalse; break; } Colour col; if (button.isFrontTab() && (button.isColourSpecified (TabbedButtonBar::frontTextColourId) || isColourSpecified (TabbedButtonBar::frontTextColourId))) col = findColour (TabbedButtonBar::frontTextColourId); else if (button.isColourSpecified (TabbedButtonBar::tabTextColourId) || isColourSpecified (TabbedButtonBar::tabTextColourId)) col = findColour (TabbedButtonBar::tabTextColourId); else col = button.getTabBackgroundColour().contrasting(); const float alpha = button.isEnabled() ? ((isMouseOver || isMouseDown) ? 1.0f : 0.8f) : 0.3f; g.setColour (col.withMultipliedAlpha (alpha)); g.setFont (font); g.addTransform (t); g.drawFittedText (button.getButtonText().trim(), 0, 0, (int) length, (int) depth, Justification::centred, //jmax (1, ((int) depth) / 12), 0.5f); 1, 0.5f); } static Range getBrightnessRange (const Image& im) { float minB = 1.0f, maxB = 0; const int w = im.getWidth(); const int h = im.getHeight(); for (int y = 0; y < h; ++y) { for (int x = 0; x < w; ++x) { const float b = im.getPixelAt (x, y).getBrightness(); minB = jmin (minB, b); maxB = jmax (maxB, b); } } return Range (minB, maxB); } Font CustomLookAndFeel::getLabelFont (Label& label) { if (fontScale == 1.0f) { return label.getFont(); } else { return label.getFont().withHeight(label.getFont().getHeight() * fontScale); } } void CustomLookAndFeel::drawLabel (Graphics& g, Label& label) { Colour olcolor = label.findColour (Label::backgroundColourId); g.setColour(olcolor); if (!olcolor.isTransparent()) { if (labelCornerRadius > 0.0f) { g.fillRoundedRectangle(label.getLocalBounds().reduced(1).toFloat(), labelCornerRadius); } else { g.fillAll (olcolor); } } if (! label.isBeingEdited()) { auto alpha = label.isEnabled() ? 1.0f : 0.5f; const Font font (getLabelFont (label)); g.setColour (label.findColour (Label::textColourId).withMultipliedAlpha (alpha)); g.setFont (font); auto textArea = getLabelBorderSize (label).subtractedFrom (label.getLocalBounds()); g.drawFittedText (label.getText(), textArea, label.getJustificationType(), jmax (1, (int) (textArea.getHeight() / font.getHeight())), label.getMinimumHorizontalScale()); olcolor = label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha); } else if (label.isEnabled()) { olcolor = label.findColour (Label::outlineColourId); } if (!olcolor.isTransparent()) { g.setColour (olcolor); if (labelCornerRadius > 0.0f) { g.drawRoundedRectangle(label.getLocalBounds().reduced(1).toFloat(), labelCornerRadius, 1.0f); } else { g.drawRect (label.getLocalBounds()); } } } Font CustomLookAndFeel::getTextButtonFont (TextButton& button, int buttonHeight) { // DBG("GetTextButton font with height: " << buttonHeight); float textRatio = 0.5f; //if (SonoTextButton* const textbutt = dynamic_cast (&button)) { // textRatio = textbutt->getTextHeightRatio(); //} return myFont.withHeight(jmin (16.0f, buttonHeight * textRatio) * fontScale); } Button* CustomLookAndFeel::createSliderButton (Slider&, const bool isIncrement) { TextButton * butt = new TextButton (isIncrement ? "+" : "-", {}); return butt; } Label* CustomLookAndFeel::createSliderTextBox (Slider& slider) { Label * lab = LookAndFeel_V4::createSliderTextBox(slider); lab->setKeyboardType(TextInputTarget::decimalKeyboard); lab->setFont(myFont.withHeight(16.0* fontScale)); lab->setMinimumHorizontalScale(0.5); lab->setJustificationType(Justification::centredRight); return lab; } Font CustomLookAndFeel::getSliderPopupFont (Slider&) { return Font (18.0f, Font::bold); } int CustomLookAndFeel::getSliderPopupPlacement (Slider&) { return BubbleComponent::above //| BubbleComponent::below | BubbleComponent::left | BubbleComponent::right ; } void CustomLookAndFeel::drawButtonTextWithAlignment (Graphics& g, TextButton& button, bool /*isMouseOverButton*/, bool /*isButtonDown*/, Justification textjust) { Font font (getTextButtonFont (button, button.getHeight())); g.setFont (font); g.setColour (button.findColour (button.getToggleState() ? TextButton::textColourOnId : TextButton::textColourOffId) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); float textRatio = 0.7f; //if (SonoTextButton* const textbutt = dynamic_cast (&button)) { // textRatio = textbutt->getTextHeightRatio(); //} const int yIndent = jmin (2, button.proportionOfHeight ((1.0 - textRatio) * 0.5)); const int cornerSize = jmin (button.getHeight(), button.getWidth()) / 2; const int fontHeight = roundToInt (font.getHeight() * 0.3); const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2)); const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2)); g.drawFittedText (button.getButtonText(), leftIndent, yIndent, button.getWidth() - leftIndent - rightIndent, button.getHeight() - yIndent * 2, textjust, 2, 0.7f); } void CustomLookAndFeel::drawButtonText (Graphics& g, TextButton& button, bool isMouseOverButton, bool isButtonDown) { drawButtonTextWithAlignment(g, button, isMouseOverButton, isButtonDown); } void CustomLookAndFeel::drawFileBrowserRow (Graphics& g, int width, int height, const File& file, const String& filename, Image* icon, const String& fileSizeDescription, const String& fileTimeDescription, bool isDirectory, bool isItemSelected, int itemIndex, DirectoryContentsDisplayComponent& dcc) { Component* const fileListComp = dynamic_cast (&dcc); if (isItemSelected) g.fillAll (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::highlightColourId) : findColour (DirectoryContentsDisplayComponent::highlightColourId)); int x = 32; g.setColour (Colours::black); if (isDirectory) { if (icon != nullptr && icon->isValid()) { g.drawImageWithin (*icon, 2, 2, x - 4, height - 4, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); } else { if (const Drawable* d = isDirectory ? getDefaultFolderImage() : getDefaultDocumentFileImage()) d->drawWithin (g, Rectangle (2.0f, 2.0f, x - 4.0f, height - 4.0f), RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); } } else { x = 4; } g.setColour (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::textColourId) : findColour (DirectoryContentsDisplayComponent::textColourId)); g.setFont (myFont.withHeight(height * 0.5f)); if (width > 450 && ! isDirectory) { const int sizeX = roundToInt (width * 0.7f); const int dateX = roundToInt (width * 0.8f); g.drawFittedText (filename, x, 0, sizeX - x, height, Justification::centredLeft, 1); g.setFont (myFont.withHeight(height * 0.5f)); g.setColour (Colours::darkgrey); if (! isDirectory) { g.drawFittedText (fileSizeDescription, sizeX, 0, dateX - sizeX - 8, height, Justification::centredRight, 1); g.drawFittedText (fileTimeDescription, dateX, 0, width - 8 - dateX, height, Justification::centredRight, 1); } } else { g.drawFittedText (filename, x, 0, width - x, height, Justification::centredLeft, 1); } } Button* CustomLookAndFeel::createFileBrowserGoUpButton() { DrawableButton* goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground); Path arrowPath; arrowPath.addArrow (Line (50.0f, 100.0f, 50.0f, 0.0f), 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::white.withAlpha (0.4f)); arrowImage.setPath (arrowPath); goUpButton->setImages (&arrowImage); return goUpButton; } void CustomLookAndFeel::layoutFileBrowserComponent (FileBrowserComponent& browserComp, DirectoryContentsDisplayComponent* fileListComponent, FilePreviewComponent* previewComp, ComboBox* currentPathBox, TextEditor* filenameBox, Button* goUpButton) { const int x = 8; int w = browserComp.getWidth() - x - x; if (previewComp != nullptr) { const int previewWidth = w / 3; previewComp->setBounds (x + w - previewWidth, 0, previewWidth, browserComp.getHeight()); w -= previewWidth + 4; } int y = 4; const int controlsHeight = 22; const int bottomSectionHeight = controlsHeight + 8; const int upButtonWidth = 50; currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight); goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight); y += controlsHeight + 4; if (Component* const listAsComp = dynamic_cast (fileListComponent)) { listAsComp->setBounds (x, y, w, browserComp.getHeight() - y - bottomSectionHeight); y = listAsComp->getBottom() + 4; } filenameBox->setBounds (x + 50, y, w - 50, controlsHeight); } PopupMenu::Options CustomLookAndFeel::getOptionsForComboBoxPopupMenu (ComboBox& box, Label& label) { auto options = PopupMenu::Options().withTargetComponent (&box) .withItemThatMustBeVisible (box.getSelectedId()) .withMinimumWidth (box.getWidth()) .withMaximumNumColumns (1) .withStandardItemHeight (label.getHeight()); #if JUCE_IOS || JUCE_ANDROID auto * dw = box.findParentComponentOfClass(); if (dw) { options = options.withParentComponent(dw); } #endif return options; } void CustomLookAndFeel::drawTreeviewPlusMinusBox (Graphics& g, const Rectangle& area, Colour backgroundColour, bool isOpen, bool isMouseOver) { Path p; p.addTriangle (0.0f, 0.0f, 1.0f, isOpen ? 0.0f : 0.5f, isOpen ? 0.5f : 0.0f, 1.0f); DBG("draw plus minus ours"); //g.setColour (backgroundColour.contrasting().withAlpha (isMouseOver ? 0.5f : 0.3f)); g.setColour (Colours::white.withAlpha (isMouseOver ? 0.5f : 0.3f)); g.fillPath (p, p.getTransformToScaleToFit (area.reduced (2, area.getHeight() / 4), true)); } void CustomLookAndFeel::drawToggleButton (Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) { /* if (button.hasKeyboardFocus (true)) { g.setColour (button.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, button.getWidth(), button.getHeight()); } */ float fontSize = jmin (15.0f, button.getHeight() * 0.75f) * fontScale; const float tickWidth = fontSize * 1.1f; drawTickBox (g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f, tickWidth, tickWidth, button.getToggleState(), button.isEnabled(), isMouseOverButton, isButtonDown); g.setColour (button.findColour (ToggleButton::textColourId)); g.setFont (myFont.withHeight(fontSize)); if (! button.isEnabled()) g.setOpacity (0.5f); const int textX = (int) tickWidth + 10; g.drawFittedText (button.getButtonText(), textX, 0, button.getWidth() - textX - 2, button.getHeight(), Justification::centredLeft, 10); } void CustomLookAndFeel::drawTickBox (Graphics& g, Component& component, float x, float y, float w, float h, const bool ticked, const bool isEnabled, const bool isMouseOverButton, const bool isButtonDown) { const float boxSize = w * 1.0f; g.setColour (component.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (x, y + (h - boxSize) * 0.5f, boxSize, boxSize); if (ticked) { Path tick; tick.startNewSubPath (1.5f, 3.0f); tick.lineTo (3.0f, 6.0f); tick.lineTo (6.0f, 0.0f); g.setColour (isEnabled ? component.findColour(ToggleButton::tickColourId) : Colours::grey); const AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f) .translated (x+2, y+1)); g.strokePath (tick, PathStrokeType (2.5f), trans); } } void CustomLookAndFeel::drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos, const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider) { const auto outline = findColour (Slider::rotarySliderOutlineColourId); const auto fill = findColour (Slider::rotarySliderFillColourId); const auto bounds = Rectangle (x, y, width, height).toFloat().reduced (3); auto radius = jmin (bounds.getWidth(), bounds.getHeight()) / 2.0f; const auto toAngle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); auto lineW = jmin (8.0f, radius * 0.3f); 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)); auto rotStartAngle = rotaryStartAngle; if (slider.getProperties().contains ("fromCentre")) { rotStartAngle = (rotStartAngle + rotaryEndAngle) / 2; } if (slider.isEnabled()) { Path valueArc; valueArc.addCentredArc (bounds.getCentreX(), bounds.getCentreY(), arcRadius, arcRadius, 0.0f, rotStartAngle, toAngle, true); g.setColour (fill); g.strokePath (valueArc, PathStrokeType (lineW, PathStrokeType::curved, PathStrokeType::rounded)); } const auto thumbWidth = lineW ; // * 1.5f; const Point thumbPoint (bounds.getCentreX() + arcRadius * std::cos (toAngle - float_Pi * 0.5f), bounds.getCentreY() + arcRadius * std::sin (toAngle - float_Pi * 0.5f)); g.setColour (findColour (Slider::thumbColourId)); g.fillEllipse (Rectangle (thumbWidth, thumbWidth).withCentre (thumbPoint)); } //============================================================================== int CustomLookAndFeel::getSliderThumbRadius (Slider& slider) { if (slider.isTwoValue() || slider.isThreeValue()) { return jmin (14, slider.isHorizontal() ? static_cast (slider.getHeight() * 0.25f) : static_cast (slider.getWidth() * 0.5f)); } return jmin (16, slider.isHorizontal() ? static_cast (slider.getHeight() * 0.5f) : static_cast (slider.getWidth() * 0.5f)); } Slider::SliderLayout CustomLookAndFeel::getSliderLayout (Slider& slider) { // 1. compute the actually visible textBox size from the slider textBox size and some additional constraints int minXSpace = 0; int minYSpace = 0; auto textBoxPos = slider.getTextBoxPosition(); if (textBoxPos == Slider::TextBoxLeft || textBoxPos == Slider::TextBoxRight) minXSpace = 30; else minYSpace = 15; auto localBounds = slider.getLocalBounds(); auto textBoxWidth = jmax (0, jmin (slider.getTextBoxWidth(), localBounds.getWidth() - minXSpace)); auto textBoxHeight = jmax (0, jmin (slider.getTextBoxHeight(), localBounds.getHeight() - minYSpace)); Slider::SliderLayout layout; // 2. set the textBox bounds if (textBoxPos != Slider::NoTextBox) { if (slider.isBar()) { layout.textBoxBounds = localBounds; } else { layout.textBoxBounds.setWidth (textBoxWidth); layout.textBoxBounds.setHeight (textBoxHeight); const int thumbIndent = getSliderThumbRadius (slider); if (textBoxPos == Slider::TextBoxLeft) layout.textBoxBounds.setX (0); else if (textBoxPos == Slider::TextBoxRight) layout.textBoxBounds.setX (localBounds.getWidth() - textBoxWidth); else if (sliderTextJustification.testFlags(Justification::right))/* above or below -> right */ layout.textBoxBounds.setX ((localBounds.getWidth() - textBoxWidth - 1)); else if (sliderTextJustification.testFlags(Justification::left))/* above or below -> left */ layout.textBoxBounds.setX (1); else /* above or below -> centre horizontally */ layout.textBoxBounds.setX ((localBounds.getWidth() - textBoxWidth) / 2); if (textBoxPos == Slider::TextBoxAbove) layout.textBoxBounds.setY (0); else if (textBoxPos == Slider::TextBoxBelow) layout.textBoxBounds.setY (localBounds.getHeight() - textBoxHeight); else if (sliderTextJustification.testFlags(Justification::top))/* left or right -> top */ layout.textBoxBounds.setY (0); else if (sliderTextJustification.testFlags(Justification::bottom))/* left or right -> bottom */ layout.textBoxBounds.setY (localBounds.getHeight() - textBoxHeight); else /* left or right -> centre vertically */ layout.textBoxBounds.setY ((localBounds.getHeight() - textBoxHeight) / 2); } } // 3. set the slider bounds layout.sliderBounds = localBounds; if (slider.isBar()) { layout.sliderBounds.reduce (1, 1); // bar border } else { if (textBoxPos == Slider::TextBoxLeft) layout.sliderBounds.removeFromLeft (textBoxWidth); else if (textBoxPos == Slider::TextBoxRight) layout.sliderBounds.removeFromRight (textBoxWidth); else if (textBoxPos == Slider::TextBoxAbove) layout.sliderBounds.removeFromTop (textBoxHeight); else if (textBoxPos == Slider::TextBoxBelow) layout.sliderBounds.removeFromBottom (textBoxHeight); const int thumbIndent = getSliderThumbRadius (slider); if (slider.isHorizontal()) layout.sliderBounds.reduce (thumbIndent, 0); else if (slider.isVertical()) layout.sliderBounds.reduce (0, thumbIndent); } return layout; } void CustomLookAndFeel::drawLinearSlider (Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { if (slider.isBar()) { if (slider.getProperties().contains ("fromCentre")) { auto centrex = x + width*0.5f; auto centrey = y + height*0.5f; if (!slider.getProperties().contains ("noFill")) { g.setColour (slider.findColour (Slider::trackColourId)); g.fillRect (slider.isHorizontal() ? Rectangle (sliderPos > centrex ? centrex : sliderPos, y + 0.5f, sliderPos > centrex ? sliderPos - centrex : centrex - sliderPos, height - 1.0f) : Rectangle (x + 0.5f, sliderPos < centrey ? sliderPos : centrey, width - 1.0f, sliderPos < centrey ? centrey - sliderPos : sliderPos - centrey)); } // draw line g.setColour (slider.findColour (Slider::thumbColourId)); g.fillRect (slider.isHorizontal() ? Rectangle (sliderPos - 1, y + 0.5f, 2, height - 1.0f) : Rectangle (x + 0.5f, sliderPos - 1, width - 1.0f, 2)); } else { if (!slider.getProperties().contains ("noFill")) { g.setColour (slider.findColour (Slider::trackColourId)); g.fillRect (slider.isHorizontal() ? Rectangle (static_cast (x), y + 0.5f, sliderPos - x, height - 1.0f) : Rectangle (x + 0.5f, sliderPos, width - 1.0f, y + (height - sliderPos))); } //else g.setColour (slider.findColour (Slider::thumbColourId)); { // draw line g.fillRect (slider.isHorizontal() ? Rectangle (sliderPos - 1, y + 0.5f, 3, height - 1.0f) : Rectangle (x + 0.5f, sliderPos - 1, width - 1.0f, 3)); } } } else { auto isTwoVal = (style == Slider::SliderStyle::TwoValueVertical || style == Slider::SliderStyle::TwoValueHorizontal); auto isThreeVal = (style == Slider::SliderStyle::ThreeValueVertical || style == Slider::SliderStyle::ThreeValueHorizontal); auto trackWidth = jmin (10.0f, slider.isHorizontal() ? height * 0.25f : width * 0.25f); Point startPoint (slider.isHorizontal() ? x : x + width * 0.5f, slider.isHorizontal() ? y + height * 0.5f : height + y); Point endPoint (slider.isHorizontal() ? width + x : startPoint.x, slider.isHorizontal() ? startPoint.y : y); Path backgroundTrack; backgroundTrack.startNewSubPath (startPoint); backgroundTrack.lineTo (endPoint); g.setColour (slider.findColour (Slider::backgroundColourId)); g.strokePath (backgroundTrack, { trackWidth, PathStrokeType::curved, PathStrokeType::rounded }); Path valueTrack; Point minPoint, maxPoint, thumbPoint; if (isTwoVal || isThreeVal) { minPoint = { slider.isHorizontal() ? minSliderPos : width * 0.5f, slider.isHorizontal() ? height * 0.5f : minSliderPos }; if (isThreeVal) thumbPoint = { slider.isHorizontal() ? sliderPos : width * 0.5f, slider.isHorizontal() ? height * 0.5f : sliderPos }; maxPoint = { slider.isHorizontal() ? maxSliderPos : width * 0.5f, slider.isHorizontal() ? height * 0.5f : maxSliderPos }; } else { auto kx = slider.isHorizontal() ? sliderPos : (x + width * 0.5f); auto ky = slider.isHorizontal() ? (y + height * 0.5f) : sliderPos; minPoint = startPoint; maxPoint = { kx, ky }; } auto thumbWidth = getSliderThumbRadius (slider); valueTrack.startNewSubPath (minPoint); valueTrack.lineTo (isThreeVal ? thumbPoint : maxPoint); g.setColour (slider.findColour (Slider::trackColourId)); g.strokePath (valueTrack, { trackWidth, PathStrokeType::curved, PathStrokeType::rounded }); if (! isTwoVal) { g.setColour (slider.findColour (Slider::thumbColourId)); g.fillEllipse (Rectangle (static_cast (thumbWidth), static_cast (thumbWidth)).withCentre (isThreeVal ? thumbPoint : maxPoint)); } if (isTwoVal || isThreeVal) { auto sr = jmin (trackWidth, (slider.isHorizontal() ? height : width) * 0.4f); auto pointerColour = slider.findColour (Slider::thumbColourId); auto wscale = 1.5f; if (slider.isHorizontal()) { /* drawPointer (g, minSliderPos - sr, height * 0.5f - trackWidth*wscale*0.5, //jmax (0.0f, y + height * 0.5f - trackWidth * 0.5f), trackWidth * wscale, pointerColour, 1); // 2 drawPointer (g, maxSliderPos - trackWidth, height * 0.5f - trackWidth*wscale*0.5, //jmin (y + height - trackWidth * 2.0f, (float)y + height * 0.5f), trackWidth * wscale, pointerColour, 3); // 4 */ drawPointer (g, minSliderPos - sr, jmax (0.0f, y + height * 0.5f - trackWidth * wscale), trackWidth * wscale, pointerColour, 2); // 2 drawPointer (g, maxSliderPos - trackWidth*0.5*wscale, jmin (y + height - trackWidth * wscale, (float)y + height * 0.5f), trackWidth * wscale, pointerColour, 4); // 4 } else { drawPointer (g, jmax (0.0f, x + width * 0.5f - trackWidth * 2.0f), minSliderPos - trackWidth, trackWidth * wscale, pointerColour, 1); drawPointer (g, jmin (x + width - trackWidth * 2.0f, x + width * 0.5f), maxSliderPos - sr, trackWidth * wscale, pointerColour, 3); } } } } void CustomLookAndFeel::drawDrawableButton (Graphics& g, DrawableButton& button, bool isMouseOverButton, bool isButtonDown) { const auto cornerSize = 6.0f; bool toggleState = button.getToggleState(); ; //Rectangle bounds = g.getClipBounds().toFloat(); Rectangle bounds = button.getLocalBounds().toFloat(); g.setColour(button.findColour (toggleState ? DrawableButton::backgroundOnColourId : DrawableButton::backgroundColourId)); //g.fillAll(); g.fillRoundedRectangle(bounds, cornerSize); if (isButtonDown) { //setColour (SonoDrawableButton::overOverlayColourId, Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.08)); //setColour (SonoDrawableButton::downOverlayColourId, Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.3)); g.setColour(Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.3)); //g.setColour(findColour(SonoDrawableButton::downOverlayColourId)); //g.fillAll(); g.fillRoundedRectangle(bounds, cornerSize); } else if (isMouseOverButton) { //g.setColour(findColour(SonoDrawableButton::overOverlayColourId)); g.setColour(Colour::fromFloatRGBA(0.8, 0.8, 0.8, 0.08)); //g.fillAll(); g.fillRoundedRectangle(bounds, cornerSize); } //g.fillAll (button.findColour (toggleState ? DrawableButton::backgroundOnColourId // : DrawableButton::backgroundColourId)); int textH = 0; int textW = 0; float imageratio = 0.75f; //if (SonoDrawableButton* const sonobutt = dynamic_cast (&button)) { // imageratio = sonobutt->getForegroundImageRatio(); //} if (button.getStyle() == DrawableButton::ImageAboveTextLabel || button.getStyle() == DrawableButton::ImageBelowTextLabel) { textH = jmin (14, button.proportionOfHeight (0.2f)); } else if (button.getStyle() == DrawableButton::ImageLeftOfTextLabel || button.getStyle() == DrawableButton::ImageRightOfTextLabel) { textH = jmin (14, button.proportionOfHeight (0.8f)); textW = jmax (20, button.proportionOfWidth (1.0f - imageratio)); } if (textH > 0) { g.setFont (myFont.withHeight((float) textH * fontScale)); g.setColour (button.findColour (toggleState ? DrawableButton::textColourOnId : DrawableButton::textColourId) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.4f)); if (button.getStyle() == DrawableButton::ImageAboveTextLabel) { g.drawFittedText (button.getButtonText(), 2, button.getHeight() - textH - 1, button.getWidth() - 4, textH, Justification::centred, 1); } else if (button.getStyle() == DrawableButton::ImageBelowTextLabel) { g.drawFittedText (button.getButtonText(), 2, 1, button.getWidth() - 4, textH, Justification::centred, 1); } else if (button.getStyle() == DrawableButton::ImageRightOfTextLabel) { g.drawFittedText (button.getButtonText(), 2, 1, textW , button.getHeight() - 2, Justification::centred, 2, 0.6f); } else if (button.getStyle() == DrawableButton::ImageLeftOfTextLabel) { g.drawFittedText (button.getButtonText(), button.getWidth() - textW - 4 , 1, textW , button.getHeight() - 2, Justification::centred, 2, 0.6f); } } } void CustomLookAndFeel::drawBubble (Graphics& g, BubbleComponent& comp, const Point& tip, const Rectangle& body) { Path p; p.addBubble (body.reduced (0.5f), body.getUnion (Rectangle (tip.x, tip.y, 1.0f, 1.0f)), tip, 5.0f, jmin (10.0f, body.getWidth() * 0.2f, body.getHeight() * 0.2f)); g.setColour (comp.findColour (BubbleComponent::backgroundColourId)); g.fillPath (p); g.setColour (comp.findColour (BubbleComponent::outlineColourId)); g.strokePath (p, PathStrokeType (1.0f)); } CustomBigTextLookAndFeel::CustomBigTextLookAndFeel(float maxTextSize) : maxSize(maxTextSize) { } Font CustomBigTextLookAndFeel::getTextButtonFont (TextButton& button, int buttonHeight) { // DBG("GetTextButton font with height: " << buttonHeight << " maxsize: " << maxSize); float textRatio = 0.8f; //if (SonoTextButton* const textbutt = dynamic_cast (&button)) { // textRatio = textbutt->getTextHeightRatio(); //} return myFont.withHeight(jmin (maxSize, buttonHeight * textRatio) * fontScale); } Label* CustomBigTextLookAndFeel::createSliderTextBox (Slider& slider) { Label * lab = LookAndFeel_V4::createSliderTextBox(slider); lab->setKeyboardType(TextInputTarget::decimalKeyboard); lab->setFont(myFont.withHeight(maxSize * fontScale)); lab->setJustificationType(textJustification); lab->setMinimumHorizontalScale(0.5); return lab; } Button* CustomBigTextLookAndFeel::createSliderButton (Slider&, const bool isIncrement) { TextButton * butt = new TextButton (isIncrement ? "+" : "-", {}); butt->setLookAndFeel(this); return butt; } void CustomBigTextLookAndFeel::drawToggleButton (Graphics& g, ToggleButton& button, bool isMouseOverButton, bool isButtonDown) { /* if (button.hasKeyboardFocus (true)) { g.setColour (button.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, button.getWidth(), button.getHeight()); } */ float fontSize = jmin (maxSize, button.getHeight() * 0.75f) * fontScale; const float tickWidth = fontSize * 1.1f; drawTickBox (g, button, 4.0f, (button.getHeight() - tickWidth) * 0.5f, tickWidth, tickWidth, button.getToggleState(), button.isEnabled(), isMouseOverButton, isButtonDown); g.setColour (button.findColour (ToggleButton::textColourId)); g.setFont (myFont.withHeight(fontSize)); if (! button.isEnabled()) g.setOpacity (0.5f); const int textX = (int) tickWidth + 10; g.drawFittedText (button.getButtonText(), textX, 0, button.getWidth() - textX - 2, button.getHeight(), Justification::centredLeft, 10); }