diff options
author | Jouni Koivuviita <jouni@vaadin.com> | 2014-06-05 17:23:01 +0300 |
---|---|---|
committer | Jouni Koivuviita <jouni@vaadin.com> | 2014-06-05 17:23:01 +0300 |
commit | 97ba8b0756b8f8ba02b2eda6857f9fda0f9b2cb2 (patch) | |
tree | c25a17dc98713fc57c38d7054a9d3cdd53a5e8bf | |
parent | dcb6cfd6bc050ad9451e14cb12345aa5a4c43727 (diff) | |
download | vaadin-framework-97ba8b0756b8f8ba02b2eda6857f9fda0f9b2cb2.tar.gz vaadin-framework-97ba8b0756b8f8ba02b2eda6857f9fda0f9b2cb2.zip |
Implement Notification animation with CSS (#13660)
Default opacity (90%), fade duration (400ms) and fade delay (1000ms) are
defined in the base theme.
“In” animations can be defined by using a dependent stylename
‘animate-in’ (prefixed with the widgets own primary stylename), e.g.
'v-Notification-animate-in'. That CSS class should then have a CSS
animation specified, and the CSS animation name should contain
'animate-in', e.g.
.v-Notification-animate-in {
animation: notification-animate-in 400ms;
}
@keyframes notification-animate-in {
...
}
“Out” animations are defined similarly, but with 'animate-out' as the
keyword.
The base theme contains mixins for declaring keyframes rules as well as
animation values, which include needed vendor prefixes.
Refactored deprecated GWT API’s to use newer API’s.
Change-Id: Iba929e8de8b6ce100852d472d65c9c9d0630c48f
-rw-r--r-- | WebContent/VAADIN/themes/base/base.scss | 1 | ||||
-rw-r--r-- | WebContent/VAADIN/themes/base/common/mixins.scss | 17 | ||||
-rw-r--r-- | WebContent/VAADIN/themes/base/notification/notification.scss | 14 | ||||
-rw-r--r-- | client/src/com/vaadin/client/AnimationUtil.java | 168 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/VNotification.java | 180 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/VOverlay.java | 157 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/VWindow.java | 20 | ||||
-rw-r--r-- | client/src/com/vaadin/client/ui/window/WindowConnector.java | 9 |
8 files changed, 421 insertions, 145 deletions
diff --git a/WebContent/VAADIN/themes/base/base.scss b/WebContent/VAADIN/themes/base/base.scss index 88a8bd9cb9..3570c5efec 100644 --- a/WebContent/VAADIN/themes/base/base.scss +++ b/WebContent/VAADIN/themes/base/base.scss @@ -1,3 +1,4 @@ +@import "common/mixins.scss"; @import "absolutelayout/absolutelayout.scss"; @import "accordion/accordion.scss"; @import "button/button.scss"; diff --git a/WebContent/VAADIN/themes/base/common/mixins.scss b/WebContent/VAADIN/themes/base/common/mixins.scss new file mode 100644 index 0000000000..79d26d6c16 --- /dev/null +++ b/WebContent/VAADIN/themes/base/common/mixins.scss @@ -0,0 +1,17 @@ +@mixin keyframes ($name) { + @-webkit-keyframes #{$name} { + @content; + } + @-moz-keyframes #{$name} { + @content; + } + @keyframes #{$name} { + @content; + } +} + +@mixin animation ($anim) { + -webkit-animation: $anim; + -moz-animation: $anim; + animation: $anim; +} diff --git a/WebContent/VAADIN/themes/base/notification/notification.scss b/WebContent/VAADIN/themes/base/notification/notification.scss index 9751422fb7..aa70a167dc 100644 --- a/WebContent/VAADIN/themes/base/notification/notification.scss +++ b/WebContent/VAADIN/themes/base/notification/notification.scss @@ -7,7 +7,8 @@ overflow: hidden; padding: 1em; max-width:85%; - + opacity: .9; + filter: alpha(opacity=90); } .#{$primaryStyleName} h1, .#{$primaryStyleName} p, @@ -39,5 +40,14 @@ display: block; margin: 0; } +.#{$primaryStyleName}-animate-out { + @include animation(animate-out 400ms 1s); +} + +} -}
\ No newline at end of file +@include keyframes(animate-out) { + 100% { + opacity: 0; + } +} diff --git a/client/src/com/vaadin/client/AnimationUtil.java b/client/src/com/vaadin/client/AnimationUtil.java new file mode 100644 index 0000000000..034ea659a9 --- /dev/null +++ b/client/src/com/vaadin/client/AnimationUtil.java @@ -0,0 +1,168 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.client; + +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style; + +/** + * Utility methods for working with CSS transitions and animations. + * + * @author Vaadin Ltd + */ +public class AnimationUtil { + + /** + * For internal use only. May be removed or replaced in the future. + * + * Set the animation-duration CSS property. + * + * @param elem + * the element whose animation-duration to set + * @param duration + * the duration as a valid CSS value + */ + public static void setAnimationDuration(Element elem, String duration) { + Style style = elem.getStyle(); + style.setProperty(ANIMATION_PROPERTY_NAME + "Duration", duration); + } + + /** + * For internal use only. May be removed or replaced in the future. + * + * Set the animation-delay CSS property. + * + * @param elem + * the element whose animation-delay to set + * @param delay + * the delay as a valid CSS value + */ + public static void setAnimationDelay(Element elem, String delay) { + Style style = elem.getStyle(); + style.setProperty(ANIMATION_PROPERTY_NAME + "Delay", delay); + } + + /** For internal use only. May be removed or replaced in the future. */ + public static native void registerAnimationEndEventListener(Element elem, + AnimationEndListener listener) + /*-{ + var callbackFunc = $entry(function(e) { + listener.@com.vaadin.client.AnimationUtil.AnimationEndListener::onAnimationEnd(Lcom/google/gwt/dom/client/NativeEvent;)(e); + }); + + elem.addEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbackFunc, false); + + // Store function reference for later removal + if(!elem._vaadin_animationend_callbacks) { + elem._vaadin_animationend_callbacks = []; + } + elem._vaadin_animationend_callbacks.push(callbackFunc); + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native void unregisterAnimationEndEventListeners(Element elem) + /*-{ + if(elem._vaadin_animationend_callbacks) { + var callbacks = elem._vaadin_animationend_callbacks; + for(var i=0; i < callbacks.length; i++) { + elem.removeEventListener(@com.vaadin.client.AnimationUtil::ANIMATION_END_EVENT_NAME, callbacks[i], false); + } + } + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public interface AnimationEndListener { + public void onAnimationEnd(NativeEvent event); + } + + /** For internal use only. May be removed or replaced in the future. */ + public static native String getAnimationName(NativeEvent event) + /*-{ + if(event.webkitAnimationName) + return event.webkitAnimationName; + else if(event.animationName) + return event.animationName; + else if(event.mozAnimationName) + return event.mozAnimationName; + else if(event.oAnimationName) + return event.oAnimationName; + + return ""; + }-*/; + + /** For internal use only. May be removed or replaced in the future. */ + public static native String getAnimationName(ComputedStyle cstyle) + /*-{ + var cs = cstyle.@com.vaadin.client.ComputedStyle::computedStyle; + + if(!cs.getPropertyValue) + return ""; + + if(cs.getPropertyValue("-webkit-animation-name")) + return cs.getPropertyValue("-webkit-animation-name"); + + else if(cs.getPropertyValue("animation-name")) + return cs.getPropertyValue("animation-name"); + + else if(cs.getPropertyValue("-moz-animation-name")) + return cs.getPropertyValue("-moz-animation-name"); + + else if(cs.getPropertyValue("-o-animation-name")) + return cs.getPropertyValue("-o-animation-name"); + + return ""; + }-*/; + + private static final String ANIMATION_END_EVENT_NAME = whichAnimationEndEvent(); + + private static native String whichAnimationEndEvent() + /*-{ + var el = document.createElement('fakeelement'); + var anims = { + 'animationName': 'animationend', + 'OAnimationName': 'oAnimationEnd', + 'MozAnimation': 'animationend', + 'WebkitAnimation': 'webkitAnimationEnd' + } + + for(var a in anims){ + if( el.style[a] !== undefined ){ + return anims[a]; + } + } + }-*/; + + private static final String ANIMATION_PROPERTY_NAME = whichAnimationProperty(); + + private static native String whichAnimationProperty() + /*-{ + var el = document.createElement('fakeelement'); + var anims = [ + 'animation', + 'oAnimation', + 'mozAnimation', + 'webkitAnimation' + ] + + for(var i=0; i < anims.length; i++) { + if( el.style[anims[i]] !== undefined ){ + return anims[i]; + } + } + }-*/; + +} diff --git a/client/src/com/vaadin/client/ui/VNotification.java b/client/src/com/vaadin/client/ui/VNotification.java index 93dc26f8be..48a3ba6df4 100644 --- a/client/src/com/vaadin/client/ui/VNotification.java +++ b/client/src/com/vaadin/client/ui/VNotification.java @@ -17,13 +17,12 @@ package com.vaadin.client.ui; import java.util.ArrayList; -import java.util.Date; import java.util.EventObject; import java.util.Iterator; import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.DOM; @@ -33,6 +32,7 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.AnimationUtil; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.UIDL; @@ -66,16 +66,11 @@ public class VNotification extends VOverlay { private static final int mouseMoveThreshold = 7; private static final int Z_INDEX_BASE = 20000; public static final String STYLE_SYSTEM = "system"; - private static final int FADE_ANIMATION_INTERVAL = 50; // == 20 fps private static final ArrayList<VNotification> notifications = new ArrayList<VNotification>(); - private int startOpacity = 90; - private int fadeMsec = 400; - private int delayMsec = 1000; - - private Timer fader; - private Timer delay; + private boolean infiniteDelay = false; + private int cssTransitionDelay = -1; private int x = -1; private int y = -1; @@ -103,13 +98,14 @@ public class VNotification extends VOverlay { @Deprecated public VNotification(int delayMsec) { this(); - this.delayMsec = delayMsec; + setDelay(delayMsec); + if (BrowserInfo.get().isTouchDevice()) { new Timer() { @Override public void run() { if (isAttached()) { - fade(); + hide(); } } }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); @@ -127,24 +123,17 @@ public class VNotification extends VOverlay { @Deprecated public VNotification(int delayMsec, int fadeMsec, int startOpacity) { this(delayMsec); - this.fadeMsec = fadeMsec; - this.startOpacity = startOpacity; + AnimationUtil.setAnimationDuration(getElement(), fadeMsec + "ms"); + getElement().getStyle().setOpacity(startOpacity / 100); } - public void startDelay() { - DOM.removeEventPreview(this); - if (delayMsec > 0) { - if (delay == null) { - delay = new Timer() { - @Override - public void run() { - fade(); - } - }; - delay.schedule(delayMsec); - } - } else if (delayMsec == 0) { - fade(); + private void setDelay(int delayMsec) { + if (delayMsec < 0) { + infiniteDelay = true; + cssTransitionDelay = 0; + } else { + infiniteDelay = false; + cssTransitionDelay = delayMsec; } } @@ -237,12 +226,17 @@ public class VNotification extends VOverlay { } public void show(Position position, String style) { - setOpacity(getElement(), startOpacity); + if (temporaryStyle != null) { + removeStyleName(temporaryStyle); + removeStyleDependentName(temporaryStyle); + temporaryStyle = null; + } if (style != null) { temporaryStyle = style; addStyleName(style); addStyleDependentName(style); } + super.show(); notifications.add(this); setPosition(position); @@ -259,97 +253,59 @@ public class VNotification extends VOverlay { @Override public void hide() { DOM.removeEventPreview(this); - cancelDelay(); - cancelFade(); - if (temporaryStyle != null) { - removeStyleName(temporaryStyle); - removeStyleDependentName(temporaryStyle); - temporaryStyle = null; + if (cssTransitionDelay >= 0) { + AnimationUtil.setAnimationDelay(getElement(), cssTransitionDelay + + "ms"); } super.hide(); notifications.remove(this); fireEvent(new HideEvent(this)); } - public void fade() { - DOM.removeEventPreview(this); - cancelDelay(); - if (fader == null) { - fader = new Timer() { - private final long start = new Date().getTime(); - - @Override - public void run() { - /* - * To make animation smooth, don't count that event happens - * on time. Reduce opacity according to the actual time - * spent instead of fixed decrement. - */ - long now = new Date().getTime(); - long timeEplaced = now - start; - float remainingFraction = 1 - timeEplaced - / (float) fadeMsec; - int opacity = (int) (startOpacity * remainingFraction); - if (opacity <= 0) { - cancel(); - hide(); - } else { - setOpacity(getElement(), opacity); - } - } - }; - fader.scheduleRepeating(FADE_ANIMATION_INTERVAL); - } - } - public void setPosition(com.vaadin.shared.Position position) { - final Element el = getElement(); - el.getStyle().clearTop(); - el.getStyle().clearLeft(); - el.getStyle().clearBottom(); - el.getStyle().clearRight(); + final Style style = getElement().getStyle(); + style.clearTop(); + style.clearRight(); + style.clearBottom(); + style.clearLeft(); switch (position) { case TOP_LEFT: - el.getStyle().setTop(0, Unit.PX); - el.getStyle().setLeft(0, Unit.PX); + style.setTop(0, Unit.PX); + style.setLeft(0, Unit.PX); break; case TOP_RIGHT: - el.getStyle().setTop(0, Unit.PX); - el.getStyle().setRight(0, Unit.PX); + style.setTop(0, Unit.PX); + style.setRight(0, Unit.PX); break; case MIDDLE_LEFT: center(); - el.getStyle().setLeft(0, Unit.PX); + style.setLeft(0, Unit.PX); break; case MIDDLE_RIGHT: center(); - el.getStyle().clearLeft(); - el.getStyle().setRight(0, Unit.PX); + style.clearLeft(); + style.setRight(0, Unit.PX); break; case BOTTOM_RIGHT: - // Avoiding strings would be ugly since another Position is imported - // TODO this is most likely redundant - el.getStyle().setProperty("position", "absolute"); - - el.getStyle().setBottom(0, Unit.PX); - el.getStyle().setRight(0, Unit.PX); + style.setBottom(0, Unit.PX); + style.setRight(0, Unit.PX); break; case BOTTOM_LEFT: - el.getStyle().setBottom(0, Unit.PX); - el.getStyle().setLeft(0, Unit.PX); + style.setBottom(0, Unit.PX); + style.setLeft(0, Unit.PX); break; case TOP_CENTER: center(); - el.getStyle().setTop(0, Unit.PX); + style.setTop(0, Unit.PX); break; case BOTTOM_CENTER: center(); - el.getStyle().clearTop(); - el.getStyle().setBottom(0, Unit.PX); + style.clearTop(); + style.setBottom(0, Unit.PX); break; case ASSISTIVE: - el.getStyle().setTop(-2000, Unit.PX); - el.getStyle().setLeft(-2000, Unit.PX); + style.setTop(-2000, Unit.PX); + style.setLeft(-2000, Unit.PX); break; default: case MIDDLE_CENTER: @@ -358,49 +314,25 @@ public class VNotification extends VOverlay { } } - private void cancelFade() { - if (fader != null) { - fader.cancel(); - fader = null; - } - } - - private void cancelDelay() { - if (delay != null) { - delay.cancel(); - delay = null; - } - } - - private void setOpacity(Element el, int opacity) { - el.getStyle().setOpacity(opacity / 100.0); - if (BrowserInfo.get().isIE()) { - el.getStyle().setProperty("filter", - "Alpha(opacity=" + opacity + ")"); - } - } - @Override public void onBrowserEvent(Event event) { DOM.removeEventPreview(this); - if (fader == null) { - fade(); - } + hide(); } @Override public boolean onEventPreview(Event event) { int type = DOM.eventGetType(event); // "modal" - if (delayMsec == -1 || temporaryStyle == STYLE_SYSTEM) { + if (infiniteDelay || temporaryStyle == STYLE_SYSTEM) { if (type == Event.ONCLICK) { if (DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) { - fade(); + hide(); return false; } } else if (type == Event.ONKEYDOWN && event.getKeyCode() == KeyCodes.KEY_ESCAPE) { - fade(); + hide(); return false; } if (temporaryStyle == STYLE_SYSTEM) { @@ -418,19 +350,19 @@ public class VNotification extends VOverlay { y = DOM.eventGetClientY(event); } else if (Math.abs(DOM.eventGetClientX(event) - x) > mouseMoveThreshold || Math.abs(DOM.eventGetClientY(event) - y) > mouseMoveThreshold) { - startDelay(); + hide(); } break; case Event.ONMOUSEDOWN: case Event.ONMOUSEWHEEL: case Event.ONSCROLL: - startDelay(); + hide(); break; case Event.ONKEYDOWN: if (event.getRepeat()) { return true; } - startDelay(); + hide(); break; default: break; @@ -511,17 +443,17 @@ public class VNotification extends VOverlay { public static VNotification createNotification(int delayMsec, Widget owner) { final VNotification notification = GWT.create(VNotification.class); notification.setWaiAriaRole(null); + notification.setDelay(delayMsec); - notification.delayMsec = delayMsec; if (BrowserInfo.get().isTouchDevice()) { new Timer() { @Override public void run() { if (notification.isAttached()) { - notification.fade(); + notification.hide(); } } - }.schedule(notification.delayMsec + TOUCH_DEVICE_IDLE_DELAY); + }.schedule(delayMsec + TOUCH_DEVICE_IDLE_DELAY); } notification.setOwner(owner); return notification; diff --git a/client/src/com/vaadin/client/ui/VOverlay.java b/client/src/com/vaadin/client/ui/VOverlay.java index aca016a90f..ab493ce383 100644 --- a/client/src/com/vaadin/client/ui/VOverlay.java +++ b/client/src/com/vaadin/client/ui/VOverlay.java @@ -16,11 +16,15 @@ package com.vaadin.client.ui; +import java.util.logging.Level; +import java.util.logging.Logger; + import com.google.gwt.animation.client.Animation; import com.google.gwt.aria.client.Roles; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.IFrameElement; +import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.BorderStyle; import com.google.gwt.dom.client.Style.Display; @@ -33,11 +37,13 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.Widget; +import com.vaadin.client.AnimationUtil; +import com.vaadin.client.AnimationUtil.AnimationEndListener; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; +import com.vaadin.client.ComputedStyle; import com.vaadin.client.Util; -import com.vaadin.client.VConsole; /** * <p> @@ -158,6 +164,9 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { */ public static final String CLASSNAME_CONTAINER = "v-overlay-container"; + private static final String ADDITIONAL_CLASSNAME_ANIMATE_IN = "animate-in"; + private static final String ADDITIONAL_CLASSNAME_ANIMATE_OUT = "animate-out"; + /** * The shadow element for this overlay. * @@ -423,8 +432,10 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { @Override public void show() { current = this; - super.show(); - if (isAnimationEnabled()) { + + boolean hasAnimationIn = maybeShowWithAnimation(); + + if (isAnimationEnabled() && !hasAnimationIn) { new ResizeAnimation().run(POPUP_PANEL_ANIMATION_DURATION); } else { positionOrSizeUpdated(1.0); @@ -432,6 +443,44 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { current = null; } + private boolean maybeShowWithAnimation() { + boolean isAttached = isAttached() && isShowing(); + super.show(); + + // Don't animate if already visible or browser is IE8 or IE9 (no CSS + // animation support) + if (isAttached || BrowserInfo.get().isIE8() + || BrowserInfo.get().isIE9()) { + return false; + } else { + // Check if animations are used + addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.addClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + + ComputedStyle cs = new ComputedStyle(getElement()); + String animationName = AnimationUtil.getAnimationName(cs); + if (animationName == null) { + animationName = ""; + } + + if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + AnimationUtil.registerAnimationEndEventListener(getElement(), + getAnimationEndListener(false)); + return true; + } else { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + return false; + } + } + } + @Override protected void onDetach() { super.onDetach(); @@ -519,17 +568,17 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { return; } // Calculate proper z-index - String zIndex = null; + int zIndex = -1; try { // Odd behaviour with Windows Hosted Mode forces us to use // this redundant try/catch block (See dev.vaadin.com #2011) - zIndex = DOM.getStyleAttribute(getElement(), "zIndex"); + zIndex = Integer.parseInt(getElement().getStyle().getZIndex()); } catch (Exception ignore) { // Ignored, will cause no harm - zIndex = "1000"; + zIndex = 1000; } - if (zIndex == null) { - zIndex = "" + Z_INDEX; + if (zIndex == -1) { + zIndex = Z_INDEX; } // Calculate position and size if (BrowserInfo.get().isIE()) { @@ -570,7 +619,7 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { * @deprecated See main JavaDoc for VOverlay */ @Deprecated - private void updateShadowPosition(final double progress, String zIndex, + private void updateShadowPosition(final double progress, int zIndex, PositionAndSize positionAndSize) { // Opera needs some shaking to get parts of the shadow showing // properly (ticket #2704) @@ -582,12 +631,8 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { } updatePositionAndSize(shadow, positionAndSize); - shadow.getStyle().setProperty("zIndex", zIndex); - if (progress < 0.9) { - shadow.getStyle().setDisplay(Display.NONE); - } else { - shadow.getStyle().clearDisplay(); - } + shadow.getStyle().setZIndex(zIndex); + shadow.getStyle().setProperty("display", progress < 0.9 ? "none" : ""); // Opera fix, part 2 (ticket #2704) if (BrowserInfo.get().isOpera()) { @@ -758,7 +803,9 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { if (ac == null) { // could not figure out which one we belong to, styling will // probably fail - VConsole.error("Could not determine ApplicationConnection for Overlay. Overlay will be attached directly to the root panel"); + Logger.getLogger(getClass().getSimpleName()) + .log(Level.WARNING, + "Could not determine ApplicationConnection for Overlay. Overlay will be attached directly to the root panel"); return RootPanel.get().getElement(); } else { return getOverlayContainer(ac); @@ -873,4 +920,80 @@ public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> { return $wnd.innerHeight !== undefined ? $wnd.innerHeight :-1; }-*/; -} + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.PopupPanel#hide() + */ + @Override + public void hide() { + hide(false); + } + + /* + * (non-Javadoc) + * + * @see com.google.gwt.user.client.ui.PopupPanel#hide(boolean) + */ + @Override + public void hide(boolean autoClosed) { + if (BrowserInfo.get().isIE8() || BrowserInfo.get().isIE9()) { + super.hide(autoClosed); + } else { + // Check if animations are used + addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.addClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + ComputedStyle cs = new ComputedStyle(getElement()); + String animationName = AnimationUtil.getAnimationName(cs); + if (animationName == null) { + animationName = ""; + } + + if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + AnimationUtil.registerAnimationEndEventListener(getElement(), + getAnimationEndListener(autoClosed)); + // No event previews should happen after the animation has + // started + VOverlay.this.setPreviewingAllNativeEvents(false); + } else { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + super.hide(autoClosed); + } + } + } + + private AnimationEndListener getAnimationEndListener( + final boolean autoClosed) { + return new AnimationEndListener() { + + @Override + public void onAnimationEnd(NativeEvent event) { + AnimationUtil + .unregisterAnimationEndEventListeners(getElement()); + String animationName = AnimationUtil.getAnimationName(event); + if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_IN); + } + } else if (animationName + .contains(ADDITIONAL_CLASSNAME_ANIMATE_OUT)) { + removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_OUT); + if (isShadowEnabled()) { + shadow.removeClassName(CLASSNAME_SHADOW + "-" + + ADDITIONAL_CLASSNAME_ANIMATE_OUT); + } + VOverlay.super.hide(autoClosed); + } + } + }; + } +}
\ No newline at end of file diff --git a/client/src/com/vaadin/client/ui/VWindow.java b/client/src/com/vaadin/client/ui/VWindow.java index 9b1f7a6f3c..e2cc33744e 100644 --- a/client/src/com/vaadin/client/ui/VWindow.java +++ b/client/src/com/vaadin/client/ui/VWindow.java @@ -30,6 +30,7 @@ import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Node; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.dom.client.Style.Position; @@ -200,6 +201,9 @@ public class VWindow extends VWindowOverlay implements /** For internal use only. May be removed or replaced in the future. */ public int bringToFrontSequence = -1; + /** For internal use only. May be removed or replaced in the future. */ + public Node windowClone; + private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200, new ScheduledCommand() { @@ -303,7 +307,10 @@ public class VWindow extends VWindowOverlay implements } private static VWindow getTopmostWindow() { - return windowOrder.get(windowOrder.size() - 1); + if (windowOrder.size() > 0) { + return windowOrder.get(windowOrder.size() - 1); + } + return null; } /** For internal use only. May be removed or replaced in the future. */ @@ -1030,6 +1037,15 @@ public class VWindow extends VWindowOverlay implements } private void onCloseClick() { + // Take a copy of the contents, since the server will detach all + // children of this window, and the window will be emptied during the + // next hierarchy update (we need to keep the contents visible for the + // duration of a possible 'out-animation') + // This is used by the WindowConnector in the case the window is + // actually closed and removed from the UI + windowClone = getElement().getFirstChild().cloneNode(true); + + // Send the close event to the server client.updateVariable(id, "close", true, true); } @@ -1263,7 +1279,7 @@ public class VWindow extends VWindowOverlay implements // are not cancelled here and target this window to be consume():d // meaning the event won't be sent to the rest of the preview handlers. - if (getTopmostWindow().vaadinModality) { + if (getTopmostWindow() != null && getTopmostWindow().vaadinModality) { // Topmost window is modal. Cancel the event if it targets something // outside that window (except debug console...) if (DOM.getCaptureElement() != null) { diff --git a/client/src/com/vaadin/client/ui/window/WindowConnector.java b/client/src/com/vaadin/client/ui/window/WindowConnector.java index 5c8f5e2d2d..40185099d3 100644 --- a/client/src/com/vaadin/client/ui/window/WindowConnector.java +++ b/client/src/com/vaadin/client/ui/window/WindowConnector.java @@ -192,6 +192,15 @@ public class WindowConnector extends AbstractSingleComponentContainerConnector public void onConnectorHierarchyChange(ConnectorHierarchyChangeEvent event) { // We always have 1 child, unless the child is hidden getWidget().contentPanel.setWidget(getContentWidget()); + + // If the window is removed from the UI, add the copy of the contents to + // the window (in case of an 'out-animation') + if (getParent() == null) { + getWidget().getElement().removeAllChildren(); + getWidget().getElement().appendChild(getWidget().windowClone); + } + // Clean reference + getWidget().windowClone = null; } @Override |