[ui]: reuse Icon Wrapper from IconUtil and support onHover icon

* draw an appropriate icon depending on the MouseLocation;
* add an interface to detect if mouse position hovers its icon:
it can be done properly only with related renderer component;
* update Icons if action presentation icon changed;
* use hoverFlag instead of manually detecting in paintIcon;
* repaint listCell if needed;
This commit is contained in:
Nadya Zabrodina
2017-01-11 16:45:33 +03:00
parent 26315aa915
commit f6b58939b8
2 changed files with 142 additions and 26 deletions
@@ -48,15 +48,16 @@ import com.intellij.ui.FocusTrackback;
import com.intellij.ui.HintHint;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.popup.list.IconListPopupRenderer;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.ui.popup.mock.MockConfirmation;
import com.intellij.ui.popup.tree.TreePopupImpl;
import com.intellij.util.IconUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.WeakHashMap;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
@@ -69,14 +70,17 @@ import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.intellij.openapi.actionSystem.Presentation.PROP_HOVERED_ICON;
import static com.intellij.openapi.actionSystem.Presentation.PROP_ICON;
public class PopupFactoryImpl extends JBPopupFactory {
/**
@@ -232,6 +236,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
private final Runnable myDisposeCallback;
private final Component myComponent;
private final String myActionPlace;
private IconHoverListener myIconsHoverListener;
public ActionGroupPopup(final String title,
@NotNull ActionGroup actionGroup,
@@ -337,6 +342,8 @@ public class PopupFactoryImpl extends JBPopupFactory {
if (myDisposeCallback != null) {
myDisposeCallback.run();
}
getList().removeMouseMotionListener(myIconsHoverListener);
getList().removeListSelectionListener(myIconsHoverListener);
ActionMenu.showDescriptionInStatusBar(true, myComponent, null);
super.dispose();
}
@@ -381,6 +388,19 @@ public class PopupFactoryImpl extends JBPopupFactory {
getList().repaint();
}
public void installOnHoverIconsSupport(@NotNull IconListPopupRenderer iconListPopupRenderer) {
//OnHover icons listener should be installed once
assert myIconsHoverListener == null;
myIconsHoverListener = new IconHoverListener(iconListPopupRenderer);
}
@Override
protected boolean beforeShow() {
getList().addMouseMotionListener(myIconsHoverListener);
getList().addListSelectionListener(myIconsHoverListener);
return super.beforeShow();
}
@Nullable
private static <T> T getActionByClass(@Nullable Object value, @NotNull ActionPopupStep actionPopupStep, @NotNull Class<T> actionClass) {
ActionItem item = value instanceof ActionItem ? (ActionItem)value : null;
@@ -388,6 +408,42 @@ public class PopupFactoryImpl extends JBPopupFactory {
if (!actionPopupStep.isSelectable(item)) return null;
return actionClass.isInstance(item.getAction()) ? actionClass.cast(item.getAction()) : null;
}
private class IconHoverListener extends MouseMotionAdapter implements ListSelectionListener {
@NotNull private IconListPopupRenderer myRenderer;
public IconHoverListener(@NotNull IconListPopupRenderer renderer) {
myRenderer = renderer;
}
@Override
public void mouseMoved(MouseEvent e) {
Point point = e.getPoint();
int index = getList().locationToIndex(point);
Rectangle bounds = getList().getCellBounds(index, index);
Object selectedValue = getList().getSelectedValue();
if (selectedValue instanceof ActionItem) {
((ActionItem)selectedValue).setIconHovered(myRenderer.iconContains(point));
}
if (bounds != null) {
getList().repaint(bounds);
}
}
@Override
public void valueChanged(ListSelectionEvent e) {
if (!e.getValueIsAdjusting()) {
int selected = getSelectedIndex();
int unselected = e.getFirstIndex() == selected ? e.getLastIndex() : e.getFirstIndex();
Object elementAt = getList().getModel().getElementAt(unselected);
if (elementAt instanceof ActionItem) {
ActionItem actionItem = (ActionItem)elementAt;
actionItem.setIconHovered(false);
getList().repaint();
}
}
}
}
}
@NotNull
@@ -697,7 +753,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
private final AnAction myAction;
private final String myText;
private final boolean myIsEnabled;
private final Icon myIcon;
@Nullable private ActionStepBuilder.IconWrapper myIcon;
private final boolean myPrependWithSeparator;
private final String mySeparatorText;
private final String myDescription;
@@ -706,7 +762,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
@NotNull String text,
@Nullable String description,
boolean enabled,
Icon icon,
@Nullable ActionStepBuilder.IconWrapper icon,
final boolean prependWithSeparator,
String separatorText) {
myAction = action;
@@ -716,6 +772,21 @@ public class PopupFactoryImpl extends JBPopupFactory {
myPrependWithSeparator = prependWithSeparator;
mySeparatorText = separatorText;
myDescription = description;
myAction.getTemplatePresentation().addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == PROP_ICON || evt.getPropertyName() == PROP_HOVERED_ICON) {
updateIcons();
}
}
});
}
private void updateIcons() {
// we can't set icons if it hasn't existed before, because alignment will be destroyed; use IconWrapper with null icon instead of null
if (myIcon == null) return;
Presentation presentation = myAction.getTemplatePresentation();
myIcon.setIcons(presentation.getIcon(), presentation.getHoveredIcon());
}
@NotNull
@@ -728,7 +799,8 @@ public class PopupFactoryImpl extends JBPopupFactory {
return myText;
}
public Icon getIcon() {
@Nullable
public ActionStepBuilder.IconWrapper getIcon() {
return myIcon;
}
@@ -756,6 +828,12 @@ public class PopupFactoryImpl extends JBPopupFactory {
public String toString() {
return myText;
}
public void setIconHovered(boolean isHovered) {
if (myIcon != null) {
myIcon.setHovered(isHovered);
}
}
}
private static class ActionPopupStep implements ListPopupStepEx<ActionItem>, MnemonicNavigationFilter<ActionItem>, SpeedSearchFilter<ActionItem> {
@@ -966,7 +1044,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
private boolean myPrependWithSeparator;
private String mySeparatorText;
private final boolean myHonorActionMnemonics;
private Icon myEmptyIcon;
private IconWrapper myEmptyIcon;
private int myMaxIconWidth = -1;
private int myMaxIconHeight = -1;
@NotNull private String myActionPlace;
@@ -998,7 +1076,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
public void buildGroup(@NotNull ActionGroup actionGroup) {
calcMaxIconSize(actionGroup);
myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? EmptyIcon.create(myMaxIconWidth, myMaxIconHeight) : null;
myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? createWrapper(null) : null;
appendActionsFromGroup(actionGroup);
@@ -1094,26 +1172,27 @@ public class PopupFactoryImpl extends JBPopupFactory {
}
Icon icon = presentation.isEnabled() ? presentation.getIcon() : IconLoader.getDisabledIcon(presentation.getIcon());
if (icon == null) {
IconWrapper iconWrapper;
if (icon == null && presentation.getHoveredIcon() == null) {
@NonNls final String actionId = ActionManager.getInstance().getId(action);
if (actionId != null && actionId.startsWith("QuickList.")) {
icon = AllIcons.Actions.QuickList;
iconWrapper = createWrapper(AllIcons.Actions.QuickList);
}
else if (action instanceof Toggleable) {
boolean toggled = Boolean.TRUE.equals(presentation.getClientProperty(Toggleable.SELECTED_PROPERTY));
icon = toggled? new IconWrapper(PlatformIcons.CHECK_ICON) : myEmptyIcon;
iconWrapper = toggled ? createWrapper(PlatformIcons.CHECK_ICON) : myEmptyIcon;
}
else {
icon = myEmptyIcon;
iconWrapper = myEmptyIcon;
}
}
else {
icon = new IconWrapper(icon);
iconWrapper = new IconWrapper(icon, presentation.getHoveredIcon(), myMaxIconWidth, myMaxIconHeight);
}
boolean prependSeparator = (!myListModel.isEmpty() || mySeparatorText != null) && myPrependWithSeparator;
assert text != null : action + " has no presentation";
myListModel.add(
new ActionItem(action, text, (String)presentation.getClientProperty(JComponent.TOOL_TIP_TEXT_KEY), presentation.isEnabled(), icon,
new ActionItem(action, text, (String)presentation.getClientProperty(JComponent.TOOL_TIP_TEXT_KEY), presentation.isEnabled(), iconWrapper,
prependSeparator, mySeparatorText));
myPrependWithSeparator = false;
mySeparatorText = null;
@@ -1123,28 +1202,40 @@ public class PopupFactoryImpl extends JBPopupFactory {
/**
* Adjusts icon size to maximum, so that icons with different sizes were aligned correctly.
*/
private class IconWrapper implements Icon {
public static class IconWrapper extends IconUtil.IconSizeWrapper {
private Icon myIcon;
@Nullable private Icon myIcon;
@Nullable private Icon myHoverIcon;
IconWrapper(Icon icon) {
myIcon = icon;
private boolean isHovered;
public IconWrapper(@Nullable Icon icon, @Nullable Icon hoverIcon, int width, int height) {
super(null, width, height);
setIcons(icon, hoverIcon);
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
myIcon.paintIcon(c, g, x, y);
paintIcon(myHoverIcon != null && isHovered ? myHoverIcon : myIcon, c, g, x, y);
}
@Override
public int getIconWidth() {
return myMaxIconWidth;
public boolean isHovered() {
return isHovered;
}
@Override
public int getIconHeight() {
return myMaxIconHeight;
public void setHovered(boolean hovered) {
isHovered = hovered;
}
public void setIcons(@Nullable Icon icon, @Nullable Icon hoveredIcon) {
myIcon = icon;
myHoverIcon = hoveredIcon;
}
}
@NotNull
public IconWrapper createWrapper(@Nullable Icon icon) {
return new IconWrapper(icon, null, myMaxIconWidth, myMaxIconHeight);
}
private Presentation getPresentation(@NotNull AnAction action) {
@@ -0,0 +1,25 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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.intellij.ui.popup.list;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
public interface IconListPopupRenderer {
boolean iconContains(@NotNull Point point);
}