Ignore:
Timestamp:
05/10/18 16:04:51 (6 years ago)
Author:
davidb
Message:

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/expeditee/gui/PopupManager.java

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