| @@ -202,6 +202,7 @@ namespace juce | |||||
| #include "layout/juce_ResizableCornerComponent.cpp" | #include "layout/juce_ResizableCornerComponent.cpp" | ||||
| #include "layout/juce_ResizableEdgeComponent.cpp" | #include "layout/juce_ResizableEdgeComponent.cpp" | ||||
| #include "layout/juce_ScrollBar.cpp" | #include "layout/juce_ScrollBar.cpp" | ||||
| #include "layout/juce_SidePanel.cpp" | |||||
| #include "layout/juce_StretchableLayoutManager.cpp" | #include "layout/juce_StretchableLayoutManager.cpp" | ||||
| #include "layout/juce_StretchableLayoutResizerBar.cpp" | #include "layout/juce_StretchableLayoutResizerBar.cpp" | ||||
| #include "layout/juce_StretchableObjectResizer.cpp" | #include "layout/juce_StretchableObjectResizer.cpp" | ||||
| @@ -263,6 +263,7 @@ namespace juce | |||||
| #include "windows/juce_ThreadWithProgressWindow.h" | #include "windows/juce_ThreadWithProgressWindow.h" | ||||
| #include "windows/juce_TooltipWindow.h" | #include "windows/juce_TooltipWindow.h" | ||||
| #include "layout/juce_MultiDocumentPanel.h" | #include "layout/juce_MultiDocumentPanel.h" | ||||
| #include "layout/juce_SidePanel.h" | |||||
| #include "filebrowser/juce_FileBrowserListener.h" | #include "filebrowser/juce_FileBrowserListener.h" | ||||
| #include "filebrowser/juce_DirectoryContentsList.h" | #include "filebrowser/juce_DirectoryContentsList.h" | ||||
| #include "filebrowser/juce_DirectoryContentsDisplayComponent.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 StretchableLayoutResizerBar::LookAndFeelMethods, | ||||
| public ExtraLookAndFeelBaseClasses::KeyMappingEditorComponentMethods, | public ExtraLookAndFeelBaseClasses::KeyMappingEditorComponentMethods, | ||||
| public ExtraLookAndFeelBaseClasses::AudioDeviceSelectorComponentMethods, | public ExtraLookAndFeelBaseClasses::AudioDeviceSelectorComponentMethods, | ||||
| public ExtraLookAndFeelBaseClasses::LassoComponentMethods | |||||
| public ExtraLookAndFeelBaseClasses::LassoComponentMethods, | |||||
| public SidePanel::LookAndFeelMethods | |||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -212,6 +212,13 @@ LookAndFeel_V2::LookAndFeel_V2() | |||||
| FileSearchPathListComponent::backgroundColourId, 0xffffffff, | FileSearchPathListComponent::backgroundColourId, 0xffffffff, | ||||
| FileChooserDialogBox::titleTextColourId, 0xff000000, | 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) | 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); | 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, | 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; | 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. | /** 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(), | FileSearchPathListComponent::backgroundColourId, currentColourScheme.getUIColour (ColourScheme::UIColour::menuBackground).getARGB(), | ||||
| FileChooserDialogBox::titleTextColourId, currentColourScheme.getUIColour (ColourScheme::UIColour::defaultText).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) | for (int i = 0; i < numElementsInArray (coloursToUse); i += 2) | ||||