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