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