1 | package org.expeditee.gui;
|
---|
2 |
|
---|
3 | import java.awt.BasicStroke;
|
---|
4 | import java.awt.Color;
|
---|
5 | import java.awt.Component;
|
---|
6 | import java.awt.Graphics;
|
---|
7 | import java.awt.Graphics2D;
|
---|
8 | import java.awt.Point;
|
---|
9 | import java.awt.Rectangle;
|
---|
10 | import java.awt.Stroke;
|
---|
11 | import java.awt.geom.Area;
|
---|
12 | import java.util.HashMap;
|
---|
13 | import java.util.HashSet;
|
---|
14 | import java.util.LinkedList;
|
---|
15 |
|
---|
16 | import javax.swing.JLayeredPane;
|
---|
17 | import javax.swing.SwingUtilities;
|
---|
18 |
|
---|
19 | import org.expeditee.items.ItemUtils;
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * A centralized container for all custom popups in expeditee.
|
---|
23 | *
|
---|
24 | * @author Brook Novak
|
---|
25 | */
|
---|
26 | public final class PopupManager implements DisplayIOObserver {
|
---|
27 |
|
---|
28 | /** Singleton */
|
---|
29 | private PopupManager() {}
|
---|
30 |
|
---|
31 | /** Singleton */
|
---|
32 | private static PopupManager _instance = new PopupManager();
|
---|
33 |
|
---|
34 | /**
|
---|
35 | * @return The singleton instance.
|
---|
36 | */
|
---|
37 | public static PopupManager getInstance() {
|
---|
38 | return _instance;
|
---|
39 | }
|
---|
40 |
|
---|
41 | // popup->invoker
|
---|
42 | private HashMap<Popup, Component> _popups = new HashMap<Popup, Component>();
|
---|
43 | // quick ref to invokers
|
---|
44 | private HashSet<Component> _invokers = new HashSet<Component>();
|
---|
45 |
|
---|
46 | private LinkedList<AnimatedPopup> _animatingPopups = new LinkedList<AnimatedPopup>();
|
---|
47 |
|
---|
48 | private AnimationThread _animationThread = null;
|
---|
49 |
|
---|
50 | private final int ANIMATION_DURATION = 180; // Tume its takes for a maximize . minimize to animate. In ms.
|
---|
51 | private final int ANIMATION_RATE = 30; // in ms
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * Determines whether a given point is over a popup.
|
---|
55 | *
|
---|
56 | * @param p
|
---|
57 | *
|
---|
58 | * @return True if p is over a popup
|
---|
59 | *
|
---|
60 | * @throws NullPointerException
|
---|
61 | * If p is null
|
---|
62 | */
|
---|
63 | public boolean isPointOverPopup(Point p) {
|
---|
64 | if (p == null) throw new NullPointerException("p");
|
---|
65 | for (Popup pp : _popups.keySet()) {
|
---|
66 | if (pp.getBounds().contains(p)) {
|
---|
67 | return true;
|
---|
68 | }
|
---|
69 | }
|
---|
70 |
|
---|
71 | return false;
|
---|
72 | }
|
---|
73 |
|
---|
74 | /**
|
---|
75 | * Tests a component to see if it is in invoker of an existing popup.
|
---|
76 | *
|
---|
77 | * @param c
|
---|
78 | * Must not be null.
|
---|
79 | *
|
---|
80 | * @return
|
---|
81 | * True if c is an invoker
|
---|
82 | *
|
---|
83 | * @throws NullPointerException
|
---|
84 | * If c is null
|
---|
85 | */
|
---|
86 | public boolean isInvoker(Component c) {
|
---|
87 | if (c == null) throw new NullPointerException("c");
|
---|
88 | return _invokers.contains(c);
|
---|
89 | }
|
---|
90 |
|
---|
91 | /**
|
---|
92 | * Use this instead of isVisible to determine if the popup is showing or not,
|
---|
93 | * since it considers animation.
|
---|
94 | *
|
---|
95 | * @return
|
---|
96 | * True if this popup is showing. <b>IMPORTANT:</b> This includes
|
---|
97 | * if the popup is not yet visible, but in an animation sequence for showing...
|
---|
98 | *
|
---|
99 | * @throws NullPointerException
|
---|
100 | * If p is null
|
---|
101 | */
|
---|
102 | public boolean isShowing(Popup p) {
|
---|
103 | if (p == null) throw new NullPointerException("p");
|
---|
104 | return _popups.containsKey(p);
|
---|
105 | }
|
---|
106 |
|
---|
107 | /**
|
---|
108 | * @return
|
---|
109 | * True if a poup is showing. False otherwise.
|
---|
110 | */
|
---|
111 | public boolean isAnyPopupsShowing () {
|
---|
112 | return !_popups.isEmpty();
|
---|
113 | }
|
---|
114 |
|
---|
115 | /**
|
---|
116 | * @return
|
---|
117 | * True if the mouse click event for going back a frame should be consumed
|
---|
118 | * Due to a popup requesting this event to be consumed currently showing.
|
---|
119 | */
|
---|
120 | public boolean shouldConsumeBackClick() {
|
---|
121 | for (Popup p : _popups.keySet()) {
|
---|
122 | if (p.shouldConsumeBackClick())
|
---|
123 | return true;
|
---|
124 | }
|
---|
125 |
|
---|
126 | return false;
|
---|
127 | }
|
---|
128 |
|
---|
129 | /**
|
---|
130 | * Clears all popups from the browser that are autohidden.
|
---|
131 | * Stops animations.
|
---|
132 | */
|
---|
133 | public void hideAutohidePopups() {
|
---|
134 |
|
---|
135 |
|
---|
136 | // Get rid of all animations that are not non-auto-hidden pops that are expanding
|
---|
137 | synchronized (_animatingPopups) {
|
---|
138 |
|
---|
139 | LinkedList<AnimatedPopup> animationsToClear = new LinkedList<AnimatedPopup>();
|
---|
140 |
|
---|
141 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
142 | if (!(ap.popup != null && !ap.popup.doesAutoHide())) {
|
---|
143 | animationsToClear.add(ap);
|
---|
144 | }
|
---|
145 | }
|
---|
146 |
|
---|
147 | _animatingPopups.removeAll(animationsToClear);
|
---|
148 |
|
---|
149 | }
|
---|
150 |
|
---|
151 | LinkedList<Popup> popupsToClear = new LinkedList<Popup>();
|
---|
152 | LinkedList<Component> invokersToClear = new LinkedList<Component>();
|
---|
153 |
|
---|
154 | // Get ride of the actual popups
|
---|
155 | for (Popup p : _popups.keySet()) {
|
---|
156 | if (p.doesAutoHide()) {
|
---|
157 |
|
---|
158 | popupsToClear.add(p);
|
---|
159 | invokersToClear.add(_popups.get(p));
|
---|
160 |
|
---|
161 | invalidatePopup(p);
|
---|
162 | p.setVisible(false);
|
---|
163 | Browser._theBrowser.getLayeredPane().remove(p);
|
---|
164 | p.onHide();
|
---|
165 | }
|
---|
166 | }
|
---|
167 |
|
---|
168 | assert (popupsToClear.size() == invokersToClear.size());
|
---|
169 |
|
---|
170 | for (int i = 0; i < popupsToClear.size(); i++) {
|
---|
171 | _popups.remove(popupsToClear.get(i));
|
---|
172 | _invokers.remove(invokersToClear.get(i));
|
---|
173 | }
|
---|
174 |
|
---|
175 |
|
---|
176 |
|
---|
177 | }
|
---|
178 |
|
---|
179 |
|
---|
180 |
|
---|
181 | // public void hideAllPopups() {
|
---|
182 | //
|
---|
183 | // for (Popup p : _popups.keySet()) {
|
---|
184 | // invalidatePopup(p);
|
---|
185 | // p.setVisible(false);
|
---|
186 | // Browser._theBrowser.getLayeredPane().remove(p);
|
---|
187 | // p.onHide();
|
---|
188 | // }
|
---|
189 | // _popups.clear();
|
---|
190 | // _invokers.clear();
|
---|
191 | //
|
---|
192 | // // Get rid of all animations
|
---|
193 | // synchronized (_animatingPopups) {
|
---|
194 | // _animatingPopups.clear();
|
---|
195 | // }
|
---|
196 | // }
|
---|
197 |
|
---|
198 | public void frameChanged() {
|
---|
199 | // Remove any popups that are showing on the current frame
|
---|
200 | hideAutohidePopups();
|
---|
201 | }
|
---|
202 |
|
---|
203 | /**
|
---|
204 | * Hides a popup - if not already hidden.
|
---|
205 | *
|
---|
206 | * @param p
|
---|
207 | * Must not be null.
|
---|
208 | *
|
---|
209 | * @throws NullPointerException
|
---|
210 | * If p is null
|
---|
211 | */
|
---|
212 | public void hidePopup(Popup p) {
|
---|
213 | if (p == null) throw new NullPointerException("p");
|
---|
214 |
|
---|
215 | if (!isShowing(p) && (!p.isVisible() || p.getParent() == null)) return;
|
---|
216 |
|
---|
217 |
|
---|
218 | // Cancel any showing animations
|
---|
219 | synchronized (_animatingPopups) {
|
---|
220 | AnimatedPopup toRemove = null;
|
---|
221 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
222 | if (ap.popup == p) {
|
---|
223 | toRemove = ap;
|
---|
224 | break;
|
---|
225 | }
|
---|
226 | }
|
---|
227 | if (toRemove != null)
|
---|
228 | _animatingPopups.remove(toRemove);
|
---|
229 | }
|
---|
230 |
|
---|
231 | invalidatePopup(p);
|
---|
232 | p.setVisible(false);
|
---|
233 | Browser._theBrowser.getLayeredPane().remove(p);
|
---|
234 | Component invoker = _popups.remove(p);
|
---|
235 | if (invoker != null) _invokers.remove(invoker);
|
---|
236 | p.onHide();
|
---|
237 |
|
---|
238 | }
|
---|
239 |
|
---|
240 | /**
|
---|
241 | * Hides a popup - with animation. - if not already hidden.
|
---|
242 | *
|
---|
243 | * @param p
|
---|
244 | * Must not be null.
|
---|
245 | * @param animator
|
---|
246 | * Must not be null.
|
---|
247 | *
|
---|
248 | * @throws NullPointerException
|
---|
249 | * If p or animator is null
|
---|
250 | *
|
---|
251 | */
|
---|
252 | public void hidePopup(Popup p, PopupAnimator animator) {
|
---|
253 |
|
---|
254 | if (p == null) throw new NullPointerException("p");
|
---|
255 | if (animator == null) throw new NullPointerException("animator");
|
---|
256 |
|
---|
257 | if (!isShowing(p) && (!p.isVisible() || p.getParent() == null)) return;
|
---|
258 |
|
---|
259 | hidePopup(p);
|
---|
260 | AnimatedPopup ap = new AnimatedPopup(
|
---|
261 | animator,
|
---|
262 | System.currentTimeMillis(),
|
---|
263 | null,
|
---|
264 | false,
|
---|
265 | p.getLocation());
|
---|
266 |
|
---|
267 | animator.starting(false, p.getBounds());
|
---|
268 |
|
---|
269 | synchronized (_animatingPopups) {
|
---|
270 | _animatingPopups.add(ap);
|
---|
271 | }
|
---|
272 |
|
---|
273 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
274 | _animationThread = new AnimationThread();
|
---|
275 | _animationThread.start();
|
---|
276 | }
|
---|
277 | }
|
---|
278 |
|
---|
279 |
|
---|
280 |
|
---|
281 | /**
|
---|
282 | * Displays a popup at a specific location.
|
---|
283 | *
|
---|
284 | * @param p
|
---|
285 | * Must not be null.
|
---|
286 | *
|
---|
287 | * @param invoker
|
---|
288 | * The component responsible for showing the popup. can be null.
|
---|
289 | * Used such that when invoker pressed, the popup will not auto hide.
|
---|
290 | *
|
---|
291 | * @param loc
|
---|
292 | * Must not be null.
|
---|
293 | *
|
---|
294 | * @throws NullPointerException
|
---|
295 | * If p or loc is null
|
---|
296 | *
|
---|
297 | */
|
---|
298 | public void showPopup(Popup p, Point loc, Component invoker) {
|
---|
299 | if (p == null) throw new NullPointerException("p");
|
---|
300 | if (loc == null) throw new NullPointerException("animator");
|
---|
301 |
|
---|
302 |
|
---|
303 | if (_popups.containsKey(p)) return;
|
---|
304 |
|
---|
305 | p.prepareToPaint();
|
---|
306 | p.setLocation(loc);
|
---|
307 | p.setVisible(true);
|
---|
308 |
|
---|
309 | Browser._theBrowser.getLayeredPane().add(p, JLayeredPane.POPUP_LAYER, 0);
|
---|
310 |
|
---|
311 | _popups.put(p, invoker);
|
---|
312 | if (invoker != null) _invokers.add(invoker);
|
---|
313 |
|
---|
314 | p.onShowing();
|
---|
315 | p.onShow();
|
---|
316 |
|
---|
317 | // Invalidate the popup border
|
---|
318 | if (p.getBorderThickness() > 0.0f) {
|
---|
319 | invalidatePopup(p);
|
---|
320 | }
|
---|
321 | }
|
---|
322 |
|
---|
323 | /**
|
---|
324 | * Displays a popup at a specific location - with animation.
|
---|
325 | *
|
---|
326 | * @param p
|
---|
327 | * Must not be null.
|
---|
328 | *
|
---|
329 | * @param invoker
|
---|
330 | * The component responsible for showing the popup. can be null.
|
---|
331 | * Used such that when invoker pressed, the popup will not auto hide.
|
---|
332 | *
|
---|
333 | * @param loc
|
---|
334 | * Must not be null.
|
---|
335 | *
|
---|
336 | * @param animator
|
---|
337 | * Must not be null.
|
---|
338 | *
|
---|
339 | * @throws NullPointerException
|
---|
340 | * If p, animator or loc is null
|
---|
341 | */
|
---|
342 | public void showPopup(Popup p, Point loc, Component invoker, PopupAnimator animator) {
|
---|
343 | if (animator == null)
|
---|
344 | throw new NullPointerException("animator");
|
---|
345 | if (p == null)
|
---|
346 | throw new NullPointerException("p");
|
---|
347 | if (loc == null)
|
---|
348 | throw new NullPointerException("loc");
|
---|
349 |
|
---|
350 | if (_popups.containsKey(p)) return;
|
---|
351 |
|
---|
352 | _popups.put(p, invoker);
|
---|
353 | if (invoker != null) _invokers.add(invoker);
|
---|
354 |
|
---|
355 |
|
---|
356 | AnimatedPopup ap = new AnimatedPopup(
|
---|
357 | animator,
|
---|
358 | System.currentTimeMillis(),
|
---|
359 | p,
|
---|
360 | true,
|
---|
361 | loc);
|
---|
362 |
|
---|
363 |
|
---|
364 | animator.starting(true, new Rectangle(loc.x, loc.y, p.getWidth(), p.getHeight()));
|
---|
365 |
|
---|
366 | p.onShowing();
|
---|
367 |
|
---|
368 | synchronized (_animatingPopups) {
|
---|
369 | _animatingPopups.add(ap);
|
---|
370 | }
|
---|
371 |
|
---|
372 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
373 | _animationThread = new AnimationThread();
|
---|
374 | _animationThread.start();
|
---|
375 | }
|
---|
376 |
|
---|
377 | }
|
---|
378 |
|
---|
379 | /**
|
---|
380 | * Does a pure asynch animation with no popups involved.
|
---|
381 | *
|
---|
382 | * For example you may want to have an effect such that an item is expanding
|
---|
383 | * or moving into a new location on the screen.
|
---|
384 | *
|
---|
385 | * @param animator
|
---|
386 | * Must not be null.
|
---|
387 | *
|
---|
388 | * @param target
|
---|
389 | * Must not be null.
|
---|
390 | *
|
---|
391 | * @throws NullPointerException
|
---|
392 | * If animator or target is null
|
---|
393 | *
|
---|
394 | */
|
---|
395 | public void doPureAnimation(PopupAnimator animator, Rectangle target) {
|
---|
396 |
|
---|
397 | if (animator == null)
|
---|
398 | throw new NullPointerException("animator");
|
---|
399 | if (target == null)
|
---|
400 | throw new NullPointerException("target");
|
---|
401 |
|
---|
402 | AnimatedPopup ap = new AnimatedPopup(
|
---|
403 | animator,
|
---|
404 | System.currentTimeMillis(),
|
---|
405 | null,
|
---|
406 | false,
|
---|
407 | target.getLocation());
|
---|
408 |
|
---|
409 |
|
---|
410 | animator.starting(true, target);
|
---|
411 |
|
---|
412 | synchronized (_animatingPopups) {
|
---|
413 | _animatingPopups.add(ap);
|
---|
414 | }
|
---|
415 |
|
---|
416 | if (_animationThread == null || !_animationThread.isAlive() || _animationThread.willDie) {
|
---|
417 | _animationThread = new AnimationThread();
|
---|
418 | _animationThread.start();
|
---|
419 | }
|
---|
420 |
|
---|
421 | }
|
---|
422 |
|
---|
423 |
|
---|
424 | /**
|
---|
425 | * Paints all popups in the browsers popup pane with the given graphics.
|
---|
426 | *
|
---|
427 | * @param g
|
---|
428 | * Where to paint to.
|
---|
429 | *
|
---|
430 | * @param clip
|
---|
431 | */
|
---|
432 | void paintLayeredPane(Graphics g, Area clip) {
|
---|
433 | if (Browser._theBrowser == null) return;
|
---|
434 |
|
---|
435 | Component[] compsOnPopup = Browser._theBrowser.getLayeredPane().getComponentsInLayer(JLayeredPane.POPUP_LAYER);
|
---|
436 |
|
---|
437 | for (int i = 0; i < compsOnPopup.length; i++) {
|
---|
438 | Component c = compsOnPopup[i];
|
---|
439 |
|
---|
440 | Point p = c.getLocation();
|
---|
441 |
|
---|
442 | if (clip == null || clip.intersects(c.getBounds())) {
|
---|
443 | g.translate(p.x, p.y);
|
---|
444 | c.paint(g);
|
---|
445 | g.translate(-p.x, -p.y);
|
---|
446 | }
|
---|
447 |
|
---|
448 | }
|
---|
449 | }
|
---|
450 |
|
---|
451 | /**
|
---|
452 | * Paints current popup animations to the expeditee browser content pane.
|
---|
453 | */
|
---|
454 | void paintAnimations() {
|
---|
455 |
|
---|
456 | if (Browser._theBrowser == null
|
---|
457 | || Browser._theBrowser.g == null) return;
|
---|
458 |
|
---|
459 | Graphics g = Browser._theBrowser.g;
|
---|
460 |
|
---|
461 | synchronized (_animatingPopups) {
|
---|
462 |
|
---|
463 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
464 | ap.animator.paint(g);
|
---|
465 | }
|
---|
466 |
|
---|
467 | }
|
---|
468 | }
|
---|
469 |
|
---|
470 |
|
---|
471 | private void invalidatePopup(Popup p) {
|
---|
472 | FrameGraphics.invalidateArea(ItemUtils.expandRectangle(p.getBounds(),
|
---|
473 | (int)Math.ceil(p.getBorderThickness())));
|
---|
474 | }
|
---|
475 |
|
---|
476 | /**
|
---|
477 | * Proccesses animation on a dedicated thread.
|
---|
478 | *
|
---|
479 | * @author Brook Novak
|
---|
480 | *
|
---|
481 | */
|
---|
482 | private class AnimationThread extends Thread {
|
---|
483 |
|
---|
484 | private boolean willDie = false;
|
---|
485 |
|
---|
486 | @Override
|
---|
487 | public void run() {
|
---|
488 |
|
---|
489 |
|
---|
490 | LinkedList<AnimatedPopup> finishedAnimations;
|
---|
491 |
|
---|
492 | while (true) {
|
---|
493 |
|
---|
494 | // Perform animation logic
|
---|
495 | finishedAnimations = animate();
|
---|
496 |
|
---|
497 | // Check if finished all animations
|
---|
498 | if (finishedAnimations == null) return; // done
|
---|
499 |
|
---|
500 | // Check for finalization of animation. That is, adding the popups to the layered pane
|
---|
501 | boolean needsFinalization = false;
|
---|
502 | for (AnimatedPopup ap : finishedAnimations) {
|
---|
503 | if (ap.isShowing) {
|
---|
504 | needsFinalization = true;
|
---|
505 | break;
|
---|
506 | }
|
---|
507 |
|
---|
508 | // FInal invalidation
|
---|
509 | FrameGraphics.invalidateArea(ap.animator.getCurrentDrawingArea());
|
---|
510 |
|
---|
511 | }
|
---|
512 |
|
---|
513 | if (needsFinalization) {
|
---|
514 | SwingUtilities.invokeLater(new AnimationFinalizor(finishedAnimations));
|
---|
515 | // Will repaint when a popup becomes anchored...
|
---|
516 | } else {
|
---|
517 | FrameGraphics.requestRefresh(true);
|
---|
518 | }
|
---|
519 |
|
---|
520 | // Limit animation rate
|
---|
521 | try {
|
---|
522 | sleep(ANIMATION_RATE);
|
---|
523 | } catch (InterruptedException e) {
|
---|
524 | e.printStackTrace();
|
---|
525 | }
|
---|
526 |
|
---|
527 | }
|
---|
528 |
|
---|
529 | }
|
---|
530 |
|
---|
531 | /**
|
---|
532 | * Performs animations
|
---|
533 | * @return
|
---|
534 | */
|
---|
535 | private LinkedList<AnimatedPopup> animate() {
|
---|
536 |
|
---|
537 | LinkedList<AnimatedPopup> finishedPopups = null;
|
---|
538 |
|
---|
539 | synchronized (_animatingPopups) {
|
---|
540 |
|
---|
541 | if (_animatingPopups.isEmpty()) {
|
---|
542 | willDie = true;
|
---|
543 | return null;
|
---|
544 | }
|
---|
545 |
|
---|
546 | long currentTime = System.currentTimeMillis();
|
---|
547 |
|
---|
548 | finishedPopups = new LinkedList<AnimatedPopup>();
|
---|
549 |
|
---|
550 | for (AnimatedPopup ap : _animatingPopups) {
|
---|
551 |
|
---|
552 | long duration = currentTime - ap.startTime;
|
---|
553 |
|
---|
554 | if (duration >= ANIMATION_DURATION) { // check if complete
|
---|
555 |
|
---|
556 | finishedPopups.add(ap);
|
---|
557 |
|
---|
558 | } else {
|
---|
559 |
|
---|
560 | float percent = ((float)duration / (float)ANIMATION_DURATION);
|
---|
561 | assert (percent >= 0.0f);
|
---|
562 | assert (percent < 1.0f);
|
---|
563 |
|
---|
564 | Rectangle dirty = ap.animator.update(percent);
|
---|
565 | if (dirty != null)
|
---|
566 | FrameGraphics.invalidateArea(dirty);
|
---|
567 |
|
---|
568 | }
|
---|
569 |
|
---|
570 | }
|
---|
571 |
|
---|
572 | _animatingPopups.removeAll(finishedPopups);
|
---|
573 |
|
---|
574 | }
|
---|
575 |
|
---|
576 | return finishedPopups;
|
---|
577 |
|
---|
578 | }
|
---|
579 |
|
---|
580 | /**
|
---|
581 | * Adds popups to layered pane
|
---|
582 | * @author Brook Novak
|
---|
583 | *
|
---|
584 | */
|
---|
585 | private class AnimationFinalizor implements Runnable {
|
---|
586 |
|
---|
587 | private LinkedList<AnimatedPopup> finished;
|
---|
588 |
|
---|
589 | AnimationFinalizor(LinkedList<AnimatedPopup> finished) {
|
---|
590 | this.finished = finished;
|
---|
591 | }
|
---|
592 |
|
---|
593 | public void run() {
|
---|
594 |
|
---|
595 | for (AnimatedPopup ap : finished) {
|
---|
596 |
|
---|
597 | if (ap.isShowing && _popups.containsKey(ap.popup)) {
|
---|
598 |
|
---|
599 | ap.popup.prepareToPaint();
|
---|
600 | ap.popup.setLocation(ap.popupLocation);
|
---|
601 | ap.popup.setVisible(true);
|
---|
602 |
|
---|
603 | Browser._theBrowser.getLayeredPane().add(ap.popup, JLayeredPane.POPUP_LAYER, 0);
|
---|
604 |
|
---|
605 | ap.popup.onShow();
|
---|
606 |
|
---|
607 | // Invalidate the popup border
|
---|
608 | if (ap.popup.getBorderThickness() > 0.0f) {
|
---|
609 | invalidatePopup(ap.popup);
|
---|
610 | }
|
---|
611 | }
|
---|
612 |
|
---|
613 | }
|
---|
614 |
|
---|
615 | }
|
---|
616 | }
|
---|
617 | }
|
---|
618 |
|
---|
619 | private class AnimatedPopup {
|
---|
620 |
|
---|
621 | PopupAnimator animator;
|
---|
622 | long startTime;
|
---|
623 | Popup popup = null;
|
---|
624 | boolean isShowing;
|
---|
625 | Point popupLocation;
|
---|
626 |
|
---|
627 | public AnimatedPopup(PopupAnimator animator, long startTime, Popup popup,
|
---|
628 | boolean isShowing, Point popupLocation) {
|
---|
629 |
|
---|
630 | assert(animator != null);
|
---|
631 | assert(popupLocation != null);
|
---|
632 |
|
---|
633 | // Only have popup if showing
|
---|
634 | assert (!isShowing && popup == null || (isShowing && popup != null));
|
---|
635 |
|
---|
636 |
|
---|
637 | this.animator = animator;
|
---|
638 | this.startTime = startTime;
|
---|
639 | this.popup = popup;
|
---|
640 | this.isShowing = isShowing;
|
---|
641 | this.popupLocation = popupLocation;
|
---|
642 |
|
---|
643 | }
|
---|
644 |
|
---|
645 | }
|
---|
646 |
|
---|
647 | /**
|
---|
648 | * Provides animations for a popup when hiding or when showing.
|
---|
649 | *
|
---|
650 | * Note that {@link PopupAnimator#paint(Graphics)} and {@link PopupAnimator#update(float)}
|
---|
651 | * will always be invoked at seperate times - so do not have to worry about thread-saefty.
|
---|
652 | *
|
---|
653 | * These should only be used once... one per popup at a time.
|
---|
654 | *
|
---|
655 | * @author Brook Novak
|
---|
656 | *
|
---|
657 | */
|
---|
658 | public interface PopupAnimator {
|
---|
659 |
|
---|
660 | /**
|
---|
661 | * Invoked before showing. Any prepaations are done here.
|
---|
662 | *
|
---|
663 | * @param isShowing
|
---|
664 | * True if this animation will be for a popup that is showing.
|
---|
665 | * False if this animation will be for a popup that is hiding.
|
---|
666 | *
|
---|
667 | * @param popupBounds
|
---|
668 | * The location of the popup. I.E. where it is, or where it will be.
|
---|
669 | *
|
---|
670 | */
|
---|
671 | void starting(boolean isShowing, Rectangle popupBounds);
|
---|
672 |
|
---|
673 | /**
|
---|
674 | *
|
---|
675 | * Called on an animation thread.
|
---|
676 | *
|
---|
677 | * @param percent
|
---|
678 | * The percent complete of the animations.
|
---|
679 | * Rangles from 0 to 1.
|
---|
680 | *
|
---|
681 | * @return dirty area that needs painting for last update... Null for no invalidation
|
---|
682 | *
|
---|
683 | *
|
---|
684 | */
|
---|
685 | Rectangle update(float percent);
|
---|
686 |
|
---|
687 | /**
|
---|
688 | * Paints the animation - on the swing thread.
|
---|
689 | * Note that this is always on the content pane - not the expeditee frame buffer.
|
---|
690 | *
|
---|
691 | * @param g
|
---|
692 | *
|
---|
693 | */
|
---|
694 | void paint(Graphics g);
|
---|
695 |
|
---|
696 |
|
---|
697 | /**
|
---|
698 | *
|
---|
699 | * @return
|
---|
700 | * The area which the animation is drawn. Used for final invalidation. Null for no final invaliation
|
---|
701 | */
|
---|
702 | Rectangle getCurrentDrawingArea();
|
---|
703 |
|
---|
704 | }
|
---|
705 |
|
---|
706 | public class ExpandShrinkAnimator implements PopupAnimator {
|
---|
707 |
|
---|
708 | private boolean isShowing;
|
---|
709 | private Rectangle popupBounds;
|
---|
710 | private Rectangle sourceRectangle;
|
---|
711 | private Rectangle currentRectangle;
|
---|
712 | private Color fillColor;
|
---|
713 |
|
---|
714 | private final Stroke stroke = new BasicStroke(2.0f);
|
---|
715 |
|
---|
716 | /**
|
---|
717 | *
|
---|
718 | * @param sourceRectangle
|
---|
719 | * @param fillColor
|
---|
720 | * The fill color of the animated rectangle. Null for no fill.
|
---|
721 | */
|
---|
722 | public ExpandShrinkAnimator(Rectangle sourceRectangle, Color fillColor) {
|
---|
723 | if (sourceRectangle == null) throw new NullPointerException("sourceRectangle");
|
---|
724 |
|
---|
725 | this.fillColor = fillColor;
|
---|
726 | this.sourceRectangle = (Rectangle)sourceRectangle.clone();
|
---|
727 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
728 | }
|
---|
729 |
|
---|
730 | public void paint(Graphics g) {
|
---|
731 |
|
---|
732 | if (fillColor != null) {
|
---|
733 | g.setColor(fillColor);
|
---|
734 | g.fillRect(currentRectangle.x, currentRectangle.y, currentRectangle.width, currentRectangle.height);
|
---|
735 | }
|
---|
736 |
|
---|
737 | g.setColor(Color.BLACK);
|
---|
738 | ((Graphics2D)g).setStroke(stroke);
|
---|
739 | g.drawRect(currentRectangle.x, currentRectangle.y, currentRectangle.width, currentRectangle.height);
|
---|
740 | }
|
---|
741 |
|
---|
742 | public void starting(boolean isShowing, Rectangle popupBounds) {
|
---|
743 | this.isShowing = isShowing;
|
---|
744 | this.popupBounds = popupBounds;
|
---|
745 |
|
---|
746 | if (isShowing) {
|
---|
747 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
748 | } else {
|
---|
749 | this.currentRectangle = (Rectangle)sourceRectangle.clone();
|
---|
750 | }
|
---|
751 |
|
---|
752 | }
|
---|
753 |
|
---|
754 | public Rectangle update(float percent) {
|
---|
755 |
|
---|
756 | Rectangle oldBounds = currentRectangle;
|
---|
757 |
|
---|
758 | if (!isShowing) { // if minimizing just reverse percent
|
---|
759 | percent = 1 - percent;
|
---|
760 | }
|
---|
761 |
|
---|
762 | // update X
|
---|
763 | currentRectangle.x = sourceRectangle.x +
|
---|
764 | (int)((popupBounds.x - sourceRectangle.x) * percent);
|
---|
765 |
|
---|
766 | // update Y
|
---|
767 | currentRectangle.y = sourceRectangle.y +
|
---|
768 | (int)((popupBounds.y - sourceRectangle.y) * percent);
|
---|
769 |
|
---|
770 | // update width
|
---|
771 | currentRectangle.width = sourceRectangle.width +
|
---|
772 | (int)((popupBounds.width - sourceRectangle.width) * percent);
|
---|
773 |
|
---|
774 | // update height
|
---|
775 | currentRectangle.height = sourceRectangle.height +
|
---|
776 | (int)((popupBounds.height - sourceRectangle.height) * percent);
|
---|
777 |
|
---|
778 | int x = Math.min(oldBounds.x, currentRectangle.x);
|
---|
779 | int y = Math.min(oldBounds.y, currentRectangle.y);
|
---|
780 | int width = Math.min(oldBounds.x + oldBounds.width, currentRectangle.x + currentRectangle.width) - x;
|
---|
781 | int height = Math.min(oldBounds.y + oldBounds.height, currentRectangle.y + currentRectangle.height) - y;
|
---|
782 |
|
---|
783 | return new Rectangle(x, y, width, height);
|
---|
784 |
|
---|
785 | }
|
---|
786 |
|
---|
787 | public Rectangle getCurrentDrawingArea() {
|
---|
788 | return currentRectangle;
|
---|
789 | }
|
---|
790 |
|
---|
791 |
|
---|
792 | }
|
---|
793 |
|
---|
794 |
|
---|
795 | }
|
---|