1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 namespace juce
27 {
28 
SidePanel(StringRef title,int width,bool positionOnLeft,Component * contentToDisplay,bool deleteComponentWhenNoLongerNeeded)29 SidePanel::SidePanel (StringRef title, int width, bool positionOnLeft,
30                       Component* contentToDisplay, bool deleteComponentWhenNoLongerNeeded)
31     : titleLabel ("titleLabel", title),
32       isOnLeft (positionOnLeft),
33       panelWidth (width)
34 {
35     lookAndFeelChanged();
36 
37     addAndMakeVisible (titleLabel);
38 
39     dismissButton.onClick = [this] { showOrHide (false); };
40     addAndMakeVisible (dismissButton);
41 
42     auto& desktop = Desktop::getInstance();
43 
44     desktop.addGlobalMouseListener (this);
45     desktop.getAnimator().addChangeListener (this);
46 
47     if (contentToDisplay != nullptr)
48         setContent (contentToDisplay, deleteComponentWhenNoLongerNeeded);
49 
50     setOpaque (false);
51     setVisible (false);
52     setAlwaysOnTop (true);
53 }
54 
~SidePanel()55 SidePanel::~SidePanel()
56 {
57     auto& desktop = Desktop::getInstance();
58 
59     desktop.removeGlobalMouseListener (this);
60     desktop.getAnimator().removeChangeListener (this);
61 
62     if (parent != nullptr)
63         parent->removeComponentListener (this);
64 }
65 
setContent(Component * newContent,bool deleteComponentWhenNoLongerNeeded)66 void SidePanel::setContent (Component* newContent, bool deleteComponentWhenNoLongerNeeded)
67 {
68     if (contentComponent.get() != newContent)
69     {
70         if (deleteComponentWhenNoLongerNeeded)
71             contentComponent.setOwned (newContent);
72         else
73             contentComponent.setNonOwned (newContent);
74 
75         addAndMakeVisible (contentComponent);
76 
77         resized();
78     }
79 }
80 
setTitleBarComponent(Component * titleBarComponentToUse,bool keepDismissButton,bool deleteComponentWhenNoLongerNeeded)81 void SidePanel::setTitleBarComponent (Component* titleBarComponentToUse,
82                                       bool keepDismissButton,
83                                       bool deleteComponentWhenNoLongerNeeded)
84 {
85     if (titleBarComponent.get() != titleBarComponentToUse)
86     {
87         if (deleteComponentWhenNoLongerNeeded)
88             titleBarComponent.setOwned (titleBarComponentToUse);
89         else
90             titleBarComponent.setNonOwned (titleBarComponentToUse);
91 
92         addAndMakeVisible (titleBarComponent);
93 
94         resized();
95     }
96 
97     shouldShowDismissButton = keepDismissButton;
98 }
99 
showOrHide(bool show)100 void SidePanel::showOrHide (bool show)
101 {
102     if (parent != nullptr)
103     {
104         isShowing = show;
105 
106         Desktop::getInstance().getAnimator().animateComponent (this, calculateBoundsInParent (*parent),
107                                                                1.0f, 250, true, 1.0, 0.0);
108 
109         if (isShowing && ! isVisible())
110             setVisible (true);
111 
112         if (onPanelShowHide != nullptr)
113             onPanelShowHide (isShowing);
114     }
115 }
116 
moved()117 void SidePanel::moved()
118 {
119     if (onPanelMove != nullptr)
120         onPanelMove();
121 }
122 
resized()123 void SidePanel::resized()
124 {
125     auto bounds = getLocalBounds();
126 
127     calculateAndRemoveShadowBounds (bounds);
128 
129     auto titleBounds = bounds.removeFromTop (titleBarHeight);
130 
131     if (titleBarComponent != nullptr)
132     {
133         if (shouldShowDismissButton)
134             dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
135                                               : titleBounds.removeFromLeft  (30).withTrimmedLeft  (10));
136 
137         titleBarComponent->setBounds (titleBounds);
138     }
139     else
140     {
141         dismissButton.setBounds (isOnLeft ? titleBounds.removeFromRight (30).withTrimmedRight (10)
142                                           : titleBounds.removeFromLeft  (30).withTrimmedLeft  (10));
143 
144         titleLabel.setBounds (isOnLeft ? titleBounds.withTrimmedRight (40)
145                                        : titleBounds.withTrimmedLeft (40));
146     }
147 
148     if (contentComponent != nullptr)
149         contentComponent->setBounds (bounds);
150 }
151 
paint(Graphics & g)152 void SidePanel::paint (Graphics& g)
153 {
154     auto& lf = getLookAndFeel();
155 
156     auto bgColour     = lf.findColour (SidePanel::backgroundColour);
157     auto shadowColour = lf.findColour (SidePanel::shadowBaseColour);
158 
159     g.setGradientFill (ColourGradient (shadowColour.withAlpha (0.7f), (isOnLeft ? shadowArea.getTopLeft()
160                                                                                 : shadowArea.getTopRight()).toFloat(),
161                                        shadowColour.withAlpha (0.0f), (isOnLeft ? shadowArea.getTopRight()
162                                                                                 : shadowArea.getTopLeft()).toFloat(), false));
163     g.fillRect (shadowArea);
164 
165     g.excludeClipRegion (shadowArea);
166     g.fillAll (bgColour);
167 }
168 
parentHierarchyChanged()169 void SidePanel::parentHierarchyChanged()
170 {
171     auto* newParent = getParentComponent();
172 
173     if ((newParent != nullptr) && (parent != newParent))
174     {
175         if (parent != nullptr)
176             parent->removeComponentListener (this);
177 
178         parent = newParent;
179         parent->addComponentListener (this);
180     }
181 }
182 
mouseDrag(const MouseEvent & e)183 void SidePanel::mouseDrag (const MouseEvent& e)
184 {
185     if (shouldResize)
186     {
187         Point<int> convertedPoint;
188 
189         if (getParentComponent() == nullptr)
190             convertedPoint = e.eventComponent->localPointToGlobal (e.getPosition());
191         else
192             convertedPoint = getParentComponent()->getLocalPoint (e.eventComponent, e.getPosition());
193 
194         auto currentMouseDragX = convertedPoint.x;
195 
196         if (isOnLeft)
197         {
198             amountMoved = startingBounds.getRight() - currentMouseDragX;
199             setBounds (getBounds().withX (startingBounds.getX() - jmax (amountMoved, 0)));
200         }
201         else
202         {
203             amountMoved = currentMouseDragX - startingBounds.getX();
204             setBounds (getBounds().withX (startingBounds.getX() + jmax (amountMoved, 0)));
205         }
206     }
207     else if (isShowing)
208     {
209         auto relativeMouseDownPosition = getLocalPoint (e.eventComponent, e.getMouseDownPosition());
210         auto relativeMouseDragPosition = getLocalPoint (e.eventComponent, e.getPosition());
211 
212         if (! getLocalBounds().contains (relativeMouseDownPosition)
213               && getLocalBounds().contains (relativeMouseDragPosition))
214         {
215             shouldResize = true;
216             startingBounds = getBounds();
217         }
218     }
219 }
220 
mouseUp(const MouseEvent &)221 void SidePanel::mouseUp (const MouseEvent&)
222 {
223     if (shouldResize)
224     {
225         showOrHide (amountMoved < (panelWidth / 2));
226 
227         amountMoved = 0;
228         shouldResize = false;
229     }
230 }
231 
232 //==============================================================================
lookAndFeelChanged()233 void SidePanel::lookAndFeelChanged()
234 {
235     auto& lf = getLookAndFeel();
236 
237     dismissButton.setShape (lf.getSidePanelDismissButtonShape (*this), false, true, false);
238 
239     dismissButton.setColours (lf.findColour (SidePanel::dismissButtonNormalColour),
240                               lf.findColour (SidePanel::dismissButtonOverColour),
241                               lf.findColour (SidePanel::dismissButtonDownColour));
242 
243     titleLabel.setFont (lf.getSidePanelTitleFont (*this));
244     titleLabel.setColour (Label::textColourId, findColour (SidePanel::titleTextColour));
245     titleLabel.setJustificationType (lf.getSidePanelTitleJustification (*this));
246 }
247 
componentMovedOrResized(Component & component,bool wasMoved,bool wasResized)248 void SidePanel::componentMovedOrResized (Component& component, bool wasMoved, bool wasResized)
249 {
250     ignoreUnused (wasMoved);
251 
252     if (wasResized && (&component == parent))
253         setBounds (calculateBoundsInParent (component));
254 }
255 
changeListenerCallback(ChangeBroadcaster *)256 void SidePanel::changeListenerCallback (ChangeBroadcaster*)
257 {
258     if (isVisible() && ! isShowing && ! Desktop::getInstance().getAnimator().isAnimating (this))
259         setVisible (false);
260 }
261 
calculateBoundsInParent(Component & parentComp) const262 Rectangle<int> SidePanel::calculateBoundsInParent (Component& parentComp) const
263 {
264     auto parentBounds = parentComp.getLocalBounds();
265 
266     if (isOnLeft)
267     {
268         return isShowing ? parentBounds.removeFromLeft (panelWidth)
269                          : parentBounds.withX (parentBounds.getX() - panelWidth).withWidth (panelWidth);
270     }
271 
272     return isShowing ? parentBounds.removeFromRight (panelWidth)
273                      : parentBounds.withX (parentBounds.getRight()).withWidth (panelWidth);
274 }
275 
calculateAndRemoveShadowBounds(Rectangle<int> & bounds)276 void SidePanel::calculateAndRemoveShadowBounds (Rectangle<int>& bounds)
277 {
278     shadowArea = isOnLeft ? bounds.removeFromRight (shadowWidth)
279                           : bounds.removeFromLeft  (shadowWidth);
280 }
281 
isMouseEventInThisOrChildren(Component * eventComponent)282 bool SidePanel::isMouseEventInThisOrChildren (Component* eventComponent)
283 {
284     if (eventComponent == this)
285         return true;
286 
287     for (auto& child : getChildren())
288         if (eventComponent == child)
289             return true;
290 
291     return false;
292 }
293 
294 } // namespace juce
295