Browse Source

Improved rendering for popupmenus that are too large for the screen.

pull/130/head
weisj 5 years ago
parent
commit
a3587d02db
  1. 125
      core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java
  2. 4
      core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java
  3. 1
      core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java
  4. 37
      core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java
  5. 139
      core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java
  6. 4
      core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java
  7. 32
      core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java
  8. 41
      core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java

125
core/src/main/java/com/github/weisj/darklaf/components/ScrollPopupMenu.java

@ -23,12 +23,10 @@
*/
package com.github.weisj.darklaf.components;
import com.github.weisj.darklaf.util.DarkUIUtil;
import com.github.weisj.darklaf.ui.popupmenu.PopupMenuContainer;
import com.github.weisj.darklaf.util.PropertyKey;
import javax.swing.*;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;
import java.awt.*;
/**
@ -36,60 +34,16 @@ import java.awt.*;
*/
public class ScrollPopupMenu extends JPopupMenu {
private final JPanel contentPane;
private final JScrollPane scrollPane;
private final PopupMenuContainer popupMenuContainer;
private int maxHeight;
private JWindow popWin;
private Popup popup;
private int posX;
private int posY;
private JPanel view;
private boolean isVisible;
public ScrollPopupMenu(final int maxHeight) {
popupMenuContainer = new PopupMenuContainer();
this.maxHeight = maxHeight;
contentPane = new JPanel(new BorderLayout());
OverlayScrollPane overlayScrollPane = createScrollPane();
scrollPane = overlayScrollPane.getScrollPane();
contentPane.add(overlayScrollPane, BorderLayout.CENTER);
contentPane.setBorder(getBorder());
setDoubleBuffered(true);
MenuKeyListener menuKeyListener = new MenuKeyListener() {
@Override
public void menuKeyTyped(final MenuKeyEvent e) {
}
@Override
public void menuKeyPressed(final MenuKeyEvent e) {
SwingUtilities.invokeLater(() -> {
MenuElement[] path = e.getMenuSelectionManager().getSelectedPath();
if (path.length == 0) {
return;
}
Rectangle bounds = path[path.length - 1].getComponent().getBounds();
Rectangle r = SwingUtilities.convertRectangle(ScrollPopupMenu.this, bounds, scrollPane);
scrollPane.getViewport().scrollRectToVisible(r);
});
}
@Override
public void menuKeyReleased(final MenuKeyEvent e) {
}
};
addMenuKeyListener(menuKeyListener);
}
private OverlayScrollPane createScrollPane() {
view = new JPanel(new BorderLayout());
view.add(this, BorderLayout.CENTER);
OverlayScrollPane overlayScrollPane =
new OverlayScrollPane(view, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JScrollBar bar = overlayScrollPane.getVerticalScrollBar();
bar.putClientProperty("JScrollBar.thin", Boolean.TRUE);
DarkUIUtil.doNotCancelPopupSetup(bar);
DarkUIUtil.doNotCancelPopupSetup(overlayScrollPane.getScrollPane());
return overlayScrollPane;
}
/**
@ -104,27 +58,10 @@ public class ScrollPopupMenu extends JPopupMenu {
this.maxHeight = maxHeight;
}
@Override
public JMenuItem add(final JMenuItem menuItem) {
menuItem.getModel().addChangeListener(e -> contentPane.repaint(menuItem.getBounds()));
return super.add(menuItem);
}
protected void showPopup() {
Component comp = getInvoker();
if (comp == null) return;
while (comp.getParent() != null) {
comp = comp.getParent();
}
if (popWin == null || popWin.getOwner() != comp) {
popWin = comp instanceof Window ? new JWindow((Window) comp) : new JWindow(new JFrame());
}
pack();
popWin.setLocation(posX, posY);
popWin.setVisible(true);
requestFocus();
isVisible = true;
popup = createPopup();
popup.show();
}
/**
@ -132,24 +69,19 @@ public class ScrollPopupMenu extends JPopupMenu {
*
* @return scroll pane;
*/
public JScrollPane getScrollPane() {
return scrollPane;
return popupMenuContainer.getScrollPane();
}
@Override
public boolean isVisible() {
return popWin != null && popWin.isShowing();
return isVisible;
}
@Override
public void setLocation(final int x, final int y) {
if (popWin != null && popWin.isShowing()) {
popWin.setLocation(x, y);
} else {
posX = x;
posY = y;
}
posX = x;
posY = y;
}
@ -177,10 +109,11 @@ public class ScrollPopupMenu extends JPopupMenu {
}
protected void hidePopup() {
if (popWin != null) {
if (popup != null) {
firePopupMenuWillBecomeInvisible();
popWin.setVisible(false);
popWin = null;
popup.hide();
isVisible = false;
popup = null;
firePropertyChange(PropertyKey.VISIBLE, Boolean.TRUE, Boolean.FALSE);
if (isPopupMenu()) {
MenuSelectionManager.defaultManager().clearSelectedPath();
@ -188,30 +121,12 @@ public class ScrollPopupMenu extends JPopupMenu {
}
}
private Popup createPopup() {
return popupMenuContainer.createPopup(this, posX, posY, maxHeight);
}
@Override
public void pack() {
if (popWin == null) {
return;
}
final Dimension prefSize = getPreferredSize();
if (maxHeight <= 0 || prefSize.height <= maxHeight) {
setBounds(0, 0, prefSize.width, prefSize.height);
popWin.setContentPane(this);
setBorderPainted(true);
popWin.setSize(prefSize.width, prefSize.height);
} else {
int increment = getComponentCount() > 0
? Math.max(1, getComponent(0).getPreferredSize().height / 2)
: 1;
JScrollBar bar = scrollPane.getVerticalScrollBar();
bar.setValue(bar.getMinimum());
bar.setUnitIncrement(increment);
setBorderPainted(false);
view.add(this);
popWin.setContentPane(contentPane);
popWin.pack();
popWin.setSize(prefSize.width + bar.getPreferredSize().width, maxHeight);
}
}
private boolean isPopupMenu() {

4
core/src/main/java/com/github/weisj/darklaf/ui/combobox/DarkComboPopup.java

@ -24,6 +24,7 @@
package com.github.weisj.darklaf.ui.combobox;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import com.github.weisj.darklaf.ui.scrollpane.DarkScrollBarUI;
import javax.swing.*;
import javax.swing.plaf.basic.BasicComboPopup;
@ -93,7 +94,7 @@ public class DarkComboPopup extends BasicComboPopup {
protected JScrollPane createScroller() {
overlayScrollPane = new OverlayScrollPane(list, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
overlayScrollPane.getVerticalScrollBar().putClientProperty("JScrollBar.thin", Boolean.TRUE);
overlayScrollPane.getVerticalScrollBar().putClientProperty(DarkScrollBarUI.KEY_SMALL, Boolean.TRUE);
return overlayScrollPane.getScrollPane();
}
@ -110,7 +111,6 @@ public class DarkComboPopup extends BasicComboPopup {
setBorderPainted(true);
setOpaque(false);
add(overlayScrollPane);
setDoubleBuffered(true);
setFocusable(false);
}

1
core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuBorder.java

@ -42,7 +42,6 @@ public class DarkPopupMenuBorder extends MutableLineBorder implements UIResource
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width,
final int height) {
setColor(UIManager.getDefaults().getColor("PopupMenu.borderColor"));
super.paintBorder(c, g, x, y, width, height);
}

37
core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/DarkPopupMenuUI.java

@ -23,7 +23,10 @@
*/
package com.github.weisj.darklaf.ui.popupmenu;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import com.github.weisj.darklaf.components.ScrollPopupMenu;
import com.github.weisj.darklaf.ui.DarkPopupFactory;
import com.github.weisj.darklaf.util.DarkUIUtil;
import sun.awt.SunToolkit;
import javax.swing.*;
@ -54,6 +57,7 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
"doNotCancelPopup"));
public static final String KEY_DEFAULT_LIGHTWEIGHT_POPUPS = "PopupMenu.defaultLightWeightPopups";
protected static MouseGrabber mouseGrabber;
private PopupMenuContainer popupMenuContainer;
public static ComponentUI createUI(final JComponent x) {
return new DarkPopupMenuUI();
@ -89,6 +93,9 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
@Override
public void installDefaults() {
if (!(popupMenu instanceof ScrollPopupMenu)) {
popupMenuContainer = new PopupMenuContainer();
}
super.installDefaults();
popupMenu.putClientProperty(DarkPopupFactory.KEY_START_HIDDEN, true);
}
@ -102,6 +109,13 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
}
}
@Override
public Popup getPopup(final JPopupMenu popup, final int x, final int y) {
if (popupMenuContainer == null) return super.getPopup(popup, x, y);
int maxHeight = DarkUIUtil.getScreenBounds(popup, x, y, false).height;
return popupMenuContainer.createPopup(popup, x, y, maxHeight);
}
/**
* This Method is responsible for removing the old MouseGrabber from the AppContext, to be able to add our own
* implementation for it that is a bit more generous with closing the popup.
@ -110,7 +124,12 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
MenuSelectionManager menuSelectionManager = MenuSelectionManager.defaultManager();
ChangeListener mouseGrabber = null;
for (ChangeListener listener : menuSelectionManager.getChangeListeners()) {
if (listener.getClass().getEnclosingClass().getName().endsWith("BasicPopupMenuUI")) {
if (listener == null) continue;
Class<?> listenerClass = listener.getClass();
if (listenerClass == null) continue;
Class<?> enclosingClass = listenerClass.getEnclosingClass();
if (enclosingClass == null) continue;
if (enclosingClass.getName().endsWith("BasicPopupMenuUI")) {
mouseGrabber = listener;
break;
}
@ -211,6 +230,22 @@ public class DarkPopupMenuUI extends BasicPopupMenuUI {
}
lastPathSelected = p;
repaintIfNecessary(e);
}
protected void repaintIfNecessary(final ChangeEvent e) {
Object source = e.getSource();
if (source instanceof MenuSelectionManager) {
MenuSelectionManager manager = (MenuSelectionManager) source;
MenuElement[] path = manager.getSelectedPath();
if (path != null && path.length > 0) {
Component comp = path[0].getComponent();
if (comp.isVisible()) {
OverlayScrollPane sp = DarkUIUtil.getParentOfType(OverlayScrollPane.class, comp);
if (sp != null) sp.repaint(comp.getBounds());
}
}
}
}
public void eventDispatched(final AWTEvent ev) {

139
core/src/main/java/com/github/weisj/darklaf/ui/popupmenu/PopupMenuContainer.java

@ -0,0 +1,139 @@
/*
* MIT License
*
* Copyright (c) 2020 Jannis Weis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.weisj.darklaf.ui.popupmenu;
import com.github.weisj.darklaf.components.OverlayScrollPane;
import com.github.weisj.darklaf.decorators.PopupMenuAdapter;
import com.github.weisj.darklaf.ui.scrollpane.DarkScrollBarUI;
import com.github.weisj.darklaf.util.DarkUIUtil;
import javax.swing.*;
import javax.swing.event.MenuKeyEvent;
import javax.swing.event.MenuKeyListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import java.awt.*;
public class PopupMenuContainer extends JPanel {
private final JScrollPane scrollPane;
private final MenuKeyListener menuKeyListener;
private final PopupMenuListener menuListener;
private JPanel view;
private JPopupMenu popupMenu;
public PopupMenuContainer() {
super(new BorderLayout());
view = new JPanel(new BorderLayout());
OverlayScrollPane overlayScrollPane = createScrollPane(view);
scrollPane = overlayScrollPane.getScrollPane();
add(overlayScrollPane, BorderLayout.CENTER);
menuKeyListener = new MenuKeyListener() {
@Override
public void menuKeyTyped(final MenuKeyEvent e) {
}
@Override
public void menuKeyPressed(final MenuKeyEvent e) {
SwingUtilities.invokeLater(() -> {
if (popupMenu == null) return;
MenuElement[] path = e.getMenuSelectionManager().getSelectedPath();
if (path.length == 0) {
return;
}
Rectangle bounds = path[path.length - 1].getComponent().getBounds();
Rectangle r = SwingUtilities.convertRectangle(popupMenu, bounds, scrollPane);
scrollPane.getViewport().scrollRectToVisible(r);
});
}
@Override
public void menuKeyReleased(final MenuKeyEvent e) {
}
};
menuListener = new PopupMenuAdapter() {
@Override
public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
onHide();
}
@Override
public void popupMenuCanceled(final PopupMenuEvent e) {
onHide();
}
private void onHide() {
if (popupMenu == null) return;
popupMenu.removePopupMenuListener(this);
popupMenu.removeMenuKeyListener(menuKeyListener);
}
};
}
public void setPopupMenu(final JPopupMenu popupMenu) {
this.popupMenu = popupMenu;
popupMenu.removeMenuKeyListener(menuKeyListener);
popupMenu.removePopupMenuListener(menuListener);
popupMenu.addMenuKeyListener(menuKeyListener);
popupMenu.addPopupMenuListener(menuListener);
}
public Popup createPopup(final JPopupMenu popupMenu, final int posX, final int posY, final int maxHeight) {
setPopupMenu(popupMenu);
final Dimension prefSize = popupMenu.getPreferredSize();
if (maxHeight <= 0 || prefSize.height <= maxHeight) {
setBounds(0, 0, prefSize.width, prefSize.height);
popupMenu.setBorderPainted(true);
return PopupFactory.getSharedInstance().getPopup(popupMenu.getInvoker(), popupMenu, posX, posY);
} else {
int increment = popupMenu.getComponentCount() > 0
? Math.max(1, popupMenu.getComponent(0).getPreferredSize().height / 2)
: 1;
JScrollBar bar = scrollPane.getVerticalScrollBar();
bar.setValue(bar.getMinimum());
bar.setUnitIncrement(increment);
view.removeAll();
view.add(popupMenu, BorderLayout.CENTER);
setBorder(popupMenu.getBorder());
popupMenu.setBorderPainted(false);
setPreferredSize(new Dimension(prefSize.width + bar.getPreferredSize().width, maxHeight));
return PopupFactory.getSharedInstance().getPopup(popupMenu.getInvoker(), this, posX, posY);
}
}
private OverlayScrollPane createScrollPane(final JComponent content) {
OverlayScrollPane overlayScrollPane =
new OverlayScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
JScrollBar bar = overlayScrollPane.getVerticalScrollBar();
bar.putClientProperty(DarkScrollBarUI.KEY_SMALL, Boolean.TRUE);
DarkUIUtil.doNotCancelPopupSetup(bar);
DarkUIUtil.doNotCancelPopupSetup(overlayScrollPane.getScrollPane());
return overlayScrollPane;
}
public JScrollPane getScrollPane() {
return scrollPane;
}
}

4
core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java

@ -73,8 +73,8 @@ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConsta
thumbFadeStartColor = UIManager.getColor("ScrollBar.fadeStartColor");
thumbFadeEndColor = UIManager.getColor("ScrollBar.fadeEndColor");
trackBackground = UIManager.getColor("ScrollBar.trackColor");
smallSize = UIManager.getInt("ScrollBar.width");
size = UIManager.getInt("ScrollBar.smallWidth");
smallSize = UIManager.getInt("ScrollBar.smallWidth");
size = UIManager.getInt("ScrollBar.width");
}
@Override

32
core/src/main/java/com/github/weisj/darklaf/ui/tooltip/ToolTipUtil.java

@ -62,7 +62,7 @@ public class ToolTipUtil {
if (!context.isBestFit()) {
return context.getToolTipLocation(p, null);
}
Rectangle screenBounds = getScreenBounds(context.getTarget(), p);
Rectangle screenBounds = DarkUIUtil.getScreenBounds(context.getTarget(), p);
Rectangle windowBounds = DarkUIUtil.getWindow(context.getTarget()).getBounds();
Rectangle tooltipBounds = new Rectangle();
tooltipBounds.setSize(context.getToolTip().getPreferredSize());
@ -161,34 +161,4 @@ public class ToolTipUtil {
Point targetPos = target.getLocationOnScreen();
window.setLocation(targetPos.x + x, targetPos.y + y);
}
protected static Rectangle getScreenBounds(final JComponent target, final Point p) {
GraphicsConfiguration gc = getDrawingGC(p);
if (gc == null) {
gc = target.getGraphicsConfiguration();
}
Rectangle sBounds = gc.getBounds();
Insets screenInsets = Toolkit.getDefaultToolkit()
.getScreenInsets(gc);
// Take into account screen insets, decrease viewport
sBounds.x += screenInsets.left;
sBounds.y += screenInsets.top;
sBounds.width -= (screenInsets.left + screenInsets.right);
sBounds.height -= (screenInsets.top + screenInsets.bottom);
return sBounds;
}
private static GraphicsConfiguration getDrawingGC(final Point location) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = env.getScreenDevices();
for (GraphicsDevice device : devices) {
GraphicsConfiguration config = device.getDefaultConfiguration();
Rectangle rect = config.getBounds();
if (rect.contains(location)) {
return config;
}
}
return null;
}
}

41
core/src/main/java/com/github/weisj/darklaf/util/DarkUIUtil.java

@ -464,6 +464,47 @@ public final class DarkUIUtil {
return p;
}
public static Rectangle getScreenBounds(final JComponent target, final Point p) {
return getScreenBounds(target, p.x, p.y);
}
public static Rectangle getScreenBounds(final JComponent target, final int x, final int y) {
return getScreenBounds(target, x, y, true);
}
public static Rectangle getScreenBounds(final JComponent target, final int x, final int y,
final boolean subtractInsets) {
GraphicsConfiguration gc = getDrawingGC(x, y);
if (gc == null) {
gc = target.getGraphicsConfiguration();
}
Rectangle sBounds = gc.getBounds();
if (subtractInsets) {
// Take into account screen insets, decrease viewport
Insets screenInsets = Toolkit.getDefaultToolkit()
.getScreenInsets(gc);
sBounds.x += screenInsets.left;
sBounds.y += screenInsets.top;
sBounds.width -= (screenInsets.left + screenInsets.right);
sBounds.height -= (screenInsets.top + screenInsets.bottom);
}
return sBounds;
}
private static GraphicsConfiguration getDrawingGC(final int x, final int y) {
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = env.getScreenDevices();
for (GraphicsDevice device : devices) {
GraphicsConfiguration config = device.getDefaultConfiguration();
Rectangle rect = config.getBounds();
if (rect.contains(x, y)) {
return config;
}
}
return null;
}
public enum Outline {
error {
@Override

Loading…
Cancel
Save