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 
BubbleComponent()29 BubbleComponent::BubbleComponent()
30   : allowablePlacements (above | below | left | right)
31 {
32     setInterceptsMouseClicks (false, false);
33 
34     shadow.setShadowProperties (DropShadow (Colours::black.withAlpha (0.35f), 5, Point<int>()));
35     setComponentEffect (&shadow);
36 }
37 
~BubbleComponent()38 BubbleComponent::~BubbleComponent() {}
39 
40 //==============================================================================
paint(Graphics & g)41 void BubbleComponent::paint (Graphics& g)
42 {
43     getLookAndFeel().drawBubble (g, *this, arrowTip.toFloat(), content.toFloat());
44 
45     g.reduceClipRegion (content);
46     g.setOrigin (content.getPosition());
47 
48     paintContent (g, content.getWidth(), content.getHeight());
49 }
50 
setAllowedPlacement(const int newPlacement)51 void BubbleComponent::setAllowedPlacement (const int newPlacement)
52 {
53     allowablePlacements = newPlacement;
54 }
55 
56 //==============================================================================
setPosition(Component * componentToPointTo,int distanceFromTarget,int arrowLength)57 void BubbleComponent::setPosition (Component* componentToPointTo, int distanceFromTarget, int arrowLength)
58 {
59     jassert (componentToPointTo != nullptr);
60 
61     Rectangle<int> target;
62 
63     if (Component* p = getParentComponent())
64         target = p->getLocalArea (componentToPointTo, componentToPointTo->getLocalBounds());
65     else
66         target = componentToPointTo->getScreenBounds().transformedBy (getTransform().inverted());
67 
68     setPosition (target, distanceFromTarget, arrowLength);
69 }
70 
setPosition(Point<int> arrowTipPos,int arrowLength)71 void BubbleComponent::setPosition (Point<int> arrowTipPos, int arrowLength)
72 {
73     setPosition (Rectangle<int> (arrowTipPos.x, arrowTipPos.y, 1, 1), arrowLength, arrowLength);
74 }
75 
setPosition(Rectangle<int> rectangleToPointTo,int distanceFromTarget,int arrowLength)76 void BubbleComponent::setPosition (Rectangle<int> rectangleToPointTo,
77                                    int distanceFromTarget, int arrowLength)
78 {
79     {
80         int contentW = 150, contentH = 30;
81         getContentSize (contentW, contentH);
82         content.setBounds (distanceFromTarget, distanceFromTarget, contentW, contentH);
83     }
84 
85     const int totalW = content.getWidth()  + distanceFromTarget * 2;
86     const int totalH = content.getHeight() + distanceFromTarget * 2;
87 
88     auto availableSpace = (getParentComponent() != nullptr ? getParentComponent()->getLocalBounds()
89                                                            : getParentMonitorArea().transformedBy (getTransform().inverted()));
90 
91     int spaceAbove = ((allowablePlacements & above) != 0) ? jmax (0, rectangleToPointTo.getY()  - availableSpace.getY()) : -1;
92     int spaceBelow = ((allowablePlacements & below) != 0) ? jmax (0, availableSpace.getBottom() - rectangleToPointTo.getBottom()) : -1;
93     int spaceLeft  = ((allowablePlacements & left)  != 0) ? jmax (0, rectangleToPointTo.getX()  - availableSpace.getX()) : -1;
94     int spaceRight = ((allowablePlacements & right) != 0) ? jmax (0, availableSpace.getRight()  - rectangleToPointTo.getRight()) : -1;
95 
96     // look at whether the component is elongated, and if so, try to position next to its longer dimension.
97     if (rectangleToPointTo.getWidth() > rectangleToPointTo.getHeight() * 2
98          && (spaceAbove > totalH + 20 || spaceBelow > totalH + 20))
99     {
100         spaceLeft = spaceRight = 0;
101     }
102     else if (rectangleToPointTo.getWidth() < rectangleToPointTo.getHeight() / 2
103               && (spaceLeft > totalW + 20 || spaceRight > totalW + 20))
104     {
105         spaceAbove = spaceBelow = 0;
106     }
107 
108     int targetX, targetY;
109 
110     if (jmax (spaceAbove, spaceBelow) >= jmax (spaceLeft, spaceRight))
111     {
112         targetX = rectangleToPointTo.getCentre().x;
113         arrowTip.x = totalW / 2;
114 
115         if (spaceAbove >= spaceBelow)
116         {
117             // above
118             targetY = rectangleToPointTo.getY();
119             arrowTip.y = content.getBottom() + arrowLength;
120         }
121         else
122         {
123             // below
124             targetY = rectangleToPointTo.getBottom();
125             arrowTip.y = content.getY() - arrowLength;
126         }
127     }
128     else
129     {
130         targetY = rectangleToPointTo.getCentre().y;
131         arrowTip.y = totalH / 2;
132 
133         if (spaceLeft > spaceRight)
134         {
135             // on the left
136             targetX = rectangleToPointTo.getX();
137             arrowTip.x = content.getRight() + arrowLength;
138         }
139         else
140         {
141             // on the right
142             targetX = rectangleToPointTo.getRight();
143             arrowTip.x = content.getX() - arrowLength;
144         }
145     }
146 
147     setBounds (targetX - arrowTip.x, targetY - arrowTip.y, totalW, totalH);
148 }
149 
150 } // namespace juce
151