/* ============================================================================== This file is part of the JUCE 7 technical preview. Copyright (c) 2022 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For the technical preview this file cannot be licensed commercially. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { namespace LookAndFeelHelpers { static Colour createBaseColour (Colour buttonColour, bool hasKeyboardFocus, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) noexcept { const float sat = hasKeyboardFocus ? 1.3f : 0.9f; const Colour baseColour (buttonColour.withMultipliedSaturation (sat)); if (shouldDrawButtonAsDown) return baseColour.contrasting (0.2f); if (shouldDrawButtonAsHighlighted) return baseColour.contrasting (0.1f); return baseColour; } static TextLayout layoutTooltipText (const String& text, Colour colour) noexcept { const float tooltipFontSize = 13.0f; const int maxToolTipWidth = 400; AttributedString s; s.setJustification (Justification::centred); s.append (text, Font (tooltipFontSize, Font::bold), colour); TextLayout tl; tl.createLayoutWithBalancedLineLengths (s, (float) maxToolTipWidth); return tl; } } //============================================================================== LookAndFeel_V2::LookAndFeel_V2() { // initialise the standard set of colours.. const uint32 textButtonColour = 0xffbbbbff; const uint32 textHighlightColour = 0x401111ee; const uint32 standardOutlineColour = 0xb2808080; static const uint32 standardColours[] = { TextButton::buttonColourId, textButtonColour, TextButton::buttonOnColourId, 0xff4444ff, TextButton::textColourOnId, 0xff000000, TextButton::textColourOffId, 0xff000000, ToggleButton::textColourId, 0xff000000, ToggleButton::tickColourId, 0xff000000, ToggleButton::tickDisabledColourId, 0xff808080, TextEditor::backgroundColourId, 0xffffffff, TextEditor::textColourId, 0xff000000, TextEditor::highlightColourId, textHighlightColour, TextEditor::highlightedTextColourId, 0xff000000, TextEditor::outlineColourId, 0x00000000, TextEditor::focusedOutlineColourId, textButtonColour, TextEditor::shadowColourId, 0x38000000, CaretComponent::caretColourId, 0xff000000, Label::backgroundColourId, 0x00000000, Label::textColourId, 0xff000000, Label::outlineColourId, 0x00000000, ScrollBar::backgroundColourId, 0x00000000, ScrollBar::thumbColourId, 0xffffffff, TreeView::linesColourId, 0x4c000000, TreeView::backgroundColourId, 0x00000000, TreeView::dragAndDropIndicatorColourId, 0x80ff0000, TreeView::selectedItemBackgroundColourId, 0x00000000, TreeView::oddItemsColourId, 0x00000000, TreeView::evenItemsColourId, 0x00000000, PopupMenu::backgroundColourId, 0xffffffff, PopupMenu::textColourId, 0xff000000, PopupMenu::headerTextColourId, 0xff000000, PopupMenu::highlightedTextColourId, 0xffffffff, PopupMenu::highlightedBackgroundColourId, 0x991111aa, ComboBox::buttonColourId, 0xffbbbbff, ComboBox::outlineColourId, standardOutlineColour, ComboBox::textColourId, 0xff000000, ComboBox::backgroundColourId, 0xffffffff, ComboBox::arrowColourId, 0x99000000, ComboBox::focusedOutlineColourId, 0xffbbbbff, PropertyComponent::backgroundColourId, 0x66ffffff, PropertyComponent::labelTextColourId, 0xff000000, TextPropertyComponent::backgroundColourId, 0xffffffff, TextPropertyComponent::textColourId, 0xff000000, TextPropertyComponent::outlineColourId, standardOutlineColour, BooleanPropertyComponent::backgroundColourId, 0xffffffff, BooleanPropertyComponent::outlineColourId, standardOutlineColour, ListBox::backgroundColourId, 0xffffffff, ListBox::outlineColourId, standardOutlineColour, ListBox::textColourId, 0xff000000, Slider::backgroundColourId, 0x00000000, Slider::thumbColourId, textButtonColour, Slider::trackColourId, 0x7fffffff, Slider::rotarySliderFillColourId, 0x7f0000ff, Slider::rotarySliderOutlineColourId, 0x66000000, Slider::textBoxTextColourId, 0xff000000, Slider::textBoxBackgroundColourId, 0xffffffff, Slider::textBoxHighlightColourId, textHighlightColour, Slider::textBoxOutlineColourId, standardOutlineColour, ResizableWindow::backgroundColourId, 0xff777777, //DocumentWindow::textColourId, 0xff000000, // (this is deliberately not set) AlertWindow::backgroundColourId, 0xffededed, AlertWindow::textColourId, 0xff000000, AlertWindow::outlineColourId, 0xff666666, ProgressBar::backgroundColourId, 0xffeeeeee, ProgressBar::foregroundColourId, 0xffaaaaee, TooltipWindow::backgroundColourId, 0xffeeeebb, TooltipWindow::textColourId, 0xff000000, TooltipWindow::outlineColourId, 0x4c000000, TabbedComponent::backgroundColourId, 0x00000000, TabbedComponent::outlineColourId, 0xff777777, TabbedButtonBar::tabOutlineColourId, 0x80000000, TabbedButtonBar::frontOutlineColourId, 0x90000000, Toolbar::backgroundColourId, 0xfff6f8f9, Toolbar::separatorColourId, 0x4c000000, Toolbar::buttonMouseOverBackgroundColourId, 0x4c0000ff, Toolbar::buttonMouseDownBackgroundColourId, 0x800000ff, Toolbar::labelTextColourId, 0xff000000, Toolbar::editingModeOutlineColourId, 0xffff0000, DrawableButton::textColourId, 0xff000000, DrawableButton::textColourOnId, 0xff000000, DrawableButton::backgroundColourId, 0x00000000, DrawableButton::backgroundOnColourId, 0xaabbbbff, HyperlinkButton::textColourId, 0xcc1111ee, GroupComponent::outlineColourId, 0x66000000, GroupComponent::textColourId, 0xff000000, BubbleComponent::backgroundColourId, 0xeeeeeebb, BubbleComponent::outlineColourId, 0x77000000, TableHeaderComponent::textColourId, 0xff000000, TableHeaderComponent::backgroundColourId, 0xffe8ebf9, TableHeaderComponent::outlineColourId, 0x33000000, TableHeaderComponent::highlightColourId, 0x8899aadd, DirectoryContentsDisplayComponent::highlightColourId, textHighlightColour, DirectoryContentsDisplayComponent::textColourId, 0xff000000, DirectoryContentsDisplayComponent::highlightedTextColourId, 0xff000000, 0x1000440, /*LassoComponent::lassoFillColourId*/ 0x66dddddd, 0x1000441, /*LassoComponent::lassoOutlineColourId*/ 0x99111111, 0x1004000, /*KeyboardComponentBase::upDownButtonBackgroundColourId*/ 0xffd3d3d3, 0x1004001, /*KeyboardComponentBase::upDownButtonArrowColourId*/ 0xff000000, 0x1005000, /*MidiKeyboardComponent::whiteNoteColourId*/ 0xffffffff, 0x1005001, /*MidiKeyboardComponent::blackNoteColourId*/ 0xff000000, 0x1005002, /*MidiKeyboardComponent::keySeparatorLineColourId*/ 0x66000000, 0x1005003, /*MidiKeyboardComponent::mouseOverKeyOverlayColourId*/ 0x80ffff00, 0x1005004, /*MidiKeyboardComponent::keyDownOverlayColourId*/ 0xffb6b600, 0x1005005, /*MidiKeyboardComponent::textLabelColourId*/ 0xff000000, 0x1005006, /*MidiKeyboardComponent::shadowColourId*/ 0x4c000000, 0x1006000, /*MPEKeyboardComponent::whiteNoteColourId*/ 0xff1a1c27, 0x1006001, /*MPEKeyboardComponent::blackNoteColourId*/ 0x99f1f1f1, 0x1006002, /*MPEKeyboardComponent::textLabelColourId*/ 0xfff1f1f1, 0x1006003, /*MPEKeyboardComponent::noteCircleFillColourId*/ 0x99ba00ff, 0x1006004, /*MPEKeyboardComponent::noteCircleOutlineColourId*/ 0xfff1f1f1, 0x1004500, /*CodeEditorComponent::backgroundColourId*/ 0xffffffff, 0x1004502, /*CodeEditorComponent::highlightColourId*/ textHighlightColour, 0x1004503, /*CodeEditorComponent::defaultTextColourId*/ 0xff000000, 0x1004504, /*CodeEditorComponent::lineNumberBackgroundId*/ 0x44999999, 0x1004505, /*CodeEditorComponent::lineNumberTextId*/ 0x44000000, 0x1007000, /*ColourSelector::backgroundColourId*/ 0xffe5e5e5, 0x1007001, /*ColourSelector::labelTextColourId*/ 0xff000000, 0x100ad00, /*KeyMappingEditorComponent::backgroundColourId*/ 0x00000000, 0x100ad01, /*KeyMappingEditorComponent::textColourId*/ 0xff000000, FileSearchPathListComponent::backgroundColourId, 0xffffffff, FileChooserDialogBox::titleTextColourId, 0xff000000, SidePanel::backgroundColour, 0xffffffff, SidePanel::titleTextColour, 0xff000000, SidePanel::shadowBaseColour, 0xff000000, SidePanel::dismissButtonNormalColour, textButtonColour, SidePanel::dismissButtonOverColour, textButtonColour, SidePanel::dismissButtonDownColour, 0xff4444ff, FileBrowserComponent::currentPathBoxBackgroundColourId, 0xffffffff, FileBrowserComponent::currentPathBoxTextColourId, 0xff000000, FileBrowserComponent::currentPathBoxArrowColourId, 0x99000000, FileBrowserComponent::filenameBoxBackgroundColourId, 0xffffffff, FileBrowserComponent::filenameBoxTextColourId, 0xff000000, }; for (int i = 0; i < numElementsInArray (standardColours); i += 2) setColour ((int) standardColours [i], Colour ((uint32) standardColours [i + 1])); } LookAndFeel_V2::~LookAndFeel_V2() {} //============================================================================== void LookAndFeel_V2::drawButtonBackground (Graphics& g, Button& button, const Colour& backgroundColour, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) { const int width = button.getWidth(); const int height = button.getHeight(); const float outlineThickness = button.isEnabled() ? ((shouldDrawButtonAsDown || shouldDrawButtonAsHighlighted) ? 1.2f : 0.7f) : 0.4f; const float halfThickness = outlineThickness * 0.5f; const float indentL = button.isConnectedOnLeft() ? 0.1f : halfThickness; const float indentR = button.isConnectedOnRight() ? 0.1f : halfThickness; const float indentT = button.isConnectedOnTop() ? 0.1f : halfThickness; const float indentB = button.isConnectedOnBottom() ? 0.1f : halfThickness; const Colour baseColour (LookAndFeelHelpers::createBaseColour (backgroundColour, button.hasKeyboardFocus (true), shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); drawGlassLozenge (g, indentL, indentT, (float) width - indentL - indentR, (float) height - indentT - indentB, baseColour, outlineThickness, -1.0f, button.isConnectedOnLeft(), button.isConnectedOnRight(), button.isConnectedOnTop(), button.isConnectedOnBottom()); } Font LookAndFeel_V2::getTextButtonFont (TextButton&, int buttonHeight) { return Font (jmin (15.0f, (float) buttonHeight * 0.6f)); } int LookAndFeel_V2::getTextButtonWidthToFitText (TextButton& b, int buttonHeight) { return getTextButtonFont (b, buttonHeight).getStringWidth (b.getButtonText()) + buttonHeight; } void LookAndFeel_V2::drawButtonText (Graphics& g, TextButton& button, bool /*shouldDrawButtonAsHighlighted*/, bool /*shouldDrawButtonAsDown*/) { 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)); const int yIndent = jmin (4, button.proportionOfHeight (0.3f)); const int cornerSize = jmin (button.getHeight(), button.getWidth()) / 2; const int fontHeight = roundToInt (font.getHeight() * 0.6f); const int leftIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnLeft() ? 4 : 2)); const int rightIndent = jmin (fontHeight, 2 + cornerSize / (button.isConnectedOnRight() ? 4 : 2)); const int textWidth = button.getWidth() - leftIndent - rightIndent; if (textWidth > 0) g.drawFittedText (button.getButtonText(), leftIndent, yIndent, textWidth, button.getHeight() - yIndent * 2, Justification::centred, 2); } void LookAndFeel_V2::drawTickBox (Graphics& g, Component& component, float x, float y, float w, float h, const bool ticked, const bool isEnabled, const bool shouldDrawButtonAsHighlighted, const bool shouldDrawButtonAsDown) { const float boxSize = w * 0.7f; drawGlassSphere (g, x, y + (h - boxSize) * 0.5f, boxSize, LookAndFeelHelpers::createBaseColour (component.findColour (TextButton::buttonColourId) .withMultipliedAlpha (isEnabled ? 1.0f : 0.5f), true, shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown), isEnabled ? ((shouldDrawButtonAsDown || shouldDrawButtonAsHighlighted) ? 1.1f : 0.5f) : 0.3f); if (ticked) { Path tick; tick.startNewSubPath (1.5f, 3.0f); tick.lineTo (3.0f, 6.0f); tick.lineTo (6.0f, 0.0f); g.setColour (component.findColour (isEnabled ? ToggleButton::tickColourId : ToggleButton::tickDisabledColourId)); const AffineTransform trans (AffineTransform::scale (w / 9.0f, h / 9.0f) .translated (x, y)); g.strokePath (tick, PathStrokeType (2.5f), trans); } } void LookAndFeel_V2::drawToggleButton (Graphics& g, ToggleButton& button, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) { if (button.hasKeyboardFocus (true)) { g.setColour (button.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, button.getWidth(), button.getHeight()); } float fontSize = jmin (15.0f, (float) button.getHeight() * 0.75f); const float tickWidth = fontSize * 1.1f; drawTickBox (g, button, 4.0f, ((float) button.getHeight() - tickWidth) * 0.5f, tickWidth, tickWidth, button.getToggleState(), button.isEnabled(), shouldDrawButtonAsHighlighted, shouldDrawButtonAsDown); g.setColour (button.findColour (ToggleButton::textColourId)); g.setFont (fontSize); if (! button.isEnabled()) g.setOpacity (0.5f); g.drawFittedText (button.getButtonText(), button.getLocalBounds().withTrimmedLeft (roundToInt (tickWidth) + 5) .withTrimmedRight (2), Justification::centredLeft, 10); } void LookAndFeel_V2::changeToggleButtonWidthToFitText (ToggleButton& button) { auto fontSize = jmin (15.0f, (float) button.getHeight() * 0.75f); auto tickWidth = fontSize * 1.1f; Font font (fontSize); button.setSize (font.getStringWidth (button.getButtonText()) + roundToInt (tickWidth) + 9, button.getHeight()); } void LookAndFeel_V2::drawDrawableButton (Graphics& g, DrawableButton& button, bool /*shouldDrawButtonAsHighlighted*/, bool /*shouldDrawButtonAsDown*/) { bool toggleState = button.getToggleState(); g.fillAll (button.findColour (toggleState ? DrawableButton::backgroundOnColourId : DrawableButton::backgroundColourId)); const int textH = (button.getStyle() == DrawableButton::ImageAboveTextLabel) ? jmin (16, button.proportionOfHeight (0.25f)) : 0; if (textH > 0) { g.setFont ((float) textH); g.setColour (button.findColour (toggleState ? DrawableButton::textColourOnId : DrawableButton::textColourId) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.4f)); g.drawFittedText (button.getButtonText(), 2, button.getHeight() - textH - 1, button.getWidth() - 4, textH, Justification::centred, 1); } } //============================================================================== AlertWindow* LookAndFeel_V2::createAlertWindow (const String& title, const String& message, const String& button1, const String& button2, const String& button3, MessageBoxIconType iconType, int numButtons, Component* associatedComponent) { AlertWindow* aw = new AlertWindow (title, message, iconType, associatedComponent); if (numButtons == 1) { aw->addButton (button1, 0, KeyPress (KeyPress::escapeKey), KeyPress (KeyPress::returnKey)); } else { const KeyPress button1ShortCut ((int) CharacterFunctions::toLowerCase (button1[0]), 0, 0); KeyPress button2ShortCut ((int) CharacterFunctions::toLowerCase (button2[0]), 0, 0); if (button1ShortCut == button2ShortCut) button2ShortCut = KeyPress(); if (numButtons == 2) { aw->addButton (button1, 1, KeyPress (KeyPress::returnKey), button1ShortCut); aw->addButton (button2, 0, KeyPress (KeyPress::escapeKey), button2ShortCut); } else if (numButtons == 3) { aw->addButton (button1, 1, button1ShortCut); aw->addButton (button2, 2, button2ShortCut); aw->addButton (button3, 0, KeyPress (KeyPress::escapeKey)); } } return aw; } void LookAndFeel_V2::drawAlertBox (Graphics& g, AlertWindow& alert, const Rectangle& textArea, TextLayout& textLayout) { g.fillAll (alert.findColour (AlertWindow::backgroundColourId)); int iconSpaceUsed = 0; const int iconWidth = 80; int iconSize = jmin (iconWidth + 50, alert.getHeight() + 20); if (alert.containsAnyExtraComponents() || alert.getNumButtons() > 2) iconSize = jmin (iconSize, textArea.getHeight() + 50); const Rectangle iconRect (iconSize / -10, iconSize / -10, iconSize, iconSize); if (alert.getAlertType() != MessageBoxIconType::NoIcon) { Path icon; uint32 colour; char character; if (alert.getAlertType() == MessageBoxIconType::WarningIcon) { colour = 0x55ff5555; character = '!'; icon.addTriangle ((float) iconRect.getX() + (float) iconRect.getWidth() * 0.5f, (float) iconRect.getY(), (float) iconRect.getRight(), (float) iconRect.getBottom(), (float) iconRect.getX(), (float) iconRect.getBottom()); icon = icon.createPathWithRoundedCorners (5.0f); } else { colour = alert.getAlertType() == MessageBoxIconType::InfoIcon ? (uint32) 0x605555ff : (uint32) 0x40b69900; character = alert.getAlertType() == MessageBoxIconType::InfoIcon ? 'i' : '?'; icon.addEllipse (iconRect.toFloat()); } GlyphArrangement ga; ga.addFittedText (Font ((float) iconRect.getHeight() * 0.9f, Font::bold), String::charToString ((juce_wchar) (uint8) character), (float) iconRect.getX(), (float) iconRect.getY(), (float) iconRect.getWidth(), (float) iconRect.getHeight(), Justification::centred, false); ga.createPath (icon); icon.setUsingNonZeroWinding (false); g.setColour (Colour (colour)); g.fillPath (icon); iconSpaceUsed = iconWidth; } g.setColour (alert.findColour (AlertWindow::textColourId)); textLayout.draw (g, Rectangle (textArea.getX() + iconSpaceUsed, textArea.getY(), textArea.getWidth() - iconSpaceUsed, textArea.getHeight()).toFloat()); g.setColour (alert.findColour (AlertWindow::outlineColourId)); g.drawRect (0, 0, alert.getWidth(), alert.getHeight()); } int LookAndFeel_V2::getAlertBoxWindowFlags() { return ComponentPeer::windowAppearsOnTaskbar | ComponentPeer::windowHasDropShadow; } Array LookAndFeel_V2::getWidthsForTextButtons (AlertWindow&, const Array& buttons) { const int n = buttons.size(); Array buttonWidths; const int buttonHeight = getAlertWindowButtonHeight(); for (int i = 0; i < n; ++i) buttonWidths.add (getTextButtonWidthToFitText (*buttons.getReference (i), buttonHeight)); return buttonWidths; } int LookAndFeel_V2::getAlertWindowButtonHeight() { return 28; } Font LookAndFeel_V2::getAlertWindowTitleFont() { Font messageFont = getAlertWindowMessageFont(); return messageFont.withHeight (messageFont.getHeight() * 1.1f).boldened(); } Font LookAndFeel_V2::getAlertWindowMessageFont() { return Font (15.0f); } Font LookAndFeel_V2::getAlertWindowFont() { return Font (12.0f); } //============================================================================== void LookAndFeel_V2::drawProgressBar (Graphics& g, ProgressBar& progressBar, int width, int height, double progress, const String& textToShow) { const Colour background (progressBar.findColour (ProgressBar::backgroundColourId)); const Colour foreground (progressBar.findColour (ProgressBar::foregroundColourId)); g.fillAll (background); if (progress >= 0.0f && progress < 1.0f) { drawGlassLozenge (g, 1.0f, 1.0f, (float) jlimit (0.0, width - 2.0, progress * (width - 2.0)), (float) (height - 2), foreground, 0.5f, 0.0f, true, true, true, true); } else { // spinning bar.. g.setColour (foreground); const int stripeWidth = height * 2; const int position = (int) (Time::getMillisecondCounter() / 15) % stripeWidth; Path p; for (float x = (float) (- position); x < (float) (width + stripeWidth); x += (float) stripeWidth) p.addQuadrilateral (x, 0.0f, x + (float) stripeWidth * 0.5f, 0.0f, x, (float) height, x - (float) stripeWidth * 0.5f, (float) height); Image im (Image::ARGB, width, height, true); { Graphics g2 (im); drawGlassLozenge (g2, 1.0f, 1.0f, (float) (width - 2), (float) (height - 2), foreground, 0.5f, 0.0f, true, true, true, true); } g.setTiledImageFill (im, 0, 0, 0.85f); g.fillPath (p); } if (textToShow.isNotEmpty()) { g.setColour (Colour::contrasting (background, foreground)); g.setFont ((float) height * 0.6f); g.drawText (textToShow, 0, 0, width, height, Justification::centred, false); } } void LookAndFeel_V2::drawSpinningWaitAnimation (Graphics& g, const Colour& colour, int x, int y, int w, int h) { const float radius = (float) jmin (w, h) * 0.4f; const float thickness = radius * 0.15f; Path p; p.addRoundedRectangle (radius * 0.4f, thickness * -0.5f, radius * 0.6f, thickness, thickness * 0.5f); const float cx = (float) x + (float) w * 0.5f; const float cy = (float) y + (float) h * 0.5f; const uint32 animationIndex = (Time::getMillisecondCounter() / (1000 / 10)) % 12; for (uint32 i = 0; i < 12; ++i) { const uint32 n = (i + 12 - animationIndex) % 12; g.setColour (colour.withMultipliedAlpha ((float) (n + 1) / 12.0f)); g.fillPath (p, AffineTransform::rotation ((float) i * (MathConstants::pi / 6.0f)) .translated (cx, cy)); } } bool LookAndFeel_V2::isProgressBarOpaque (ProgressBar& progressBar) { return progressBar.findColour (ProgressBar::backgroundColourId).isOpaque(); } bool LookAndFeel_V2::areScrollbarButtonsVisible() { return true; } void LookAndFeel_V2::drawScrollbarButton (Graphics& g, ScrollBar& scrollbar, int width, int height, int buttonDirection, bool /*isScrollbarVertical*/, bool /*shouldDrawButtonAsHighlighted*/, bool shouldDrawButtonAsDown) { Path p; const auto w = (float) width; const auto h = (float) height; if (buttonDirection == 0) p.addTriangle (w * 0.5f, h * 0.2f, w * 0.1f, h * 0.7f, w * 0.9f, h * 0.7f); else if (buttonDirection == 1) p.addTriangle (w * 0.8f, h * 0.5f, w * 0.3f, h * 0.1f, w * 0.3f, h * 0.9f); else if (buttonDirection == 2) p.addTriangle (w * 0.5f, h * 0.8f, w * 0.1f, h * 0.3f, w * 0.9f, h * 0.3f); else if (buttonDirection == 3) p.addTriangle (w * 0.2f, h * 0.5f, w * 0.7f, h * 0.1f, w * 0.7f, h * 0.9f); if (shouldDrawButtonAsDown) g.setColour (scrollbar.findColour (ScrollBar::thumbColourId).contrasting (0.2f)); else g.setColour (scrollbar.findColour (ScrollBar::thumbColourId)); g.fillPath (p); g.setColour (Colour (0x80000000)); g.strokePath (p, PathStrokeType (0.5f)); } void LookAndFeel_V2::drawScrollbar (Graphics& g, ScrollBar& scrollbar, int x, int y, int width, int height, bool isScrollbarVertical, int thumbStartPosition, int thumbSize, bool /*isMouseOver*/, bool /*isMouseDown*/) { g.fillAll (scrollbar.findColour (ScrollBar::backgroundColourId)); Path slotPath, thumbPath; const float slotIndent = jmin (width, height) > 15 ? 1.0f : 0.0f; const float slotIndentx2 = slotIndent * 2.0f; const float thumbIndent = slotIndent + 1.0f; const float thumbIndentx2 = thumbIndent * 2.0f; float gx1 = 0.0f, gy1 = 0.0f, gx2 = 0.0f, gy2 = 0.0f; if (isScrollbarVertical) { slotPath.addRoundedRectangle ((float) x + slotIndent, (float) y + slotIndent, (float) width - slotIndentx2, (float) height - slotIndentx2, ((float) width - slotIndentx2) * 0.5f); if (thumbSize > 0) thumbPath.addRoundedRectangle ((float) x + thumbIndent, (float) thumbStartPosition + thumbIndent, (float) width - thumbIndentx2, (float) thumbSize - thumbIndentx2, ((float) width - thumbIndentx2) * 0.5f); gx1 = (float) x; gx2 = (float) x + (float) width * 0.7f; } else { slotPath.addRoundedRectangle ((float) x + slotIndent, (float) y + slotIndent, (float) width - slotIndentx2, (float) height - slotIndentx2, ((float) height - slotIndentx2) * 0.5f); if (thumbSize > 0) thumbPath.addRoundedRectangle ((float) thumbStartPosition + thumbIndent, (float) y + thumbIndent, (float) thumbSize - thumbIndentx2, (float) height - thumbIndentx2, ((float) height - thumbIndentx2) * 0.5f); gy1 = (float) y; gy2 = (float) y + (float) height * 0.7f; } const Colour thumbColour (scrollbar.findColour (ScrollBar::thumbColourId)); Colour trackColour1, trackColour2; if (scrollbar.isColourSpecified (ScrollBar::trackColourId) || isColourSpecified (ScrollBar::trackColourId)) { trackColour1 = trackColour2 = scrollbar.findColour (ScrollBar::trackColourId); } else { trackColour1 = thumbColour.overlaidWith (Colour (0x44000000)); trackColour2 = thumbColour.overlaidWith (Colour (0x19000000)); } g.setGradientFill (ColourGradient (trackColour1, gx1, gy1, trackColour2, gx2, gy2, false)); g.fillPath (slotPath); if (isScrollbarVertical) { gx1 = (float) x + (float) width * 0.6f; gx2 = (float) x + (float) width; } else { gy1 = (float) y + (float) height * 0.6f; gy2 = (float) y + (float) height; } g.setGradientFill (ColourGradient (Colours::transparentBlack,gx1, gy1, Colour (0x19000000), gx2, gy2, false)); g.fillPath (slotPath); g.setColour (thumbColour); g.fillPath (thumbPath); g.setGradientFill (ColourGradient (Colour (0x10000000), gx1, gy1, Colours::transparentBlack, gx2, gy2, false)); { Graphics::ScopedSaveState ss (g); if (isScrollbarVertical) g.reduceClipRegion (x + width / 2, y, width, height); else g.reduceClipRegion (x, y + height / 2, width, height); g.fillPath (thumbPath); } g.setColour (Colour (0x4c000000)); g.strokePath (thumbPath, PathStrokeType (0.4f)); } ImageEffectFilter* LookAndFeel_V2::getScrollbarEffect() { return nullptr; } int LookAndFeel_V2::getMinimumScrollbarThumbSize (ScrollBar& scrollbar) { return jmin (scrollbar.getWidth(), scrollbar.getHeight()) * 2; } int LookAndFeel_V2::getDefaultScrollbarWidth() { return 18; } int LookAndFeel_V2::getScrollbarButtonSize (ScrollBar& scrollbar) { return 2 + (scrollbar.isVertical() ? scrollbar.getWidth() : scrollbar.getHeight()); } //============================================================================== void LookAndFeel_V2::drawTreeviewPlusMinusBox (Graphics& g, const Rectangle& area, Colour /*backgroundColour*/, bool isOpen, bool /*isMouseOver*/) { auto boxSize = roundToInt (jmin (16.0f, area.getWidth(), area.getHeight()) * 0.7f) | 1; auto x = ((int) area.getWidth() - boxSize) / 2 + (int) area.getX(); auto y = ((int) area.getHeight() - boxSize) / 2 + (int) area.getY(); Rectangle boxArea ((float) x, (float) y, (float) boxSize, (float) boxSize); g.setColour (Colour (0xe5ffffff)); g.fillRect (boxArea); g.setColour (Colour (0x80000000)); g.drawRect (boxArea); auto size = (float) boxSize * 0.5f + 1.0f; auto centre = (float) (boxSize / 2); g.fillRect ((float) x + ((float) boxSize - size) * 0.5f, (float) y + centre, size, 1.0f); if (! isOpen) g.fillRect ((float) x + centre, (float) y + ((float) boxSize - size) * 0.5f, 1.0f, size); } bool LookAndFeel_V2::areLinesDrawnForTreeView (TreeView&) { return true; } int LookAndFeel_V2::getTreeViewIndentSize (TreeView&) { return 24; } //============================================================================== void LookAndFeel_V2::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 (15.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)); } //============================================================================== Font LookAndFeel_V2::getPopupMenuFont() { return Font (17.0f); } void LookAndFeel_V2::getIdealPopupMenuItemSize (const String& text, const bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight) { if (isSeparator) { idealWidth = 50; idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight / 2 : 10; } else { Font font (getPopupMenuFont()); if (standardMenuItemHeight > 0 && font.getHeight() > (float) standardMenuItemHeight / 1.3f) font.setHeight ((float) standardMenuItemHeight / 1.3f); idealHeight = standardMenuItemHeight > 0 ? standardMenuItemHeight : roundToInt (font.getHeight() * 1.3f); idealWidth = font.getStringWidth (text) + idealHeight * 2; } } void LookAndFeel_V2::getIdealPopupMenuItemSizeWithOptions (const String& text, bool isSeparator, int standardMenuItemHeight, int& idealWidth, int& idealHeight, const PopupMenu::Options&) { getIdealPopupMenuItemSize (text, isSeparator, standardMenuItemHeight, idealWidth, idealHeight); } void LookAndFeel_V2::drawPopupMenuBackground (Graphics& g, int width, int height) { auto background = findColour (PopupMenu::backgroundColourId); g.fillAll (background); g.setColour (background.overlaidWith (Colour (0x2badd8e6))); for (int i = 0; i < height; i += 3) g.fillRect (0, i, width, 1); #if ! JUCE_MAC g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.6f)); g.drawRect (0, 0, width, height); #endif } void LookAndFeel_V2::drawPopupMenuBackgroundWithOptions (Graphics& g, int width, int height, const PopupMenu::Options&) { drawPopupMenuBackground (g, width, height); } void LookAndFeel_V2::drawPopupMenuUpDownArrow (Graphics& g, int width, int height, bool isScrollUpArrow) { auto background = findColour (PopupMenu::backgroundColourId); g.setGradientFill (ColourGradient (background, 0.0f, (float) height * 0.5f, background.withAlpha (0.0f), 0.0f, isScrollUpArrow ? ((float) height) : 0.0f, false)); g.fillRect (1, 1, width - 2, height - 2); auto hw = (float) width * 0.5f; auto arrowW = (float) height * 0.3f; auto y1 = (float) height * (isScrollUpArrow ? 0.6f : 0.3f); auto y2 = (float) height * (isScrollUpArrow ? 0.3f : 0.6f); Path p; p.addTriangle (hw - arrowW, y1, hw + arrowW, y1, hw, y2); g.setColour (findColour (PopupMenu::textColourId).withAlpha (0.5f)); g.fillPath (p); } void LookAndFeel_V2::drawPopupMenuUpDownArrowWithOptions (Graphics& g, int width, int height, bool isScrollUpArrow, const PopupMenu::Options&) { drawPopupMenuUpDownArrow (g, width, height, isScrollUpArrow); } void LookAndFeel_V2::drawPopupMenuItem (Graphics& g, const Rectangle& area, const bool isSeparator, const bool isActive, const bool isHighlighted, const bool isTicked, const bool hasSubMenu, const String& text, const String& shortcutKeyText, const Drawable* icon, const Colour* const textColourToUse) { if (isSeparator) { auto r = area.reduced (5, 0); r.removeFromTop (r.getHeight() / 2 - 1); g.setColour (Colour (0x33000000)); g.fillRect (r.removeFromTop (1)); g.setColour (Colour (0x66ffffff)); g.fillRect (r.removeFromTop (1)); } else { auto textColour = findColour (PopupMenu::textColourId); if (textColourToUse != nullptr) textColour = *textColourToUse; auto r = area.reduced (1); if (isHighlighted) { g.setColour (findColour (PopupMenu::highlightedBackgroundColourId)); g.fillRect (r); g.setColour (findColour (PopupMenu::highlightedTextColourId)); } else { g.setColour (textColour); } if (! isActive) g.setOpacity (0.3f); Font font (getPopupMenuFont()); auto maxFontHeight = (float) area.getHeight() / 1.3f; if (font.getHeight() > maxFontHeight) font.setHeight (maxFontHeight); g.setFont (font); auto iconArea = r.removeFromLeft ((r.getHeight() * 5) / 4).reduced (3).toFloat(); if (icon != nullptr) { icon->drawWithin (g, iconArea, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); } else if (isTicked) { auto tick = getTickShape (1.0f); g.fillPath (tick, tick.getTransformToScaleToFit (iconArea, true)); } if (hasSubMenu) { auto arrowH = 0.6f * getPopupMenuFont().getAscent(); auto x = (float) r.removeFromRight ((int) arrowH).getX(); auto halfH = (float) r.getCentreY(); Path p; p.addTriangle (x, halfH - arrowH * 0.5f, x, halfH + arrowH * 0.5f, x + arrowH * 0.6f, halfH); g.fillPath (p); } r.removeFromRight (3); g.drawFittedText (text, r, Justification::centredLeft, 1); if (shortcutKeyText.isNotEmpty()) { Font f2 (font); f2.setHeight (f2.getHeight() * 0.75f); f2.setHorizontalScale (0.95f); g.setFont (f2); g.drawText (shortcutKeyText, r, Justification::centredRight, true); } } } void LookAndFeel_V2::drawPopupMenuItemWithOptions (Graphics& g, const Rectangle& area, bool isHighlighted, const PopupMenu::Item& item, const PopupMenu::Options&) { const auto colour = item.colour != Colour() ? &item.colour : nullptr; const auto hasSubMenu = item.subMenu != nullptr && (item.itemID == 0 || item.subMenu->getNumItems() > 0); drawPopupMenuItem (g, area, item.isSeparator, item.isEnabled, isHighlighted, item.isTicked, hasSubMenu, item.text, item.shortcutKeyDescription, item.image.get(), colour); } void LookAndFeel_V2::drawPopupMenuSectionHeader (Graphics& g, const Rectangle& area, const String& sectionName) { g.setFont (getPopupMenuFont().boldened()); g.setColour (findColour (PopupMenu::headerTextColourId)); g.drawFittedText (sectionName, area.getX() + 12, area.getY(), area.getWidth() - 16, (int) ((float) area.getHeight() * 0.8f), Justification::bottomLeft, 1); } void LookAndFeel_V2::drawPopupMenuSectionHeaderWithOptions (Graphics& g, const Rectangle& area, const String& sectionName, const PopupMenu::Options&) { drawPopupMenuSectionHeader (g, area, sectionName); } //============================================================================== int LookAndFeel_V2::getMenuWindowFlags() { return ComponentPeer::windowHasDropShadow; } void LookAndFeel_V2::drawMenuBarBackground (Graphics& g, int width, int height, bool, MenuBarComponent& menuBar) { auto baseColour = LookAndFeelHelpers::createBaseColour (menuBar.findColour (PopupMenu::backgroundColourId), false, false, false); if (menuBar.isEnabled()) drawShinyButtonShape (g, -4.0f, 0.0f, (float) width + 8.0f, (float) height, 0.0f, baseColour, 0.4f, true, true, true, true); else g.fillAll (baseColour); } Font LookAndFeel_V2::getMenuBarFont (MenuBarComponent& menuBar, int /*itemIndex*/, const String& /*itemText*/) { return Font ((float) menuBar.getHeight() * 0.7f); } int LookAndFeel_V2::getMenuBarItemWidth (MenuBarComponent& menuBar, int itemIndex, const String& itemText) { return getMenuBarFont (menuBar, itemIndex, itemText) .getStringWidth (itemText) + menuBar.getHeight(); } void LookAndFeel_V2::drawMenuBarItem (Graphics& g, int width, int height, int itemIndex, const String& itemText, bool isMouseOverItem, bool isMenuOpen, bool /*isMouseOverBar*/, MenuBarComponent& menuBar) { if (! menuBar.isEnabled()) { g.setColour (menuBar.findColour (PopupMenu::textColourId) .withMultipliedAlpha (0.5f)); } else if (isMenuOpen || isMouseOverItem) { g.fillAll (menuBar.findColour (PopupMenu::highlightedBackgroundColourId)); g.setColour (menuBar.findColour (PopupMenu::highlightedTextColourId)); } else { g.setColour (menuBar.findColour (PopupMenu::textColourId)); } g.setFont (getMenuBarFont (menuBar, itemIndex, itemText)); g.drawFittedText (itemText, 0, 0, width, height, Justification::centred, 1); } Component* LookAndFeel_V2::getParentComponentForMenuOptions (const PopupMenu::Options& options) { return options.getParentComponent(); } void LookAndFeel_V2::preparePopupMenuWindow (Component&) {} bool LookAndFeel_V2::shouldPopupMenuScaleWithTargetComponent (const PopupMenu::Options&) { return true; } int LookAndFeel_V2::getPopupMenuBorderSize() { return 2; } int LookAndFeel_V2::getPopupMenuBorderSizeWithOptions (const PopupMenu::Options&) { return getPopupMenuBorderSize(); } void LookAndFeel_V2::drawPopupMenuColumnSeparatorWithOptions (Graphics&, const Rectangle&, const PopupMenu::Options&) {} int LookAndFeel_V2::getPopupMenuColumnSeparatorWidthWithOptions (const PopupMenu::Options&) { return 0; } //============================================================================== void LookAndFeel_V2::fillTextEditorBackground (Graphics& g, int /*width*/, int /*height*/, TextEditor& textEditor) { g.fillAll (textEditor.findColour (TextEditor::backgroundColourId)); } void LookAndFeel_V2::drawTextEditorOutline (Graphics& g, int width, int height, TextEditor& textEditor) { if (textEditor.isEnabled()) { if (textEditor.hasKeyboardFocus (true) && ! textEditor.isReadOnly()) { const int border = 2; g.setColour (textEditor.findColour (TextEditor::focusedOutlineColourId)); g.drawRect (0, 0, width, height, border); g.setOpacity (1.0f); auto shadowColour = textEditor.findColour (TextEditor::shadowColourId).withMultipliedAlpha (0.75f); drawBevel (g, 0, 0, width, height + 2, border + 2, shadowColour, shadowColour); } else { g.setColour (textEditor.findColour (TextEditor::outlineColourId)); g.drawRect (0, 0, width, height); g.setOpacity (1.0f); auto shadowColour = textEditor.findColour (TextEditor::shadowColourId); drawBevel (g, 0, 0, width, height + 2, 3, shadowColour, shadowColour); } } } CaretComponent* LookAndFeel_V2::createCaretComponent (Component* keyFocusOwner) { return new CaretComponent (keyFocusOwner); } //============================================================================== void LookAndFeel_V2::drawComboBox (Graphics& g, int width, int height, const bool isMouseButtonDown, int buttonX, int buttonY, int buttonW, int buttonH, ComboBox& box) { g.fillAll (box.findColour (ComboBox::backgroundColourId)); if (box.isEnabled() && box.hasKeyboardFocus (false)) { g.setColour (box.findColour (ComboBox::focusedOutlineColourId)); g.drawRect (0, 0, width, height, 2); } else { g.setColour (box.findColour (ComboBox::outlineColourId)); g.drawRect (0, 0, width, height); } auto outlineThickness = box.isEnabled() ? (isMouseButtonDown ? 1.2f : 0.5f) : 0.3f; auto baseColour = LookAndFeelHelpers::createBaseColour (box.findColour (ComboBox::buttonColourId), box.hasKeyboardFocus (true), false, isMouseButtonDown) .withMultipliedAlpha (box.isEnabled() ? 1.0f : 0.5f); drawGlassLozenge (g, (float) buttonX + outlineThickness, (float) buttonY + outlineThickness, (float) buttonW - outlineThickness * 2.0f, (float) buttonH - outlineThickness * 2.0f, baseColour, outlineThickness, -1.0f, true, true, true, true); if (box.isEnabled()) { const float arrowX = 0.3f; const float arrowH = 0.2f; const auto x = (float) buttonX; const auto y = (float) buttonY; const auto w = (float) buttonW; const auto h = (float) buttonH; Path p; p.addTriangle (x + w * 0.5f, y + h * (0.45f - arrowH), x + w * (1.0f - arrowX), y + h * 0.45f, x + w * arrowX, y + h * 0.45f); p.addTriangle (x + w * 0.5f, y + h * (0.55f + arrowH), x + w * (1.0f - arrowX), y + h * 0.55f, x + w * arrowX, y + h * 0.55f); g.setColour (box.findColour (ComboBox::arrowColourId)); g.fillPath (p); } } Font LookAndFeel_V2::getComboBoxFont (ComboBox& box) { return Font (jmin (15.0f, (float) box.getHeight() * 0.85f)); } Label* LookAndFeel_V2::createComboBoxTextBox (ComboBox&) { return new Label (String(), String()); } void LookAndFeel_V2::positionComboBoxText (ComboBox& box, Label& label) { label.setBounds (1, 1, box.getWidth() + 3 - box.getHeight(), box.getHeight() - 2); label.setFont (getComboBoxFont (box)); } PopupMenu::Options LookAndFeel_V2::getOptionsForComboBoxPopupMenu (ComboBox& box, Label& label) { return PopupMenu::Options().withTargetComponent (&box) .withItemThatMustBeVisible (box.getSelectedId()) .withInitiallySelectedItem (box.getSelectedId()) .withMinimumWidth (box.getWidth()) .withMaximumNumColumns (1) .withStandardItemHeight (label.getHeight()); } void LookAndFeel_V2::drawComboBoxTextWhenNothingSelected (Graphics& g, ComboBox& box, Label& label) { g.setColour (findColour (ComboBox::textColourId).withMultipliedAlpha (0.5f)); auto font = label.getLookAndFeel().getLabelFont (label); g.setFont (font); auto textArea = getLabelBorderSize (label).subtractedFrom (label.getLocalBounds()); g.drawFittedText (box.getTextWhenNothingSelected(), textArea, label.getJustificationType(), jmax (1, (int) ((float) textArea.getHeight() / font.getHeight())), label.getMinimumHorizontalScale()); } //============================================================================== Font LookAndFeel_V2::getLabelFont (Label& label) { return label.getFont(); } void LookAndFeel_V2::drawLabel (Graphics& g, Label& label) { g.fillAll (label.findColour (Label::backgroundColourId)); 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) ((float) textArea.getHeight() / font.getHeight())), label.getMinimumHorizontalScale()); g.setColour (label.findColour (Label::outlineColourId).withMultipliedAlpha (alpha)); } else if (label.isEnabled()) { g.setColour (label.findColour (Label::outlineColourId)); } g.drawRect (label.getLocalBounds()); } BorderSize LookAndFeel_V2::getLabelBorderSize (Label& label) { return label.getBorderSize(); } //============================================================================== void LookAndFeel_V2::drawLinearSliderBackground (Graphics& g, int x, int y, int width, int height, float /*sliderPos*/, float /*minSliderPos*/, float /*maxSliderPos*/, const Slider::SliderStyle /*style*/, Slider& slider) { auto sliderRadius = (float) (getSliderThumbRadius (slider) - 2); auto trackColour = slider.findColour (Slider::trackColourId); auto gradCol1 = trackColour.overlaidWith (Colours::black.withAlpha (slider.isEnabled() ? 0.25f : 0.13f)); auto gradCol2 = trackColour.overlaidWith (Colour (0x14000000)); Path indent; if (slider.isHorizontal()) { const float iy = (float) y + (float) height * 0.5f - sliderRadius * 0.5f; const float ih = sliderRadius; g.setGradientFill (ColourGradient::vertical (gradCol1, iy, gradCol2, iy + ih)); indent.addRoundedRectangle ((float) x - sliderRadius * 0.5f, iy, (float) width + sliderRadius, ih, 5.0f); } else { const float ix = (float) x + (float) width * 0.5f - sliderRadius * 0.5f; const float iw = sliderRadius; g.setGradientFill (ColourGradient::horizontal (gradCol1, ix, gradCol2, ix + iw)); indent.addRoundedRectangle (ix, (float) y - sliderRadius * 0.5f, iw, (float) height + sliderRadius, 5.0f); } g.fillPath (indent); g.setColour (Colour (0x4c000000)); g.strokePath (indent, PathStrokeType (0.5f)); } void LookAndFeel_V2::drawLinearSliderThumb (Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { auto sliderRadius = (float) (getSliderThumbRadius (slider) - 2); auto knobColour = LookAndFeelHelpers::createBaseColour (slider.findColour (Slider::thumbColourId), slider.hasKeyboardFocus (false) && slider.isEnabled(), slider.isMouseOverOrDragging() && slider.isEnabled(), slider.isMouseButtonDown() && slider.isEnabled()); const float outlineThickness = slider.isEnabled() ? 0.8f : 0.3f; if (style == Slider::LinearHorizontal || style == Slider::LinearVertical) { float kx, ky; if (style == Slider::LinearVertical) { kx = (float) x + (float) width * 0.5f; ky = sliderPos; } else { kx = sliderPos; ky = (float) y + (float) height * 0.5f; } drawGlassSphere (g, kx - sliderRadius, ky - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } else { if (style == Slider::ThreeValueVertical) { drawGlassSphere (g, (float) x + (float) width * 0.5f - sliderRadius, sliderPos - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } else if (style == Slider::ThreeValueHorizontal) { drawGlassSphere (g,sliderPos - sliderRadius, (float) y + (float) height * 0.5f - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness); } if (style == Slider::TwoValueVertical || style == Slider::ThreeValueVertical) { auto sr = jmin (sliderRadius, (float) width * 0.4f); drawGlassPointer (g, jmax (0.0f, (float) x + (float) width * 0.5f - sliderRadius * 2.0f), minSliderPos - sliderRadius, sliderRadius * 2.0f, knobColour, outlineThickness, 1); drawGlassPointer (g, jmin ((float) x + (float) width - sliderRadius * 2.0f, (float) x + (float) width * 0.5f), maxSliderPos - sr, sliderRadius * 2.0f, knobColour, outlineThickness, 3); } else if (style == Slider::TwoValueHorizontal || style == Slider::ThreeValueHorizontal) { auto sr = jmin (sliderRadius, (float) height * 0.4f); drawGlassPointer (g, minSliderPos - sr, jmax (0.0f, (float) y + (float) height * 0.5f - sliderRadius * 2.0f), sliderRadius * 2.0f, knobColour, outlineThickness, 2); drawGlassPointer (g, maxSliderPos - sliderRadius, jmin ((float) y + (float) height - sliderRadius * 2.0f, (float) y + (float) height * 0.5f), sliderRadius * 2.0f, knobColour, outlineThickness, 4); } } } void LookAndFeel_V2::drawLinearSlider (Graphics& g, int x, int y, int width, int height, float sliderPos, float minSliderPos, float maxSliderPos, const Slider::SliderStyle style, Slider& slider) { g.fillAll (slider.findColour (Slider::backgroundColourId)); if (style == Slider::LinearBar || style == Slider::LinearBarVertical) { const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled(); auto baseColour = LookAndFeelHelpers::createBaseColour (slider.findColour (Slider::thumbColourId) .withMultipliedSaturation (slider.isEnabled() ? 1.0f : 0.5f), false, isMouseOver, isMouseOver || slider.isMouseButtonDown()); drawShinyButtonShape (g, (float) x, style == Slider::LinearBarVertical ? sliderPos : (float) y, style == Slider::LinearBarVertical ? (float) width : (sliderPos - (float) x), style == Slider::LinearBarVertical ? ((float) height - sliderPos) : (float) height, 0.0f, baseColour, slider.isEnabled() ? 0.9f : 0.3f, true, true, true, true); } else { drawLinearSliderBackground (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); } } int LookAndFeel_V2::getSliderThumbRadius (Slider& slider) { return jmin (7, slider.getHeight() / 2, slider.getWidth() / 2) + 2; } void LookAndFeel_V2::drawRotarySlider (Graphics& g, int x, int y, int width, int height, float sliderPos, const float rotaryStartAngle, const float rotaryEndAngle, Slider& slider) { const float radius = jmin ((float) width * 0.5f, (float) height * 0.5f) - 2.0f; const float centreX = (float) x + (float) width * 0.5f; const float centreY = (float) y + (float) height * 0.5f; const float rx = centreX - radius; const float ry = centreY - radius; const float rw = radius * 2.0f; const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle); const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled(); if (radius > 12.0f) { if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f)); else g.setColour (Colour (0x80808080)); const float thickness = 0.7f; { Path filledArc; filledArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, angle, thickness); g.fillPath (filledArc); } { const float innerRadius = radius * 0.2f; Path p; p.addTriangle (-innerRadius, 0.0f, 0.0f, -radius * thickness * 1.1f, innerRadius, 0.0f); p.addEllipse (-innerRadius, -innerRadius, innerRadius * 2.0f, innerRadius * 2.0f); g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); } if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderOutlineColourId)); else g.setColour (Colour (0x80808080)); Path outlineArc; outlineArc.addPieSegment (rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, thickness); outlineArc.closeSubPath(); g.strokePath (outlineArc, PathStrokeType (slider.isEnabled() ? (isMouseOver ? 2.0f : 1.2f) : 0.3f)); } else { if (slider.isEnabled()) g.setColour (slider.findColour (Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f)); else g.setColour (Colour (0x80808080)); Path p; p.addEllipse (-0.4f * rw, -0.4f * rw, rw * 0.8f, rw * 0.8f); PathStrokeType (rw * 0.1f).createStrokedPath (p, p); p.addLineSegment (Line (0.0f, 0.0f, 0.0f, -radius), rw * 0.2f); g.fillPath (p, AffineTransform::rotation (angle).translated (centreX, centreY)); } } Button* LookAndFeel_V2::createSliderButton (Slider&, const bool isIncrement) { return new TextButton (isIncrement ? "+" : "-", String()); } class LookAndFeel_V2::SliderLabelComp : public Label { public: SliderLabelComp() : Label ({}, {}) {} void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override {} std::unique_ptr createAccessibilityHandler() override { return createIgnoredAccessibilityHandler (*this); } }; Label* LookAndFeel_V2::createSliderTextBox (Slider& slider) { auto l = new SliderLabelComp(); l->setJustificationType (Justification::centred); l->setKeyboardType (TextInputTarget::decimalKeyboard); l->setColour (Label::textColourId, slider.findColour (Slider::textBoxTextColourId)); l->setColour (Label::backgroundColourId, (slider.getSliderStyle() == Slider::LinearBar || slider.getSliderStyle() == Slider::LinearBarVertical) ? Colours::transparentBlack : slider.findColour (Slider::textBoxBackgroundColourId)); l->setColour (Label::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); l->setColour (TextEditor::textColourId, slider.findColour (Slider::textBoxTextColourId)); l->setColour (TextEditor::backgroundColourId, slider.findColour (Slider::textBoxBackgroundColourId) .withAlpha ((slider.getSliderStyle() == Slider::LinearBar || slider.getSliderStyle() == Slider::LinearBarVertical) ? 0.7f : 1.0f)); l->setColour (TextEditor::outlineColourId, slider.findColour (Slider::textBoxOutlineColourId)); l->setColour (TextEditor::highlightColourId, slider.findColour (Slider::textBoxHighlightColourId)); return l; } ImageEffectFilter* LookAndFeel_V2::getSliderEffect (Slider&) { return nullptr; } Font LookAndFeel_V2::getSliderPopupFont (Slider&) { return Font (15.0f, Font::bold); } int LookAndFeel_V2::getSliderPopupPlacement (Slider&) { return BubbleComponent::above | BubbleComponent::below | BubbleComponent::left | BubbleComponent::right; } //============================================================================== Slider::SliderLayout LookAndFeel_V2::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); if (textBoxPos == Slider::TextBoxLeft) layout.textBoxBounds.setX (0); else if (textBoxPos == Slider::TextBoxRight) layout.textBoxBounds.setX (localBounds.getWidth() - textBoxWidth); 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 /* 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; } //============================================================================== Rectangle LookAndFeel_V2::getTooltipBounds (const String& tipText, Point screenPos, Rectangle parentArea) { const TextLayout tl (LookAndFeelHelpers::layoutTooltipText (tipText, Colours::black)); auto w = (int) (tl.getWidth() + 14.0f); auto h = (int) (tl.getHeight() + 6.0f); return Rectangle (screenPos.x > parentArea.getCentreX() ? screenPos.x - (w + 12) : screenPos.x + 24, screenPos.y > parentArea.getCentreY() ? screenPos.y - (h + 6) : screenPos.y + 6, w, h) .constrainedWithin (parentArea); } void LookAndFeel_V2::drawTooltip (Graphics& g, const String& text, int width, int height) { g.fillAll (findColour (TooltipWindow::backgroundColourId)); #if ! JUCE_MAC // The mac windows already have a non-optional 1 pix outline, so don't double it here.. g.setColour (findColour (TooltipWindow::outlineColourId)); g.drawRect (0, 0, width, height, 1); #endif LookAndFeelHelpers::layoutTooltipText (text, findColour (TooltipWindow::textColourId)) .draw (g, Rectangle ((float) width, (float) height)); } //============================================================================== Button* LookAndFeel_V2::createFilenameComponentBrowseButton (const String& text) { return new TextButton (text, TRANS("click to browse for a different file")); } void LookAndFeel_V2::layoutFilenameComponent (FilenameComponent& filenameComp, ComboBox* filenameBox, Button* browseButton) { if (browseButton == nullptr || filenameBox == nullptr) return; browseButton->setSize (80, filenameComp.getHeight()); if (auto* tb = dynamic_cast (browseButton)) tb->changeWidthToFitText(); browseButton->setTopRightPosition (filenameComp.getWidth(), 0); filenameBox->setBounds (0, 0, browseButton->getX(), filenameComp.getHeight()); } //============================================================================== void LookAndFeel_V2::drawConcertinaPanelHeader (Graphics& g, const Rectangle& area, bool isMouseOver, bool /*isMouseDown*/, ConcertinaPanel&, Component& panel) { g.fillAll (Colours::grey.withAlpha (isMouseOver ? 0.9f : 0.7f)); g.setColour (Colours::black.withAlpha (0.5f)); g.drawRect (area); g.setColour (Colours::white); g.setFont (Font ((float) area.getHeight() * 0.7f).boldened()); g.drawFittedText (panel.getName(), 4, 0, area.getWidth() - 6, area.getHeight(), Justification::centredLeft, 1); } //============================================================================== void LookAndFeel_V2::drawImageButton (Graphics& g, Image* image, int imageX, int imageY, int imageW, int imageH, const Colour& overlayColour, float imageOpacity, ImageButton& button) { if (! button.isEnabled()) imageOpacity *= 0.3f; AffineTransform t = RectanglePlacement (RectanglePlacement::stretchToFit) .getTransformToFit (image->getBounds().toFloat(), Rectangle (imageX, imageY, imageW, imageH).toFloat()); if (! overlayColour.isOpaque()) { g.setOpacity (imageOpacity); g.drawImageTransformed (*image, t, false); } if (! overlayColour.isTransparent()) { g.setColour (overlayColour); g.drawImageTransformed (*image, t, true); } } //============================================================================== void LookAndFeel_V2::drawCornerResizer (Graphics& g, int w, int h, bool /*isMouseOver*/, bool /*isMouseDragging*/) { auto lineThickness = jmin ((float) w, (float) h) * 0.075f; for (float i = 0.0f; i < 1.0f; i += 0.3f) { g.setColour (Colours::lightgrey); g.drawLine ((float) w * i, (float) h + 1.0f, (float) w + 1.0f, (float) h * i, lineThickness); g.setColour (Colours::darkgrey); g.drawLine ((float) w * i + lineThickness, (float) h + 1.0f, (float) w + 1.0f, (float) h * i + lineThickness, lineThickness); } } void LookAndFeel_V2::drawResizableFrame (Graphics& g, int w, int h, const BorderSize& border) { if (! border.isEmpty()) { const Rectangle fullSize (0, 0, w, h); auto centreArea = border.subtractedFrom (fullSize); Graphics::ScopedSaveState ss (g); g.excludeClipRegion (centreArea); g.setColour (Colour (0x50000000)); g.drawRect (fullSize); g.setColour (Colour (0x19000000)); g.drawRect (centreArea.expanded (1, 1)); } } //============================================================================== void LookAndFeel_V2::fillResizableWindowBackground (Graphics& g, int /*w*/, int /*h*/, const BorderSize& /*border*/, ResizableWindow& window) { g.fillAll (window.getBackgroundColour()); } void LookAndFeel_V2::drawResizableWindowBorder (Graphics&, int /*w*/, int /*h*/, const BorderSize& /*border*/, ResizableWindow&) { } void LookAndFeel_V2::drawDocumentWindowTitleBar (DocumentWindow& window, Graphics& g, int w, int h, int titleSpaceX, int titleSpaceW, const Image* icon, bool drawTitleTextOnLeft) { if (w * h == 0) return; const bool isActive = window.isActiveWindow(); g.setGradientFill (ColourGradient::vertical (window.getBackgroundColour(), 0, window.getBackgroundColour().contrasting (isActive ? 0.15f : 0.05f), (float) h)); g.fillAll(); Font font ((float) h * 0.65f, Font::bold); g.setFont (font); int textW = font.getStringWidth (window.getName()); int iconW = 0; int iconH = 0; if (icon != nullptr) { iconH = (int) font.getHeight(); iconW = icon->getWidth() * iconH / icon->getHeight() + 4; } textW = jmin (titleSpaceW, textW + iconW); int 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 (window.getBackgroundColour().contrasting (isActive ? 0.7f : 0.4f)); g.drawText (window.getName(), textX, 0, textW, h, Justification::centredLeft, true); } //============================================================================== class LookAndFeel_V2::GlassWindowButton : public Button { public: GlassWindowButton (const String& name, Colour col, const Path& normalShape_, const Path& toggledShape_) noexcept : Button (name), colour (col), normalShape (normalShape_), toggledShape (toggledShape_) { } //============================================================================== void paintButton (Graphics& g, bool shouldDrawButtonAsHighlighted, bool shouldDrawButtonAsDown) override { float alpha = shouldDrawButtonAsHighlighted ? (shouldDrawButtonAsDown ? 1.0f : 0.8f) : 0.55f; if (! isEnabled()) alpha *= 0.5f; float x = 0, y = 0, diam; if (getWidth() < getHeight()) { diam = (float) getWidth(); y = (float) (getHeight() - getWidth()) * 0.5f; } else { diam = (float) getHeight(); y = (float) (getWidth() - getHeight()) * 0.5f; } x += diam * 0.05f; y += diam * 0.05f; diam *= 0.9f; g.setGradientFill (ColourGradient (Colour::greyLevel (0.9f).withAlpha (alpha), 0, y + diam, Colour::greyLevel (0.6f).withAlpha (alpha), 0, y, false)); g.fillEllipse (x, y, diam, diam); x += 2.0f; y += 2.0f; diam -= 4.0f; LookAndFeel_V2::drawGlassSphere (g, x, y, diam, colour.withAlpha (alpha), 1.0f); Path& p = getToggleState() ? toggledShape : normalShape; const AffineTransform t (p.getTransformToScaleToFit (x + diam * 0.3f, y + diam * 0.3f, diam * 0.4f, diam * 0.4f, true)); g.setColour (Colours::black.withAlpha (alpha * 0.6f)); g.fillPath (p, t); } private: Colour colour; Path normalShape, toggledShape; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (GlassWindowButton) }; Button* LookAndFeel_V2::createDocumentWindowButton (int buttonType) { Path shape; const float crossThickness = 0.25f; if (buttonType == DocumentWindow::closeButton) { shape.addLineSegment (Line (0.0f, 0.0f, 1.0f, 1.0f), crossThickness * 1.4f); shape.addLineSegment (Line (1.0f, 0.0f, 0.0f, 1.0f), crossThickness * 1.4f); return new GlassWindowButton ("close", Colour (0xffdd1100), shape, shape); } if (buttonType == DocumentWindow::minimiseButton) { shape.addLineSegment (Line (0.0f, 0.5f, 1.0f, 0.5f), crossThickness); return new GlassWindowButton ("minimise", Colour (0xffaa8811), shape, shape); } if (buttonType == DocumentWindow::maximiseButton) { shape.addLineSegment (Line (0.5f, 0.0f, 0.5f, 1.0f), crossThickness); shape.addLineSegment (Line (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 GlassWindowButton ("maximise", Colour (0xff119911), shape, fullscreenShape); } jassertfalse; return nullptr; } void LookAndFeel_V2::positionDocumentWindowButtons (DocumentWindow&, int titleBarX, int titleBarY, int titleBarW, int titleBarH, Button* minimiseButton, Button* maximiseButton, Button* closeButton, bool positionTitleBarButtonsOnLeft) { const int buttonW = titleBarH - titleBarH / 8; int x = positionTitleBarButtonsOnLeft ? titleBarX + 4 : titleBarX + titleBarW - buttonW - buttonW / 4; if (closeButton != nullptr) { closeButton->setBounds (x, titleBarY, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW : -(buttonW + buttonW / 4); } if (positionTitleBarButtonsOnLeft) std::swap (minimiseButton, maximiseButton); if (maximiseButton != nullptr) { maximiseButton->setBounds (x, titleBarY, buttonW, titleBarH); x += positionTitleBarButtonsOnLeft ? buttonW : -buttonW; } if (minimiseButton != nullptr) minimiseButton->setBounds (x, titleBarY, buttonW, titleBarH); } int LookAndFeel_V2::getDefaultMenuBarHeight() { return 24; } //============================================================================== std::unique_ptr LookAndFeel_V2::createDropShadowerForComponent (Component&) { return std::make_unique (DropShadow (Colours::black.withAlpha (0.4f), 10, Point (0, 2))); } std::unique_ptr LookAndFeel_V2::createFocusOutlineForComponent (Component&) { struct WindowProperties : public FocusOutline::OutlineWindowProperties { Rectangle getOutlineBounds (Component& c) override { return c.getScreenBounds(); } void drawOutline (Graphics& g, int width, int height) override { g.setColour (Colours::yellow.withAlpha (0.6f)); g.drawRoundedRectangle ({ (float) width, (float) height }, 3.0f, 3.0f); } }; return std::make_unique (std::make_unique()); } //============================================================================== void LookAndFeel_V2::drawStretchableLayoutResizerBar (Graphics& g, int w, int h, bool /*isVerticalBar*/, bool isMouseOver, bool isMouseDragging) { auto alpha = 0.5f; if (isMouseOver || isMouseDragging) { g.fillAll (Colour (0x190000ff)); alpha = 1.0f; } auto cx = (float) w * 0.5f; auto cy = (float) h * 0.5f; auto cr = (float) jmin (w, h) * 0.4f; g.setGradientFill (ColourGradient (Colours::white.withAlpha (alpha), cx + cr * 0.1f, cy + cr, Colours::black.withAlpha (alpha), cx, cy - cr * 4.0f, true)); g.fillEllipse (cx - cr, cy - cr, cr * 2.0f, cr * 2.0f); } //============================================================================== void LookAndFeel_V2::drawGroupComponentOutline (Graphics& g, int width, int height, const String& text, const Justification& position, GroupComponent& group) { const float textH = 15.0f; const float indent = 3.0f; const float textEdgeGap = 4.0f; auto cs = 5.0f; Font f (textH); Path p; auto x = indent; auto y = f.getAscent() - 3.0f; auto w = jmax (0.0f, (float) width - x * 2.0f); auto h = jmax (0.0f, (float) height - y - indent); cs = jmin (cs, w * 0.5f, h * 0.5f); auto cs2 = 2.0f * cs; auto textW = text.isEmpty() ? 0 : jlimit (0.0f, jmax (0.0f, w - cs2 - textEdgeGap * 2), (float) f.getStringWidth (text) + textEdgeGap * 2.0f); auto textX = cs + textEdgeGap; if (position.testFlags (Justification::horizontallyCentred)) textX = cs + (w - cs2 - textW) * 0.5f; else if (position.testFlags (Justification::right)) textX = w - cs - textW - textEdgeGap; p.startNewSubPath (x + textX + textW, y); p.lineTo (x + w - cs, y); p.addArc (x + w - cs2, y, cs2, cs2, 0, MathConstants::halfPi); p.lineTo (x + w, y + h - cs); p.addArc (x + w - cs2, y + h - cs2, cs2, cs2, MathConstants::halfPi, MathConstants::pi); p.lineTo (x + cs, y + h); p.addArc (x, y + h - cs2, cs2, cs2, MathConstants::pi, MathConstants::pi * 1.5f); p.lineTo (x, y + cs); p.addArc (x, y, cs2, cs2, MathConstants::pi * 1.5f, MathConstants::twoPi); p.lineTo (x + textX, y); auto alpha = group.isEnabled() ? 1.0f : 0.5f; g.setColour (group.findColour (GroupComponent::outlineColourId) .withMultipliedAlpha (alpha)); g.strokePath (p, PathStrokeType (2.0f)); g.setColour (group.findColour (GroupComponent::textColourId) .withMultipliedAlpha (alpha)); g.setFont (f); g.drawText (text, roundToInt (x + textX), 0, roundToInt (textW), roundToInt (textH), Justification::centred, true); } //============================================================================== int LookAndFeel_V2::getTabButtonOverlap (int tabDepth) { return 1 + tabDepth / 3; } int LookAndFeel_V2::getTabButtonSpaceAroundImage() { return 4; } int LookAndFeel_V2::getTabButtonBestWidth (TabBarButton& button, int tabDepth) { int width = Font ((float) tabDepth * 0.6f).getStringWidth (button.getButtonText().trim()) + getTabButtonOverlap (tabDepth) * 2; if (auto* extraComponent = button.getExtraComponent()) width += button.getTabbedButtonBar().isVertical() ? extraComponent->getHeight() : extraComponent->getWidth(); return jlimit (tabDepth * 2, tabDepth * 8, width); } Rectangle LookAndFeel_V2::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 { 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; } } return extraComp; } void LookAndFeel_V2::createTabButtonShape (TabBarButton& button, Path& p, bool /*isMouseOver*/, bool /*isMouseDown*/) { auto activeArea = button.getActiveArea(); auto w = (float) activeArea.getWidth(); auto h = (float) activeArea.getHeight(); auto length = w; auto depth = h; if (button.getTabbedButtonBar().isVertical()) std::swap (length, depth); const float indent = (float) getTabButtonOverlap ((int) depth); const float overhang = 4.0f; switch (button.getTabbedButtonBar().getOrientation()) { case TabbedButtonBar::TabsAtLeft: p.startNewSubPath (w, 0.0f); p.lineTo (0.0f, indent); p.lineTo (0.0f, h - indent); p.lineTo (w, h); p.lineTo (w + overhang, h + overhang); p.lineTo (w + overhang, -overhang); break; case TabbedButtonBar::TabsAtRight: p.startNewSubPath (0.0f, 0.0f); p.lineTo (w, indent); p.lineTo (w, h - indent); p.lineTo (0.0f, h); p.lineTo (-overhang, h + overhang); p.lineTo (-overhang, -overhang); break; case TabbedButtonBar::TabsAtBottom: p.startNewSubPath (0.0f, 0.0f); p.lineTo (indent, h); p.lineTo (w - indent, h); p.lineTo (w, 0.0f); p.lineTo (w + overhang, -overhang); p.lineTo (-overhang, -overhang); break; case TabbedButtonBar::TabsAtTop: default: p.startNewSubPath (0.0f, h); p.lineTo (indent, 0.0f); p.lineTo (w - indent, 0.0f); p.lineTo (w, h); p.lineTo (w + overhang, h + overhang); p.lineTo (-overhang, h + overhang); break; } p.closeSubPath(); p = p.createPathWithRoundedCorners (3.0f); } void LookAndFeel_V2::fillTabButtonShape (TabBarButton& button, Graphics& g, const Path& path, bool /*isMouseOver*/, bool /*isMouseDown*/) { auto tabBackground = button.getTabBackgroundColour(); const bool isFrontTab = button.isFrontTab(); g.setColour (isFrontTab ? tabBackground : tabBackground.withMultipliedAlpha (0.9f)); g.fillPath (path); g.setColour (button.findColour (isFrontTab ? TabbedButtonBar::frontOutlineColourId : TabbedButtonBar::tabOutlineColourId, false) .withMultipliedAlpha (button.isEnabled() ? 1.0f : 0.5f)); g.strokePath (path, PathStrokeType (isFrontTab ? 1.0f : 0.5f)); } Font LookAndFeel_V2::getTabButtonFont (TabBarButton&, float height) { return { height * 0.6f }; } void LookAndFeel_V2::drawTabButtonText (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) { auto area = button.getTextArea().toFloat(); auto length = area.getWidth(); auto depth = area.getHeight(); if (button.getTabbedButtonBar().isVertical()) std::swap (length, depth); Font font (getTabButtonFont (button, depth)); font.setUnderline (button.hasKeyboardFocus (false)); AffineTransform t; switch (button.getTabbedButtonBar().getOrientation()) { case TabbedButtonBar::TabsAtLeft: t = t.rotated (MathConstants::pi * -0.5f).translated (area.getX(), area.getBottom()); break; case TabbedButtonBar::TabsAtRight: t = t.rotated (MathConstants::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(); auto 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)); } void LookAndFeel_V2::drawTabButton (TabBarButton& button, Graphics& g, bool isMouseOver, bool isMouseDown) { Path tabShape; createTabButtonShape (button, tabShape, isMouseOver, isMouseDown); auto activeArea = button.getActiveArea(); tabShape.applyTransform (AffineTransform::translation ((float) activeArea.getX(), (float) activeArea.getY())); DropShadow (Colours::black.withAlpha (0.5f), 2, Point (0, 1)).drawForPath (g, tabShape); fillTabButtonShape (button, g, tabShape, isMouseOver, isMouseDown); drawTabButtonText (button, g, isMouseOver, isMouseDown); } void LookAndFeel_V2::drawTabbedButtonBarBackground (TabbedButtonBar&, Graphics&) {} void LookAndFeel_V2::drawTabAreaBehindFrontButton (TabbedButtonBar& bar, Graphics& g, const int w, const int h) { auto shadowSize = 0.2f; Rectangle shadowRect, line; ColourGradient gradient (Colours::black.withAlpha (bar.isEnabled() ? 0.25f : 0.15f), 0, 0, Colours::transparentBlack, 0, 0, false); switch (bar.getOrientation()) { case TabbedButtonBar::TabsAtLeft: gradient.point1.x = (float) w; gradient.point2.x = (float) 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 = (float) 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 = (float) 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 = (float) 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 (Colour (0x80000000)); g.fillRect (line); } Button* LookAndFeel_V2::createTabBarExtrasButton() { auto thickness = 7.0f; auto indent = 22.0f; Path p; p.addEllipse (-10.0f, -10.0f, 120.0f, 120.0f); DrawablePath ellipse; ellipse.setPath (p); ellipse.setFill (Colour (0x99ffffff)); p.clear(); p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f); p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f); p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness); p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness); p.setUsingNonZeroWinding (false); DrawablePath dp; dp.setPath (p); dp.setFill (Colour (0x59000000)); DrawableComposite normalImage; normalImage.addAndMakeVisible (ellipse.createCopy().release()); normalImage.addAndMakeVisible (dp.createCopy().release()); dp.setFill (Colour (0xcc000000)); DrawableComposite overImage; overImage.addAndMakeVisible (ellipse.createCopy().release()); overImage.addAndMakeVisible (dp.createCopy().release()); auto db = new DrawableButton (TRANS ("Additional Items"), DrawableButton::ImageFitted); db->setImages (&normalImage, &overImage, nullptr); return db; } //============================================================================== void LookAndFeel_V2::drawTableHeaderBackground (Graphics& g, TableHeaderComponent& header) { g.fillAll (Colours::white); auto area = header.getLocalBounds(); area.removeFromTop (area.getHeight() / 2); auto backgroundColour = header.findColour (TableHeaderComponent::backgroundColourId); g.setGradientFill (ColourGradient (backgroundColour, 0.0f, (float) area.getY(), backgroundColour.withMultipliedSaturation (.5f), 0.0f, (float) area.getBottom(), false)); g.fillRect (area); g.setColour (header.findColour (TableHeaderComponent::outlineColourId)); g.fillRect (area.removeFromBottom (1)); for (int i = header.getNumColumns (true); --i >= 0;) g.fillRect (header.getColumnPosition (i).removeFromRight (1)); } void LookAndFeel_V2::drawTableHeaderColumn (Graphics& g, TableHeaderComponent& header, const String& columnName, int /*columnId*/, int width, int height, bool isMouseOver, bool isMouseDown, int columnFlags) { auto highlightColour = header.findColour (TableHeaderComponent::highlightColourId); if (isMouseDown) g.fillAll (highlightColour); else if (isMouseOver) g.fillAll (highlightColour.withMultipliedAlpha (0.625f)); Rectangle area (width, height); area.reduce (4, 0); if ((columnFlags & (TableHeaderComponent::sortedForwards | TableHeaderComponent::sortedBackwards)) != 0) { Path sortArrow; sortArrow.addTriangle (0.0f, 0.0f, 0.5f, (columnFlags & TableHeaderComponent::sortedForwards) != 0 ? -0.8f : 0.8f, 1.0f, 0.0f); g.setColour (Colour (0x99000000)); g.fillPath (sortArrow, sortArrow.getTransformToScaleToFit (area.removeFromRight (height / 2).reduced (2).toFloat(), true)); } g.setColour (header.findColour (TableHeaderComponent::textColourId)); g.setFont (Font ((float) height * 0.5f, Font::bold)); g.drawFittedText (columnName, area, Justification::centredLeft, 1); } //============================================================================== void LookAndFeel_V2::drawLasso (Graphics& g, Component& lassoComp) { const int outlineThickness = 1; g.fillAll (lassoComp.findColour (0x1000440 /*lassoFillColourId*/)); g.setColour (lassoComp.findColour (0x1000441 /*lassoOutlineColourId*/)); g.drawRect (lassoComp.getLocalBounds(), outlineThickness); } //============================================================================== void LookAndFeel_V2::paintToolbarBackground (Graphics& g, int w, int h, Toolbar& toolbar) { auto background = toolbar.findColour (Toolbar::backgroundColourId); g.setGradientFill (ColourGradient (background, 0.0f, 0.0f, background.darker (0.1f), toolbar.isVertical() ? (float) w - 1.0f : 0.0f, toolbar.isVertical() ? 0.0f : (float) h - 1.0f, false)); g.fillAll(); } Button* LookAndFeel_V2::createToolbarMissingItemsButton (Toolbar& /*toolbar*/) { return createTabBarExtrasButton(); } void LookAndFeel_V2::paintToolbarButtonBackground (Graphics& g, int /*width*/, int /*height*/, bool isMouseOver, bool isMouseDown, ToolbarItemComponent& component) { if (isMouseDown) g.fillAll (component.findColour (Toolbar::buttonMouseDownBackgroundColourId, true)); else if (isMouseOver) g.fillAll (component.findColour (Toolbar::buttonMouseOverBackgroundColourId, true)); } void LookAndFeel_V2::paintToolbarButtonLabel (Graphics& g, int x, int y, int width, int height, const String& text, ToolbarItemComponent& component) { g.setColour (component.findColour (Toolbar::labelTextColourId, true) .withAlpha (component.isEnabled() ? 1.0f : 0.25f)); auto fontHeight = jmin (14.0f, (float) height * 0.85f); g.setFont (fontHeight); g.drawFittedText (text, x, y, width, height, Justification::centred, jmax (1, height / (int) fontHeight)); } //============================================================================== void LookAndFeel_V2::drawPropertyPanelSectionHeader (Graphics& g, const String& name, bool isOpen, int width, int height) { auto buttonSize = (float) height * 0.75f; auto buttonIndent = ((float) height - buttonSize) * 0.5f; drawTreeviewPlusMinusBox (g, Rectangle (buttonIndent, buttonIndent, buttonSize, buttonSize), Colours::white, isOpen, false); auto textX = (int) (buttonIndent * 2.0f + buttonSize + 2.0f); g.setColour (Colours::black); g.setFont (Font ((float) height * 0.7f, Font::bold)); g.drawText (name, textX, 0, width - textX - 4, height, Justification::centredLeft, true); } void LookAndFeel_V2::drawPropertyComponentBackground (Graphics& g, int width, int height, PropertyComponent& component) { g.setColour (component.findColour (PropertyComponent::backgroundColourId)); g.fillRect (0, 0, width, height - 1); } void LookAndFeel_V2::drawPropertyComponentLabel (Graphics& g, int, int height, PropertyComponent& component) { g.setColour (component.findColour (PropertyComponent::labelTextColourId) .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.6f)); g.setFont ((float) jmin (height, 24) * 0.65f); auto r = getPropertyComponentContentPosition (component); g.drawFittedText (component.getName(), 3, r.getY(), r.getX() - 5, r.getHeight(), Justification::centredLeft, 2); } Rectangle LookAndFeel_V2::getPropertyComponentContentPosition (PropertyComponent& component) { const int textW = jmin (200, component.getWidth() / 3); return Rectangle (textW, 1, component.getWidth() - textW - 1, component.getHeight() - 3); } int LookAndFeel_V2::getPropertyPanelSectionHeaderHeight (const String& sectionTitle) { return sectionTitle.isEmpty() ? 0 : 22; } //============================================================================== void LookAndFeel_V2::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 (Colour::greyLevel (0.23f).withAlpha (0.9f)); g.fillPath (path); g.setColour (Colours::white.withAlpha (0.8f)); g.strokePath (path, PathStrokeType (2.0f)); } int LookAndFeel_V2::getCallOutBoxBorderSize (const CallOutBox&) { return 20; } float LookAndFeel_V2::getCallOutBoxCornerSize (const CallOutBox&) { return 9.0f; } //============================================================================== AttributedString LookAndFeel_V2::createFileChooserHeaderText (const String& title, const String& instructions) { AttributedString s; s.setJustification (Justification::centred); auto colour = findColour (FileChooserDialogBox::titleTextColourId); s.append (title + "\n\n", Font (17.0f, Font::bold), colour); s.append (instructions, Font (14.0f), colour); return s; } void LookAndFeel_V2::drawFileBrowserRow (Graphics& g, int width, int height, const File&, const String& filename, Image* icon, const String& fileSizeDescription, const String& fileTimeDescription, bool isDirectory, bool isItemSelected, int /*itemIndex*/, DirectoryContentsDisplayComponent& dcc) { auto fileListComp = dynamic_cast (&dcc); if (isItemSelected) g.fillAll (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::highlightColourId) : findColour (DirectoryContentsDisplayComponent::highlightColourId)); const int x = 32; g.setColour (Colours::black); if (icon != nullptr && icon->isValid()) { g.drawImageWithin (*icon, 2, 2, x - 4, height - 4, RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, false); } else { if (auto* d = isDirectory ? getDefaultFolderImage() : getDefaultDocumentFileImage()) d->drawWithin (g, Rectangle (2.0f, 2.0f, x - 4.0f, (float) height - 4.0f), RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize, 1.0f); } if (isItemSelected) g.setColour (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::highlightedTextColourId) : findColour (DirectoryContentsDisplayComponent::highlightedTextColourId)); else g.setColour (fileListComp != nullptr ? fileListComp->findColour (DirectoryContentsDisplayComponent::textColourId) : findColour (DirectoryContentsDisplayComponent::textColourId)); g.setFont ((float) height * 0.7f); if (width > 450 && ! isDirectory) { auto sizeX = roundToInt ((float) width * 0.7f); auto dateX = roundToInt ((float) width * 0.8f); g.drawFittedText (filename, x, 0, sizeX - x, height, Justification::centredLeft, 1); g.setFont ((float) 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* LookAndFeel_V2::createFileBrowserGoUpButton() { auto goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground); Path arrowPath; arrowPath.addArrow ({ 50.0f, 100.0f, 50.0f, 0.0f }, 40.0f, 100.0f, 50.0f); DrawablePath arrowImage; arrowImage.setFill (Colours::black.withAlpha (0.4f)); arrowImage.setPath (arrowPath); goUpButton->setImages (&arrowImage); return goUpButton; } void LookAndFeel_V2::layoutFileBrowserComponent (FileBrowserComponent& browserComp, DirectoryContentsDisplayComponent* fileListComponent, FilePreviewComponent* previewComp, ComboBox* currentPathBox, TextEditor* filenameBox, Button* goUpButton) { const int x = 8; auto w = browserComp.getWidth() - x - x; if (previewComp != nullptr) { auto previewWidth = w / 3; previewComp->setBounds (x + w - previewWidth, 0, previewWidth, browserComp.getHeight()); w -= previewWidth + 4; } int y = 4; const int controlsHeight = 22; const int upButtonWidth = 50; auto bottomSectionHeight = controlsHeight + 8; currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight); goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight); y += controlsHeight + 4; if (auto 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); } //============================================================================== static std::unique_ptr createDrawableFromSVG (const char* data) { auto xml = parseXML (data); jassert (xml != nullptr); return Drawable::createFromSVG (*xml); } const Drawable* LookAndFeel_V2::getDefaultFolderImage() { if (folderImage == nullptr) folderImage = createDrawableFromSVG (R"svgdata( )svgdata"); return folderImage.get(); } const Drawable* LookAndFeel_V2::getDefaultDocumentFileImage() { if (documentImage == nullptr) documentImage = createDrawableFromSVG (R"svgdata( )svgdata"); return documentImage.get(); } //============================================================================== static Path createPathFromData (float height, const unsigned char* data, size_t size) { Path p; p.loadPathFromData (data, size); p.scaleToFit (0, 0, height * 2.0f, height, true); return p; } Path LookAndFeel_V2::getTickShape (float height) { static const unsigned char data[] = { 109,0,224,168,68,0,0,119,67,108,0,224,172,68,0,128,146,67,113,0,192,148,68,0,0,219,67,0,96,110,68,0,224,56,68,113,0,64,51,68,0,32,130,68,0,64,20,68,0,224, 162,68,108,0,128,3,68,0,128,168,68,113,0,128,221,67,0,192,175,68,0,0,207,67,0,32,179,68,113,0,0,201,67,0,224,173,68,0,0,181,67,0,224,161,68,108,0,128,168,67, 0,128,154,68,113,0,128,141,67,0,192,138,68,0,128,108,67,0,64,131,68,113,0,0,62,67,0,128,119,68,0,0,5,67,0,128,114,68,113,0,0,102,67,0,192,88,68,0,128,155, 67,0,192,88,68,113,0,0,190,67,0,192,88,68,0,128,232,67,0,224,131,68,108,0,128,246,67,0,192,139,68,113,0,64,33,68,0,128,87,68,0,0,93,68,0,224,26,68,113,0, 96,140,68,0,128,188,67,0,224,168,68,0,0,119,67,99,101 }; return createPathFromData (height, data, sizeof (data)); } Path LookAndFeel_V2::getCrossShape (float height) { static const unsigned char data[] = { 109,0,0,17,68,0,96,145,68,108,0,192,13,68,0,192,147,68,113,0,0,213,67,0,64,174,68,0,0,168,67,0,64,174,68,113,0,0,104,67,0,64,174,68,0,0,5,67,0,64, 153,68,113,0,0,18,67,0,64,153,68,0,0,24,67,0,64,153,68,113,0,0,135,67,0,64,153,68,0,128,207,67,0,224,130,68,108,0,0,220,67,0,0,126,68,108,0,0,204,67, 0,128,117,68,113,0,0,138,67,0,64,82,68,0,0,138,67,0,192,57,68,113,0,0,138,67,0,192,37,68,0,128,210,67,0,64,10,68,113,0,128,220,67,0,64,45,68,0,0,8, 68,0,128,78,68,108,0,192,14,68,0,0,87,68,108,0,64,20,68,0,0,80,68,113,0,192,57,68,0,0,32,68,0,128,88,68,0,0,32,68,113,0,64,112,68,0,0,32,68,0, 128,124,68,0,64,68,68,113,0,0,121,68,0,192,67,68,0,128,119,68,0,192,67,68,113,0,192,108,68,0,192,67,68,0,32,89,68,0,96,82,68,113,0,128,69,68,0,0,97,68, 0,0,56,68,0,64,115,68,108,0,64,49,68,0,128,124,68,108,0,192,55,68,0,96,129,68,113,0,0,92,68,0,224,146,68,0,192,129,68,0,224,146,68,113,0,64,110,68,0,64, 168,68,0,64,87,68,0,64,168,68,113,0,128,66,68,0,64,168,68,0,64,27,68,0,32,150,68,99,101 }; return createPathFromData (height, data, sizeof (data)); } //============================================================================== void LookAndFeel_V2::drawLevelMeter (Graphics& g, int width, int height, float level) { g.setColour (Colours::white.withAlpha (0.7f)); g.fillRoundedRectangle (0.0f, 0.0f, (float) width, (float) height, 3.0f); g.setColour (Colours::black.withAlpha (0.2f)); g.drawRoundedRectangle (1.0f, 1.0f, (float) width - 2.0f, (float) height - 2.0f, 3.0f, 1.0f); const int totalBlocks = 7; const int numBlocks = roundToInt (totalBlocks * level); auto w = ((float) width - 6.0f) / (float) totalBlocks; for (int i = 0; i < totalBlocks; ++i) { if (i >= numBlocks) g.setColour (Colours::lightblue.withAlpha (0.6f)); else g.setColour (i < totalBlocks - 1 ? Colours::blue.withAlpha (0.5f) : Colours::red); g.fillRoundedRectangle (3.0f + (float) i * w + w * 0.1f, 3.0f, (float) w * 0.8f, (float) height - 6.0f, (float) w * 0.4f); } } //============================================================================== void LookAndFeel_V2::drawKeymapChangeButton (Graphics& g, int width, int height, Button& button, const String& keyDescription) { auto textColour = button.findColour (0x100ad01 /*KeyMappingEditorComponent::textColourId*/, true); if (keyDescription.isNotEmpty()) { if (button.isEnabled()) { auto alpha = button.isDown() ? 0.3f : (button.isOver() ? 0.15f : 0.08f); g.fillAll (textColour.withAlpha (alpha)); g.setOpacity (0.3f); drawBevel (g, 0, 0, width, height, 2); } g.setColour (textColour); g.setFont ((float) height * 0.6f); g.drawFittedText (keyDescription, 3, 0, width - 6, height, Justification::centred, 1); } else { const float thickness = 7.0f; const float indent = 22.0f; Path p; p.addEllipse (0.0f, 0.0f, 100.0f, 100.0f); p.addRectangle (indent, 50.0f - thickness, 100.0f - indent * 2.0f, thickness * 2.0f); p.addRectangle (50.0f - thickness, indent, thickness * 2.0f, 50.0f - indent - thickness); p.addRectangle (50.0f - thickness, 50.0f + thickness, thickness * 2.0f, 50.0f - indent - thickness); p.setUsingNonZeroWinding (false); g.setColour (textColour.withAlpha (button.isDown() ? 0.7f : (button.isOver() ? 0.5f : 0.3f))); g.fillPath (p, p.getTransformToScaleToFit (2.0f, 2.0f, (float) width - 4.0f, (float) height - 4.0f, true)); } if (button.hasKeyboardFocus (false)) { g.setColour (textColour.withAlpha (0.4f)); g.drawRect (0, 0, width, height); } } //============================================================================== Font LookAndFeel_V2::getSidePanelTitleFont (SidePanel&) { return Font (18.0f); } Justification LookAndFeel_V2::getSidePanelTitleJustification (SidePanel& panel) { return panel.isPanelOnLeft() ? Justification::centredRight : Justification::centredLeft; } Path LookAndFeel_V2::getSidePanelDismissButtonShape (SidePanel& panel) { return getCrossShape ((float) panel.getTitleBarHeight()); } //============================================================================== void LookAndFeel_V2::drawBevel (Graphics& g, const int x, const int y, const int width, const int height, const int bevelThickness, const Colour& topLeftColour, const Colour& bottomRightColour, const bool useGradient, const bool sharpEdgeOnOutside) { if (g.clipRegionIntersects (Rectangle (x, y, width, height))) { auto& context = g.getInternalContext(); Graphics::ScopedSaveState ss (g); for (int i = bevelThickness; --i >= 0;) { const float op = useGradient ? (float) (sharpEdgeOnOutside ? bevelThickness - i : i) / (float) bevelThickness : 1.0f; context.setFill (topLeftColour.withMultipliedAlpha (op)); context.fillRect (Rectangle (x + i, y + i, width - i * 2, 1), false); context.setFill (topLeftColour.withMultipliedAlpha (op * 0.75f)); context.fillRect (Rectangle (x + i, y + i + 1, 1, height - i * 2 - 2), false); context.setFill (bottomRightColour.withMultipliedAlpha (op)); context.fillRect (Rectangle (x + i, y + height - i - 1, width - i * 2, 1), false); context.setFill (bottomRightColour.withMultipliedAlpha (op * 0.75f)); context.fillRect (Rectangle (x + width - i - 1, y + i + 1, 1, height - i * 2 - 2), false); } } } //============================================================================== void LookAndFeel_V2::drawShinyButtonShape (Graphics& g, float x, float y, float w, float h, float maxCornerSize, const Colour& baseColour, float strokeWidth, bool flatOnLeft, bool flatOnRight, bool flatOnTop, bool flatOnBottom) noexcept { if (w <= strokeWidth * 1.1f || h <= strokeWidth * 1.1f) return; auto cs = jmin (maxCornerSize, w * 0.5f, h * 0.5f); Path outline; outline.addRoundedRectangle (x, y, w, h, cs, cs, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); ColourGradient cg (baseColour, 0.0f, y, baseColour.overlaidWith (Colour (0x070000ff)), 0.0f, y + h, false); cg.addColour (0.5, baseColour.overlaidWith (Colour (0x33ffffff))); cg.addColour (0.51, baseColour.overlaidWith (Colour (0x110000ff))); g.setGradientFill (cg); g.fillPath (outline); g.setColour (Colour (0x80000000)); g.strokePath (outline, PathStrokeType (strokeWidth)); } //============================================================================== void LookAndFeel_V2::drawGlassSphere (Graphics& g, const float x, const float y, const float diameter, const Colour& colour, const float outlineThickness) noexcept { if (diameter <= outlineThickness) return; Path p; p.addEllipse (x, y, diameter, diameter); { ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y, Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false); cg.addColour (0.4, Colours::white.overlaidWith (colour)); g.setGradientFill (cg); g.fillPath (p); } g.setGradientFill (ColourGradient (Colours::white, 0, y + diameter * 0.06f, Colours::transparentWhite, 0, y + diameter * 0.3f, false)); g.fillEllipse (x + diameter * 0.2f, y + diameter * 0.05f, diameter * 0.6f, diameter * 0.4f); ColourGradient cg (Colours::transparentBlack, x + diameter * 0.5f, y + diameter * 0.5f, Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()), x, y + diameter * 0.5f, true); cg.addColour (0.7, Colours::transparentBlack); cg.addColour (0.8, Colours::black.withAlpha (0.1f * outlineThickness)); g.setGradientFill (cg); g.fillPath (p); g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha())); g.drawEllipse (x, y, diameter, diameter, outlineThickness); } //============================================================================== void LookAndFeel_V2::drawGlassPointer (Graphics& g, const float x, const float y, const float diameter, const Colour& colour, const float outlineThickness, const int direction) noexcept { if (diameter <= outlineThickness) return; Path p; p.startNewSubPath (x + diameter * 0.5f, y); p.lineTo (x + diameter, y + diameter * 0.6f); p.lineTo (x + diameter, y + diameter); p.lineTo (x, y + diameter); p.lineTo (x, y + diameter * 0.6f); p.closeSubPath(); p.applyTransform (AffineTransform::rotation ((float) direction * MathConstants::halfPi, x + diameter * 0.5f, y + diameter * 0.5f)); { ColourGradient cg (Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y, Colours::white.overlaidWith (colour.withMultipliedAlpha (0.3f)), 0, y + diameter, false); cg.addColour (0.4, Colours::white.overlaidWith (colour)); g.setGradientFill (cg); g.fillPath (p); } ColourGradient cg (Colours::transparentBlack, x + diameter * 0.5f, y + diameter * 0.5f, Colours::black.withAlpha (0.5f * outlineThickness * colour.getFloatAlpha()), x - diameter * 0.2f, y + diameter * 0.5f, true); cg.addColour (0.5, Colours::transparentBlack); cg.addColour (0.7, Colours::black.withAlpha (0.07f * outlineThickness)); g.setGradientFill (cg); g.fillPath (p); g.setColour (Colours::black.withAlpha (0.5f * colour.getFloatAlpha())); g.strokePath (p, PathStrokeType (outlineThickness)); } //============================================================================== void LookAndFeel_V2::drawGlassLozenge (Graphics& g, float x, float y, float width, float height, const Colour& colour, float outlineThickness, float cornerSize, bool flatOnLeft, bool flatOnRight, bool flatOnTop, bool flatOnBottom) noexcept { if (width <= outlineThickness || height <= outlineThickness) return; auto intX = (int) x; auto intY = (int) y; auto intW = (int) width; auto intH = (int) height; auto cs = cornerSize < 0 ? jmin (width * 0.5f, height * 0.5f) : cornerSize; auto edgeBlurRadius = height * 0.75f + (height - cs * 2.0f); auto intEdge = (int) edgeBlurRadius; Path outline; outline.addRoundedRectangle (x, y, width, height, cs, cs, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); { ColourGradient cg (colour.darker (0.2f), 0, y, colour.darker (0.2f), 0, y + height, false); cg.addColour (0.03, colour.withMultipliedAlpha (0.3f)); cg.addColour (0.4, colour); cg.addColour (0.97, colour.withMultipliedAlpha (0.3f)); g.setGradientFill (cg); g.fillPath (outline); } ColourGradient cg (Colours::transparentBlack, x + edgeBlurRadius, y + height * 0.5f, colour.darker (0.2f), x, y + height * 0.5f, true); cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.5f) / edgeBlurRadius), Colours::transparentBlack); cg.addColour (jlimit (0.0, 1.0, 1.0 - (cs * 0.25f) / edgeBlurRadius), colour.darker (0.2f).withMultipliedAlpha (0.3f)); if (! (flatOnLeft || flatOnTop || flatOnBottom)) { Graphics::ScopedSaveState ss (g); g.setGradientFill (cg); g.reduceClipRegion (intX, intY, intEdge, intH); g.fillPath (outline); } if (! (flatOnRight || flatOnTop || flatOnBottom)) { cg.point1.setX (x + width - edgeBlurRadius); cg.point2.setX (x + width); Graphics::ScopedSaveState ss (g); g.setGradientFill (cg); g.reduceClipRegion (intX + intW - intEdge, intY, 2 + intEdge, intH); g.fillPath (outline); } { auto leftIndent = (flatOnTop || flatOnLeft) ? 0.0f : cs * 0.4f; auto rightIndent = (flatOnTop || flatOnRight) ? 0.0f : cs * 0.4f; Path highlight; highlight.addRoundedRectangle (x + leftIndent, y + cs * 0.1f, width - (leftIndent + rightIndent), height * 0.4f, cs * 0.4f, cs * 0.4f, ! (flatOnLeft || flatOnTop), ! (flatOnRight || flatOnTop), ! (flatOnLeft || flatOnBottom), ! (flatOnRight || flatOnBottom)); g.setGradientFill (ColourGradient (colour.brighter (10.0f), 0, y + height * 0.06f, Colours::transparentWhite, 0, y + height * 0.4f, false)); g.fillPath (highlight); } g.setColour (colour.darker().withMultipliedAlpha (1.5f)); g.strokePath (outline, PathStrokeType (outlineThickness)); } } // namespace juce