1 /* 2 * Copyright (c) 2006, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.java.swing.plaf.windows; 27 28 import java.security.AccessController; 29 import sun.security.action.GetBooleanAction; 30 31 import java.util.*; 32 import java.beans.PropertyChangeListener; 33 import java.beans.PropertyChangeEvent; 34 import java.awt.*; 35 import java.awt.event.*; 36 import javax.swing.*; 37 38 39 40 import sun.swing.UIClientPropertyKey; 41 import com.sun.java.swing.plaf.windows.TMSchema.State; 42 import static com.sun.java.swing.plaf.windows.TMSchema.State.*; 43 import com.sun.java.swing.plaf.windows.TMSchema.Part; 44 import com.sun.java.swing.plaf.windows.TMSchema.Prop; 45 import com.sun.java.swing.plaf.windows.XPStyle.Skin; 46 47 import sun.awt.AppContext; 48 49 /** 50 * A class to help mimic Vista theme animations. The only kind of 51 * animation it handles for now is 'transition' animation (this seems 52 * to be the only animation which Vista theme can do). This is when 53 * one picture fadein over another one in some period of time. 54 * According to 55 * https://connect.microsoft.com/feedback/ViewFeedback.aspx?FeedbackID=86852&SiteID=4 56 * The animations are all linear. 57 * 58 * This class has a number of responsibilities. 59 * <ul> 60 * <li> It trigger rapaint for the UI components involved in the animation 61 * <li> It tracks the animation state for every UI component involved in the 62 * animation and paints {@code Skin} in new {@code State} over the 63 * {@code Skin} in last {@code State} using 64 * {@code AlphaComposite.SrcOver.derive(alpha)} where {code alpha} 65 * depends on the state of animation 66 * </ul> 67 * 68 * @author Igor Kushnirskiy 69 */ 70 class AnimationController implements ActionListener, PropertyChangeListener { 71 72 private final static boolean VISTA_ANIMATION_DISABLED = 73 AccessController.doPrivileged(new GetBooleanAction("swing.disablevistaanimation")); 74 75 76 private final static Object ANIMATION_CONTROLLER_KEY = 77 new StringBuilder("ANIMATION_CONTROLLER_KEY"); 78 79 private final Map<JComponent, Map<Part, AnimationState>> animationStateMap = 80 new WeakHashMap<JComponent, Map<Part, AnimationState>>(); 81 82 //this timer is used to cause repaint on animated components 83 //30 repaints per second should give smooth animation affect 84 private final javax.swing.Timer timer = 85 new javax.swing.Timer(1000/30, this); 86 87 private static synchronized AnimationController getAnimationController() { 88 AppContext appContext = AppContext.getAppContext(); 89 Object obj = appContext.get(ANIMATION_CONTROLLER_KEY); 90 if (obj == null) { 91 obj = new AnimationController(); 92 appContext.put(ANIMATION_CONTROLLER_KEY, obj); 93 } 94 return (AnimationController) obj; 95 } 96 97 private AnimationController() { 98 timer.setRepeats(true); 99 timer.setCoalesce(true); 100 //we need to dispose the controller on l&f change 101 UIManager.addPropertyChangeListener(this); 102 } 103 104 private static void triggerAnimation(JComponent c, 105 Part part, State newState) { 106 if (c instanceof javax.swing.JTabbedPane 107 || part == Part.TP_BUTTON) { 108 //idk: we can not handle tabs animation because 109 //the same (component,part) is used to handle all the tabs 110 //and we can not track the states 111 //Vista theme might have transition duration for toolbar buttons 112 //but native application does not seem to animate them 113 return; 114 } 115 AnimationController controller = 116 AnimationController.getAnimationController(); 117 State oldState = controller.getState(c, part); 118 if (oldState != newState) { 119 controller.putState(c, part, newState); 120 if (newState == State.DEFAULTED) { 121 // it seems for DEFAULTED button state Vista does animation from 122 // HOT 123 oldState = State.HOT; 124 } 125 if (oldState != null) { 126 long duration; 127 if (newState == State.DEFAULTED) { 128 //Only button might have DEFAULTED state 129 //idk: do not know how to get the value from Vista 130 //one second seems plausible value 131 duration = 1000; 132 } else { 133 XPStyle xp = XPStyle.getXP(); 134 duration = (xp != null) 135 ? xp.getThemeTransitionDuration( 136 c, part, 137 normalizeState(oldState), 138 normalizeState(newState), 139 Prop.TRANSITIONDURATIONS) 140 : 1000; 141 } 142 controller.startAnimation(c, part, oldState, newState, duration); 143 } 144 } 145 } 146 147 // for scrollbar up, down, left and right button pictures are 148 // defined by states. It seems that theme has duration defined 149 // only for up button states thus we doing this translation here. 150 private static State normalizeState(State state) { 151 State rv; 152 switch (state) { 153 case DOWNPRESSED: 154 /* falls through */ 155 case LEFTPRESSED: 156 /* falls through */ 157 case RIGHTPRESSED: 158 rv = UPPRESSED; 159 break; 160 161 case DOWNDISABLED: 162 /* falls through */ 163 case LEFTDISABLED: 164 /* falls through */ 165 case RIGHTDISABLED: 166 rv = UPDISABLED; 167 break; 168 169 case DOWNHOT: 170 /* falls through */ 171 case LEFTHOT: 172 /* falls through */ 173 case RIGHTHOT: 174 rv = UPHOT; 175 break; 176 177 case DOWNNORMAL: 178 /* falls through */ 179 case LEFTNORMAL: 180 /* falls through */ 181 case RIGHTNORMAL: 182 rv = UPNORMAL; 183 break; 184 185 default : 186 rv = state; 187 break; 188 } 189 return rv; 190 } 191 192 private synchronized State getState(JComponent component, Part part) { 193 State rv = null; 194 Object tmpObject = 195 component.getClientProperty(PartUIClientPropertyKey.getKey(part)); 196 if (tmpObject instanceof State) { 197 rv = (State) tmpObject; 198 } 199 return rv; 200 } 201 202 private synchronized void putState(JComponent component, Part part, 203 State state) { 204 component.putClientProperty(PartUIClientPropertyKey.getKey(part), 205 state); 206 } 207 208 private synchronized void startAnimation(JComponent component, 209 Part part, 210 State startState, 211 State endState, 212 long millis) { 213 boolean isForwardAndReverse = false; 214 if (endState == State.DEFAULTED) { 215 isForwardAndReverse = true; 216 } 217 Map<Part, AnimationState> map = animationStateMap.get(component); 218 if (millis <= 0) { 219 if (map != null) { 220 map.remove(part); 221 if (map.size() == 0) { 222 animationStateMap.remove(component); 223 } 224 } 225 return; 226 } 227 if (map == null) { 228 map = new EnumMap<Part, AnimationState>(Part.class); 229 animationStateMap.put(component, map); 230 } 231 map.put(part, 232 new AnimationState(startState, millis, isForwardAndReverse)); 233 if (! timer.isRunning()) { 234 timer.start(); 235 } 236 } 237 238 static void paintSkin(JComponent component, Skin skin, 239 Graphics g, int dx, int dy, int dw, int dh, State state) { 240 if (VISTA_ANIMATION_DISABLED) { 241 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 242 return; 243 } 244 triggerAnimation(component, skin.part, state); 245 AnimationController controller = getAnimationController(); 246 synchronized (controller) { 247 AnimationState animationState = null; 248 Map<Part, AnimationState> map = 249 controller.animationStateMap.get(component); 250 if (map != null) { 251 animationState = map.get(skin.part); 252 } 253 if (animationState != null) { 254 animationState.paintSkin(skin, g, dx, dy, dw, dh, state); 255 } else { 256 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 257 } 258 } 259 } 260 261 public synchronized void propertyChange(PropertyChangeEvent e) { 262 if ("lookAndFeel" == e.getPropertyName() 263 && ! (e.getNewValue() instanceof WindowsLookAndFeel) ) { 264 dispose(); 265 } 266 } 267 268 public synchronized void actionPerformed(ActionEvent e) { 269 java.util.List<JComponent> componentsToRemove = null; 270 java.util.List<Part> partsToRemove = null; 271 for (JComponent component : animationStateMap.keySet()) { 272 component.repaint(); 273 if (partsToRemove != null) { 274 partsToRemove.clear(); 275 } 276 Map<Part, AnimationState> map = animationStateMap.get(component); 277 if (! component.isShowing() 278 || map == null 279 || map.size() == 0) { 280 if (componentsToRemove == null) { 281 componentsToRemove = new ArrayList<JComponent>(); 282 } 283 componentsToRemove.add(component); 284 continue; 285 } 286 for (Part part : map.keySet()) { 287 if (map.get(part).isDone()) { 288 if (partsToRemove == null) { 289 partsToRemove = new ArrayList<Part>(); 290 } 291 partsToRemove.add(part); 292 } 293 } 294 if (partsToRemove != null) { 295 if (partsToRemove.size() == map.size()) { 296 //animation is done for the component 297 if (componentsToRemove == null) { 298 componentsToRemove = new ArrayList<JComponent>(); 299 } 300 componentsToRemove.add(component); 301 } else { 302 for (Part part : partsToRemove) { 303 map.remove(part); 304 } 305 } 306 } 307 } 308 if (componentsToRemove != null) { 309 for (JComponent component : componentsToRemove) { 310 animationStateMap.remove(component); 311 } 312 } 313 if (animationStateMap.size() == 0) { 314 timer.stop(); 315 } 316 } 317 318 private synchronized void dispose() { 319 timer.stop(); 320 UIManager.removePropertyChangeListener(this); 321 synchronized (AnimationController.class) { 322 AppContext.getAppContext() 323 .put(ANIMATION_CONTROLLER_KEY, null); 324 } 325 } 326 327 private static class AnimationState { 328 private final State startState; 329 330 //animation duration in nanoseconds 331 private final long duration; 332 333 //animatin start time in nanoseconds 334 private long startTime; 335 336 //direction the alpha value is changing 337 //forward - from 0 to 1 338 //!forward - from 1 to 0 339 private boolean isForward = true; 340 341 //if isForwardAndReverse the animation continually goes 342 //forward and reverse. alpha value is changing from 0 to 1 then 343 //from 1 to 0 and so forth 344 private boolean isForwardAndReverse; 345 346 private float progress; 347 348 AnimationState(final State startState, 349 final long milliseconds, 350 boolean isForwardAndReverse) { 351 assert startState != null && milliseconds > 0; 352 assert SwingUtilities.isEventDispatchThread(); 353 354 this.startState = startState; 355 this.duration = milliseconds * 1000000; 356 this.startTime = System.nanoTime(); 357 this.isForwardAndReverse = isForwardAndReverse; 358 progress = 0f; 359 } 360 private void updateProgress() { 361 assert SwingUtilities.isEventDispatchThread(); 362 363 if (isDone()) { 364 return; 365 } 366 long currentTime = System.nanoTime(); 367 368 progress = ((float) (currentTime - startTime)) 369 / duration; 370 progress = Math.max(progress, 0); //in case time was reset 371 if (progress >= 1) { 372 progress = 1; 373 if (isForwardAndReverse) { 374 startTime = currentTime; 375 progress = 0; 376 isForward = ! isForward; 377 } 378 } 379 } 380 void paintSkin(Skin skin, Graphics _g, 381 int dx, int dy, int dw, int dh, State state) { 382 assert SwingUtilities.isEventDispatchThread(); 383 384 updateProgress(); 385 if (! isDone()) { 386 Graphics2D g = (Graphics2D) _g.create(); 387 skin.paintSkinRaw(g, dx, dy, dw, dh, startState); 388 float alpha; 389 if (isForward) { 390 alpha = progress; 391 } else { 392 alpha = 1 - progress; 393 } 394 g.setComposite(AlphaComposite.SrcOver.derive(alpha)); 395 skin.paintSkinRaw(g, dx, dy, dw, dh, state); 396 g.dispose(); 397 } else { 398 skin.paintSkinRaw(_g, dx, dy, dw, dh, state); 399 } 400 } 401 boolean isDone() { 402 assert SwingUtilities.isEventDispatchThread(); 403 404 return progress >= 1; 405 } 406 } 407 408 private static class PartUIClientPropertyKey 409 implements UIClientPropertyKey { 410 411 private static final Map<Part, PartUIClientPropertyKey> map = 412 new EnumMap<Part, PartUIClientPropertyKey>(Part.class); 413 414 static synchronized PartUIClientPropertyKey getKey(Part part) { 415 PartUIClientPropertyKey rv = map.get(part); 416 if (rv == null) { 417 rv = new PartUIClientPropertyKey(part); 418 map.put(part, rv); 419 } 420 return rv; 421 } 422 423 private final Part part; 424 private PartUIClientPropertyKey(Part part) { 425 this.part = part; 426 } 427 public String toString() { 428 return part.toString(); 429 } 430 } 431 }