| @@ -202,6 +202,7 @@ namespace juce | |||
| #include "layout/juce_ResizableCornerComponent.cpp" | |||
| #include "layout/juce_ResizableEdgeComponent.cpp" | |||
| #include "layout/juce_ScrollBar.cpp" | |||
| #include "layout/juce_SidePanel.cpp" | |||
| #include "layout/juce_StretchableLayoutManager.cpp" | |||
| #include "layout/juce_StretchableLayoutResizerBar.cpp" | |||
| #include "layout/juce_StretchableObjectResizer.cpp" | |||
| @@ -263,6 +263,7 @@ namespace juce | |||
| #include "windows/juce_ThreadWithProgressWindow.h" | |||
| #include "windows/juce_TooltipWindow.h" | |||
| #include "layout/juce_MultiDocumentPanel.h" | |||
| #include "layout/juce_SidePanel.h" | |||
| #include "filebrowser/juce_FileBrowserListener.h" | |||
| #include "filebrowser/juce_DirectoryContentsList.h" | |||
| #include "filebrowser/juce_DirectoryContentsDisplayComponent.h" | |||
| @@ -0,0 +1,233 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| 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 | |||
| { | |||
| SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft, | |||
| Component* contentToDisplay, bool deleteComponentWhenNoLongerNeeded) | |||
| : titleLabel ("titleLabel", title), | |||
| isOnLeft (positionOnLeft), | |||
| panelWidth (width) | |||
| { | |||
| lookAndFeelChanged(); | |||
| addAndMakeVisible (titleLabel); | |||
| dismissButton.addListener (this); | |||
| addAndMakeVisible (dismissButton); | |||
| Desktop::getInstance().addGlobalMouseListener (this); | |||
| if (contentToDisplay != nullptr) | |||
| setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded); | |||
| setOpaque (false); | |||
| } | |||
| SidePanel::~SidePanel() | |||
| { | |||
| if (parent != nullptr) | |||
| parent->removeComponentListener (this); | |||
| } | |||
| void SidePanel::setContent (Component* newContent, bool deleteComponentWhenNoLongerNeeded) | |||
| { | |||
| if (contentComponent.get() != newContent) | |||
| { | |||
| if (deleteComponentWhenNoLongerNeeded) | |||
| contentComponent.setOwned (newContent); | |||
| else | |||
| contentComponent.setNonOwned (newContent); | |||
| addAndMakeVisible (contentComponent); | |||
| } | |||
| } | |||
| void SidePanel::showOrHide (bool show) | |||
| { | |||
| if (parent != nullptr) | |||
| { | |||
| isShowing = show; | |||
| Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent), | |||
| 1.0f, 250, true, 1.0, 0.0); | |||
| } | |||
| } | |||
| void SidePanel::resized() | |||
| { | |||
| auto bounds = getLocalBounds(); | |||
| calculateAndRemoveShadowBounds (bounds); | |||
| auto titleBounds = bounds.removeFromTop (titleBarHeight); | |||
| dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10) | |||
| : titleBounds.removeFromLeft (30).withTrimmedLeft (10)); | |||
| titleLabel.setBounds (isOnLeft ? titleBounds.withTrimmedRight (40) | |||
| : titleBounds.withTrimmedLeft (40)); | |||
| if (contentComponent != nullptr) | |||
| contentComponent->setBounds (bounds); | |||
| } | |||
| void SidePanel::paint (Graphics& g) | |||
| { | |||
| auto& lf = getLookAndFeel(); | |||
| auto bgColour = lf.findColour (SidePanel::backgroundColour); | |||
| auto shadowColour = lf.findColour (SidePanel::shadowBaseColour); | |||
| g.setGradientFill (ColourGradient (shadowColour.withAlpha (0.7f), (isOnLeft ? shadowArea.getTopLeft() | |||
| : shadowArea.getTopRight()).toFloat(), | |||
| shadowColour.withAlpha (0.0f), (isOnLeft ? shadowArea.getTopRight() | |||
| : shadowArea.getTopLeft()).toFloat(), false)); | |||
| g.fillRect (shadowArea); | |||
| g.excludeClipRegion (shadowArea); | |||
| g.fillAll (bgColour); | |||
| } | |||
| void SidePanel::parentHierarchyChanged() | |||
| { | |||
| auto* newParent = getParentComponent(); | |||
| if ((newParent != nullptr) && (parent != newParent)) | |||
| { | |||
| if (parent != nullptr) | |||
| parent->removeComponentListener (this); | |||
| parent = newParent; | |||
| parent->addComponentListener (this); | |||
| } | |||
| } | |||
| void SidePanel::mouseDrag (const MouseEvent& e) | |||
| { | |||
| if (shouldResize) | |||
| { | |||
| auto currentMouseDragX = static_cast<int> (e.position.x); | |||
| if (isOnLeft) | |||
| { | |||
| amountMoved = startingBounds.getRight() - currentMouseDragX; | |||
| setBounds (getBounds().withX (startingBounds.getX() - jmax (amountMoved, 0))); | |||
| } | |||
| else | |||
| { | |||
| amountMoved = currentMouseDragX - startingBounds.getX(); | |||
| setBounds (getBounds().withX (startingBounds.getX() + jmax (amountMoved, 0))); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| auto relativeMouseDownPosition = getLocalPoint (e.eventComponent, e.getMouseDownPosition()); | |||
| auto relativeMouseDragPosition = getLocalPoint (e.eventComponent, e.getPosition()); | |||
| if (! getLocalBounds().contains (relativeMouseDownPosition) | |||
| && getLocalBounds().contains (relativeMouseDragPosition)) | |||
| { | |||
| shouldResize = true; | |||
| startingBounds = getBounds(); | |||
| } | |||
| } | |||
| } | |||
| void SidePanel::mouseUp (const MouseEvent&) | |||
| { | |||
| if (shouldResize) | |||
| { | |||
| showOrHide (amountMoved < (panelWidth / 2)); | |||
| amountMoved = 0; | |||
| shouldResize = false; | |||
| } | |||
| } | |||
| //========================================================================== | |||
| void SidePanel::lookAndFeelChanged() | |||
| { | |||
| auto& lf = getLookAndFeel(); | |||
| dismissButton.setShape (lf.getSidePanelDismissButtonShape (*this), false, true, false); | |||
| dismissButton.setColours (lf.findColour (SidePanel::dismissButtonNormalColour), | |||
| lf.findColour (SidePanel::dismissButtonOverColour), | |||
| lf.findColour (SidePanel::dismissButtonDownColour)); | |||
| titleLabel.setFont (lf.getSidePanelTitleFont (*this)); | |||
| titleLabel.setColour (Label::textColourId, findColour (SidePanel::titleTextColour)); | |||
| titleLabel.setJustificationType (lf.getSidePanelTitleJustification (*this)); | |||
| } | |||
| void SidePanel::componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) | |||
| { | |||
| ignoreUnused (wasMoved); | |||
| if (wasResized && (&component == parent)) | |||
| setBounds (calculateBoundsInParent (component)); | |||
| } | |||
| void SidePanel::buttonClicked (Button*) | |||
| { | |||
| showOrHide (false); | |||
| } | |||
| Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const | |||
| { | |||
| auto parentBounds = parentComp.getBounds(); | |||
| if (isOnLeft) | |||
| { | |||
| return isShowing ? parentBounds.removeFromLeft (panelWidth) | |||
| : parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth); | |||
| } | |||
| return isShowing ? parentBounds.removeFromRight (panelWidth) | |||
| : parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth); | |||
| } | |||
| void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds) | |||
| { | |||
| shadowArea = isOnLeft ? bounds.removeFromRight (shadowWidth) | |||
| : bounds.removeFromLeft (shadowWidth); | |||
| } | |||
| bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent) | |||
| { | |||
| if (eventComponent == this) | |||
| return true; | |||
| for (auto& child : getChildren()) | |||
| if (eventComponent == child) | |||
| return true; | |||
| return false; | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,188 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| 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 | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A component that is positioned on either the left- or right-hand side of its parent, | |||
| containing a header and some content. This sort of component is typically used for | |||
| navigation and forms in mobile applications. | |||
| When triggered with the showOrHide() method, the SidePanel will animate itself to its | |||
| new position. This component also contains some logic to reactively resize and dismiss | |||
| itself when the user drags it. | |||
| */ | |||
| //============================================================================== | |||
| class SidePanel : public Component, | |||
| private ComponentListener, | |||
| private Button::Listener | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| /** Creates a SidePanel component. | |||
| @param title the text to use for the SidePanel's title bar | |||
| @param width the width of the SidePanel | |||
| @param positionOnLeft if true, the SidePanel will be positioned on the left of its parent component and | |||
| if false, the SidePanel will be positioned on the right of its parent component | |||
| @param contentComponent the component to add to this SidePanel - this content will take up the full | |||
| size of the SidePanel, minus the height of the title bar. You can pass nullptr | |||
| to this if you like and set the content component later using the setContent() method | |||
| @param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when | |||
| the SidePanel is deleted or when a different component is added. If false, | |||
| the caller must manage the lifetime of the component | |||
| */ | |||
| SidePanel (StringRef title, int width, bool positionOnLeft, | |||
| Component* contentComponent = nullptr, bool deleteComponentWhenNoLongerNeeded = true); | |||
| /** Destructor */ | |||
| ~SidePanel(); | |||
| //============================================================================== | |||
| /** Sets the component that this SidePanel will contain. | |||
| This will add the given component to this SidePanel and position it below the title bar. | |||
| (Don't add or remove any child components directly using the normal | |||
| Component::addChildComponent() methods). | |||
| @param newViewedComponent the component to add to this SidePanel, or null to remove | |||
| the current component. | |||
| @param deleteComponentWhenNoLongerNeeded if true, the component will be deleted automatically when | |||
| the SidePanel is deleted or when a different component is added. If false, | |||
| the caller must manage the lifetime of the component | |||
| @see getContent | |||
| */ | |||
| void setContent (Component* newContentComponent, | |||
| bool deleteComponentWhenNoLongerNeeded = true); | |||
| /** Returns the component that's currently being used inside the SidePanel. | |||
| @see setViewedComponent | |||
| */ | |||
| Component* getContent() { return contentComponent.get(); } | |||
| /** Shows or hides the SidePanel. | |||
| This will animate the SidePanel to either its full width or to be hidden on the | |||
| left- or right-hand side of its parent component depending on the value of positionOnLeft | |||
| that was passed to the constructor. | |||
| @param show if true, this will show the SidePanel and if false the SidePanel will be hidden | |||
| */ | |||
| void showOrHide (bool show); | |||
| //============================================================================== | |||
| /** Returns true if the SidePanel is currently showing. */ | |||
| bool isPanelShowing() const noexcept { return isShowing; } | |||
| /** Returns true if the SidePanel is positioned on the left of its parent. */ | |||
| bool isPanelOnLeft() const noexcept { return isOnLeft; } | |||
| /** Sets the width of the shadow that will be drawn on the side of the panel. */ | |||
| void setShadowWidth (int newWidth) noexcept { shadowWidth = newWidth; } | |||
| /** Sets the height of the title bar at the top of the SidePanel. */ | |||
| void setTitleBarHeight (int newHeight) noexcept { titleBarHeight = newHeight; } | |||
| //============================================================================== | |||
| void resized() override; | |||
| void paint (Graphics& g) override; | |||
| void parentHierarchyChanged() override; | |||
| void mouseDrag (const MouseEvent&) override; | |||
| void mouseUp (const MouseEvent&) override; | |||
| //============================================================================== | |||
| /** This abstract base class is implemented by LookAndFeel classes to provide | |||
| SidePanel drawing functionality. | |||
| */ | |||
| struct JUCE_API LookAndFeelMethods | |||
| { | |||
| virtual ~LookAndFeelMethods() {} | |||
| virtual Font getSidePanelTitleFont (SidePanel&) = 0; | |||
| virtual Justification getSidePanelTitleJustification (SidePanel&) = 0; | |||
| virtual Path getSidePanelDismissButtonShape (SidePanel&) = 0; | |||
| }; | |||
| /** A set of colour IDs to use to change the colour of various aspects of the SidePanel. | |||
| These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||
| methods. | |||
| @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||
| */ | |||
| enum ColourIds | |||
| { | |||
| backgroundColour = 0x100f001, | |||
| titleTextColour = 0x100f002, | |||
| shadowBaseColour = 0x100f003, | |||
| dismissButtonNormalColour = 0x100f004, | |||
| dismissButtonOverColour = 0x100f004, | |||
| dismissButtonDownColour = 0x100f005 | |||
| }; | |||
| private: | |||
| //========================================================================== | |||
| Component* parent = nullptr; | |||
| OptionalScopedPointer<Component> contentComponent; | |||
| Label titleLabel; | |||
| ShapeButton dismissButton {"dismissButton", Colours::lightgrey, Colours::lightgrey, Colours::white}; | |||
| Rectangle<int> shadowArea; | |||
| bool isOnLeft = false; | |||
| bool isShowing = false; | |||
| int panelWidth = 0; | |||
| int shadowWidth = 15; | |||
| int titleBarHeight = 40; | |||
| Rectangle<int> startingBounds; | |||
| bool shouldResize = false; | |||
| int amountMoved = 0; | |||
| //========================================================================== | |||
| void lookAndFeelChanged() override; | |||
| void componentMovedOrResized (Component&, bool wasMoved, bool wasResized) override; | |||
| void buttonClicked (Button*) override; | |||
| Rectangle<int> calculateBoundsInParent (Component&) const; | |||
| void calculateAndRemoveShadowBounds (Rectangle<int>& bounds); | |||
| bool isMouseEventInThisOrChildren (Component*); | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SidePanel) | |||
| }; | |||
| } // namespace juce | |||
| @@ -100,7 +100,8 @@ class JUCE_API LookAndFeel : public ScrollBar::LookAndFeelMethods, | |||
| public StretchableLayoutResizerBar::LookAndFeelMethods, | |||
| public ExtraLookAndFeelBaseClasses::KeyMappingEditorComponentMethods, | |||
| public ExtraLookAndFeelBaseClasses::AudioDeviceSelectorComponentMethods, | |||
| public ExtraLookAndFeelBaseClasses::LassoComponentMethods | |||
| public ExtraLookAndFeelBaseClasses::LassoComponentMethods, | |||
| public SidePanel::LookAndFeelMethods | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| @@ -212,6 +212,13 @@ LookAndFeel_V2::LookAndFeel_V2() | |||
| FileSearchPathListComponent::backgroundColourId, 0xffffffff, | |||
| FileChooserDialogBox::titleTextColourId, 0xff000000, | |||
| SidePanel::backgroundColour, 0xffffffff, | |||
| SidePanel::titleTextColour, 0xff000000, | |||
| SidePanel::shadowBaseColour, 0xff000000, | |||
| SidePanel::dismissButtonNormalColour, textButtonColour, | |||
| SidePanel::dismissButtonOverColour, textButtonColour, | |||
| SidePanel::dismissButtonDownColour, 0xff4444ff, | |||
| }; | |||
| for (int i = 0; i < numElementsInArray (standardColours); i += 2) | |||
| @@ -2775,6 +2782,37 @@ void LookAndFeel_V2::drawKeymapChangeButton (Graphics& g, int width, int height, | |||
| 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) | |||
| { | |||
| Path p; | |||
| if (panel.isPanelOnLeft()) | |||
| { | |||
| p.startNewSubPath (10, 0); | |||
| p.lineTo (0, 5); | |||
| p.lineTo (10, 10); | |||
| } | |||
| else | |||
| { | |||
| p.startNewSubPath (0, 10); | |||
| p.lineTo (10, 5); | |||
| p.lineTo (0, 0); | |||
| } | |||
| return p; | |||
| } | |||
| //============================================================================== | |||
| void LookAndFeel_V2::drawBevel (Graphics& g, const int x, const int y, const int width, const int height, | |||
| @@ -323,6 +323,11 @@ public: | |||
| void drawKeymapChangeButton (Graphics&, int width, int height, Button&, const String& keyDescription) override; | |||
| //============================================================================== | |||
| Font getSidePanelTitleFont (SidePanel&) override; | |||
| Justification getSidePanelTitleJustification (SidePanel&) override; | |||
| Path getSidePanelDismissButtonShape (SidePanel&) override; | |||
| //============================================================================== | |||
| /** Draws a 3D raised (or indented) bevel using two colours. | |||
| @@ -1439,6 +1439,13 @@ void LookAndFeel_V4::initialiseColours() | |||
| FileSearchPathListComponent::backgroundColourId, currentColourScheme.getUIColour (ColourScheme::UIColour::menuBackground).getARGB(), | |||
| FileChooserDialogBox::titleTextColourId, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultText).getARGB(), | |||
| SidePanel::backgroundColour, currentColourScheme.getUIColour (ColourScheme::UIColour::widgetBackground).getARGB(), | |||
| SidePanel::titleTextColour, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultText).getARGB(), | |||
| SidePanel::shadowBaseColour, currentColourScheme.getUIColour (ColourScheme::UIColour::widgetBackground).darker().getARGB(), | |||
| SidePanel::dismissButtonNormalColour, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultFill).getARGB(), | |||
| SidePanel::dismissButtonOverColour, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultFill).darker().getARGB(), | |||
| SidePanel::dismissButtonDownColour, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultFill).brighter().getARGB(), | |||
| }; | |||
| for (int i = 0; i < numElementsInArray (coloursToUse); i += 2) | |||