diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeMenuBar.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeMenuBar.java index 808b987c79e4..387caa553b5d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeMenuBar.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeMenuBar.java @@ -29,11 +29,21 @@ import com.intellij.openapi.actionSystem.impl.WeakTimerListener; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.ui.ScreenUtil; +import com.intellij.util.ui.Animator; import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.AWTEventListener; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.geom.AffineTransform; import java.util.ArrayList; /** @@ -43,6 +53,17 @@ import java.util.ArrayList; * @author Vladimir Kondratyev */ public class IdeMenuBar extends JMenuBar { + + private static final int COLLAPSED_HEIGHT = 2; + + private enum State { + EXPANDED, COLLAPSING, COLLAPSED, EXPANDING, TEMPORARY_EXPANDED; + + boolean isInProgress() { + return this == COLLAPSING || this == EXPANDING; + } + } + private final MyTimerListener myTimerListener; private ArrayList myVisibleActions; private ArrayList myNewVisibleActions; @@ -52,6 +73,13 @@ public class IdeMenuBar extends JMenuBar { private final Disposable myDisposable = Disposer.newDisposable(); private boolean DISABLED = false; + @Nullable private final Animator myAnimator; + @Nullable private final Timer myActivationWatcher; + @NotNull private State myState = State.EXPANDED; + private double myProgress = 0; + private boolean myMouseInside = false; + private boolean myActivated = false; + public IdeMenuBar(ActionManagerEx actionManager, DataManager dataManager) { myActionManager = actionManager; myTimerListener = new MyTimerListener(); @@ -59,6 +87,121 @@ public class IdeMenuBar extends JMenuBar { myNewVisibleActions = new ArrayList(); myPresentationFactory = new MenuItemPresentationFactory(); myDataManager = dataManager; + if (SystemInfo.isWindows) { + myAnimator = new Animator("MenuBarAnimator", 8, 300, false) { + @Override + public void paintNow(int frame, int totalFrames, int cycle) { + myProgress = (1 - Math.cos(Math.PI * ((float)frame / totalFrames))) / 2; + revalidate(); + repaint(); + } + + @Override + protected void paintCycleEnd() { + myProgress = 1; + switch (myState) { + case COLLAPSING: + setState(State.COLLAPSED); + break; + case EXPANDING: + setState(State.TEMPORARY_EXPANDED); + break; + default: + } + revalidate(); + repaint(); + } + }; + Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() { + @Override + public void eventDispatched(AWTEvent event) { + MouseEvent mouseEvent = (MouseEvent)event; + Component component = mouseEvent.getComponent(); + Component deepestComponent = SwingUtilities.getDeepestComponentAt(mouseEvent.getComponent(), mouseEvent.getX(), mouseEvent.getY()); + if (deepestComponent != null) { + component = deepestComponent; + } + + if (myState != State.EXPANDED && !myState.isInProgress()) { + myMouseInside = myActivated || isDescendingFrom(component, IdeMenuBar.this); + if (myMouseInside && myState == State.COLLAPSED) { + setState(State.EXPANDING); + restartAnimator(); + } + else if (!myMouseInside && myState != State.COLLAPSING && myState != State.COLLAPSED) { + setState(State.COLLAPSING); + restartAnimator(); + } + } + } + }, AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK); + myActivationWatcher = new Timer(100, new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + if (myState == State.EXPANDED || myState == State.EXPANDING) + return; + boolean activated = isActivated(); + if (myActivated && !activated && myState == State.TEMPORARY_EXPANDED) { + myActivated = false; + setState(State.COLLAPSING); + restartAnimator(); + } + if (activated) + myActivated = true; + } + }); + } + else { + myAnimator = null; + myActivationWatcher = null; + } + } + + private static boolean isDescendingFrom(@Nullable Component a, @NotNull Component b) { + while (a != null) { + if (a == b) { + return true; + } + + if (a instanceof JPopupMenu) { + a = ((JPopupMenu)a).getInvoker(); + } else { + a = a.getParent(); + } + } + return false; + } + + private boolean isActivated() { + int index = getSelectionModel().getSelectedIndex(); + if (index ==-1) + return false; + return getMenu(index).isPopupMenuVisible(); + } + + + private void setState(@NotNull State state) { + myState = state; + } + + @Override + public Dimension getPreferredSize() { + Dimension dimension = super.getPreferredSize(); + if (myState.isInProgress()) { + dimension.height = + COLLAPSED_HEIGHT + (int)((myState == State.COLLAPSING ? (1 - myProgress) : myProgress) * (dimension.height - COLLAPSED_HEIGHT)); + } + else if (myState == State.COLLAPSED) { + dimension.height = COLLAPSED_HEIGHT; + } + return dimension; + } + + private void restartAnimator() { + if (myAnimator != null) { + myAnimator.reset(); + myAnimator.resume(); + } } /** @@ -68,8 +211,24 @@ public class IdeMenuBar extends JMenuBar { public void addNotify() { super.addNotify(); updateMenuActions(); - if (!ScreenUtil.isStandardAddRemoveNotify(this)) + if (!ScreenUtil.isStandardAddRemoveNotify(this)) { + Window window = SwingUtilities.getWindowAncestor(this); + if (window instanceof IdeFrameImpl) { + boolean fullScreen = WindowManagerEx.getInstanceEx().isFullScreen((IdeFrameImpl)window); + if (fullScreen) { + setState(State.COLLAPSING); + restartAnimator(); + } else { + if (myAnimator != null) { + myAnimator.suspend(); + } + setState(State.EXPANDED); + revalidate(); + repaint(); + } + } return; + } // Add updater for menus myActionManager.addTimerListener(1000, new WeakTimerListener(myActionManager, myTimerListener)); UISettingsListener UISettingsListener = new UISettingsListener() { @@ -80,6 +239,9 @@ public class IdeMenuBar extends JMenuBar { }; UISettings.getInstance().addUISettingsListener(UISettingsListener, myDisposable); Disposer.register(ApplicationManager.getApplication(), myDisposable); + if (myActivationWatcher != null) { + myActivationWatcher.start(); + } } /** @@ -88,6 +250,12 @@ public class IdeMenuBar extends JMenuBar { @Override public void removeNotify() { if (ScreenUtil.isStandardAddRemoveNotify(this)) { + if (myActivationWatcher != null) { + myActivationWatcher.stop(); + } + if (myAnimator != null) { + myAnimator.suspend(); + } Disposer.dispose(myDisposable); } super.removeNotify(); @@ -144,6 +312,21 @@ public class IdeMenuBar extends JMenuBar { } } + @Override + protected void paintChildren(Graphics g) { + if (SystemInfo.isWindows && myState.isInProgress()) { + Graphics2D g2 = (Graphics2D)g; + AffineTransform oldTransform = g2.getTransform(); + AffineTransform newTransform = oldTransform != null ? new AffineTransform(oldTransform) : new AffineTransform(); + newTransform.concatenate(AffineTransform.getTranslateInstance(0, getHeight() - super.getPreferredSize().height)); + g2.setTransform(newTransform); + super.paintChildren(g2); + g2.setTransform(oldTransform); + } else { + super.paintChildren(g); + } + } + /** * Hacks a problem under Alloy LaF which draws menu bar in different background menu items are drawn in. */